毛毛张分享的本篇博客是对《代码随想录》中的链表一章的LeetCode题目解法的总结,一共包含了八道题目,毛毛张全部使用Java代码进行编写,并在第3题中给出了毛毛张递归的解法,这个题目如果清楚递归的逻辑,用递归求解是比较方便的
[toc]
1.链表基础理论
在刷下面的LeetCode题目之前,让我们先来回顾一下链表的相关基础知识!
1.1 什么是链表?
链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思),链表的入口节点称为链表的头节点也就是head,如下图所示
1.2 链表的类型
单链表:上面说的就是单链表,单链表中的指针域只能指向节点的下一个节点
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点,因此双链表既可以向前查询也可以向后查询,如下图所示:
循环链表: 即链表首尾相连,循环链表可以用来解决约瑟夫问题,如下图所示:
1.3 链表结点定义
在平时刷LeetCode的时候,链表的结点都默认定义好了,但是在面试的是时候可能不会给出,所以大家一定要熟悉链表结点的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ListNode { int val; ListNode next; public ListNode () { } public ListNode (int val) { this .val = val; } public ListNode (int val, ListNode next) { this .val = val; this .next = next; } }
1.4 链表的操作
对于不同类型的数据结构无外乎四种操作:增、删、改、查,对于链表来说,后面两种操作比较简单,复杂的是删除结点和增加结点
1.4.1 删除结点
下图展示了要删除一个结点的操作的图解,假设要删除的结点是D结点,如果要删除D结点就只需要找到要删除的结点的前一个结点C,然后将C结点的next指针指向E结点就可以了
1.4.2 增加结点
1.5 小结
链表的长度是不固定的,并且可以动态增删,适合数量不固定,频繁增删,较少查询的场景
上面知识对链表的相关知识点进行了一个简单的回顾,更多的可以参见毛毛张的这篇博客:
LeetCode标签:简单
2.1 题目描述 给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
1 2 输入:head = [1,2,6,3,4,5,6] , val = 6 输出:[1,2,3,4,5]
示例 2:
示例 3:
提示:
列表中的节点数目在范围 [0, 104]
内
1 <= Node.val <= 50
0 <= val <= 50
2.2 题解 2.2.1 迭代 写法1:直接使用原来的链表来进行移除结点操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public ListNode removeElements (ListNode head, int val) { while (head != null && head.val ==val){ head = head.next; } ListNode pre = head; while (pre != null ){ if (pre.next != null && val == pre.next.val){ pre.next = pre.next.next; }else { pre = pre.next; } } return head; } }
图解: 以链表 1->2->6->3->4->5->6, val = 6 为例
首先,pre 指向 head
当 pre 指向的节点的下一个节点的值不等于 val 时,右移 pre 进行遍历
等于时,将 pre 指向其下一个节点的下一个节点,相当于删除了 pre 之前指向节点的下一个节点
继续遍历,操作步骤跟前面两步一样
处理后的结果:
看到上面的代码发现有两种情况,原因就是头节点也存储了元素,如果要删除的第一个结点的时候,无法知道该头节点的前一个结点,无法进行统一的删除结点操作,于是我们可以虚设一个头节点,这样就方便执行统一的删除操作
写法2:设置一个虚拟头结点在进行移除节点操作 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) { if (head == null ) return head; ListNode newHead = new ListNode (0 ,head); ListNode pre = newHead; while (pre != null ){ if (pre.next != null && pre.next.val == val){ pre.next = pre.next.next; }else { pre = pre.next; } } return newHead.next; } }
代码说明:
设置一个虚拟头节点之后,要删除题设链表的第一个结点的操作和后面一个结点的操作就是相同的,整个代码逻辑比较清晰,毛毛张就不在这里过多阐述了
2.2.2 递归 分析:
链表具有天然的递归性,我们要删除整个链表中符合条件的结点,可以递归到更短的链表,删除短的链表中符合条件的结点
上面的迭代法是从前往后进行删除,因此在删除前需要知道要删除的结点的前一个结点,而递归法的逻辑则是从后往前处理
递归法的代码比较简洁,大家可以先看代码,然后再看图解,大家一定要理解这个逻辑,我们可以通过这个思路来求解第三题
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public ListNode removeElements (ListNode head, int val) { if (head == null ) return null ; head.next = removeElements(head.next,val); return head.val == val ? head.next : head; } }
图解:
看完递归求解上面这个题目,大家可以使用递归法来求解下面这道题目
LeetCode标签:中等
3.1 题目描述 给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
1 2 输入:head = [1,2,3,4 ,5 ], n = 2 输出:[1,2,3,5 ]
示例 2:
示例 3:
提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
进阶: 你能尝试使用一趟扫描实现吗?
3.2 题解 3.2.1 迭代 方法1:暴力解法
首先计算链表的长度$Length$
删除倒数第$N$个结点,就是删除正着数的第$Length-N+1$个结点
删除第$Length-N+1$个结点,需要找到要删除结点的前一个结点,就是第$Length-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 class Solution { public ListNode removeNthFromEnd (ListNode head, int n) { int length = getLength(head); int index = length - n; if (index == 0 ) return head.next; ListNode pre = null ; ListNode cur = head; for (int i=0 ;i<index;i++){ pre = cur; cur = cur.next; } pre.next = cur.next; return head; } public int getLength (ListNode head) { int length = 0 ; while (head != null ){ length++; head = head.next; } return length; } }
方法2:借助栈 思路:
我们也可以在遍历链表的同时将所有节点依次入栈。
根据栈「先进后出」的原则,我们弹出栈的第 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 class Solution { public ListNode removeNthFromEnd (ListNode head, int n) { ListNode newHead = new ListNode (0 ,head); Stack<ListNode> stack = new Stack <>(); ListNode cur = newHead; while (cur != null ){ stack.push(cur); cur = cur.next; } for (int i=0 ;i<n;i++){ stack.pop(); } ListNode pre = stack.pop(); pre.next = pre.next.next; return newHead.next; } }
图解:
方法3:双指针法 思路:
由于我们需要找到倒数第 n 个节点,因此我们可以使用两个指针 first 和 second 同时对链表进行遍历,并且 first 比 second 超前 n 个节点。当 first 遍历到链表的末尾时,second 就恰好处于倒数第 n 个节点。
具体地,初始时 first 和 second 均指向头节点。我们首先使用 first 对链表进行遍历,遍历的次数为 n 。此时,first 和 second 之间间隔了 n −1 个节点,即 first 比 second 超前了 n 个节点。
在这之后,我们同时使用 first 和 second 对链表进行遍历。当 first 遍历到链表的末尾(即 first 为空指针)时,second 恰好指向倒数第 n 个节点。
根据方法一和方法二,如果我们能够得到的是倒数第 n 个节点的前驱节点而不是倒数第 n 个节点的话,删除操作会更加方便。因此我们可以考虑在初始时将 second 指向哑节点,其余的操作步骤不变。这样一来,当 first 遍历到链表的末尾时,second 的下一个节点 就是我们需要删除的节点。
图解:
代码:
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 removeNthFromEnd (ListNode head, int n) { ListNode newHead = new ListNode (0 ,head); ListNode first = newHead; ListNode second = newHead; for (int i=0 ;i<=n;i++){ first = first.next; } while (first != null ){ first = first.next; second = second.next; } second.next = second.next.next; return newHead.next; } }
3.2.2 递归 思路:
上面介绍过一种方法,利用栈来存储元素,迭代到链表的尾部,再从栈顶弹出n个结点就是倒数第n个结点
然而递归的本质也是栈,所以我们也可以尝试使用递归的方式,递归到链表的尾部,再从后往前处理
代码如下,代码本身比较简洁,如果对代码的逻辑不理解的可以结合着下面的动态图来理解
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { int count = 0 ; public ListNode removeNthFromEnd (ListNode head, int n) { if (head == null ) return null ; head.next = removeNthFromEnd(head.next,n); count++; if (count == n){ return head.next; }else { return head; } } }
图解:
4.1 题目描述 你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val
和 next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList
类:
MyLinkedList()
初始化 MyLinkedList
对象。
int get(int index)
获取链表中下标为 index
的节点的值。如果下标无效,则返回 -1
。
void addAtHead(int val)
将一个值为 val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
void addAtTail(int val)
将一个值为 val
的节点追加到链表中作为链表的最后一个元素。
void addAtIndex(int index, int val)
将一个值为 val
的节点插入到链表中下标为 index
的节点之前。如果 index
等于链表的长度,那么该节点会被追加到链表的末尾。如果 index
比长度更大,该节点将 不会插入 到链表中。
void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为 index
的节点。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 输入 ["MyLinkedList" , "addAtHead" , "addAtTail" , "addAtIndex" , "get" , "deleteAtIndex" , "get" ] [[], [1 ], [3 ], [1 , 2 ], [1 ], [1 ], [1 ]] 输出 [null , null , null , null , 2 , null , 3 ] 解释 MyLinkedList myLinkedList = new MyLinkedList(); myLinkedList.addAtHead(1 ); myLinkedList.addAtTail(3 ); myLinkedList.addAtIndex(1 , 2 ); myLinkedList.get (1 ); myLinkedList.deleteAtIndex(1 ); myLinkedList.get (1 );
提示:
0 <= index, val <= 1000
请不要使用内置的 LinkedList 库。
调用 get
、addAtHead
、addAtTail
、addAtIndex
和 deleteAtIndex
的次数不超过 2000
。
4.2 题解
4.2.1 单链表 写法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 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 class ListNode { int val; ListNode next; ListNode(){} ListNode(int val) { this .val=val; } } class MyLinkedList { int size; ListNode head; public MyLinkedList () { this .size = 0 ; this .head = new ListNode (0 ); } public int get (int index) { if (index < 0 || index >= this .size) return -1 ; ListNode cur = head; for (int i=0 ;i<=index;i++){ cur = cur.next; } return cur.val; } public void addAtHead (int val) { ListNode node = new ListNode (val); ListNode temp = head.next; node.next = temp; head.next = node; this .size++; } public void addAtTail (int val) { ListNode node = new ListNode (val); ListNode cur = head; for (int i=0 ;i<this .size;i++){ cur = cur.next; } cur.next = node; this .size++; } public void addAtIndex (int index, int val) { if (index < 0 || index > this .size){ return ; }else if (index == this .size){ addAtTail(val); return ; } ListNode cur = head; for (int i=0 ;i<index;i++){ cur = cur.next; } ListNode node = new ListNode (val); ListNode temp = cur.next; node.next = temp; cur.next = node; this .size++; } public void deleteAtIndex (int index) { if (index < 0 || index >= this .size) return ; size--; if (index == 0 ){ head = head.next; return ; } ListNode pre = head; for (int i=0 ;i<index;i++){ pre = pre.next; } pre.next = pre.next.next; } }
写法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 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 class ListNode { int val; ListNode next; ListNode(){} ListNode(int val) { this .val=val; } } class MyLinkedList { int size; ListNode head; public MyLinkedList () { this .size = 0 ; this .head = new ListNode (0 ); } public int get (int index) { if (index < 0 || index >= this .size) return -1 ; ListNode cur = head; for (int i=0 ;i<=index;i++){ cur = cur.next; } return cur.val; } public void addAtHead (int val) { addAtIndex(0 ,val); } public void addAtTail (int val) { addAtIndex(this .size,val); } public void addAtIndex (int index, int val) { if (index > size) return ; if (index < 0 ) index = 0 ; this .size++; ListNode pre = head; for (int i=0 ;i<index;i++){ pre = pre.next; } ListNode node = new ListNode (val); node.next = pre.next; pre.next = node; } public void deleteAtIndex (int index) { if (index < 0 || index >= this .size) return ; size--; if (index == 0 ){ head = head.next; return ; } ListNode pre = head; for (int i=0 ;i<index;i++){ pre = pre.next; } pre.next = pre.next.next; } }
4.2.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 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 120 121 122 123 124 125 126 127 128 class ListNode { int val; ListNode next; ListNode pre; ListNode(){} ListNode(int val) { this .val=val; } } class MyLinkedList { int size; ListNode head; ListNode tail; public MyLinkedList () { this .size = 0 ; this .head = new ListNode (0 ); this .tail = new ListNode (0 ); head.next = tail; tail.pre = head; } public int get (int index) { if (index < 0 || index >= this .size) return -1 ; ListNode cur; if (index + 1 <= size - index) { cur = this .head; for (int i = 0 ; i <= index; i++) { cur = cur.next; } } else { cur = this .tail; for (int i = 0 ; i < size - index; i++) { cur = cur.pre; } } return cur.val; } public void addAtHead (int val) { addAtIndex(0 ,val); } public void addAtTail (int val) { addAtIndex(this .size,val); } public void addAtIndex (int index, int val) { if (index > size) return ; index = Math.max(0 ,index); ListNode pre,post; if (index < size - index){ pre = this .head; for (int i = 0 ; i < index; i++){ pre = pre.next; } post = pre.next; }else { post = this .tail; for (int i = 0 ; i < size-index; i++){ post = post.pre; } pre = post.pre; } ListNode node = new ListNode (val); node.pre = pre; node.next = post; pre.next = node; post.pre = node; this .size++; } public void deleteAtIndex (int index) { if (index < 0 || index >= this .size) return ; ListNode pre,post; if (index < size - index){ pre = this .head; for (int i = 0 ; i < index; i++){ pre = pre.next; } post = pre.next.next; }else { post = this .tail; for (int i = 0 ; i < size - index -1 ; i++){ post = post.pre; } pre = post.pre.pre; } pre.next = post; post.pre = pre; this .size--; } }
LeetCode标签:简单
5.1 题目描述 给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
1 2 输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
示例 2:
示例 3:
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
进阶: 链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
5.2 题解
5.2.1 迭代
迭代法的思路就是在迭代原始链表的元素的过程中,用头插法创建一个新的链表就可以实现反转的目的
写法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 class Solution { public ListNode reverseList (ListNode head) { ListNode newHead = null ; ListNode cur = head; while (cur != null ){ int val = cur.val; ListNode node = new ListNode (val); if (newHead == null ){ newHead = node; }else { ListNode temp = newHead; node.next = temp; newHead = node; } cur = cur.next; } return newHead; } }
优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public ListNode reverseList (ListNode head) { ListNode newHead = null ; ListNode cur = head; while (cur != null ){ ListNode next = cur.next; cur.next = newHead; newHead = cur; cur = next; } return newHead; } }
图解:
5.2.2 递归 方式1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public ListNode reverseList (ListNode head) { if (head == null ) return null ; if (head.next == null ) return head; ListNode newHead = reverseList(head.next); head.next.next = head; head.next = null ; ListNode newHead; } }
方式2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public ListNode reverseList (ListNode head) { return reverse(null ,head); } public ListNode reverse (ListNode pre,ListNode cur) { if (cur == null ) return pre; ListNode temp = cur.next; cur.next = pre; return reverse(cur,temp); } }
LeetCode标签:中等
6.1 题目描述 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
1 2 输入:head = [1,2,3,4 ] 输出:[2,1,4,3 ]
示例 2:
示例 3:
提示:
链表中节点的数目在范围 [0, 100]
内
0 <= Node.val <= 100
6.2 题解 6.2.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 class Solution { public ListNode swapPairs (ListNode head) { ListNode newHead = new ListNode (0 ,head); ListNode pre = newHead; while (pre.next != null && pre.next.next != null ){ ListNode temp1 = pre.next; ListNode temp2 = pre.next.next.next; pre.next = pre.next.next; pre.next.next = temp1; temp1.next = temp2; pre = pre.next.next; } return newHead.next; } }
6.2.2 递归 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public ListNode swapPairs (ListNode head) { if (head == null || head.next == null ) return head; ListNode newHead = head.next; head.next = swapPairs(newHead.next); newHead.next = head; return newHead; } }
LeetCode标签:简答
7.1 题目描述 给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意 ,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal
- 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA
- 第一个链表
listB
- 第二个链表
skipA
- 在 listA
中(从头节点开始)跳到交叉节点的节点数
skipB
- 在 listB
中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA
和 headB
传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
1 2 3 4 5 6 输入:intersectVal = 8 , listA = [4,1,8,4 ,5 ], listB = [5,6,1,8 ,4 ,5 ], skipA = 2 , skipB = 3 输出:Intersected at '8 ' 解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0 )。 从各自的表头开始算起,链表 A 为 [4,1,8,4 ,5 ],链表 B 为 [5,6,1,8 ,4 ,5 ]。 在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 — 请注意相交节点的值不为 1 ,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
示例 2:
1 2 3 4 5 输入:intersectVal = 2 , listA = [1 ,9 ,1 ,2 ,4 ], listB = [3 ,2 ,4 ], skipA = 3 , skipB = 1 输出:Intersected at '2' 解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0 )。 从各自的表头开始算起,链表 A 为 [1 ,9 ,1 ,2 ,4 ],链表 B 为 [3 ,2 ,4 ]。 在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
1 2 3 4 5 输入:int ersectVal = 0 , listA = [2 ,6 ,4 ], listB = [1 ,5 ], skipA = 3 , skipB = 2 输出:null 解释:从各自的表头开始算起,链表 A 为 [2 ,6 ,4 ],链表 B 为 [1 ,5 ]。 由于这两个链表不相交,所以 int ersectVal 必须为 0 ,而 skipA 和 skipB 可以是任意值。 这两个链表不相交,因此返回 null 。
提示:
listA
中节点数目为 m
listB
中节点数目为 n
1 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA
和 listB
没有交点,intersectVal
为 0
如果 listA
和 listB
有交点,intersectVal == listA[skipA] == listB[skipB]
进阶: 你能否设计一个时间复杂度 O(m + n)
、仅用 O(1)
内存的解决方案?
7.2 题解
7.2.1 方法1 - 哈希表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Solution { public ListNode getIntersectionNode (ListNode headA, ListNode headB) { Set<ListNode> set = new HashSet <>(); ListNode cur = headA; while (cur != null ){ set.add(cur); cur = cur.next; } cur = headB; while (cur != null ){ if (set.contains(cur)) return cur; cur = cur.next; } return null ; } }
7.2.2 方法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 32 33 34 35 36 37 38 39 40 41 42 43 public class Solution { public ListNode getIntersectionNode (ListNode headA, ListNode headB) { int lenA = 0 ; int lenB = 0 ; ListNode temp = headA; while (temp != null ){ lenA++; temp = temp.next; } temp = headB; while (temp != null ){ lenB++; temp = temp.next; } if (lenA > lenB){ for (int i=0 ;i<lenA-lenB;i++){ headA = headA.next; } }else { for (int i=0 ;i<lenB-lenA;i++){ headB = headB.next; } } ListNode node = null ; while (headA != null ){ if (headA == headB){ node = headA; break ; } headA = headA.next; headB = headB.next; } return node; } }
7.2.3 方法3 1 2 3 4 5 6 7 8 9 10 11 12 13 public class Solution { public ListNode getIntersectionNode (ListNode headA, ListNode headB) { ListNode p1 = headA, p2 = headB; while (p1 != p2) { p1 = p1 == null ? headB : p1.next; p2 = p2 == null ? headA : p2.next; } return p1; } }
复杂度分析:
时间复杂度: $O(m+n)$,其中$m$和$n$分别是链表$headA$和$headB$的长度。两个指针同时遍历两个链表,每个指针遍历两个链表各一次
空间复杂度: $O(1)$
LeetCode标签:简单
8.1 题目描述 给你一个链表的头节点 head
,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos
不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true
。 否则,返回 false
。
示例 1:
1 2 3 输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
1 2 3 输入:head = [1,2], pos = 0 输出:true 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
1 2 3 输入:head = [1], pos = -1 输出:false 解释:链表中没有环。
提示:
链表中节点的数目范围是 [0, 104]
-105 <= Node.val <= 105
pos
为 -1
或者链表中的一个 有效索引 。
进阶: 你能用 O(1)
(即,常量)内存解决此问题吗?
8.2 题解 方法1 - 哈希表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Solution { public boolean hasCycle (ListNode head) { ListNode cur = head; Set<ListNode> visited = new HashSet <ListNode>(); while (cur != null ) { if (visited.contains(cur)) { return true ; } else { visited.add(cur); } cur = cur.next; } return false ; } }
方法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 public class Solution { public boolean hasCycle (ListNode head) { ListNode fast = head; ListNode slow = head; while (fast != null && fast.next != null ){ fast = fast.next.next; slow = slow.next; if (fast == slow){ return true ; } } return false ; } }
LeetCode标签:中等
9.2 题目描述 给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始 )。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递 ,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
1 2 3 输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
1 2 3 输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
1 2 3 输入:head = [1 ], pos = -1 输出:返回 null 解释:链表中没有环。
提示:
链表中节点的数目范围在范围 [0, 104]
内
-105 <= Node.val <= 105
pos
的值为 -1
或者链表中的一个有效索引
进阶: 你是否可以使用 O(1)
空间解决此题?
9.2 题解 方法1 - 哈希表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Solution { public ListNode detectCycle (ListNode head) { ListNode cur = head; Set<ListNode> visited = new HashSet <ListNode>(); while (cur != null ) { if (visited.contains(cur)) { return cur; } else { visited.add(cur); } cur = cur.next; } return null ; } }
方法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 public class Solution { public ListNode detectCycle (ListNode head) { ListNode fast = head; ListNode slow = head; while (fast != null && fast.next != null ){ fast = fast.next.next; slow = slow.next; if (fast == slow){ ListNode index1 = fast; ListNode index2 = head; while (index1 != index2){ index1 = index1.next; index2 = index2.next; } return index1; } } return null ; } }
10.总结
链表应该是我们接触数据结构的第一种比较复杂的数据结构,上面毛毛张分享了关于链表的8道题目,并且都尽量从迭代法和递归法的角度分享的解法
关键点:
如果在关于链表的删除和增加操作中,如果使用迭代法 ,经常使用到双指针,找到要删除的结点的前面一个结点比较关键
如果使用递归法 ,通常是递归到链表的底部,从链表的底部执行操作,大家可以仔细体会一下第3道、第4道题目
参考文献