链表很多时候都是考察基本功,因为链表题大部分都不是很复杂,主要是对指针的操作,当然也有难的。
简单的题目包括 删除/插入节点、翻转、去重、排序等,难度高一些的题目依然是这些,不过会有一些条件,比如多个链表或者局部操作。
对链表题的两个技巧:
- 如果不确定最终结果的head,比如对两个链表进行排序,那么新建一个 dummy node。
- 可以通过快慢指针的方式取中间节点
第一题 https://leetcode.com/problems/reverse-linked-list/
翻转链表,最最基本的一个操作。
var reverseList = function(head) {
if(!head || !head.next) return head;
var h = head, prev = null;
while(h) {
var t = h.next;
h.next = prev;
prev = h;
h = t;
}
return prev;
};
第二题 https://leetcode.com/problems/remove-duplicates-from-sorted-list/
去重,基本思路就是找到一个节点 h
,使 h.val = h.next.val
,然后删除 h.next
,如果重复到链表结束。
var deleteDuplicates = function(head) {
if(!head || !head.next) return head;
var h = head;
while(h && h.next) {
if(h.val == h.next.val) {
h.next = h.next.next;
} else {
h = h.next;
}
}
return head;
};
第三题:https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/
和上一题相比,这一题更难一些。思路依然不变,但是我们需要做一个标记,标记 h 节点后面有没有重复,如果有重复的节点,那么把重复的节点全部删除之后要记得把h本身也删除。虽然看上去只是增加了一步操作,但是实际做的时候很容易出错。
var deleteDuplicates = function(head) {
if(!head || !head.next) return head;
var dummy = new ListNode();
dummy.next = head;
var prev = dummy, h = head, tag = false;
while(h && h.next) {
while(h && h.next && h.next.val == h.val) {
tag = true;
h.next = h.next.next;
}
if(tag) {
prev.next = h.next;
h = h.next;
tag = false;
} else {
h = h.next;
prev = prev.next;
}
}
return dummy.next;
};
中级题:
第一题 https://leetcode.com/problems/partition-list/
这个题目并有太大难度,注意就是需要用两个链表分别记录小于等于x和大于x的节点,如果试图不创建新节点而直接在原链表上进行移动操作会很麻烦。
注意一个细节就是当把节点放入新的链表之后,注意清除next。
var partition = function(head, x) {
if(!head || !head.next) return head;
var h = head;
left = new ListNode(),
right = new ListNode(),
h1 = left,
h2 = right;
while(h) {
var n = h.next;
if(h.val < x) {
h1.next = h;
h1 = h1.next;
} else {
h2.next = h;
h2 = h2.next;
}
h.next = null; //注意这里!!!
h = n;
}
h1.next = right.next;
return left.next;
};
第二题 https://leetcode.com/problems/reverse-linked-list-ii/
这个题目很容易变成上面的题目,直觉的反应就是需要找到开始和结束的节点,然后翻转他们,然后在拼接起来即可。
这里我们换一个思路:从m开始,把 m ~ n-1 节点分别移动到 n 的下一个。
var reverseBetween = function(head, m, n) {
var dummy = new ListNode();
dummy.next = head;
var i = 1, start = dummy, end = head;
while(i<n) {
end = end.next;
if(i>n-m) start = start.next;
i++;
}
var count = n - m;
while(count > 0) {
var startNext = start.next;
start.next = startNext.next;
var endNext = end.next;
end.next = startNext;
startNext.next = endNext;
count --;
}
return dummy.next;
};
第三题 https://leetcode.com/problems/sort-list/
这一题我们用两路归并排序,把链表从中间切成两半,然后分别递归排序,再把左右两半进行一个合并即可。这里就用到了 merge two sorted array。
//merge sort
var sortList = function(head) {
if(!head || !head.next) return head;
//part to left and right;
var f = head, s = head, step=1;
while(f.next) {
f = f.next;
if(!(step%2)) s = s.next;
step++;
}
var left = head, right = s.next;
s.next = null;
//sort left and right
left = sortList(left);
right = sortList(right);
//merge
var d = new ListNode(), h = d, l=left, r=right;
while(l && r) {
if(l.val < r.val) {
h.next = l;
l = l.next;
} else {
h.next = r;
r = r.next;
}
h = h.next;
}
if(r) h.next = r;
else if(l) h.next = l;
return d.next;
};
题目 https://leetcode.com/problems/merge-k-sorted-lists/
如果面试碰到这一题,一般不会直接出现,肯定会先出一合并两个排序链表的题目(也就是上面一题)。所以这一题我们最直观的思路就是把它分解成合并两个排序链表的问题。
那么如何分解呢,我们这样做:1和2合并,3和4合并,5和6合并。然后对合并的结果重复这个操作直到只剩一个为止。
//merge sort
var sortList = function(head) {
if(!head || !head.next) return head;
//part to left and right;
var f = head, s = head, step=1;
while(f.next) {
f = f.next;
if(!(step%2)) s = s.next;
step++;
}
var left = head, right = s.next;
s.next = null;
//sort left and right
left = sortList(left);
right = sortList(right);
//merge
var d = new ListNode(), h = d, l=left, r=right;
while(l && r) {
if(l.val < r.val) {
h.next = l;
l = l.next;
} else {
h.next = r;
r = r.next;
}
h = h.next;
}
if(r) h.next = r;
else if(l) h.next = l;
return d.next;
};
时间复杂度可以证明是 O(nlog(n))
注意,这个题目千万不要这么分解:1和2合并,再和3合并,再和4合并。因为这样时间复杂度就变成了 O(n^2)
。
这一题还有一个方式是用 Heap 来做,可惜JS没有内置的Heap类。
另外一个奇怪的题目就不再说了,只能把解法背下来,直接看最高票的答案 https://leetcode.com/problems/copy-list-with-random-pointer/