链表Linked List

1. 链表

数组是一种顺序表,index与value之间是一种顺序映射,以O(1)O(1)的复杂度访问数据元素。但是,若要在表的中间部分插入(或删除)某一个元素时,需要将后续的数据元素进行移动,复杂度大概为O(n)O(n)。链表(Linked List)是一种链式表,克服了上述的缺点,插入和删除操作均不会引起元素的移动;数据结构定义如下:

  1. public class ListNode {
  2. String val;
  3. ListNode next;
  4. // ...
  5. }

常见的链表有单向链表(也称之为chain),只有next指针指向后继结点,而没有previous指针指向前驱结点。

链表的插入与删除操作只涉及到next指针的更新,而不会移动数据元素。比如,要在FAT与HAT插入结点GAT,如图所示:

Java实现:

  1. ListNode fat, gat;
  2. gat.next = fat.next;
  3. fat.next = gat;

又比如,要删除结点GAT,如图所示:

Java实现:

  1. fat.next = fat.next.next;

从上述代码中,可以看出:因为没有前驱指针,一般在做插入和删除操作时,我们需要通过操作前驱结点的next指针开始。

2. 题解

LeetCode题目 归类
237. Delete Node in a Linked List 删除
203. Remove Linked List Elements  
19. Remove Nth Node From End of List  
83. Remove Duplicates from Sorted List  
82. Remove Duplicates from Sorted List II  
24. Swap Nodes in Pairs 移动
206. Reverse Linked List  
92. Reverse Linked List II  
61. Rotate List  
86. Partition List  
328. Odd Even Linked List  
21. Merge Two Sorted Lists 合并
23. Merge k Sorted Lists  
141. Linked List Cycle 有环
142. Linked List Cycle II 有环
234. Palindrome Linked List  
143. Reorder List  
160. Intersection of Two Linked Lists  
2. Add Two Numbers  
445. Add Two Numbers II  

237. Delete Node in a Linked List
删除指定结点。由于是单向链表,因此只需更新待删除节点即可。

  1. public void deleteNode(ListNode node) {
  2. node.val = node.next.val;
  3. node.next = node.next.next;
  4. }

203. Remove Linked List Elements
删除指定值的结点。用两个指针实现,curr用于遍历,prev用于暂存前驱结点。

  1. public ListNode removeElements(ListNode head, int val) {
  2. ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
  3. fakeHead.next = head;
  4. for (ListNode curr = head, prev = fakeHead; curr != null; curr = curr.next) {
  5. if (curr.val == val) { // remove
  6. prev.next = curr.next;
  7. } else { // traverse
  8. prev = prev.next;
  9. }
  10. }
  11. return fakeHead.next;
  12. }

19. Remove Nth Node From End of List
删除链表的倒数第n个结点。思路:因为单向链表是没有前驱指针的,所以应找到倒数第n+1个结点;n有可能等于链表的长度,故先new一个head的前驱结点fakeHead。用两个指针slow、fast从fakeHead开始,先移动fast n+1步,使得其距离slow为n+1;然后,两个指针同步移动,当fast走到null时,slow即处于倒数第n+1个结点,删除slow的next结点即可。

  1. public ListNode removeNthFromEnd(ListNode head, int n) {
  2. ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
  3. fakeHead.next = head;
  4. ListNode slow = fakeHead, fast = fakeHead;
  5. for (int i = 1; i <= n + 1; i++) {
  6. fast = fast.next;
  7. }
  8. while(fast != null) {
  9. fast = fast.next;
  10. slow = slow.next;
  11. }
  12. slow.next = slow.next.next; // the n-th node from end is `slow.next`
  13. return fakeHead.next;
  14. }

83. Remove Duplicates from Sorted List
删除有序链表中的重复元素。处理思路有上一问题类似,不同的是判断删除的条件。

  1. public ListNode deleteDuplicates(ListNode head) {
  2. ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
  3. fakeHead.next = head;
  4. for (ListNode curr = head, prev = fakeHead; curr != null && curr.next != null; curr = curr.next) {
  5. if (curr.val == curr.next.val) { // remove
  6. prev.next = curr.next;
  7. } else {
  8. prev = prev.next;
  9. }
  10. }
  11. return fakeHead.next;
  12. }

