List Vs Vector
数组由于是紧凑连续存储,可以随机访问, 通过索引快速找到对应元素,而且相对节约存储空间。
但正因为连续存储, 内存空间必须一次性分配够, 所以说数组如果要扩容, 需要重新分配一块更大的空间,再把数据全部复制过去,时间复杂度 O(N)。
数组中间进行插入或删除,每次必须搬移后面的所有数据以保持连续, 时间复杂度 O(N)。
链表因为元素不连续, 而是靠指针指向下一个元素的位置, 所以不存在数组的扩容问题。
- 根据某个元素的前驱和后驱, 操作指针即可删除该元素或插入新元素, 时间复杂度 O(1)。
- 存储空间不连续,无法根据索引算出对应元素的地址,索引不能随机访问;
- 每个元素必须存储指向前后元素位置的指针, 会消耗相对更多的储存空间。
通常情况下,刷题中遇到的ListNode都是只有next指针,没有pre指针的结构。
List的解题思路,通常用双指针和递归方法。
Two Pointers
两个指针指向List节点,不再是Index
两个指针必定同向而行。
需要明确:
- 一个快一个慢,距离隔开多少
- 两个指针的移动速度
实践应用
List中找中间节点
public ListNode* linkedlistMiddleNode(ListNode* head){
ListNode* i = head, j = head;
while(j!=nullptr && j->next!=nullptr){
i=i->next;
j=j->next->next;
}
return i;
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
List中找倒数第K个节点
public ListNode* linkedlistLastKthNode(ListNode* head, int k){
ListNode* i = head, j = head;
for(int c=0;c<k;c++){
j=j->next;
}
while(j!=nullptr ){
i=i->next;
j=j->next;
}
return i;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Recursion
Bottom up recursion 3 steps:
- Ask for subproblem result
- Do something in current level of recursion
- return result
Reverse Linked List
ListNode *reverseList(ListNode *head)
{
if(head == nullptr || head->next == nullptr)
return head;
ListNode * reversed_head = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return reversed_head;
}
ListNode *reverseList(ListNode *head)
{
ListNode *prev = nullptr;
ListNode *curr = head;
while (curr)
{
ListNode *next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
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
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
← 哈希表 HashMap 滑动窗口 →