本节内容
链表理论基础
203.移除链表元素
707.设计链表
206.反转链表
链表理论基础※ 建议:了解一下链接基础,以及链表和数组的区别
文章链接:https://programmercarl.com/%E9%93%BE%E8%A1%A8%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html
203.移除链表元素※ 建议 : 本题最关键是要理解 虚拟头结点的使用技巧,这个对链表题目很重要。
题目链接: https://leetcode.cn/problems/remove-linked-list-elements/ 文章讲解: https://programmercarl.com/0203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.html
https://www.yuanql.top/2023/06/04/02_leetcode/203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0/
题目分析
方案一 设置一个虚拟的头节点,根据头节点去操作会更加的顺滑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public ListNode removeElements (ListNode head, int val) { ListNode temp = new ListNode (); temp.next = head; head = temp; while (temp != null && temp.next != null ) { while (temp.next != null && temp.next.val == val) { temp.next = temp.next.next; } temp = temp.next; } return head.next; } }
结果 解答成功: 执行耗时:1 ms,击败了45.66% 的Java用户 内存消耗:44.2 MB,击败了28.54% 的Java用户
分析 时间复杂度: 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 class Solution { public ListNode removeElements (ListNode head, int val) { ListNode temp = new ListNode (); temp.next = head; head = temp; while (temp.next != null ) { if (temp.next.val == val) { temp.next = temp.next.next; } else { temp = temp.next; } } return head.next; } }
结果 解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:43.9 MB,击败了61.02% 的Java用户
分析 时间复杂度: O( n )
空间复杂度: O( 1 )
代码随想录 https://programmercarl.com/0203.%E7%A7%BB%E9%99%A4%E9%93%BE%E8%A1%A8%E5%85%83%E7%B4%A0.html
文章中介绍了两种方式,一种是直接对链接进行操作 ,另一种是设置一个虚拟头节点 ,对虚拟头节点进行操作。
使用直接对链接进行操作 的方式操作,需要的头节点进行特殊判断 而设置一个虚拟头节点 ,可以很好的解决此问题,因为链表中的头节点变为了虚拟节点,其不需要特殊判断,只需要进行统一的处理。
代码实现 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 public ListNode removeElements (ListNode head, int val) { if (head == null ) { return head; } ListNode dummy = new ListNode (-1 , head); ListNode pre = dummy; ListNode cur = head; while (cur != null ) { if (cur.val == val) { pre.next = cur.next; } else { pre = cur; } cur = cur.next; } return dummy.next; }public ListNode removeElements (ListNode head, int val) { while (head != null && head.val == val) { head = head.next; } if (head == null ) { return head; } ListNode pre = head; ListNode cur = head.next; while (cur != null ) { if (cur.val == val) { pre.next = cur.next; } else { pre = cur; } cur = cur.next; } return head; }public ListNode removeElements (ListNode head, int val) { while (head!=null && head.val==val){ head = head.next; } ListNode curr = head; while (curr!=null ){ while (curr.next!=null && curr.next.val == val){ curr.next = curr.next.next; } curr = curr.next; } return head; }
707.设计链表※ 建议 : 这是一道考察 链表综合操作的题目,不算容易,可以练一练 使用虚拟头结点
题目链接: https://leetcode.cn/problems/design-linked-list/ 文章讲解: https://programmercarl.com/0707.%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.html
https://www.yuanql.top/2023/06/04/02_leetcode/707.%20%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8/
题目分析
方案一 本方案通过单链表的方式实现。 重点考察代码的编写能力
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 class MyLinkedList { private int val; private MyLinkedList next; public MyLinkedList () { } public int get (int index) { MyLinkedList temp = this ; for (int i = 0 ; i <= index; i++) { if (temp.next == null ) { return -1 ; } temp = temp.next; } return temp.val; } public void addAtHead (int val) { MyLinkedList temp = this .next; this .next = new MyLinkedList (); this .next.val = val; this .next.next = temp; } public void addAtTail (int val) { MyLinkedList temp = this ; while (temp.next != null ) { temp = temp.next; } temp.next = new MyLinkedList (); temp.next.val = val; } public void addAtIndex (int index, int val) { MyLinkedList temp = this ; for (int i = 0 ; i < index; i++) { if (temp.next == null ) { return ; } temp = temp.next; } MyLinkedList temp1 = temp.next; temp.next = new MyLinkedList (); temp.next.val = val; temp.next.next = temp1; } public void deleteAtIndex (int index) { MyLinkedList temp = this ; for (int i = 0 ; i < index; i++) { if (temp.next == null ) { return ; } temp = temp.next; } if (temp.next != null ){ temp.next = temp.next.next; } } }
结果 解答成功: 执行耗时:9 ms,击败了87.73% 的Java用户 内存消耗:43.5 MB,击败了12.62% 的Java用户
代码随想录 https://programmercarl.com/0707.%E8%AE%BE%E8%AE%A1%E9%93%BE%E8%A1%A8.html
这道题目设计链表的五个接口:
获取链表第index个节点的数值
在链表的最前面插入一个节点
在链表的最后面插入一个节点
在链表第index个节点前面插入一个节点
删除链表的第index个节点
可以说这五个接口,已经覆盖了链表的常见操作,是练习链表操作非常好的一道题目
链表操作的两种方式:
直接使用原来的链表来进行操作。
设置一个虚拟头结点在进行操作。
代码实现 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 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 class ListNode { int val; ListNode next; ListNode(){} ListNode(int val) { this .val=val; } }class MyLinkedList { int size; ListNode head; public MyLinkedList () { size = 0 ; head = new ListNode (0 ); } public int get (int index) { if (index < 0 || index >= size) { return -1 ; } ListNode currentNode = head; for (int i = 0 ; i <= index; i++) { currentNode = currentNode.next; } return currentNode.val; } public void addAtHead (int val) { addAtIndex(0 , val); } public void addAtTail (int val) { addAtIndex(size, val); } public void addAtIndex (int index, int val) { if (index > size) { return ; } if (index < 0 ) { index = 0 ; } size++; ListNode pred = head; for (int i = 0 ; i < index; i++) { pred = pred.next; } ListNode toAdd = new ListNode (val); toAdd.next = pred.next; pred.next = toAdd; } public void deleteAtIndex (int index) { if (index < 0 || index >= size) { return ; } size--; if (index == 0 ) { head = head.next; return ; } ListNode pred = head; for (int i = 0 ; i < index ; i++) { pred = pred.next; } pred.next = pred.next.next; } }class ListNode { int val; ListNode next,prev; ListNode() {}; ListNode(int val){ this .val = val; } }class MyLinkedList { int size; ListNode head,tail; public MyLinkedList () { this .size = 0 ; this .head = new ListNode (0 ); this .tail = new ListNode (0 ); head.next=tail; tail.prev=head; } public int get (int index) { if (index<0 || index>=size){ return -1 ; } ListNode cur = this .head; if (index >= size / 2 ){ cur = tail; for (int i=0 ; i< size-index; i++){ cur = cur.prev; } }else { 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(size,val); } public void addAtIndex (int index, int val) { if (index>size){ return ; } if (index<0 ){ index = 0 ; } size++; ListNode pre = this .head; for (int i=0 ; i<index; i++){ pre = pre.next; } ListNode newNode = new ListNode (val); newNode.next = pre.next; pre.next.prev = newNode; newNode.prev = pre; pre.next = newNode; } public void deleteAtIndex (int index) { if (index<0 || index>=size){ return ; } size--; ListNode pre = this .head; for (int i=0 ; i<index; i++){ pre = pre.next; } pre.next.next.prev = pre; pre.next = pre.next.next; } }
206.反转链表※
题目链接: https://leetcode.cn/problems/reverse-linked-list/ 文章讲解: https://programmercarl.com/0206.%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.html
https://www.yuanql.top/2023/06/05/02_leetcode/206.%20%E5%8F%8D%E8%BD%AC%E9%93%BE%E8%A1%A8/
题目分析
需要三个变量,两个用于遍历,一个用于存储中间值
temp:用于存储中间的数值 left:用于指向需要将next指针改变的节点 right:用于记录原链表的下一个节点,主要目的是 left 的next指向改变的时候其就无法找到原链表的下一个节点了。
方案一 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public ListNode reverseList (ListNode head) { if (head == null ) { return head; } ListNode temp = null , left = head, right = head.next; while (right != null ) { left.next = temp; temp = left; left = right; right = right.next; } left.next = temp; return left; } }
结果 解答成功: 执行耗时:0 ms,击败了100.00% 的Java用户 内存消耗:40.3 MB,击败了51.04% 的Java用户
分析 时间复杂度: O( n )
空间复杂度: O( 1 )
代码随想录 https://programmercarl.com/0206.%E7%BF%BB%E8%BD%AC%E9%93%BE%E8%A1%A8.html
双指针 拿有示例中的链表来举例,如动画所示:(纠正:动画应该是先移动pre,在移动cur)
首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public ListNode reverseList (ListNode head) { ListNode prev = null ; ListNode cur = head; ListNode temp = null ; while (cur != null ) { temp = cur.next; cur.next = prev; prev = cur; cur = temp; } return prev; } }
递归 递归遍历三步走: 1、确认递归函数的参数和返回值 2、确定终止条件 3、确认单层递归的逻辑
全面理解递归
递归法相对抽象一些,但是其实和双指针法是一样的逻辑,同样是当cur为空的时候循环结束,不断将cur指向pre的过程。
关键是初始化的地方,可能有的同学会不理解, 可以看到双指针法中初始化 cur = head,pre = NULL,在递归法中可以从如下代码看出初始化的逻辑也是一样的,只不过写法变了。
具体可以看代码(已经详细注释),双指针法写出来之后,理解如下递归写法就不难了,代码逻辑都是一样的。
代码实现 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) { return reverse(null , head); } private ListNode reverse (ListNode prev, ListNode cur) { if (cur == null ) { return prev; } ListNode temp = null ; temp = cur.next; cur.next = prev; return reverse(cur, temp); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { ListNode reverseList (ListNode head) { if (head == null ) return null ; if (head.next == null ) return head; ListNode last = reverseList(head.next); head.next.next = head; head.next = null ; return last; } }