82. Remove Duplicates from Sorted List II
上一问题的升级版,删除所有重复元素结点。在里层增加一个while循环,跳过重复元素结点。

  1. public ListNode deleteDuplicates(ListNode head) {
  2. ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
  3. fakeHead.next = head;
  4. for (ListNode curr = head, prev = fakeHead; curr != null; curr = curr.next) {
  5. while (curr.next != null && curr.val == curr.next.val) { // find the last duplicate
  6. curr = curr.next;
  7. }
  8. if (prev.next == curr) prev = prev.next;
  9. else prev.next = curr.next;
  10. }
  11. return fakeHead.next;
  12. }

24. Swap Nodes in Pairs
链表中两两交换。按step = 2 遍历链表并交换;值得注意的是在更新next指针是有次序的。

  1. public ListNode swapPairs(ListNode head) {
  2. ListNode fakeHead = new ListNode(Integer.MIN_VALUE);
  3. fakeHead.next = head;
  4. for (ListNode prev = fakeHead, p = head; p != null && p.next != null; ) {
  5. ListNode temp = p.next.next;
  6. prev.next = p.next; // update next pointer
  7. p.next.next = p;
  8. p.next = temp;
  9. prev = p;
  10. p = temp;
  11. }
  12. return fakeHead.next;
  13. }

206. Reverse Linked List
逆序整个链表。逆序操作可以看作:依次遍历链表,将当前结点插入到链表头。

  1. public ListNode reverseList(ListNode head) {
  2. ListNode newHead = null;
  3. for (ListNode curr = head; curr != null; ) {
  4. ListNode temp = curr.next;
  5. curr.next = newHead; // insert to the head of list
  6. newHead = curr;
  7. curr = temp;
  8. }
  9. return newHead;
  10. }

92. Reverse Linked List II
上一问题的升级,指定区间[m, n]内做逆序;相当于把该区间的链表逆序后,再拼接到原链表中。

  1. public ListNode reverseBetween(ListNode head, int m, int n) {
  2. ListNode newHead = null, curr = head, firstHead = null, firstHeadPrev = null;
  3. for (int i = 1; curr != null && i <= n; i++) {
  4. if (i < m - 1) {
  5. curr = curr.next;
  6. continue;
  7. }
  8. if (i == m - 1) {
  9. firstHeadPrev = curr; // mark first head previous node
  10. curr = curr.next;
  11. } else {
  12. if (i == m) firstHead = curr; // mark first head node
  13. ListNode temp = curr.next;
  14. curr.next = newHead;
  15. newHead = curr;
  16. curr = temp;
  17. }
  18. }
  19. firstHead.next = curr;
  20. if (firstHeadPrev != null) firstHeadPrev.next = newHead;
  21. if (m == 1) return newHead;
  22. return head;
  23. }

61. Rotate List
指定分隔位置,将链表的左右部分互换。只需修改左右部分的最后节点的next指针即可,有一些special case需要注意,诸如:链表为空,k为链表长度的倍数等。为了得到链表的长度,需要做一次pass。故总共需要遍历链表两次。

  1. public ListNode rotateRight(ListNode head, int k) {
  2. if (head == null || k == 0) return head;
  3. int n = 0, i;
  4. ListNode curr, leftLast = head, rightFist, rightLast = head;
  5. for (curr = head; curr != null; curr = curr.next) { // get the length of list
  6. n++;
  7. }
  8. k %= n; // k maybe larger than n
  9. if (k == 0) return head;
  10. for (i = 1, curr = head; i <= n; i++, curr = curr.next) { // mark the split node
  11. if (i == n - k) leftLast = curr;
  12. if (i == n) rightLast = curr;
  13. }
  14. rightFist = leftLast.next;
  15. leftLast.next = null;
  16. rightLast.next = head;
  17. return rightFist;
  18. }

86. Partition List
类似于quick sort的partition,不同的是要保持链表的原顺序。思路:用两个链表,一个保留小于指定数x,一个保留不大于指定数x;最后拼接到一起即可。

  1. public ListNode partition(ListNode head, int x) {
  2. if (head == null) return null;
  3. ListNode lt = new ListNode(-1), gte = new ListNode(-2); // less than, greater than and equal
  4. ListNode p, p1, p2;
  5. for (p = head, p1 = lt, p2 = gte; p != null; p = p.next) {
  6. if (p.val < x) {
  7. p1.next = p;
  8. p1 = p1.next;
  9. } else {
  10. p2.next = p;
  11. p2 = p2.next;
  12. }
  13. }
  14. p2.next = null;
  15. p1.next = gte.next;
  16. return lt.next;
  17. }

