1.反转链表(206-易)
题目描述:反转单链表 (迭代和递归实现)
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
思路:
法1:递归(自下向上)实现
- 递归函数是得到反转的链表
- 终止条件,只有一个节点或者节点为空
- 最后进行反转(递归的基本逻辑),不要忘记断开正向连接的指针。
法2:迭代遍历数组,修改指针,分三步:
- 记录head.next的值(防止指针丢失)
- 断指针,重新连接(反转)
- 移动pre和head指针
代码:
// 递归
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = reverseList(head.next);
// 反转两个节点
head.next.next = head;
head.next = null;
return newHead;
}
// 迭代
public ListNode reverseList(ListNode head) {
if (head == null) {
return head;
}
ListNode pre = null, next;
while (head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
2.反转链表II(92-中)
题目描述:给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。即反转指定区间的链表。
示例:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
思路:本题使用头插法+双指针反转链表,即定义双指针,一个指向区间起点的前一个节点pre,另一个指针cur遍历区间的链表进行头插。具体步骤如下:
- 定义虚拟节点dummy,便于返回结果
- 寻找pre节点(区间起点的上一个节点)
- cur指针遍历剩余区间的节点,从区间第二个节点开始(注意记录),依次取出,进行头插并连接。
注意两个细节:
- 头插法连接链表时,为了避免指针丢失,我们先连后边再连前边。
- 在寻找pre节点的时候,right边界也要跟着同步缩小
代码:
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode pre = dummy;
ListNode cur = dummy.next;
while (left-- > 1) {
pre = pre.next;
cur = cur.next;
right--;
}
while (right-- > 1) {
ListNode remove = cur.next;
cur.next = cur.next.next;
remove.next = pre.next;
pre.next = remove;
}
return dummy.next;
}
3.交换链表中的相邻节点(24-中)
题目描述:两两交换相邻的节点,不修改val值,空间复杂度O(1)
示例:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
思路:本题可参考T206反转链表。
法1:使用递归求解,采用自上而下的递归(假设子链表已经满足条件)
递归函数:获取两两交换的子链表的头结点(注:函数输入为下一组的头节点)
终止条件:
head == null || head.next == null
(没有节点或还有一个节点)递归逻辑:反转两个相邻的节点
法2:使用迭代(双指针),遍历链表,进行两两交换。几个注意点:
- 设置哨兵节点,便于结果返回
- pre指针维护每组交换的前驱节点
- 断(防止指针丢失,连接下一组节点)-> 反(反转本组两个节点)-> 连(连接上一组节点)。
代码:
// 代码1:递归
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) return head;
ListNode next = head.next;
head.next = swapPairs(next.next);
next.next = head;
return next;
}
// 代码2:双指针
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
ListNode l1, l2;
while (pre.next != null && pre.next.next != null) {
l1 = pre.next;
l2 = pre.next.next;
// 连接链表
l1.next = l2.next;
l2.next = l1;
pre.next = l2;
// 更新一组交换节点的前驱节点
pre = l1;
}
return dummy.next;
}
4.重排链表(143-中)
题目描述:给定一个单链表 L:L0→L1→…→Ln-1→Ln ,将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…,即重排链表保证头尾交替出现。
示例:
给定链表 1->2->3->4, 重新排列为 1->4->2->3.
思路:由于链表不支持下标访问,并且单链表不能逆序查找节点。我们能想到比较容易想到的方案就是前两种:
法1:用List集合存储链表节点,这样就可以按照要求通过下标构建链表,使用了额外空间。
法2:将链表后半部分逆序,然后根据规则依次连接左右连部分。具体步骤是:
- 快慢指针找到链表中点,断开链表。注意偶数链表为下中间节点。
- 反转右半部分链表
- 定义指针,遍历两个链表并合并,注意保存下一个节点(防止指针丢失)
代码:
// 法1:线性表
public void reorderList(ListNode head) {
if (head == null) {
return;
}
List list = new ArrayList<>();
ListNode cur = head;
while (cur != null) {
list.add(cur);
cur = cur.next;
}
int i = 0, j = list.size() - 1;
while (i < j) {
list.get(i).next = list.get(j);
i++;
list.get(j).next = list.get(i);
j--;
}
list.get(i).next = null;
}
// 法2:反转右半部分链表 + 合并重排
public void reorderList(ListNode head) {
// 1.找中点(偶数为右中点)
if (head == null) return;
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 2.反转链表
ListNode right = slow.next;
slow.next = null;
ListNode rightHead = reverseList(right);
// 3.合并重排链表
ListNode l1 = head, l2 = rightHead, next1, next2;
while (l2 != null) {
next1 = l1.next;
next2 = l2.next;
l1.next = l2;
l1 = next1;
l2.next = next1;
l2 = next2;
}
}
private ListNode reverseList(ListNode head) {
ListNode pre = null, cur = head;
while (cur != null) {
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
5.旋转链表(61-中)
问题描述:给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
案例:
输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
思路:
法1:闭合成环,先成环,然后断连接。注意更新有效的k值(即i)。
法2:类比T19题双指针,记数组长度为len,为了防止多次翻转,这里使用i = k % len
,分为下边两种情况:
- i < len,直接类比T19题思路
- i == len,直接返回原链表
代码:
// 闭合成环
public ListNode rotateRight(ListNode head, int k) {
if (head == null || head.next == null || k == 0) return head;
int len = 1;
ListNode cur = head;
while (cur.next != null) {
cur = cur.next;
len++;
}
int i = len - k % len;
if (i == len) return head; // 相当于每移动
cur.next = head; // 成环
while (i-- > 0) {
cur = cur.next;
}
ListNode newHead = cur.next;
cur.next = null;
return newHead;
}
// 双指针
public ListNode rotateRight(ListNode head, int k) {
if (head == null) return head;
ListNode slow = head, fast = head;
int i = k % length(head);
if (i == 0) return head;
while (i-- > 0) {
fast = fast.next;
}
while (fast.next != null) {
slow = slow.next;
fast = fast.next;
}
ListNode newHead = slow.next;
slow.next = null;
fast.next = head;
return newHead;
}
private int length(ListNode node) {
if (node == null) return 0;
return length(node.next) + 1;
}
6.K 个一组翻转链表(25-难)
题目描述:给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
思路:本题是对链表进行分区反转连接,即可以大致分为已反转、待反转和未反转。需要注意以下几点:
反转前,需要确定链表反转的范围k,即找到本次反转的尾结点;
需要记录每次待反转链表的前驱和末尾的后继节点,方便连接;
连接三部分之后,更新链表前驱和末尾节点的后继节点指针,进入下一次循环;
特殊的,当反转长度不足k时,即待反转链表末尾的后继节点为null,直接返回即可。
时间复杂度:O(KN),空间复杂度O(1)。
代码:
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode pre = dummyHead;
ListNode end = dummyHead;
while (end.next != null) {
for (int i = 0; i < k && end != null; ++i) {
end = end.next;
}
if (end == null) break;
ListNode start = pre.next;
ListNode next = end.next;
end.next = null;
pre.next = reverse(start);
start.next = next;
pre = start;
end = pre;
}
return dummyHead.next;
}
private ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode cur = head, next;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
7.奇偶链表(328-中)
题目描述:给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例:
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
思路:注意记录偶数位节点头部进行连接。
代码:
public ListNode oddEvenList(ListNode head) {
if (head == null) {
return head;
}
ListNode odd = head, even = head.next, evenHead = even;
while (even != null && even.next != null) {
odd.next = odd.next.next;
odd = odd.next;
even.next = even.next.next;
even = even.next;
}
odd.next = evenHead;
return head;
}