0. 数组模拟链表 本质上就是实现了一个memory allocator,追求运行效率放弃可读性和拓展性,一般不会在工程中使用。
假如要往链表中存入非常多的数据,如果使用指针实现的链表(创建node类,每存入一个数就要new一个node),它的速度相对较慢。而数组模拟的链表可以完成指针链表所能完成的所有操作,且创建新节点就是给数组某个位置赋值,所以速度比指针链表快的多。并且数组还可以被模拟用于存储树和图,因此掌握数组模拟数据结构是十分必要的。
思路
定义模拟要用到的变量:
e[ ] 数组相当于用来存储链表节点的内存空间,i代表节点i的地址,e[i]代表节点i的值
ne[i] 表示节点i的next指针(空节点的下标为-1,如果数组中下标为3的节点就是尾节点了,则ne[3]=-1),里面的值就是节点i的next指针指向的元素在内存数组e中的位置
head 表示第一个节点的下标
idx 表示e数组当前可用的位置,即下一块可分配内存的地址
栗子
假如要模拟如下链表
模拟完成后数组e和ne的状态如下
模板如下
0.1 初始化 1 2 3 4 5 6 7 8 int N = 100010 ; int head, e[N], ne[N], idx;void init () { for (int i = 0 ;i<N;i++) ne[i] = -1 ; head = -1 ; idx = 0 ; }
0.2 插入新节点 0.2.1 新节点插入链表的头部 1 2 3 4 5 6 7 void AddToHead (int value) { e[idx] = value; ne[idx] = head; head = idx; idx++; }
0.2.2 新节点插入下标为k的节点之后 1 2 3 4 5 6 7 8 void Add (int value, int k) { e[idx] = value; ne[idx] = ne[k]; ne[k] = idx; idx++; }
0.3 删除下标为k的后面一个节点 1 2 3 4 void Delete (int k) { ne[k] = ne[ne[k]]; }
1. 环形链表 1.1 环形链表Ⅰ 【题目】
Given head
, the head of a linked list, determine if the linked list has a cycle in it.
There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next
pointer. Internally, pos
is used to denote the index of the node that tail’s next
pointer is connected to.
**Note that pos
is not passed as a parameter **.
Return true
if there is a cycle in the linked list. Otherwise, return false
.
Follow up:
Can you solve it using O(1) (i.e. constant) memory?
《哈希表法》
【解析】
很容易想到的一种解法就是遍历链表的每一个节点,每经过一个节点就把该节点记录到哈希表中,随后每经过一个节点就去哈希表中查找这个节点是否存在,如果存在就是有环。
这种方法需要消耗比较多的空间,其实这类题可以用一种更巧妙的方法求解。
【ac代码】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : bool hasCycle (ListNode *head) { ListNode* cur = head; unordered_map<ListNode*, int > hashmap; while (cur!=NULL ) { auto value = hashmap.find (cur); if (value!=hashmap.end ()) return true ; hashmap[cur] = 1 ; cur = cur->next; } return false ; } };
《快慢指针法》
【解析】
使用快慢指针(比如慢指针每次前进一步,快指针每次前进两步)。如果链表中存在环,那么快慢指针迟早会相遇。
这种方法比哈希表解法快的多,也节省了大量的空间。
【ac代码】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : bool hasCycle (ListNode *head) { if (head==NULL || head->next==NULL ) return false ; ListNode* fast = head->next; ListNode* slow = head; while (fast!=slow) { if (fast==NULL || fast->next==NULL ) return false ; fast = fast->next->next; slow = slow->next; } return true ; } };
1.2 环形链表Ⅱ 【题目】
Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail’s next pointer is connected to. Note that pos is not passed as a parameter.
Notice that you should not modify the linked list.
Follow up:
Can you solve it using O(1) (i.e. constant) memory?
《哈希表法》
【解析】
做过环形链表Ⅰ,第一个想到的办法就是哈希表了。只需要在环形链表Ⅰ的解法中,当在哈希表中找到已存在的节点时,把这个节点返回即可。
【ac代码】
时间复杂度与空间复杂度都为O(n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : ListNode *detectCycle (ListNode *head) { unordered_map<ListNode*, int > hashmap; ListNode* cur = head; while (cur!=NULL ) { auto another = hashmap.find (cur); if (another!=hashmap.end ()) return cur; hashmap[cur] = 1 ; cur = cur->next; } return NULL ; } };
《双指针法》
【解析】
双指针解法需要一些数学证明,这里直接上结论,快慢指针(快一次走两步,慢一次一步)同时从head出发,如果链表存在环,那么第一次快慢指针相遇时将快指针重新放到head并将其放慢(与慢指针同速),第二次快慢指针相遇的节点就是环的入口。
【ac代码】
时间复杂度线性,空间复杂度常数
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 class Solution {public : ListNode *detectCycle (ListNode *head) { if (head==NULL || head->next==NULL ) return NULL ; ListNode* fast = head; ListNode* slow = head; do { if (fast==NULL || fast->next==NULL ) return NULL ; fast = fast->next->next; slow = slow->next; } while (fast!=slow); fast = head; while (fast!=slow) { fast = fast->next; slow = slow->next; } return fast; } };
2. 链表中点 《快慢指针法》
【题目】 给定链表,返回链表中点(如果有两个则返回后者)
【解析】
快慢指针 。慢指针一次一步,快指针一次两步,当快指针走到头时,慢指针就是链表中点。
【ac代码】
快慢指针
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 class Solution {public : ListNode* middleNode (ListNode* head) { if (head==NULL ) return NULL ; ListNode* slow = head; ListNode* fast = head; while (fast) { if (!fast->next) break ; slow = slow->next; fast = fast->next->next; } return slow; } };
《数组法》
【解析】
把链表所有节点放到数组中,取下标n/2的节点
【ac代码】
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 class Solution {private : vector<ListNode*> vec; public : ListNode* middleNode (ListNode* head) { if (head==NULL ) return NULL ; ListNode* cur = head; while (cur) { vec.push_back (cur); cur = cur->next; } int length = vec.size (); return vec[length/2 ]; } };
《单指针法》
【解析】
先遍历一遍链表得知长度,然后重新遍历到n/2个节点即可
【ac代码】
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 class Solution {public : ListNode* middleNode (ListNode* head) { if (head==NULL ) return NULL ; ListNode* cur = head; int length = 0 ; while (cur) { cur = cur->next; length++; } cur = head; for (int i = 0 ;i<length/2 ;i++) { cur = cur->next; } return cur; } };
3. 旋转链表 【题目】
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: 1->2->3->4->5->NULL, k = 2 输出: 4->5->1->2->3->NULL 解释: 向右旋转 1 步: 5->1->2->3->4->NULL 向右旋转 2 步: 4->5->1->2->3->NULL
示例 2:
输入: 0->1->2->NULL, k = 4 输出: 2->0->1->NULL 解释: 向右旋转 1 步: 2->0->1->NULL 向右旋转 2 步: 1->2->0->NULL 向右旋转 3 步: 0->1->2->NULL 向右旋转 4 步: 2->0->1->NULL
【解析】
首先获取链表长度,然后计算移动k个位置后新链表的头尾节点位置,最后将头尾重新连接即可。
【ac代码】
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 class Solution {public : ListNode* rotateRight (ListNode* head, int k) { int length = 0 ; ListNode* cur = head; while (cur) { length++; cur = cur->next; } if (length==1 ||length==0 || k%length==0 ) return head; k = length - k % length - 1 ; cur = head; while (k--) cur = cur->next; ListNode* newTail = cur; ListNode* newHead = cur->next; while (cur->next) cur = cur->next; cur->next = head; newTail->next = NULL ; return newHead; } };
4. 反转链表 【题目】反转链表
【解析】用两个辅助指针(prev、tail),和一个工作指针(cur),辅助指针在cur的一前一后,prev用来给next指,tail保证链不断
【ac代码】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : ListNode* reverseList (ListNode* head) { ListNode* prev = NULL ; ListNode* cur = head; ListNode* tail = head; while (cur) { tail = tail->next; cur->next = prev; prev = cur; cur = tail; } return prev; } };
5. 排序链表 【题目】
要求时间复杂度O(nlogn),且空间复杂度为常数级。因此不能使用上次写过的插入排序。
【解析】
参考了官方题解。
需要两个子方法:
合并两个链表使其有序
寻找链表中点
就可以组合成归并排序了。
【ac代码】
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 class Solution {public : ListNode* mergeTwoLists (ListNode* head1, ListNode* head2) { ListNode* dumpHead = new ListNode (); ListNode* opa = dumpHead; ListNode* cur1 = head1; ListNode* cur2 = head2; while (cur1 && cur2) { if (cur1->val < cur2->val) { opa->next = cur1; cur1 = cur1->next; } else { opa->next = cur2; cur2 = cur2->next; } opa = opa->next; } if (cur1) opa->next = cur1; if (cur2) opa->next = cur2; return dumpHead->next; } ListNode* sorting (ListNode* head, ListNode* tail) { if (!head) return NULL ; if (head->next==tail) { head->next = NULL ; return head; } ListNode* fast = head, *slow = head; while (fast!=tail) { fast = fast->next; slow = slow->next; if (fast!=tail) fast = fast->next; } ListNode* mid = slow; return mergeTwoLists (sorting (head, mid), sorting (mid, tail)); } ListNode* sortList (ListNode* head) { return sorting (head, NULL ); } };
6. 重排链表 【题目】
Given a singly linked list L: L0→L1→…→Ln-1→Ln, reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→…
You may not modify the values in the list’s nodes, only nodes itself may be changed.
【解析】
将链表节点装进数组 , 然后对数组进行操作即可。
【ac代码】
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 class Solution {private : vector<ListNode*> arr; public : void reorderList (ListNode* head) { if (head==NULL ) return ; ListNode* cur = head; while (cur) { arr.push_back (cur); cur = cur->next; } int length = arr.size (); int i = 0 , j = length-1 ; while (1 ) { arr[i]->next = arr[j]; if (i==j) break ; i++; arr[j]->next = arr[i]; if (i==j) break ; j--; } arr[i]->next = NULL ; return ; } };
7. 对链表进行插入排序 【题目】
对链表进行插入排序。首先要知道对数组进行插入排序的原理是啥。
{连接到排序算法笔记}
【解析】
假定有哑节点。
先从大的方向考虑,可以使用两个指针,一个指针cur指向当前【待插入有序部分的元素】,一个指针opa指向哑节点,每次从[opa, cur) 这个区间(即链表中当前的有序部分)中找出这样一个节点FOO,它的下一个节点的值严格大于cur节点的值 ,然后把cur节点插入到这个节点之后。
因为每次执行插入操作时,cur节点的位置需要移动,为了不丢失cur->next这个节点,我们设置一个save节点,它总是指向cur的上一个节点。
每次找出节点FOO后,可能出现两种情况:
FOO的下一个就是cur节点。这种情况下cur所在节点不需要移动,我们只需让cur继续往下走即可
FOO的下一个不是cur节点。这种情况下,首先利用save指针保存cur的下一个节点,然后将cur插入到FOO的后面,然后把cur移动到save的位置。这就等同于数组插入排序中的插入这一步。
【ac代码】
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 class Solution {public : ListNode* insertionSortList (ListNode* head) { ListNode* cur = head; ListNode* dummyHead = new ListNode (); dummyHead->next = head; ListNode* save = dummyHead; ListNode* opa = new ListNode (); while (cur) { opa = dummyHead; while (opa->next!=cur && (opa->next->val <= cur->val)) opa = opa->next; if (opa->next == cur) { cur = cur->next; save = save->next; continue ; } save->next = cur->next; cur->next = opa->next; opa->next = cur; cur = save->next; } return dummyHead->next; } };
总结来看,首先从大的方向上确定解题的大体思路,然后分情况讨论,最后处理边界问题。
8. 合并两个有序链表 【题目】
示例:
1 2 输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
【解析】
两个指针分别指向两个链表,不断移动,将较小的那个连接到答案链表上。
ps:哑节点经常可以统一解决不同corner case(尤其是链表问题),让代码更加简洁。
【ac代码】
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 class Solution {public : ListNode* mergeTwoLists (ListNode* head1, ListNode* head2) { ListNode* dumpHead = new ListNode (); ListNode* opa = dumpHead; ListNode* cur1 = head1; ListNode* cur2 = head2; while (cur1 && cur2) { if (cur1->val < cur2->val) { opa->next = cur1; cur1 = cur1->next; } else { opa->next = cur2; cur2 = cur2->next; } opa = opa->next; } if (cur1) opa->next = cur1; if (cur2) opa->next = cur2; return dumpHead->next; } };
9. 删除中间结点 【题目】
只给链表中的一个节点,从逻辑上删除它
【解析】
将下一个节点的值赋予当前节点,然后让当前节点的next指向下下个节点
【ac代码】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution {public : void deleteNode (ListNode* node) { node->val = node->next->val; node->next = node->next->next; } };
10. 两数相加 10.1 两数相加Ⅰ 【题目】
You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
Example 1:
l1: 2→4→3
l2: 5→6→4
结果: 7→0→8
Input: l1 = [2,4,3], l2 = [5,6,4] Output: [7,0,8] Explanation: 342 + 465 = 807.
Example 2:
Input: l1 = [0], l2 = [0] Output: [0]
Example 3:
Input: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9] Output: [8,9,9,9,0,0,0,1]
【解析】
这题是用链表实现大数相加。
逆序存储刚好方便做加法(因为本来加法就是从低位到高位),就跟用数组模拟的加法一样操作:用一个t作为进位标志,两链表双指针,用t存储对应数位上相加的结果,将t%10接到答案链表的尾部,然后令t/=10(若t大于10,则变1,相当于进一位;若小于10,则变0,相当于没进位),让t参与下一次迭代运算。
要注意的是,迭代完毕退出循环后,要判断t是否为1,也就是最后一次运算是否产生了进位,若为1则要将其追加在答案尾部。
【ac代码】
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 class Solution {public : void AddToTail (ListNode* dumpyHead, int val) { ListNode* newNode = new ListNode (val); ListNode* cur = dumpyHead; while (cur->next) { cur = cur->next; } cur->next = newNode; } ListNode* addTwoNumbers (ListNode* l1, ListNode* l2) { ListNode* cur1 = l1, *cur2 = l2; ListNode* dumpyHead = new ListNode (); int t = 0 ; while (cur1 || cur2) { if (cur1) { t+=cur1->val; cur1 = cur1->next; } if (cur2) { t+=cur2->val; cur2 = cur2->next; } AddToTail (dumpyHead, t%10 ); t/=10 ; } if (t) AddToTail (dumpyHead, 1 ); return dumpyHead->next; } };
10.2 两数相加Ⅱ 【题目】
给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。
进阶:
如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
示例:
输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) 输出:7 -> 8 -> 0 -> 7
【解析】
先用数组存放两个链表的值,然后用数组实现大数相加,再把结果腾到新链表中。
【ac代码】
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 class Solution {public : vector<int > add (vector<int > v1, vector<int > v2) { vector<int > re; int length1 = v1.size (); int length2 = v2.size (); int i = length1-1 , j = length2-1 ; int t = 0 ; while (i>=0 || j>=0 ) { if (i>=0 ) t+=v1[i--]; if (j>=0 ) t+=v2[j--]; re.push_back (t%10 ); t/=10 ; } if (t) re.push_back (1 ); return re; } ListNode* addTwoNumbers (ListNode* l1, ListNode* l2) { vector<int > v1, v2; ListNode* cur1 = l1, *cur2 = l2; while (cur1) { v1.push_back (cur1->val); cur1 = cur1->next; } while (cur2) { v2.push_back (cur2->val); cur2 = cur2->next; } vector<int > foo = add (v1, v2); ListNode* re = new ListNode (foo[foo.size ()-1 ]); ListNode* newHead = re; for (int i = foo.size ()-2 ; i>=0 ; i--) { ListNode* newNode = new ListNode (foo[i]); re->next = newNode; re = re->next; } return newHead; } };
11. 删除排序链表中的重复元素 【题目】
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2 输出: 1->2
示例 2:
输入: 1->1->2->3->3 输出: 1->2->3
【解析】
遍历的过程中判断,有重复就删除节点即可
【ac代码】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : ListNode* deleteDuplicates (ListNode* head) { ListNode* cur = head; while (cur) { while (cur->next && cur->val == cur->next->val) { cur->next = cur->next->next; } cur = cur->next; } return head; } };
12. 删除排序链表中的重复元素Ⅱ 【题目】
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 1:
输入: 1->2->3->3->4->4->5 输出: 1->2->5
示例 2:
输入: 1->1->1->2->3 输出: 2->3
【解析】
在排序链表中找到存在重复的元素很简单,跟12题一样,只需要判断它与它的下一个节点值是否相等就行,不过这题要把重复元素删干净,也就是说,可能要删除当前已经走到的节点,立刻想到使用prev指针,它总是指向cur的上一个节点。
prev指针的移动分为两种情况:一种是cur没有碰到重复元素,那么prev直接往下移动即可;另外一种是cur碰到重复元素,这时prev要将所有的重复元素删除(改变prev-next的指向)。必须确保prev总是cur的上一个节点。
有了这个思路,剩下的代码细节只需要手动模拟一遍即可。
【ac代码】
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 class Solution {public : ListNode* deleteDuplicates (ListNode* head) { ListNode* dumpyHead = new ListNode (0 ); dumpyHead->next = head; ListNode* prev = dumpyHead; ListNode* cur = head; while (cur) { int flag = 0 ; while (cur->next && cur->val == cur->next->val) { cur->next = cur->next->next; flag = 1 ; } if (flag) prev->next = prev->next->next; else prev = prev->next; cur = cur->next; } return dumpyHead->next; } };
13. 设计链表 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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 class Node { public : int val; Node* next; Node (int value) { this ->val = value; this ->next = NULL ; } Node () { } }; class MyLinkedList {public : Node* head; int length; MyLinkedList () { head = new Node (0 ); length = -1 ; } int get (int index) { if (index>length) return -1 ; Node* cur = head; index++; while (index--) { cur = cur->next; } return cur->val; } void addAtHead (int val) { length++; Node* newNode = new Node (val); newNode->next = head->next; head->next = newNode; } void addAtTail (int val) { length++; Node* newNode = new Node (val); Node* cur = head; while (cur && cur->next) { cur = cur->next; } cur->next = newNode; } void addAtIndex (int index, int val) { if (index>length+1 ) { return ; } else if (length+1 ==index) { addAtTail (val); return ; } else if (index==0 ) { addAtHead (val); return ; } else { Node* cur = head; while (index--) { cur = cur->next; } Node* newNode = new Node (val); newNode->next = cur->next; cur->next = newNode; length++; } } void deleteAtIndex (int index) { if (index>length) return ; Node* cur = head; while (index--) { cur = cur->next; } cur->next = cur->next->next; length--; } };
14. 复杂链表的复制 【题目】
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
【解析】
random指针怎么复制?直接复制就指向原链表的节点了,违背题意,必须让它指向复制出来的节点,那就先把原链表按next指针复制出来,再修复random指针?也不行,原链表random指针指向的是一块内存,我们无法通过原链表的指针来判断新链表指针的指向。我的第一个想法是用数组存储原链表,这样就可以记录每一个节点random指针指向链表中哪个【位置】的节点,然后先用next指针复制原链表,把新链表的节点存储到一个临时数组中,再用我们之前得出的位置信息修复每一个节点的random指针即可。
【ac代码】
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 class Solution {public : vector<Node*> tmp; vector<int > index; vector<Node*> re; Node* copyRandomList (Node* head) { if (!head) return NULL ; Node* cur = head; while (cur) { tmp.push_back (cur); cur = cur->next; } int length = tmp.size (); for (int i = 0 ;i<length;i++) { if (tmp[i]->random == NULL ) { index.push_back (-1 ); continue ; } for (int j = 0 ;j<length;j++) { if (tmp[i]->random == tmp[j]) index.push_back (j); } } for (int i = 0 ;i<length;i++) { Node* newNode = new Node (tmp[i]->val); re.push_back (newNode); } for (int i = 0 ;i<length;i++) { if (index[i] == -1 ) re[i]->random = NULL ; else re[i]->random = re[index[i]]; if (i<length-1 ) re[i]->next = re[i+1 ]; } return re[0 ]; } };
15. 移除链表元素 【题目】
删除链表中等于给定值 val 的所有节点。
示例:
输入: 1->2->6->3->4->5->6, val = 6 输出: 1->2->3->4->5
【解析】
设置一个哑节点,使得头结点值等于val时的处理方法与普通情况一致。
【ac代码】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution {public : ListNode* removeElements (ListNode* head, int val) { ListNode* dumpyHead = new ListNode (); dumpyHead->next = head; ListNode* cur = dumpyHead; while (cur) { if (cur->next && cur->next->val==val) cur->next = cur->next->next; else cur = cur->next; } return dumpyHead->next; } };
16. 两个链表的第一个公共节点 【题目】
输入两个链表,找出它们的第一个公共节点。注意公共节点之前两个链表的子链表不一定等长,且第一个公共节点后全是公共节点。
【解析】
先将第一个链表的所有节点作为key存入hashmap,然后遍历第二个链表,每经过一个节点就去哈希表中寻找是否存在相同。
【ac代码】
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 class Solution {public : ListNode *getIntersectionNode (ListNode *headA, ListNode *headB) { unordered_map<ListNode*, int > hashmap; ListNode* cur = headA; while (cur) { hashmap[cur]++; cur = cur->next; } cur = headB; while (cur) { if (hashmap.count (cur)) return cur; cur = cur->next; } return NULL ; } };
【解析2】
另外一种比较经典的解法为:让两个指针在各自的链表中遍历,一旦走到自己的链表尾部就退回到【另外一个链表的头部】,每走一步判断两个指针是否相等,如果相等就返回任意一个指针即可。
该方法正确的原因在于,如果两个链表存在公共节点,那么两个指针先第一次遍历完各自的链表,然后退回到对方的链表头重新开始遍历后,它们必然会在第一个公共节点相遇(因为这时走过的节点数相等);如果两个链表不存在公共节点,那么两个指针必然会在第二次遍历完整个链表的终点NULL处相遇。
【ac代码】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution {public : ListNode *getIntersectionNode (ListNode *headA, ListNode *headB) { ListNode* a = headA; ListNode* b = headB; while (a!=b) { if (a==NULL ) a = headB; else a = a->next; if (b==NULL ) b = headA; else b = b->next; } return a; } };
【解析3】
还可以先计算两个链表公共节点前的子链表的长度差N,让公共节点前子链表较长的指针先多走N步。有兴趣可以自己实现下。
17. 链表组件 【题目】
给定链表头结点 head,该链表上的每个结点都有一个 唯一的整型值 。
同时给定列表 G,该列表是上述链表中整型值的一个子集。
返回列表 G 中组件的个数,这里对组件的定义为:链表中一段最长连续结点的值(该值必须在列表 G 中)构成的集合。
【解析】
遍历链表,每遍历一个节点都去列表G中寻找该节点,如果该节点在G中存在,那么开始计算当前组件数量,不断前进判断下一个节点…一直到走到链表某个节点在G中不存在,则【开始计算组件数量时的节点】到【当前节点的上一个节点】形成一个组件;如果该节点不存在G中,前进到下一个节点继续判断。
【ac代码】
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 class Solution {public : int numComponents (ListNode* head, vector<int >& G) { unordered_map<int , int > hashmap; int length = G.size (); for (int i = 0 ;i<length;i++) hashmap[G[i]]++; ListNode* cur = head; int count = 0 ; while (cur) { if (hashmap[cur->val]) { while (cur && hashmap[cur->val]) cur = cur->next; count++; } if (cur) cur = cur->next; } return count; } };
18. 奇偶链表 【题目】
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
【解析】
定义一个奇链表一个偶链表,他们的节点都属于原链表,定义完成后将偶链表接到奇链表尾部即可。
【ac代码】
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 class Solution {public : ListNode* oddEvenList (ListNode* head) { if (head==NULL || head->next==NULL ) return head; ListNode* odd = head; ListNode* even = head->next; ListNode* evenHead = even; while (odd->next && even->next) { odd->next = even->next; odd = odd->next; even->next = odd->next; even = even->next; } odd->next = evenHead; return head; } };
19. 分割链表 【题目】
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3 输出: 1->2->2->4->3->5
【解析】
设置两个头little和big,一个专门指向比x小的节点,另一个指向大于等于x的节点,然后遍历原链表,将相应的节点连接到对应的头上,最后拼接即可。本题能深切的体会到使用哑节点的好处。
【ac代码】
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 class Solution {public : ListNode* partition (ListNode* head, int x) { ListNode* little = new ListNode (); ListNode* big = new ListNode (); ListNode* saveBig = big; ListNode* saveLittle = little; ListNode* cur = head; while (cur) { if (cur->val<x) { little->next = cur; little = little->next; } else { big->next = cur; big = big->next; } cur = cur->next; } big->next = NULL ; little->next = saveBig->next; return saveLittle->next; } };
20. 分隔链表 【题目】
给定一个1~50的整数k,将链表分为k个子链表,其中任意两个子链表的长度差不超过1,且左边的子链表长度总是【大于等于】右边子链表的长度。
【解析】
根据提示,定义链表长度为N,则前N%k个子链表长度为N/k+1,后面所有子链表长度为N/k。
两遍遍历链表,第一遍获取总长度,第二遍按照上述规律在原链表上直接分隔链表,要注意的是每分隔完一个子链表要记录下一个节点,并把这个分隔完的子链表的最后一个节点next指针置为NULL,然后从记录的下一个节点开始继续分隔下一个子链表。
【ac代码】
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 class Solution {public : vector<ListNode*> splitListToParts (ListNode* root, int k) { vector<ListNode*> re; ListNode* cur = root; int length = 0 ; while (cur) { length++; cur = cur->next; } int everyPart = length/k; int extraPart = length%k; int partCount = 0 ; ListNode* save = root; int loop = 0 ; cur = root; while (cur) { cur = save; if (save) re.push_back (save); if (partCount<extraPart) loop = everyPart; else loop = everyPart-1 ; for (int i = 0 ;i<loop && cur;i++) cur = cur->next; if (cur) { save = cur->next; cur->next = NULL ; partCount++; } } while (partCount<k) { re.push_back (NULL ); partCount++; } return re; } };