1、反转链表
1.1 环境准备,可以自己先尝试实现
/**
* @Author Miku
* @Date 2024/09/02 09:54
* @Description TODO
* @Version 1.0
*/
public class Solution {
static class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode reverseList(ListNode head) {
// 补充实现代码
return null;
}
public static void main(String[] args) {
ListNode head = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5)))));
Solution solution = new Solution();
ListNode result = solution.reverseList(head);
printList(result); // 输出:5 -> 4 -> 3 -> 2 -> 1
}
private static void printList(ListNode head) {
while (head != null) {
System.out.print(head.val);
head = head.next;
if (head != null) System.out.print(" -> ");
}
System.out.println();
}
}
1.2 实现思路
实际我们看到的这个链表并不完整,完整的链表结尾应该有null,我们遍历链表的时候也是判断当前指针是否是null,为null时代表结束了,所以我们补充完整上图得到:
得到完整之后我们再来想如何反转
a. 了解两个节点之间的关系
如图所示:
反转前:节点1指向节点2
反转后:节点1指向null,节点2指向节点1
现在我们只能操作一个节点,一般都是操作head(头节点),我们需要思考一件事,就是怎么凑齐反转后的那些变量
首先:head头节点可以看作是节点1,当时null和节点2是我们需要新建的变量,为了方便理解,
我们可以用新变量 cur 来表示当前的节点,即在一开始 ListNode cur = head;
所以我们还需要添加两个变量,分别是 pre(上一个) 和 next(下一个),其中 pre = null,next = cur.next
public ListNode reverseList(ListNode head) {
// 补充实现代码
ListNode pre = null;
ListNode cur = head;
ListNode next = cur.next;
return null;
}
b. 扩展一个节点
如图可以知道,一开始 cur 的上一个节点为null,但是当 cur 为节点2时,他的上一个节点就变成了节点1,
所以 pre 和 next 需要跟着我们的 cur 改变而改变,初始化为 pre,next = cur.next
public ListNode reverseList(ListNode head) {
// 补充实现代码
ListNode pre = null;
ListNode cur = head;
while(cur != null) {
// next 需要随着 cur 的变化而变化,所以放循环里面
ListNode next = cur.next;
// pre 需要随着 cur 的变化而变化,所以也要放循环里面
pre = cur;
}
return null;
}
c. 执行反转操作
了解上述之后我们就可以来写反转操作了
执行反转操作无非就是将 cur 的下一个指针指向 pre 罢了
(原来指向下一个的变成指向上一个就是反转了)指向下一个就是cur.next,指向上一个前面保存在 pre 了
这一步操作需要在 pre 发生改变之前执行
// 将 cur 的下一个指针指向 pre,这一步操作需要在 pre 发生改变之前执行
cur.next = pre;
反转之后为了让循环继续,把当前指针变为 next ,注意这个应该在前面都完成的基础上最后一步执行
// 反转已经执行完毕了,该转移到下一个节点了,所以是循环的最后一步
cur = next;
完整代码
public ListNode reverseList(ListNode head) {
// 补充实现代码
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
// next 需要随着 cur 的变化而变化,所以放循环里面
ListNode next = cur.next;
// 将 cur 的下一个指针指向 pre,这一步操作需要在 pre 发生改变之前执行
cur.next = pre;
// pre 需要随着 cur 的变化而变化,所以也要放循环里面
pre = cur;
// 反转已经执行完毕了,该转移到下一个节点了,所以是循环的最后一步
cur = next;
}
// 这一步纯粹是为了方便理解,形成闭环(大白话就是让我们理解最终 pre 就是 head ,因为 cur 为 null,结合图可理解)
head = pre;
return head;
}
2、删除链表的倒数第N个节点
力扣原题:https://leetcode.cn/problems/remove-nth-node-from-end-of-list/
2.1 环境准备,可以自己先尝试实现
/**
* @Author Miku
* @Date 2024/09/02 09:54
* @Description TODO
* @Version 1.0
*/
public class Solution {
static class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode removeNthFromEnd(ListNode head, int n) {
// 补充实现代码
return null;
}
public static void main(String[] args) {
ListNode head = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5)))));
Solution solution = new Solution();
ListNode result = solution.removeNthFromEnd(head, 2);
printList(result); // 输出:1 -> 2 -> 3 -> 5
}
private static void printList(ListNode head) {
while (head != null) {
System.out.print(head.val);
head = head.next;
if (head != null) System.out.print(" -> ");
}
System.out.println();
}
}
2.2 实现思路
a. 扫描删除
扫描一遍链表,得到总长后,根据计算可以得出要删除的是正向的第几个元素,然后删除即可
// 计算总长度 len
int len = 0;
while(head != null) {
++ len;
head = head.next;
}
// 遍历到第 n 个元素
for(int i = 1; i < len - n; i ++) {
head = head.next;
}
// 删除(链表中的删除实际就是将连接的节点跳过一个即可达到删除目的)
head.next = head.next.next;
走到这里应该有兄弟发现有问题了
- 前面计算总长度的时候head节点就已经不在原来的位置了,这里的head都在结尾了,根本不能用head从头遍历,我后面还要拿着头指针从头遍历呢~
- 如果只有一个节点,然后又要删除倒数第一个,那head.next.next 就是 null.next(空指针异常)
解决:
- 添加一个新变量cur = head,然后操作 cur 就好啦,这样就不会影响 节点head
- 加一个空值判断就可以避免空指针了
// 保存head,以便返回需要
ListNode ans = head;
ListNode cur = head;
int len = 0;
while(head != null) {
++ len;
head = head.next;
}
// 使用临时变量遍历
for(int i = 1; i < len - n; i ++) {
cur = cur.next;
}
if(cur.next != null){
cur.next = cur.next.next;
}
return ans;
一执行发现当只有一个元素的时候出错了
错误原因:删除第一个元素的时候,由于我们是从head开始的,所以没办法跳过head这个元素。
解决方案:我们新添加一个助力节点(官方叫哑节点dummy),使用一个节点帮忙后我们既可以通过它跳过head元素,也可以避免空指针现象
// 助力节点,指向头指针
ListNode dummy = new ListNode(0, head);
// 从助力节点开始遍历,避免空指针
ListNode cur = dummy;
int len = 0;
while(head != null) {
++ len;
head = head.next;
}
// 因为多了助力节点,所以要 + 1(加的就是助力节点)
for(int i = 1; i < len - n + 1; i ++) {
cur = cur.next;
}
// 多了助力节点,不会报空啦
cur.next = cur.next.next;
return dummy.next;
完整代码:
/**
* @Author Miku
* @Date 2024/09/02 09:54
* @Description TODO
* @Version 1.0
*/
public class Solution {
static class ListNode {
int val;
ListNode next;
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode removeNthFromEnd(ListNode head, int n) {
// 补充实现代码
ListNode dummy = new ListNode(0, head);
ListNode cur = dummy;
int len = 0;
while(head != null) {
++ len;
head = head.next;
}
for(int i = 1; i < len - n + 1; i ++) {
cur = cur.next;
}
cur.next = cur.next.next;
return dummy.next;
}
public static void main(String[] args) {
ListNode head = new ListNode(1, new ListNode(2, new ListNode(3, new ListNode(4, new ListNode(5)))));
Solution solution = new Solution();
ListNode result = solution.removeNthFromEnd(head, 2);
printList(result); // 输出:1 -> 2 -> 3 -> 5
}
private static void printList(ListNode head) {
while (head != null) {
System.out.print(head.val);
head = head.next;
if (head != null) System.out.print(" -> ");
}
System.out.println();
}
}