文章目录
- 一、编程题:206. 反转链表(双指针-头插法)
- 解题思路
- 1.思路
- 2.复杂度分析:
- 3.算法图解
- 代码实现
- 二、编程题:203. 移除链表元素
- 解题思路
- 1.思路
- 2.复杂度分析:
- 3.算法图解
- 代码实现
- 三、编程题:328. 奇偶链表(双指针)
- 解题思路
- 1.思路
- 2.复杂度分析:
- 3.算法图解
- 代码实现
- 四、编程题:234. 回文链表(双指针-快慢指针)
- 解题思路
- 1.思路
- 2.复杂度分析:
- 3.算法图解
- 代码实现
- 总结
一、编程题:206. 反转链表(双指针-头插法)
1.题目描述
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 LeetCode题目链接。
2.示例1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
3.示例2:
输入:head = [1,2]
输出:[2,1]
4.示例3:
输入:head = []
输出:[]
5.提示:
- 链表中节点的数目范围是 [0, 5000]
- -5000 <= Node.val <= 5000
6.进阶:
- 链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
解题思路
可以根据滑动窗口的思想来解决本题;
这题采用双指针,本题主要关键点为:
- 1.该怎么去移动这两个指针?这一点要理清楚。这里双指针移动就比较简单了,分别向下移动一步即可;
1.思路
解决方法1(个人想法):
- Step 1.创建指针last指向head,和临时结点temp;
- Step 2.对原链表进行遍历,当last的下一个结点不为空时,则将该结点插入到头结点面前并更新头结点;
- Step 3.重复Step2直到遍历结束,此时头结点就是反转后的链表;
看不懂解释的话,直接看算法图解比较容易理解点
2.复杂度分析:
时间复杂度:O(L),其中L是链表的长度。
空间复杂度:O(1)
3.算法图解
灰色部分代表头指针head(注:本人不会做成流程动画,希望会的朋友可以私信我指点一二,说个软件名字也可以,谢谢)
代码实现
每个代码块都写了注释,方便理解,代码还可以改进;
class Solution {
public ListNode reverseList(ListNode head) {
ListNode temp, last = head;
while(last!=null){
if(last.next != null){
temp = last.next;
last.next = temp.next;
temp.next = head;
head = temp;
}else break;
}
return head;
}
}
提交结果:
二、编程题:203. 移除链表元素
1.题目描述
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。 LeetCode题目链接。
2.示例1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
3.示例2:
输入:head = [], val = 1
输出:[]
4.示例3:
输入:head = [7,7,7,7], val = 7
输出:[]
5.提示:
- 列表中的节点数目在范围 [0, 104] 内
- 1 <= Node.val <= 50
- 0 <= val <= 50
解题思路
这题比较简单,只要遍历链表找到相同值的前驱节点进行操作即可。这里对第一个节点的处理可分为两种:是否带有头节点;
1.思路
解决方法1(个人想法):
1、不带头结点
- Step 1.当head节点为null可直接返回,创建temp来遍历链表;
- Step 2.可以先对第一个节点不做处理,从第二个节点开始进行处理,遍历链表遇到相同的值进行删除即可;
- Step 3.直到遍历完之后再来处理第一个结点,看是否需要删除;
2、带头结点
- Step 1.与前面的思路同理,只需要创建一个头结点来指向链表,这样链表中的结点就可以从第二个结点开始处理了;
看不懂解释的话,直接看算法图解比较容易理解点
2.复杂度分析:
时间复杂度:O(L),其中L是链表的长度。
空间复杂度:O(1)
3.算法图解
红色部分代表删除的节点,灰色部分代表头节点(不带虚拟头节点)(注:本人不会做成流程动画,希望会的朋友可以私信我指点一二,说个软件名字也可以,谢谢)
代码实现
每个代码块都写了注释,方便理解,代码还可以改进;
不带头结点
class Solution {
public ListNode removeElements(ListNode head, int val) {
// 不带头结点
if(head == null) return head;
ListNode temp = head;
while(temp.next != null){
if(temp.next.val == val)
temp.next = temp.next.next;
else temp = temp.next;
}
return head.val == val ? head.next : head;
}
}
带头结点
class Solution {
public ListNode removeElements(ListNode head, int val) {
// 带头结点解决
ListNode first = new ListNode(0, head);
ListNode temp = first;
while(temp.next != null){
if(temp.next.val == val)
temp.next = temp.next.next;
else temp = temp.next;
}
return first.next;
}
}
提交结果:
三、编程题:328. 奇偶链表(双指针)
1.题目描述
给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。LeetCode题目链接。
2.示例1:
输入: head = [1,2,3,4,5]
输出: [1,3,5,2,4]
3.示例2:
输入: head = [2,1,3,5,6,4,7]
输出: [2,3,6,7,1,5,4]
5.提示:
- n == 链表中的节点数
- 0 <= n <= 104
- -106 <= Node.val <= 106
解题思路
这题可以将奇数节点和偶数节点抽离为奇数链表和偶数链表,然后将偶数链表合并在奇数链表即可;
这题采用双指针,本题主要关键点为:
- first指针负责指向奇数节点,second指针偶数节点,然后在遍历过程中维护两个指针即可;
1.思路
解决方法1(个人想法):
- Step 1.创建一个偶数链表的头节点,first指向原链表的头节点(奇数节点),second指向偶数链表的头节点;
- Step 2.对原链表进行遍历,每一步首先更新偶数节点,然后在更新奇数节点;
- Step 3.重复step 2的操作,直到全部节点分离完为止,遍历结束的条件为fisrt.next == null和second.next == null,也就是偶数节点为空或者下一个奇数节点为空时结束循环,;
- Step 4.此时first指向最后一个奇数节点,second指向最后一个偶数节点,最后将fisrt.next指向偶数链表头节点的后继节点temp.next即可;
看不懂解释的话,直接看算法图解比较容易理解点
2.复杂度分析:
时间复杂度:O(L),其中 L 是链表的节点数。需要遍历链表中的每个节点,并更新指针。
空间复杂度:O(1)
3.算法图解
红色部分代表偶数链表,蓝色部分代表奇数链表(注:本人不会做成流程动画,希望会的朋友可以私信我指点一二,说个软件名字也可以,谢谢)
代码实现
每个代码块都写了注释,方便理解,代码还可以改进;
class Solution {
public ListNode oddEvenList(ListNode head) {
if(head == null) return head;
ListNode temp = new ListNode(0);
// ListNode first = new ListNode(0, head);
ListNode first = head, second = temp;
while(first != null){
// 对当前偶数链表进行添加操作
second.next = first.next;
// 切换到偶数链表的最后一个节点
second = second.next;
// 判断偶数节点和下一个奇数节点是否存在
if(first.next != null && second.next != null){
// 将奇数节点进行链接
first.next = second.next;
first = first.next;
}else break;
}
first.next = temp.next;
return head;
}
}
提交结果:
四、编程题:234. 回文链表(双指针-快慢指针)
1.题目描述
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。LeetCode题目链接。
2.示例1:
输入:head = [1,2,2,1]
输出:true
3.示例2:
输入:head = [1,2]
输出:false
5.提示:
- 链表中节点数目在范围[1, 105] 内
- 0 <= Node.val <= 9
6.进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
解题思路
这题采用快慢指针和反转链表来进行解决;
这题采用双指针,本题主要关键点为:
- 1.该怎么去移动这两个指针?这一点要理清楚。这里双指针移动就比较简单了,分别向下移动一步即可;
- 2.在调用next字段之前,始终检查节点是否为空。获取空节点的下一个节点将导致空指针错误。例如,在我们运行fast = fast.next.next之前,需要检查fast和fast.next不为空;
- 3.对链表的后半部分进行反转,然后将前半跟后半部分进行比较,结束之后恢复并返回结果;
1.思路
解决方法1(个人想法):
- Step 1.创建指针fast和slow均指向head,在运行过程中,slow每次向后走一步,fast每次向后走两步,当快指针fast移动到链表的末尾时,慢指针slow恰好到链表的中间;
- Step 2.可以使用206. 反转链表问题中的解决方法来反转链表的后半部分;
- Step 3.比较链表前后两个部分的值可得出结果,当后半部分到达末尾则比较完成;
- Step 4.比较结束之后,通过Step2的方法对链表后半部分进行反转使其恢复原状;
看不懂解释的话,直接看算法图解比较容易理解点
2.复杂度分析:
时间复杂度:O(n),其中 n 指的是链表的大小。
空间复杂度:O(1)。我们只会修改原本链表中节点的指向,而在堆栈上的堆栈帧不超过 O(1)。
3.算法图解
快慢指针找中间节点的前驱节点(红色部分代表快指针,灰色部分代表慢指针)(注:本人不会做成流程动画,希望会的朋友可以私信我指点一二,说个软件名字也可以,谢谢)
反转后半链表
注:下面代码中第一次反转并没有将中间节点的前驱节点与反转后头节点进行连接,也就是打印出来是1-2-2,而不是1-2-1-2
代码实现
每个代码块都写了注释,方便理解,代码还可以改进;
class Solution {
public boolean isPalindrome(ListNode head) {
if(head.next == null) return true;
boolean result = true;
ListNode fast = head, slow = head, even;
// 要找中间节点的前驱节点
slow = searchMiddle(head);
// 根据中间结点反转后边的链表相与前面的链表进行对比
even = reverseList(slow.next);
fast = head;
while(even != null){
if(fast.val != even.val) result = false;
even = even.next;
fast = fast.next;
}
// 恢复链表的可以在将后半在反转一次
slow.next = reverseList(slow.next);
return result;
}
private ListNode searchMiddle(ListNode head) {
// 先用快慢指针找到链表中间节点的前驱节点
ListNode fast = head;
ListNode slow = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
private ListNode reverseList(ListNode head){
ListNode temp = null;
ListNode curr = head;
while (curr.next != null) {
temp = curr.next;
curr.next = temp.next;
temp.next = head;
head = temp;
}
return head;
}
}
提交结果:
总结
1、可以同时使用多个指针。
有时,当你为链表问题设计算法时,可能需要同时跟踪多个结点。您应该记住需要跟踪哪些结点,并且可以自由地使用几个不同的结点指针来同时跟踪这些结点。
2、在许多情况下,你需要跟踪当前结点的前一个结点。
你无法追溯单链表中的前一个结点。因此,您不仅要存储当前结点,还要存储前一个结点。这在双链表中是不同的。