328. Odd Even Linked List
链表分成两部分:偶数编号与奇数编号,将偶数链表拼接到奇数链表的后面。

  1. public ListNode oddEvenList(ListNode head) {
  2. if (head == null || head.next == null) return head;
  3. ListNode odd = head, even = head.next, evenHead = head.next;
  4. while (odd.next != null && odd.next.next != null) {
  5. odd.next = odd.next.next;
  6. odd = odd.next;
  7. if (even != null && even.next != null) {
  8. even.next = even.next.next;
  9. even = even.next;
  10. }
  11. }
  12. odd.next = evenHead; // splice even next to odd
  13. return head;
  14. }

21. Merge Two Sorted Lists
合并两个有序链表。比较简单,分情况比较。

  1. public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
  2. ListNode head = new ListNode(-1), p, p1, p2;
  3. for (p = head, p1 = l1, p2 = l2; p1 != null || p2 != null; p = p.next) {
  4. if (p1 != null) {
  5. if (p2 != null && p1.val > p2.val) {
  6. p.next = p2;
  7. p2 = p2.next;
  8. } else {
  9. p.next = p1;
  10. p1 = p1.next;
  11. }
  12. } else {
  13. p.next = p2;
  14. p2 = p2.next;
  15. }
  16. }
  17. return head.next;
  18. }

23. Merge k Sorted Lists
合并k个有序链表。思路:借助于堆,堆的大小为k,先将每个链表的首结点入堆,堆顶元素即为最小值,堆顶出堆后将next入堆;依此往复,即可得到整个有序链表。

  1. public ListNode mergeKLists(ListNode[] lists) {
  2. if (lists.length == 0) return null;
  3. PriorityQueue<ListNode> minHeap = new PriorityQueue<>(lists.length, new Comparator<ListNode>() {
  4. @Override
  5. public int compare(ListNode o1, ListNode o2) {
  6. return o1.val - o2.val;
  7. }
  8. });
  9. // initialization
  10. for (ListNode node : lists) {
  11. if (node != null)
  12. minHeap.offer(node);
  13. }
  14. ListNode head = new ListNode(-1);
  15. for (ListNode p = head; !minHeap.isEmpty(); ) {
  16. ListNode top = minHeap.poll();
  17. p.next = top;
  18. p = p.next;
  19. if (top.next != null)
  20. minHeap.offer(top.next);
  21. }
  22. return head.next;
  23. }

141. Linked List Cycle
判断链表是否有环。用两个指针,一个快指针一个慢指针,一个每次移动两步,一个每次移动一步;最后两者相遇,即说明有环。

  1. public boolean hasCycle(ListNode head) {
  2. if (head == null) return false;
  3. for (ListNode slow = head, fast = head; fast.next != null && fast.next.next != null; ) {
  4. slow = slow.next;
  5. fast = fast.next.next;
  6. if (slow == fast) return true;
  7. }
  8. return false;
  9. }

142. Linked List Cycle II
找出链表中环的起始节点ss。解决思路:用两个指针——fast、slow,先判断是否环;两者第一次相遇的节点与ss的距离 == 链表起始节点与ss的距离(有兴趣可以证明一下)。

  1. public ListNode detectCycle(ListNode head) {
  2. if (head == null || head.next == null) return null;
  3. ListNode slow = head, fast = head;
  4. boolean isCycled = false;
  5. while (slow != null && fast != null && fast.next != null) { // first meeting
  6. slow = slow.next;
  7. fast = fast.next.next;
  8. if (slow == fast) {
  9. isCycled = true;
  10. break;
  11. }
  12. }
  13. if (!isCycled) return null;
  14. for (fast = head; slow != fast; ) { // find the cycle start node
  15. slow = slow.next;
  16. fast = fast.next;
  17. }
  18. return slow;
  19. }

234. Palindrome Linked List
判断链表LL是否中心对称。中心对称的充分必要条件:对于任意的 i <= n/2 其中n为链表长度,有L[i] = L[n+1-i]成立。因此先找出middle结点(在距离首结点n/2处),然后逆序右半部分链表,与左半部分链表的结点一一比较,即可得到结果。在找出middle结点时也用到了小技巧——快慢两个指针遍历链表,当fast遍历完成时,slow即为middle结点(证明分n为奇偶情况);当n为偶数时,middle结点有两个,此时slow为左middle结点。换句话说,无论n为奇数或偶数,此时的slow为右半部分子链表的第一个结点的前驱结点。

  1. public boolean isPalindrome(ListNode head) {
  2. if (head == null) return true;
  3. ListNode slow = head, fast = head, p;
  4. while (fast.next != null && fast.next.next != null) {
  5. slow = slow.next;
  6. fast = fast.next.next;
  7. }
  8. ListNode q = reverseList(slow.next); // the first node of the right half is `slow.next`
  9. for (p = head; q != null; p = p.next, q = q.next) {
  10. if (p.val != q.val) return false;
  11. }
  12. return true;
  13. }

