什么叫ARTS?
每周完成一个ARTS:
- Algorithm:每周至少做一个 leetcode 的算法题
- Review:阅读并点评至少一篇英文技术文章
- Tip:学习至少一个技术技巧
- Share:分享一篇有观点和思考的技术文章
作者:陈皓
链接:https://www.zhihu.com/question/301150832/answer/529809529
来源:知乎
Delete Node in a Linked List
从节点唯一的单链表中删除一个节点,不用返回任意数值。
1 | Input: head = [4,5,1,9], node = 5 |
方法一
- 将下一个节点的值复制到当前节点。注意node=node.next不会改变原链表。
- 删除下一个节点。
1 | /** |
Remove Nth Node From End of List
在一个循环内删掉链表的倒数第几个数。
1 | Given linked list: 1->2->3->4->5, and n = 2. |
方法一
- 先保存好头指针。
- 采用双指针进行遍历,第一个指针遍历到第n位后,第二个指针再从头开始遍历。
- 第一个指针到达末尾时,第二个指针的位置即是需要删除的位置,head就是头指针?
1 | /** |
单链表反转
使用递归和迭代两种方法实现。
1 | Input: 1->2->3->4->5->NULL |
方法一
- 迭代法:在首节点前插入头节点0,名字叫pre,另外cur节点指向head,不断的在pre后面插入cur->next,此时cur后移,直到cur为最后一个节点。
- 0->1->2->3->4->5->NULL origin
- 0->1->3->4->5->NULL 先把cur->next即2取出来,此时cur->next变成3
- 0->2->1->3->4->5->NULL 再把取出来的值即2插入到pre后面,注意插入的时候一定要以pre为基准
- 0->2->1->4->5->NULL 先把cur->next即3取出来,此时cur->next变成4
- 0->3->2->1->4->5->NULL 再把取出来的值即4插入到pre后面
1 | /** |
方法二
- 1->2->3->4->5->NULL
- 1->NULL 2->3->4->5->NULL
- 2->1->NULL 3->4->5->NULL
- 3->2->1->NULL 4->5->NULL
- 4->3->2->1->NULL 5->NULL
- 5->4->3->2->1->NULL
- 设置cur为NULL,先将head的next保存为next,将head插入cur头部,并更新cur。
- 更新head为next。
- 直到head为NULL。
1 | /** |
方法三
- 递归法。
- ⬇️1->2->3->4->5->NULL
- ⬇️1 head为1,head->next=2,⬆️5->4->3->2->1->NULL
- ⬇️2 head为2,head->next=3,⬆️5->4->3->2->NULL
- ⬇️3 head为3,head->next=4,⬆️5->4->3->NULL
- ⬇️4 head为4,head->next=5,⬆️5->4->NULL
- ⬇️5 head为5,head->next=NULL,⬆️递归开始返回,结果为head即5
- 先将问题递给最后的节点。最后的节点依次递回答案。
1 | /** |
Merge Two Sorted Lists
合并两个有序节点。
1 | Input: 1->2->4, 1->3->4 |
方法一
- 依次遍历两链表。
- 同时比较链表节点的大小,将节点大的插入到新链表中。(相等的情况就不需要考虑了,再比较一次就好了)
- 如果一方已经遍历完成,则将剩下的链表直接接入即可。
1 | /** |
方法二
- 递归法?如果是尾递归,答案不需要返回来,不容易堆栈溢出,如果再加一个行参作为答案栈即可使用。
- 1->2->4, 1->3->4
- 1 ⬇️ 2->4, 1->3->4
- 1->1⬇️ 2->4, 3->4
- 1->1->2 ⬇️ 4, 3->4
- 1->1->2->3 ⬇️ 4, 4
- 1->1->2->3->4 ⬇️ 4
- 1->1->2->3->4->4
- 还需要向上递归找回头指针
1 | /** |
Palindrome Linked List
判断一个单链表是否为回文链表。
1 | Input: 1->2 |
方法一
- 使用快慢指针,先找到中间的节点。
- 再由中间的节点往两边做对比,需要提前做好数据备份,需要判断是奇偶数。
- 如果碰到不相等的,则返回false。
- 此时间复杂度为O(n)
1 | /** |
方法二
- 使用快慢指针,先找到中间的节点。
- 再由中间的节点往两边做对比,需要将后半段进行反转。
- 如果碰到不相等的,则返回false。
- 此时间复杂度为O(1)
1 | /** |
Linked List Cycle
判断链表中是否存在环。
1 | Input: head = [3,2,0,-4], pos = 1 |
方法一
- 采用快慢指针,判断指针是否会相遇,只能通过判断地址是否相等。
- 3,3
- 3 2,3 0
- 3 2 0,3 0 2
- 3 2 0 -4,3 0 2 -4
1 | /** |
方法二
- 使用hash set,不断查看之前的set中是否存在相同的节点了,如果存在,证明就是一个环。
1 | /** |
再定义一个指针指向链表起点,一次走一步,slow 指针也同步继续往后走,那么这两个指针就一定会在链表的入口位置相遇。1
2
3
4
5
6
7
8if (slow == fast){//快慢指针相遇时
ListNode ptr = head;//定义一个新指针
while (ptr != slow){
ptr = ptr.next;
slow = slow.next;
}
return ptr;//返回环的入口位置
}
k 个一组翻转链表
思路一:
用栈,我们把 k 个数压入栈中,然后弹出来的顺序就是翻转的!
这里要注意几个问题:
第一,剩下的链表个数够不够 k 个(因为不够 k 个不用翻转);
第二,已经翻转的部分要与剩下链表连接起来。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26ListNode* reverseKGroup(ListNode* head, int k) {
stack<ListNode*> st;
ListNode *dummy = new ListNode(0);
ListNode *p = dummy;
while (true) {
int count = 0;
ListNode* tmp = head;
while (tmp != nullptr && count < k) { // 将k个放入栈中
st.push(tmp);
tmp = tmp->next;
count++;
}
if (count != k) { // 不够k个不用翻转,直接跳出
p->next = head;
break;
}
while (!st.empty()) {
p->next = st.top();
st.pop();
p = p->next;
}
p->next = tmp;
head = tmp;
}
return dummy->next;
}
思路二:
尾插法。
直接举个例子:k = 3。
pre
tail head
dummy 1 2 3 4 5
我们用tail 移到要翻转的部分最后一个元素
pre head tail
dummy 1 2 3 4 5
cur
我们尾插法的意思就是,依次把cur移到tail后面
pre tail head
dummy 2 3 1 4 5
cur
依次类推
pre tail head
dummy 3 2 1 4 5
cur1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24ListNode* reverseKGroup(ListNode* head, int k) {
ListNode *dummy = new ListNode(0);
dummy->next = head;
ListNode *pre = dummy;
ListNode *tail = dummy;
while (true) {
int count = 0;
while (tail != nullptr && count != k) {
count++;
tail = tail->next;
}
if (tail == nullptr) break;
ListNode *head1 = pre->next;
while (pre->next != tail) { // 将tail移到要翻转的部分最后一个元素
ListNode *cur = pre->next; // cur为待移动的元素
pre->next = cur->next;
cur->next = tail->next; // 放到tail后面
tail->next = cur;
}
pre = head1;
tail = head1;
}
return dummy->next;
}
递归
将头部的 k 个节点反转,并拼接上后面的第 k + 1 个节点1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public ListNode reverseKGroup(ListNode *head, int k) {
ListNode *newHead = head;
int count = 0;
while (newHead != nullptr && count != k) {
newHead = newHead->next;
count++;
}
if (count == k) { // 找到了k个
newHead = reverseKGroup(newHead, k); // 将头部的 k 个节点反转,并拼接上后面的第 k + 1 个节点
// 尾插法
// head newHead
// 1 2 3 4 5
// head newHead
// 2 3 4 1 5
// head newHead
// 3 4 2 1 5
// head
// newHead
// 4 3 2 1 5
while (count != 0) {
count--;
ListNode *tmp = head->next;
head->next = newHead;
newHead = head;
head = tmp;
}
head = newHead;
}
return head;
}
从链表中删去总和值为零的连续节点
1 | 输入:head = [1,2,-3,3,1] |
1 | ListNode* removeZeroSumSublists(ListNode* head) { |
链表内指定区间反转
将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转。
1 | ListNode* reverseBetween(ListNode* head, int m, int n) { |