作者:Grey
原文地址:
题目描述见:LeetCode 206. Reverse Linked List
思路如下
对于任何一个节点 cur 来说,记录一个前驱节点 pre (第一个节点的前驱节点是 null )
先用一个临时节点 tmp 记录 cur 的下一个节点,然后设置
cur.next = pre;
pre = cur;
cur = tmp;
以下是示例图
假设原始链表如下

第一个节点的反转流程如下

第二个节点的反转流程如下

最后返回 pre 节点即为反转后的节点。
代码如下
class Solution {
public ListNode reverseList(ListNode cur) {
if (cur == null || cur.next == null) {
return cur;
}
ListNode pre = null;
while (cur != null) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
反转链表也可以用递归方法来实现
定义递归函数 ListNode reverse(ListNode cur),这个递归函数的含义是
反转以 cur 为头的链表,并把反转后的头节点返回。
这个递归函数的 base case 是,只有一个节点的时候,即
if (cur == null || cur.next == null) {
return cur;
}
这种情况下,直接返回当前节点即可。
接下来是普遍情况:

当前来到 cur 节点,c,d,e 已经完成了反转。
此时 cur 需要做如下操作:把 c , d, e 反转后的头节点获取到,假设为 pre , 在上图中,pre 就是 e 节点,然后 cur 再做如下操作
cur.next.next = cur;
cur.next = null;


其中cur.next = null非常重要,只有这样,才能防止出现环。完整代码如下
class Solution {
public ListNode reverseList(ListNode cur) {
return reverse(cur);
}
// 反转cur为头的链表,并把反转后的头节点返回
public ListNode reverse(ListNode cur) {
if (cur == null || cur.next == null) {
return cur;
}
ListNode pre = reverse(cur.next);
cur.next.next = cur;
cur.next = null;
return pre;
}
}
时间复杂度O(N)
空间复杂度O(N)(递归栈占用的空间)
双向链表和单链表的反转类似,每个节点要多处理一次每个节点的前驱指针,
完整代码如下
import java.util.ArrayList;
import java.util.List;
public class Code_ReverseDoubleList {
public static class DoubleNode {
public int value;
public DoubleNode last;
public DoubleNode next;
public DoubleNode(int data) {
value = data;
}
}
public static DoubleNode reverseDoubleList(DoubleNode cur) {
DoubleNode pre = null;
while (cur != null) {
DoubleNode tmp = cur.next;
cur.next = pre;
cur.last = tmp;
pre = cur;
cur = tmp;
}
return pre;
}
}
题目描述见:LeetCode 92. Reverse Linked List II
本题核心依然是反转链表,只是增加了一些变量来记录需要反转链表的头位置和结尾位置,不过需要注意的是,本题的链表开始位置是从 1 开始,所以,如果m = 1 && n != 1,说明反转链表后需要返回新的头部,只要m > 1,反转链表一部分以后,返回原先的头即可。
此外,本题的 follow up提到
Follow up: Could you do it in one pass?
就是遍历一次链表能否解决问题,这里我们可以引入 dummy 节点来方便处理边界情况,具体代码如下
public ListNode reverseBetween(ListNode h, int m, int n) {
if (m == n || h == null || h.next == null) {
return h;
}
ListNode dummy = new ListNode(0, h);
ListNode startPre = dummy;
ListNode start = h;
for (int i = 1; i < m; i++) {
startPre = startPre.next;
start = start.next;
}
ListNode pre = null;
ListNode cur = start;
for (int i = m; i <= n; i++) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
if (m != 1) {
startPre.next = pre;
start.next = cur;
return h;
} else {
start.next = cur;
return pre;
}
}
题目链接:LeetCode 203. Remove Linked List Elements
主要思路就是遍历链表,找到对应值的元素,就做删除操作,对于普遍位置来说,删除操作可以按如下方式进行



不过,需要注意一个边界条件,就是:如果要删除的节点就是头节点,那么经过删除后,会面临要换头的情况。
所以在一开始的时候,需要做如下判断
while (head != null && head.val == val) {
head = head.next;
}
即找到第一个不需要删的节点。
完整代码见
class Solution {
public static ListNode removeElements(ListNode head, int val) {
if (null == head) {
return null;
}
while (head != null && head.val == val) {
head = head.next;
}
if (head == null) {
return null;
}
ListNode c = head;
ListNode n = c.next;
while (n != null) {
if (n.val == val) {
c.next = n.next;
n = n.next;
} else {
c = n;
n = c.next;
}
}
return head;
}
}
题目链接见:LeetCode 2. Add Two Numbers
没有特别的算法,就是要注意每次相加可能会有进位的问题,还有一个边界条件,由于是从左往右依次相加,所以最右侧如果相加后超过了 9 ,那么需要在最右侧的右侧继续进一位。例如:
.....8
+
.....7
=
.....51 <--注意得到5以后,还要继续向右侧进1。
完整代码见:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int val = (l1.val + l2.val) % 10;
ListNode h = new ListNode(val);
int carry = (l1.val + l2.val) / 10;
ListNode cur = h;
l1 = l1.next;
l2 = l2.next;
while (l1 != null && l2 != null) {
val = (l1.val + l2.val + carry) % 10;
carry = (l1.val + l2.val + carry) / 10;
cur.next = new ListNode(val);
cur = cur.next;
l1 = l1.next;
l2 = l2.next;
}
while (l1 != null) {
val = (l1.val + carry) % 10;
carry = (l1.val + carry) / 10;
cur.next = new ListNode(val);
cur = cur.next;
l1 = l1.next;
}
while (l2 != null) {
val = (l2.val + carry) % 10;
carry = (l2.val + carry) / 10;
cur.next = new ListNode(val);
cur = cur.next;
l2 = l2.next;
}
if(carry != 0) {
cur.next = new ListNode(carry);
}
return h;
}
LeetCode 25. Reverse Nodes in k-Group
本题需要设计两个方法:
第一个方法ListNode getKGroupEnd(ListNode start, int k):从链表 start 位置开始,数够 k 个位置,返回 k 个位置后的那个节点。
比如链表为:
...-> start -> b -> c -> d -> e
k = 3
表示从 start 开始,数够 3 个,所以返回 c 节点
如果是下述情况
...-> start -> b -> c -> null
k = 6
由于 start 后面不够 6 个,所以返回 null。
public static ListNode getKGroupEnd(ListNode start, int k) {
while (--k != 0 && start != null) {
start = start.next;
}
return start;
}
第二个方法void reverse(ListNode start, ListNode end),表示反转 start 到 end 之间的链表。
例如:原链表为:
....->a->b->c->d->e....
假设start = a, end = d
经过reverse方法,会变成
...d->c->b->a->e.....
reverse方法也相对比较简单,实现代码如下
public static void reverse(ListNode start, ListNode end) {
end = end.next;
ListNode pre = null;
ListNode cur = start;
while (cur != end) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
start.next = end;
}
有了上述两个方法,我们可以比较方便实现原题要求,主流程如下
public static ListNode reverseKGroup(ListNode head, int k) {
ListNode start = head;
ListNode end = getKGroupEnd(start, k);
if (end == null) {
return head;
}
// 第一组凑齐了!
head = end;
reverse(start, end);
// 上一组的结尾节点
ListNode lastEnd = start;
while (lastEnd.next != null) {
start = lastEnd.next;
end = getKGroupEnd(start, k);
if (end == null) {
return head;
}
reverse(start, end);
lastEnd.next = end;
lastEnd = start;
}
return head;
}
题目描述见:LeetCode 876. Middle of the Linked List
本题主要解决的问题是:
如果一个链表中的节点个数是奇数,则返回中点;如果个数是偶数,则返回下中点。
通常这类问题都是使用快慢指针来做,思路如下
设置一个快指针 fast,一个慢指针 slow, 快指针一次走两步,慢指针一次走一步,快指针走到结尾的时候,慢指针正好到中点位置。
完整代码见:
public ListNode middleNode(ListNode h) {
if (null == h || h.next == null) {
return h;
}
ListNode slow = h;
ListNode fast = h;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
快慢指针还可以解决如下类似的问题,只不过是初始化快慢指针的节点有所不同而已。
输入链表头节点,奇数长度返回中点,偶数长度返回上中点;
输入链表头节点,奇数长度返回中点,偶数长度返回下中点;
输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个;
输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个。
解决上述问题,建议用举例子的方式来确定快慢指针的移动。
题��链接为:LeetCode 234. Palindrome Linked List
本题比较好理解的一种解法是使用栈的方式,先将节点全部入栈,然后依次弹出并和原链表一一对比。空间复杂度是O(N)。
// 利用栈O(n)
public static boolean isPalindrome(ListNode head) {
Stack<ListNode> stack = new Stack<>();
ListNode c = head;
while (c != null) {
stack.push(c);
c = c.next;
}
c = head;
while (c != null) {
if (c.val != stack.pop().val) {
return false;
}
c = c.next;
}
return true;
}
本题也可以使用链表操作,将空间复杂度优化为O(1)。
同时本题也需要使用快慢指针找到链表的中间位置,然后中间位置拆分左右两侧的链表来进行比较。整体流程如下图





完整代码见:
class Solution {
// 修改原链表,空间O(1)
public static boolean isPalindrome(ListNode head) {
// 0个节点
// 1个节点 都是回文
if (head == null || head.next == null) {
return true;
}
// 判断两个节点
if (head.next.next == null) {
return head.val == head.next.val;
}
// 判断三个节点
if (head.next.next.next == null) {
return head.val == head.next.next.val;
}
//到这一步,至少有四个节点
// 使用快慢指针
// 奇数来到中点前一个位置(假设为a)和中点后一个位置(假设为b)
// 偶数来到上中点位置(假设为a)和下中点位置(假设为b)
// head ... a 这个链表,链表反转一下 a...head
// 设置两个指针,一个指向a,一个指向b,每个位置对比,结果记录在result中
// 恢复整个链表
ListNode slow = head;
ListNode fast = head.next.next;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
ListNode a = slow;
ListNode b;
ListNode mid = null;
if (fast != null) {
// 链表个数为奇数
mid = a.next;
b = a.next.next;
} else {
b = a.next;
// 链表个数为偶数
}
// 断开链表
a.next = null;
// 反转前半部分链表
ListNode c = reverse(head);
boolean result = true;
ListNode leftStart = c;
ListNode rightStart = b;
while (leftStart.next != null) {
if (leftStart.val != rightStart.val) {
result = false;
}
leftStart = leftStart.next;
rightStart = rightStart.next;
}
if (leftStart.val != rightStart.val) {
result = false;
}
// leftStart来到开始节点
// rightStart来到末尾节点
ListNode cur = reverse(leftStart);
while (cur.next != null) {
cur = cur.next;
}
if (mid == null) {
cur.next = b;
} else {
cur.next = mid;
mid.next = b;
}
return result;
}
private static ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
本文中所有图例见:processon