143. Reorder List
对于除去首结点外的链表,将右半部分子链表从后往前依次插入进左半部分链表。解决思路与上类似,找出middle结点,然后依次插入。值得注意:Java的对象传参是引用类型,需要更新左半部份子链表的最后一个结点的next指针,不然则链表的结点的无限循环导致OOM。

  1. public void reorderList(ListNode head) {
  2. if (head == null || head.next == null) return;
  3. ListNode slow = head.next, fast = head.next;
  4. while (fast.next != null && fast.next.next != null) {
  5. slow = slow.next;
  6. fast = fast.next.next;
  7. }
  8. ListNode p, q = reverseList(slow.next);
  9. slow.next = null; // update the next pointer of the left half's last node
  10. for (p = head; q != null; ) {
  11. ListNode pNext = p.next, qNext = q.next;
  12. p.next = q; // insert qNode into the next of p node
  13. q.next = pNext;
  14. p = pNext;
  15. q = qNext;
  16. }
  17. }

160. Intersection of Two Linked Lists
求两个链表相交的第一个结点PP。假定两个链表的长度分别为m、n,相交的第一个结点PP分别距离两个链表的首结点为a、b,则根据链表相交的特性:两个链表的尾节点都是同一个,即m-a = n-b;移项后有m+b = n+a。根据上述性质,在遍历完第一个链表后,再往右b个结点,即到达了结点PP。

  1. public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
  2. if (headA == null || headB == null) return null;
  3. ListNode ptrA = headA, ptrB = headB;
  4. while (ptrA != ptrB) { // in case ptrA == ptrB == null
  5. ptrA = (ptrA != null) ? ptrA.next : headB;
  6. ptrB = (ptrB != null) ? ptrB.next : headA;
  7. }
  8. return ptrA;
  9. }

2. Add Two Numbers
模拟两个链表的加法。开始的时候没理解清楚题意,被坑了多次WA。链表的head表示整数的个位,则应从首端对齐开始做加法。

  1. public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
  2. ListNode head = new ListNode(-1);
  3. boolean carry = false; // mark whether has carry
  4. for (ListNode p = l1, q = l2, r = head; p != null || q != null || carry; r = r.next) {
  5. int pVal = (p == null) ? 0 : p.val;
  6. int qVal = (q == null) ? 0 : q.val;
  7. int sum = carry ? pVal + qVal + 1 : pVal + qVal;
  8. carry = sum >= 10;
  9. r.next = new ListNode(sum % 10);
  10. if (p != null) p = p.next;
  11. if (q != null) q = q.next;
  12. }
  13. return head.next;
  14. }

445. Add Two Numbers II
与上一题不同的是,链表的head表示整数的最高位,则应是尾端对齐相加。为了尾端对齐,将采用stack来逆序链表,之后相加步骤与上类似;但创建新链表应使用头插法。

  1. public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
  2. ListNode p;
  3. Stack<Integer> s1 = new Stack<>();
  4. Stack<Integer> s2 = new Stack<>();
  5. for (p = l1; p != null; p = p.next) {
  6. s1.push(p.val);
  7. }
  8. for (p = l2; p != null; p = p.next) {
  9. s2.push(p.val);
  10. }
  11. ListNode head = new ListNode(-1);
  12. boolean carry = false; // mark whether has carry
  13. for (ListNode r = null; !s1.isEmpty() || !s2.isEmpty() || carry; ) {
  14. int pVal = (s1.isEmpty()) ? 0 : s1.pop();
  15. int qVal = (s2.isEmpty()) ? 0 : s2.pop();
  16. int sum = carry ? pVal + qVal + 1 : pVal + qVal;
  17. carry = sum >= 10;
  18. ListNode node = new ListNode(sum % 10);
  19. node.next = r;
  20. head.next = node;
  21. r = node;
  22. }
  23. return head.next;
  24. }
如需转载,请注明作者及出处.
作者:Treant

