链表
链表考点
知识点
- 链表中的Dummy Node
- 基本链表技能
- 链表的Two Pointers(Fast-slow pointers)
入门题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void print(){ for(ListNode node = head; node != null; node = node.next){ System.out.print(node.val); System.out.print("->"); } System.out.println("null"); }
void main(){ ListNode node1 = new ListNode(1); ListNode node2 = new ListNode(2); ListNode node3 = new ListNode(3); ListNode head = node1; node1.next = node2; node2.next = node3; print(head); node1 = node2; print(head); }
|
答案:
1 2 3
| 1->2->3->null
1->2->3->null
|
思路:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 初始:
n1 n2 n3 [1,-]--> [2,-]--> [3,null] =================================== head = n1 :
head n1 n2 n3 [1,-]--> [2,-]--> [3,null]
sizeof(head) = 4 =================================== n1 = n2 :
head n1 n2 n3 [1,-]--> [2,-]--> [3,null]
|
例题
例题1,Remove Duplicates from Sorted List
将有序链表去重
例题2,Remove Duplicates from Sorted List II
将有序链表中的全部重复元素删除
思路:使用dummy node
当链表结构发生变化时,使用dummy node
1 2 3 4 5 6 7 8 9 10 11 12
| 套路:伪节点
head dummy n1 n2 n3 [,-]--> [1,-]--> [2,-]--> [3,null]
ListNode dummy = new ListNode(0); dummy.next = head;
return dummy.next;
|
删除节点p方式:
开始写了!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ListNode dummy = new ListNode(0); dummy.next = head;
ListNode prev = dummy; ListNode curt = head;
while(null != curt){ if(null != curt.next && curt.val == curt.next.val){ int val = curt.val; while(curt != null && curt.val == val){ curt = curt.next; } prev.next = curt; }else{ prev = curt; curt = curt.next; } } return dummy.next;
|
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public ListNode deleteDuplicates(ListNode head) { if(null == head)return head; ListNode dummy = new ListNode(0); dummy.next = head;
ListNode prev = dummy; ListNode curt = head; while (null != curt){ if(null != curt.next && curt.val == curt.next.val){ int temp = curt.val; curt = curt.next; while (null != curt && curt.val == temp){ curt = curt.next; } prev.next = curt; }else { prev = curt; curt = curt.next; } } return dummy.next; }
|
例题,Remove Linked List Elements
删除链表中的某个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public ListNode removeElements(ListNode head, int val) { if(null == head)return head; ListNode dummy = new ListNode(0); dummy.next = head;
ListNode prev = dummy; ListNode curt = head;
while (null != curt){ if(curt.val == val){ curt = curt.next; prev.next = curt; }else { prev = curt; curt = curt.next; } } return dummy.next; }
|
例题3,Reverse Linked List
将链表反转
1 2 3 4 5 6 7 8 9 10 11
| ListNode prev = null; ListNode curt = head;
while( null != curt){ ListNode temp = curt.next; curt.next = prev; prev = curt; curt = temp; }
return prev;
|
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public ListNode reverseList(ListNode head) { if(null == head){return head;} ListNode curt = head; ListNode prev = null; ListNode temp; while (null != curt){ temp = curt.next; curt.next = prev; if(null != temp) { prev = curt; curt = temp; }else { break; } } return curt; }
|
例题4,Reverse Linked List II
将一个链表的m到n反转
1 2 3 4 5
| 拆解问题
1. 寻找第m个点 2. 将m-n反转 3. 把整个链表连成一个链表!(如下图所示)
|
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
| if(m >= n || head == null){ return head; }
ListNode dummy = new ListNode(0); dummy.next = head;
ListNode prev = dummy;
for(int i = 1 ; i < m ; ++i){ if(null == head){ return null; }else{ head = head.next; } } ListNode premNode = head; if(null == head){ return null; } ListNode mNode = head.next;
ListNode nNode = mNode, postnNode = mNode.next; for(int i = m; i < n; i++){ if(null == postnNode){ return null; } ListNode temp = postnNode.next; postnNODE.next = nNode; nNode = postnNode; postnNode = temp; }
mNode.next = postnNode; premNode.next = nNode;
return dummy.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
| public ListNode reverseBetween(ListNode head, int m, int n) { ListNode curt = head; ListNode prev = null; for(int i = 1 ; i < m ; ++i){ if(null == curt.next) return null; prev = curt; curt = curt.next; } ListNode mNodePrev = prev; ListNode mNode = curt; ListNode next = curt.next; n = n - m + 1; while (null != curt && n > 0){ next = curt.next; curt.next = prev; prev = curt; curt = next; --n; } ListNode nNode = prev; mNode.next = next; if(null == mNodePrev){ return nNode; } mNodePrev.next = nNode; return head; }
|
例题5,Reverse Nodes in k-Group
将链表按照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 26 27 28 29 30 31 32 33 34 35 36 37 38
| public ListNode reverseKGroup(ListNode head, int k) { ListNode dummy = new ListNode(0); dummy.next = head;
ListNode prev = dummy; ListNode curt = head;
int c; while (null != curt) { c = k; ListNode firstNodePrev = prev; ListNode firstNode = curt; if(null == curt) break; while (null != curt && c > 0) { ListNode next = curt.next; curt.next = prev; prev = curt; curt = next; --c; } if(c > 0){ c = k - c; while (c > 0){ ListNode last = prev.next; prev.next = curt; curt = prev; prev = last; --c; } return dummy.next; } firstNodePrev.next = prev; firstNode.next = curt;
prev = firstNode; } return dummy.next; }
|
例题6,Partition List
要求把小于x的元素放到链表前面
思路:排两个队,小的一队,大的一队
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
| if(null == head){ return null; }
ListNode leftDummy = new ListNode(0); ListNode rightDummy = new ListNode(0); ListNode left = leftDummy, right = rightDummy;
while(null != head){ if(head.val < x){ left.next = head; left = head; }else{ right.next = head; right = head; } head = head.next; }
right.next = null; left.next = rightDummy.next; return leftDummy.next;
代码:
public ListNode partition(ListNode head, int x) { ListNode leftTail = new ListNode(0), rightTail = new ListNode(0);
ListNode leftHead = leftTail, rightHead = rightTail;
while (null != head){ if(head.val < x){ leftTail.next = head; leftTail = leftTail.next; }else { rightTail.next = head; rightTail = rightTail.next; } head = head.next; } rightTail.next = null; leftTail.next = rightHead.next; return leftHead.next; }
|
Basic Skills in Linked List
增、删、反转、合并、中点
Fast-slow Pointers问题
1 2 3 4 5 6 7 8 9 10 11
| 链表中点的骚操作
private ListNode getMiddle(ListNode head){ ListNode slow = head; ListNode fast = head.next; while (null != fast && null != fast.next){ fast = fast.next.next; slow = slow.next; } return slow; }
|
应用场景:
- 链表中点
- 移除倒数第N个元素
- Linked List Cycle I,II
- Rotate List
例题7,Sort List
用O(nlogn)的时间和空间复杂度排序链表
思路:n log n的排序 - 快速排序、归并排序、堆排序
思路 |
先整体有序,后局部(左右有序)有序 |
先局部(左右有序)有序,后整体有序 |
|
不稳定排序(原来在前面的不一定还在前面) |
稳定排序-同样的key保持原来的顺序 |
时间 |
\(O(nlogn --- O(n^2))\) |
\(O(nlogn)\) |
空间 |
O(1) |
数组上O(n),链表上\(O(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 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
| 关键点:怎么取中点?
取中点骚操作:
private ListNode findMiddle(ListNode head){ ListNode slow = head, fast = head.next; while(null != fast && null != fast.next){ fast = fast.next.next; slow = slow.next; } return slow; }
合并:
private ListNode merge(ListNode head1, ListNode head2){ ListNode dummy = new ListNode(0); ListNode tail = dummy; while(null != head1 && null != head2){ if(head1.val < head2.val){ tail.next = head1; head1 = head1.next; }else{ tail.next = head2; head2 = head2.next; } tail = tail.next; } if(null != head1){ tail.next = head1; }else if(null != head2){ tail.next = head2; } return dummy.next;
}
主函数
public ListNode sortList(ListNode head){ if(null != head || null != head.next){ return head; } ListNode mid = findMiddle(head);
ListNode right = sortList(mid.next); mid.next = null; ListNode left = sortList(head);
return merge(left,right); }
|
代码
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
| private ListNode getMiddle(ListNode head){ ListNode slow = head; ListNode fast = head.next; while (null != fast && null != fast.next){ slow = slow.next; fast = fast.next.next; } return slow; } private ListNode merge(ListNode right, ListNode left){ ListNode dummy = new ListNode(0); ListNode head = dummy; dummy.next = head; while (null != right && null != left){ if(right.val < left.val){ head.next = right; right = right.next; }else { head.next = left; left = left.next; } head = head.next; } if(null != right){ head.next = right; }else if(null != left){ head.next = left; } return dummy.next; } public ListNode sortList(ListNode head) { if(null == head) return null; if(null == head.next) return head; ListNode mid = getMiddle(head); ListNode right = sortList(mid.next); mid.next = null; ListNode left = sortList(head); return merge(left,right); }
|
例题8,Reorder List
将链表 L0→L1→…→Ln-1→Ln, 重排序为L0→Ln→L1→Ln-1→L2→*L**n*-2→…
思路:
- 找中点
- 中点右半部分reverse
- 合并
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
| private ListNode getMiddle(ListNode head){ ListNode slow = head; ListNode fast = head.next; while (null != fast && null != fast.next){ fast = fast.next.next; slow = slow.next; } return slow; } private ListNode reverse(ListNode head){ ListNode last = null; while (null != head){ ListNode temp = head.next; head.next = last; last = head; head = temp; } return last; } public void reorderList(ListNode head) { if(null == head || null == head.next) return; ListNode mid = getMiddle(head); ListNode right = reverse(mid.next); mid.next = null; ListNode dummy = new ListNode(0); dummy.next = head; boolean fromRight = false; while (null != head && null != right) { ListNode temp = head.next; head.next = right; right = right.next; head.next.next = temp; head = head.next.next; } }
|
例题9,Linked List Cycle
判断链表里有没有环形
暴力方法:用hash表
骚操作:快指针和慢指针如果相遇了,就有环
代码:
1 2 3 4 5 6 7 8 9 10 11
| public boolean hasCycle(ListNode head) { if(null == head || null == head.next) return false; ListNode fast = head.next; ListNode slow = head; while (null != fast && null != fast.next){ if(fast == slow){return true;} fast = fast.next.next; slow = slow.next; } return false; }
|
例题10,Linked List Cycle II
如果有环,找到环的入口
骚操作:快指针和慢指针如果相遇了,就有环。相遇之后,再让head和slow分别开始,每次走一步。相遇后一定是环入口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public ListNode detectCycle(ListNode head) { if(null == head || null == head.next) return null; ListNode fast = head.next; ListNode slow = head; while (null != fast && null != fast.next){ if(fast == slow){ slow = slow.next; while (true){ if(slow == head) return slow; slow = slow.next; head = head.next; } } fast = fast.next.next; slow = slow.next; } return null; }
|
例题11,Rotate List
- 循环移动
- 找到倒数第k个
- 断开连上就好
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public ListNode rotateRight(ListNode head, int k) { if(null == head || null == head.next) return head; int n = getLength(head); k = k % n; if(k == 0) return head; ListNode fast = head; for(int i = 0 ; i < k ; ++i){ fast = fast.next; } ListNode slow = head; while (null != fast && null != fast.next){ slow = slow.next; fast = fast.next; } ListNode right = slow.next; slow.next = null; fast.next = head;
return right; }
|
例题12,Merge k Sorted Lists
三种方法:
堆,维护当前每个list的head。复杂度\(Nlogk\)
分治法。复杂度\(Nlogk\)--每个数都经过\(logk\) 次合并
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
| * / \ / * / / \ * / * / \ / / \ 1 2 3 4 5
将两个 k/2 的链表组,合并为一个链表
public ListNode mergeKLists(ListNode[] lists){ if(lists.length == 0){ return null; } return mergeHelper(lists, 0, lists.length - 1); } private ListNode mergeHelper(ListNode[] lists, int start, int end){
if(end == start) return lists[start]; int mid = start + (end - start) / 2; ListNode left = mergeHelper(lists, start, mid); ListNode right = mergeHelper(lists,mid + 1, end); return mergeTwoLists(left,right); } private ListNode mergeTwoLists(ListNode list1, ListNode list2){ ListNode dummy = new ListNode(0); ListNode tail = dummy; while (null != list1 && null != list2){ if(list1.val < list2.val){ tail.next = list1; list1 = list1.next; }else{ tail.next = list2; list2 = list2.next; } tail = tail.next; } if(null != list1){ tail.next = list1; }else if(null != list2){ tail.next = list2; } return dummy.next; }
|
两两合并,复杂度\(Nlogk\)
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
| 9 / \ 8 \ / \ \ 6 7 \ / \ / \ \ 1 2 3 4 5 public ListNode mergeKLists(List<ListNode> lists){ if(lists.size() == 0){ return null; } while(lists.size() > 1){ List<ListNode> new_lists = new ArrayList<ListNode>(); for (int i = 0; i + 1 < lists.size(); i += 2) { ListNode merged_list = merge(lists.get(i), lists.get(i+1)); new_lists.add(merged_list); } if (lists.size() % 2 == 1) { new_lists.add(lists.get(lists.size() - 1)); } lists = new_lists; } return lists.get(0); }
private ListNode merge(ListNode list1, ListNode list2){ ListNode dummy = new ListNode(0); ListNode tail = dummy; while(list1 != null && list2 != null){ if(list1.val < list2.val){ tail.next = list1; tail = list1; list1 = list1.next; }else{ tail.next = list2; tail = list2; list2 = list2.next; } } if (list1 != null) { tail.next = list1; } else { tail.next = list2; } return dummy.next; }
|
例题13,Copy List with Random Pointer
1 -> 2 -> 3 -> 4...
然后每个点都有一个random指针,随机指
要求将这个random list 深拷贝一次
方法一,依次拷贝node.next,用一个哈希表存储每个[oldNode,newNode]
方法二,骚操作,优化,用O(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 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
| 1 -> 2 -> 3 -> 4 -> null
变成 1 -> 1` -> 2 -> 2` -> 3 -> 3` -> 4 -> 4` -> null a.next.next -->保留了原来链表的关系 a.next -->新的链表
那么如果链表中有random指针,如下
__ __________ ↑↓ ↑ ↓ 1 -> 1` -> 2 -> 2` -> 3 -> 3` -> 4 -> 4` -> null 那么复制这个random关系即可!!
然后再把这个链表拆开即可!
总结: 1. copy next 2. copy random 3. split
代码: public RandomListNode copyRandomList(RandomListNode head) { if(null == head) return null; RandomListNode curr = head;
while(null != curr){ RandomListNode copy = new RandomListNode(curr.label); copy.next = curr.next; curr.next = copy; curr = curr.next.next; } curr = head; while(null != curr){ if(curr.random != null){ curr.next.random = curr.random.next; } curr = curr.next.next; } RandomListNode dummy = head.next; while (null != head){ RandomListNode temp = head.next; head.next = temp.next; head = head.next; if (temp.next != null) { temp.next = temp.next.next; } } return dummy;
}
|
Swap Nodes in Pairs
将链表两两反转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| private void swap(ListNode obj,ListNode prev){ prev.next = obj.next; obj.next = prev.next.next; prev.next.next = obj; } public ListNode swapPairs(ListNode head) { if(null == head || null == head.next) return head; ListNode dummy = new ListNode(0); dummy.next = head; ListNode prev = dummy; while (null != head && null != head.next){ swap(head,prev); head = head.next; prev = prev.next.next; } return dummy.next; }
|
Delete Node in a Linked List
1 2 3 4 5 6
| public void deleteNode(ListNode node) { if(node != null && node.next != null) { node.val = node.next.val; node.next = node.next.next; } }
|
Palindrome Linked List
判断一个list是不是回文串
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
| private ListNode getMiddle(ListNode head){ ListNode fast = head.next; ListNode slow = head; while (null != fast && null != fast.next){ fast = fast.next.next; slow = slow.next; } return slow; } private ListNode reverse(ListNode head){ ListNode last = null; while (null != head){ ListNode temp = head.next; head.next = last; last = head; head = temp; } return last; } public boolean isPalindrome(ListNode head) { if(null == head || null == head.next) return true; ListNode middle = getMiddle(head); ListNode right = middle.next; middle.next = null; right = reverse(right); while (null != right && null != head && right.val == head.val){ right = right.next; head = head.next; } if((null == head && null == right)|| (null == head.next && null == right)|| (null == right.next && null == head)){ return true; } return false; }
|
Odd Even Linked List
将链表的奇数位放前面,偶数位放后面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public ListNode oddEvenList(ListNode head) { if(null == head || null == head.next) return head; ListNode oddDummy = new ListNode(0); ListNode evenDummy = new ListNode(0); oddDummy.next = head; evenDummy.next = head.next; ListNode odd = head; ListNode even = head.next; while (null != even && null != even.next){ odd.next = even.next; even.next = odd.next.next; odd = odd.next; even = even.next; } odd.next = evenDummy.next; return oddDummy.next; }
|
Split Linked List in Parts
很无聊的题。
将一个链表分为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 30 31 32 33 34 35
| private int getLength(ListNode root){ int length = 0; while (null != root){ root = root.next; ++length; } return length; } public ListNode[] splitListToParts(ListNode root, int k) { int length = getLength(root); int n = length/k; int m = length % k; ListNode[] results = new ListNode[k]; int pos = 0; while (null != root){ int i = m > 0 ? n : n - 1; m-=1; ListNode dummy = new ListNode(0); dummy.next = root; while (i > 0){ root = root.next; --i; } ListNode temp = root.next; root.next = null; root = temp; results[pos] = dummy.next; ++pos; } while (pos < k){ results[pos] = null; ++pos; } return results; }
|
Insertion Sort List
实现链表的插入排序
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
| public ListNode insertionSortList(ListNode head) { if(null == head || null == head.next) return head; ListNode dummy = new ListNode(0); dummy.next = head; ListNode curt = head; while (null != curt && null != curt.next){ if(curt.next.val >= curt.val){ curt = curt.next; }else { ListNode prev = dummy.next; ListNode insert = curt.next; if(dummy.next.val >= insert.val) prev = dummy; else { while (prev.next.val < insert.val) { prev = prev.next; } } curt.next = curt.next.next; ListNode prevNext = prev.next; prev.next = insert; insert.next = prevNext; } } return dummy.next; }
|
总结
- 凡是链表结构发生变化的,都需要Dummy Node
- 链表常用基本功:
- 反转 reverse
- 归并 merge
- 找中点 median
- 增删查改
- Linked List Cycle , 知道怎么做,理解
- Linked List Cycle II,知道怎么做,课后分析一下为什么,背下程序
- Copy List with Random Pointers
- Merge k Sorted Arrays
- k路归并一定要掌握
- 三种方式分别实现,并熟练理解和掌握
- 顺便做一下merge k sorted arrays
其他题
Intersection of Two Linked Lists
寻找两个链表的交叉点,如下所示,返回节点c1。如果不存在,返回Null
1 2 3 4 5
| A: a1 → a2 ↘ c1 → c2 → c3 ↗ B: b1 → b2 → b3
|
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
| private int getLen(ListNode head){ int length = 0; while (head != null){ ++length; head = head.next; } return length; } public ListNode getIntersectionNode(ListNode headA, ListNode headB) { int remainA = getLen(headA); int remainB = getLen(headB); while (remainA > remainB){ headA = headA.next; --remainA; } while (remainA < remainB){ headB = headB.next; --remainB; } while (headA != null && headB != null && headA != headB){ headA = headA.next; headB = headB.next; } if(headA == headB) return headA; else return null; }
|