#javascript #algorithm #linked-list
#javascript #алгоритм #связанный список
Вопрос:
Вопрос: Объединить два отсортированных связанных списка и вернуть его в виде нового отсортированного списка. Новый список должен быть создан путем объединения узлов первых двух списков.
Пример: Ввод: 1->2->4, 1->3->4 Вывод: 1->1->2->3->4->4
Мое решение:
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var mergeTwoLists = function(l1, l2, l3) {
if (l2 === null) {
l3.next = l1;
return l3;
}
if (l1 === null) {
l3.next = l2;
return l3;
}
if (l2.val < l1.val) {
if (l3) {
l3.next = new ListNode(l2.val)
}
else {
l3 = new ListNode(l2.val)
}
return mergeTwoLists(l1, l2.next, l3)
} else {
if (l3) {
l3.next = new ListNode(l1.val);
}
else {
l3 = new ListNode(l1.val);
}
return mergeTwoLists(l1.next, l2, l3)
}
};
Мой вывод просто 1-> 4 вместо 1->1->2->3->4->4. Кто-нибудь, пожалуйста, может сказать мне, почему?
Комментарии:
1. что такое
ListNode
— без этого ответ — ананас2. Вы разместили весь код здесь? Я думаю, что приведенный выше блок кода является рекурсивной частью решения.
3. @JaromandaX обновлен для определения ListNode.
4. @Spikie да. Я использую параметр по умолчанию для l3, поэтому мне не нужна вспомогательная функция для рекурсивной части.
Ответ №1:
Основная причина проблем, с которыми вы сталкиваетесь, заключается в третьем параметре, который имеет ваша функция: это не соответствует описанию проблемы. Третьего параметра нет. Вам нужно создать возвращаемый список без такого значения.
Я понимаю, что вы пытались передать частичный результат в качестве третьего аргумента и хотите расширить этот частичный результат при каждом рекурсивном вызове, но затем все идет не так:
Во-первых, в первых двух if
блоках вы предполагаете, что l3
не равно null, но вы не можете быть уверены в этом. В случае, если входные данные состоят из пустого списка, ваш код создаст исключение.
Во-вторых, если l3
представляет список, содержащий более одного элемента, то этот код перезапишет существующую связь между l3
и l3.next
, и поэтому оригинал l3.next
(и все последующие узлы) будут потеряны.
Хотя вы можете решить эту проблему, сначала найдя конечный узел в l3
, есть лучший способ. И этот лучший способ на самом деле является основным принципом хорошо продуманной рекурсии:
Если возможно, не передавайте частичный результат рекурсивному вызову с целью расширения этого частичного результата до конечного результата. Вместо этого попробуйте создать функцию таким образом, чтобы ей не требовался какой-либо такой частичный результат от вызывающего, но она могла выполнять свою работу с вводом, как если бы это был самый оригинальный ввод. Вызывающий должен использовать возвращаемое значение для обработки этого как частичного результата и расширить его, прежде чем возвращать его вызывающему.
function ListNode(val, next) {
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
var mergeTwoLists = function(l1, l2) { // Only 2 parameters
if (l2 === null) return l1; // don't prepend anything
if (l1 === null) return l2; // don't prepend anything
let node;
if (l2.val < l1.val) {
node = new ListNode(l2.val);
node.next = mergeTwoLists(l1, l2.next);
} else {
node = new ListNode(l1.val);
node.next = mergeTwoLists(l1.next, l2);
}
return node;
};
// Helper function for demo
const listFromArray = a => a.length ? new ListNode(a[0], listFromArray(a.slice(1)))
: null;
let l1 = listFromArray([1, 2, 4]);
let l2 = listFromArray([1, 3, 4]);
console.log(mergeTwoLists(l1, l2));
Комментарии:
1. Спасибо за отличное объяснение. Я предполагаю, что моя концепция рекурсии со связанными списками была неверной
Ответ №2:
Вы можете использовать сторожевой узел, чтобы это было намного проще:
const mergeTwoLists = function(l1, l2) {
const sentinel = {
val: -1,
next: null
}
let head = sentinel
while (l1 amp;amp; l2) {
if (l1.val > l2.val) {
head.next = l2
l2 = l2.next
} else {
head.next = l1
l1 = l1.next
}
head = head.next
}
head.next = l1 || l2
return sentinel.next
}
Если вы хотите сделать рекурсивно, нам не понадобится l3
:
const mergeTwoLists = function(l1, l2) {
if (l1 === null) {
return l2
}
if (l2 === null) {
return l1
}
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2)
return l1
} else {
l2.next = mergeTwoLists(l1, l2.next)
return l2
}
}
Комментарии:
1. Это имеет смысл. Можете ли вы точно определить, где я ошибаюсь, хотя в моей логике или понимании связанных списков? Я также пытаюсь условно обновить следующий узел до l1 или l2 в зависимости от того, какой из них меньше, и обновить все узлы в рекурсивной функции. Одна вещь, которую я попробовал, это сделать его l3.next.next при обновлении внутри условных блоков, но затем это не удалось, потому что он сказал, что я не могу обновить свойство null
2. Я думаю, что @Kushh нужна причина, по которой его решение не сработало, но не ответ для leetcode
Ответ №3:
Ваш ответ возвращается только 1->4
потому, что вы не повторяете свой недавно созданный объединенный список, т. Е. l3
. Вы непосредственно делаете l3.next=somevalue
. Поскольку l3
это список, сначала вам нужно выполнить итерацию по нему и добавить значение или список в его последний узел, где l3.next
будет null
.
Вот код, который должен дать вам желаемый результат
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val)
this.next = (next === undefined ? null : next)
}
var mergeTwoLists = function(l1, l2, l3) {
let addToMergedList = (l3, val) => {
let rootNode = l3
while (l3.next !== null)
l3 = l3.next;
l3.next = new ListNode(val);
return rootNode;
}
if (l2 === null) {
let root = l3
if(!root)
return l1
while (l3.next)
l3 = l3.next;
l3.next = l1;
return root;
}
if (l1 === null) {
let root = l3
if(!root)
return l2
while (l3.next)
l3 = l3.next;
l3.next = l2;
return root;
}
if (l2.val < l1.val) {
if (l3) {
l3 = addToMergedList(l3, l2.val)
} else {
l3 = new ListNode(l2.val)
}
return mergeTwoLists(l1, l2.next, l3)
} else {
if (l3) {
l3 = addToMergedList(l3, l1.val)
} else {
l3 = new ListNode(l1.val);
}
return mergeTwoLists(l1.next, l2, l3)
}
};
let l1={val:1,next:{val:2,next:{val:4,next:null}}}
let l2={val:1,next:{val:3,next:{val:4,next:null}}
console.log(mergeTwoLists(l1, l2))
Комментарии:
1. Это становится довольно медленным, когда входные списки длинные. Проблема в том, что каждый раз, когда выполняется рекурсивный вызов,
l3
список должен повторяться от начала до конца. Поскольку этот список становится все длиннее и длиннее, эта работа также становится длиннее, что приводит к квадратичной временной сложности.