链表Linked List的更多相关文章

  1. 算法与数据结构基础 - 链表(Linked List)

    链表基础 链表(Linked List)相比数组(Array),物理存储上非连续.不支持O(1)时间按索引存取:但链表也有其优点,灵活的内存管理.允许在链表任意位置上插入和删除节点.单向链表结构一般如 ...

  2. 数据结构之链表(Linked list)

    说明:如果仔细阅读完全文后,可能感觉有些不统一,这里先说明下原因. 链表尾引用不统一:在介绍单链表时,只有一个链表首部的引用(head) 指向第一个节点.你看到后面关于双链表及循环列表时,除了指向第一 ...

  3. 【LeetCode题解】链表Linked List

    1. 链表 数组是一种顺序表,index与value之间是一种顺序映射,以\(O(1)\)的复杂度访问数据元素.但是,若要在表的中间部分插入(或删除)某一个元素时,需要将后续的数据元素进行移动,复杂度 ...

  4. 数据结构与算法 —— 链表linked list(01)

    链表(维基百科) 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer).由于不必须按顺序存储, ...

  5. 数据结构与算法 —— 链表linked list(03)

    继续关于linked list的算法题: 删除排序链表中的重复元素 给定一个排序链表,删除所有重复的元素使得每个元素只留下一个. 案例: 给定 1->1->2,返回 1->2 给定  ...

  6. [Java]LeetCode141. 环形链表 | Linked List Cycle

    Given a linked list, determine if it has a cycle in it. Follow up:Can you solve it without using ext ...

  7. 带环链表 linked list cycle

    1 [抄题]: 给定一个链表,判断它是否有环. [思维问题]: 反而不知道没有环怎么写了:快指针fast(奇数个元素)或fast.next(偶数个元素) == null [一句话思路]: 快指针走2步 ...

  8. LeetCode 141:环形链表 Linked List Cycle

    给定一个链表,判断链表中是否有环. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始). 如果 pos 是 -1,则在该链表中没有环. Given a l ...

  9. LeetCode 141. 环形链表(Linked List Cycle) 19

    141. 环形链表 141. Linked List Cycle 题目描述 给定一个链表,判断链表中是否有环. 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 ...

随机推荐

  1. 动词 + to do、动词 + doing

    1. 含义有重大区别 动词+to do 与 动词 + doing,具有较大含义上的差别的动词主要有: stop finish forget 在这些单词的后面,自然 to do 表示未做的事,doing ...

  2. 9.4 Binder系统_驱动情景分析_服务使用过程

    5. 服务使用过程 test_client进程: 用户态: (1)已结获得了“hello”服务,handle=1; (2)构造数据:code(那个函数)和函数参数 (3)发送ioctl后进入内核态,先 ...

  3. 使用boost::property_tree生成带attribute的xml

    曾经写过一篇"使用Boost property tree来解析带attribute的xml", 但是还有姐妹篇一直没贴.看看前一篇贴了都快都快3年了,时间过的真快. 这一小篇就算是 ...

  4. Android 利用an框架快速实现网络请求(含下载上传文件)

    作者:Bgwan链接:https://zhuanlan.zhihu.com/p/22573081来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. an框架的网络框架是完全 ...

  5. 35、在编译Linux内核中增加程序需要完成以下3项工作

    在编译Linux内核中增加程序需要完成以下3项工作: 将编写的源代码拷入Linux内核源代码的相应目录. 在目录的Kconfig文件中增加关于新源代码对应项目的编译配置选项 在目录的Makefile文 ...

  6. 微服务API模拟框架frock介绍

    本文来源于我在InfoQ中文站翻译的文章,原文地址是:http://www.infoq.com/cn/news/2016/02/introducing-frock Urban Airship是一家帮助 ...

  7. 如何在hadoop中控制map的个数 分类: A1_HADOOP 2015-03-13 20:53 86人阅读 评论(0) 收藏

    hadooop提供了一个设置map个数的参数mapred.map.tasks,我们可以通过这个参数来控制map的个数.但是通过这种方式设置map的个数,并不是每次都有效的.原因是mapred.map. ...

  8. C# .NET Socket

    C# .NET Socket 简单实用框架 背景: 首先向各位前辈,大哥哥小姐姐问一声好~ 这是我第一次写博客,目前为一个即将步入大四的学生,上学期在一家公司实习了半年,后期发现没有动力,而且由于薪水 ...

  9. UE4 Editor快捷键(ShortCut Key)

    转载请注明出处,所有权利保留. Unreal Engine4的快捷键现在无官方文档,因为他们工作比较忙啊. 记录时间:2014-10-15 现在自己整理一个,仅供参考. 因为他们的team成员说的还有 ...

  10. Loader之二:CursorLoader基本实例 分类: H1_ANDROID 2013-11-16 10:50 5447人阅读 评论(0) 收藏

    参考APIDEMO:sdk\samples\android-19\content\LoaderCursor 1.创建主布局文件,里面只包含一个Fragment. <FrameLayout xml ...