【Weekly Algorithm】 算法周记之《代码随想录》链表
学习小结
本周跟随《代码随想录》学习链表部分,较深入地了解了链表的相关算法题,并利用双指针解决部分题目。
链表
移除链表元素
- 代码随想录 (programmercarl.com)
- 由于链表结构特殊,移除操作需要对指针操作,稍微复杂点
- 比较方便的方法是,用一个虚拟的指针头,将这个链表的删除操作转化为统一的链表中元素的移除
- 参考代码随想录思路的解法
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
30
31
32
33
34
35/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode* vir_head = new ListNode(0);
vir_head->next = head;
// 用虚拟头统一操作
ListNode*p = vir_head;
while(p->next != nullptr) {
// 下个符合就删
if (p->next->val == val) {
ListNode *tmp = p->next;
p->next = p->next->next;
delete tmp;
}
// 没删 next 的情况再更新到 next,若刚删了 next 且 null,会在循环那里停下来
else {
p = p->next;
}
}
return vir_head->next;
}
};
设计链表
- 代码随想录 (programmercarl.com)
- 考察很综合,增删查改
- TODO 独立单链表实现一次
- TODO 双链表独立实现一次
- 参考代码随想录思路的解法
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96typedef struct _link {
int val;
struct _link *next = nullptr;
//struct _link *pre = nullptr;
_link(int num) {
val = num;
}
}link_node;
class MyLinkedList {
public:
int size = 0;
link_node * head;
MyLinkedList() {
this->size = 0;
head = new link_node(0);
}
int get(int index) {
if (index >= size || index < 0) {
return -1;
}
link_node *p = head->next;
for (int i = 0; i < index; i++) {
p = p->next;
}
return p->val;
}
void addAtHead(int val) {
link_node *p = new link_node(val);
p->next = head->next;
head->next = p;
this->size++;
}
void addAtTail(int val) {
link_node *p = new link_node(val);
link_node *cur = head;
while(cur->next != nullptr) {
cur = cur->next;
}
cur->next = p;
this->size++;
//addAtIndex(this->size, val);
}
void addAtIndex(int index, int val) {
if(index > this->size) {
return;
}
if (index < 0) {
index = 0;
}
link_node *p = head;
for (int i = 0; i < index; i++) {
p = p->next;
}
link_node *new_node = new link_node(val);
new_node->next = p->next;
p->next = new_node;
this->size++;
}
void deleteAtIndex(int index) {
if (index >= this->size || index < 0) {
return;
}
link_node *p = head;
for (int i = 0; i < index; i++) {
p = p->next;
}
link_node *q = p->next;
p->next = p->next->next;
delete q;
this->size--;
}
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
翻转链表
本题要求,将一个单链表顺序翻转过来
有递归法、双指针法,时间复杂度皆为O(n),双指针空间复杂度O(1),递归O(n)
双指针法如下
参考代码随想录思路的解法
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/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *pre = nullptr;
ListNode *cur = head;
ListNode *tmp = nullptr;// 存储 cur 的下个节点
while(cur != nullptr) {
tmp = cur->next; // 先存下个要操作的节点
cur->next = pre;// 翻转当前节点
pre = cur; // 整体右移一个
cur = tmp;
}
return pre;
}
};两两交换链表中的节点
该题就是要求把链表中每两个元素的顺序调换
方法就是选定锚点,然后以锚点后两个交换
参考代码随想录思路的解法
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
30
31
32
33
34
35/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
// 建个哨兵以统一后面的交换进程
ListNode *setinel = new ListNode(0);
setinel->next = head;
ListNode *cur = setinel;
// 以cur为基准,交换后两个结点
while(cur->next != nullptr && cur->next->next != nullptr) {
ListNode *tmp1 = cur->next;
ListNode *tmp2 = cur->next->next;
// 先连接到后面,剩下两个随意换
tmp1->next = tmp2->next;
tmp2->next = tmp1;
cur->next = tmp2;
// 向右移动两节点
cur = cur->next->next;
}
// 由于head可能变了,传setinel回去
return setinel->next;
}
};删除链表的倒数第N个节点
题意易见
一次遍历解决的方法是,用双指针,快指针先走N步,再两指针一起走,最后删除慢指针后面那个。
参考代码随想录思路的解法
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
30
31
32
33
34
35/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 哨兵节点统一处理
ListNode *sentinel = new ListNode(0);
sentinel->next = head;
ListNode *right = sentinel, *left = sentinel;
// 先走N步
for (int i = 0; i < n; i++) {
right = right->next;
}
// 一起走
while(right->next != nullptr) {
right = right->next;
left = left->next;
}
// 删慢指针后面那个
ListNode *tmp = left->next;
left->next = tmp->next;
delete tmp;
return sentinel->next;
}
};链表相交
即有两条长短不一的链表,在后面某一点交会到一条,找到交汇的那个点
方法即是先找到两个链表的长度,然后让长短两条各有一指针从相同位置同时检测两指针是否相等,毕竟前面的长短不一的部分不可能交汇
参考代码随想录思路的解法
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int len_a = 0, len_b = 0;
ListNode *cur_a = headA, *cur_b = headB;
// 分别测量 a 和 b 的长度
while(cur_a != nullptr) {
cur_a = cur_a->next;
len_a++;
}
while(cur_b != nullptr) {
cur_b = cur_b->next;
len_b++;
}
cur_a = headA;
cur_b = headB;
int len_dif = abs(len_a - len_b);
// 保证 a 是较长的一端
if (len_a < len_b) {
swap(cur_a, cur_b);
swap(len_a, len_b);
}
// 对齐 a 和 b
for (int i = 0; i < len_dif; i++) {
cur_a = cur_a->next;
}
// 逐一检查是否相等
while(cur_a != nullptr) {
if (cur_a == cur_b) {
break;
}
cur_a = cur_a->next;
cur_b = cur_b->next;
}
return cur_a;
}
};环形链表||
即链表后面可能有个环,要求返回入环点
两个问题,一、有没有环;二、入环点怎么找
有没有环的问题,可以通过简单的双指针解决,即快指针走两步,慢指针走一步,若是有环,一定在某点相遇。倘若慢指针刚入环,无论快指针在环走多少,此时设快指针到慢指针距离 x,每次行动距离减一,迟早相遇。
接着是入环点的问题,接下来就是数学为主的时间,设 head 到入环点距离 x,入环点到相遇点距离 y,相遇点到入环点距离 z,慢指针就走了 x+y,快指针走了 x+n* (y+z) +y (毕竟可能走了几圈),快指针移速为慢指针两倍,可列式子 2*(x+y) = x+n* (y+z) +y,消减得 x = n(y+z) - y,变换得 x = (n-1)(y+z) + z,一看从相遇点开始走 z 步(可能加上好几圈)以及从头开始走的不就可以在入环点相遇了嘛。
假设 n=1,x==z,两边直接相遇;n=2,相遇点处的指针多走一圈,也能相遇
参考代码随想录思路的解法
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
30
31/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head, *slow = head;
// 探明是否有环
while(fast != nullptr && fast->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) {
// 从交汇点与起点同时走,交汇于入环点
slow = head;
while (fast != slow) {
fast = fast->next;
slow = slow->next;
}
return fast;
}
}
return nullptr;
}
};