剑指 offer 第 2 天

2023-04-27,

第 2 天

链表(简单)

剑指 Offer 06. 从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]

限制:

0 <= 链表长度 <= 10000

题解思路:辅助栈、反向填充、递归

辅助栈:利用栈先进后出的性质,实现反向节点

/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
Stack<ListNode> stack = new Stack<ListNode>();
ListNode temp = head;
while (temp != null) {
stack.push(temp);
temp = temp.next;
}
int size = stack.size();
int[] print = new int[size];
for (int i = 0; i < size; i++) {
print[i] = stack.pop().val;
}
return print;
}
}

反向填充:反向填充数组下标

/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
ListNode currNode = head;
int len = 0;
while (currNode != null) {
len ++;
currNode = currNode.next;
}
int[] print = new int[len]; for (int i = len-1; i >= 0; i --) {
print[i] = head.val;
head = head.next;
}
return print;
}
}

递归:利用递归走到链表最后结点

/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
ArrayList<Integer> tmp = new ArrayList<Integer>();
recur(head, tmp);
int[] res = new int[tmp.size()];
for(int i = 0; i < res.length; i++)
res[i] = tmp.get(i);
return res;
}
void recur(ListNode head, ArrayList<Integer> tmp) {
if(head == null) return;
recur(head.next, tmp);
tmp.add(head.val);
}
}

剑指 Offer 24. 反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

限制:

0 <= 节点个数 <= 5000

题解思路:辅助栈、递归、迭代

辅助栈:利用栈结构先进后出的特点反转链表

/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null) {
return head;
}
ListNode curr = head;
Stack<ListNode> stack = new Stack<>();
while (curr != null) {
stack.push(curr);
curr = curr.next;
}
ListNode listNode = stack.pop();
ListNode res = listNode;
while (!stack.isEmpty()) {
listNode.next = stack.pop();
listNode = listNode.next;
listNode.next = null;
}
return res;
}
}

递归:通过递归利用系统栈反转链表,递归到最后一个节点中止,途中完成 head.next.next = head 的交换

/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
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;
}
}

迭代:通过头插法再次重建链表

/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
// 暂存后续节点
ListNode next = curr.next;
// 修改节点指向
curr.next = prev;
// 暂存当前头节点
prev = curr;
// 访问下一个节点
curr = next;
}
return prev;
}
}

反转链表的题目不止于此,目前常考察的还有区间反转、多次区间反转等

92. 反转链表 II

给你单链表的头指针 head 和两个整数 leftright ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表

示例 1:

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]

示例 2:

输入:head = [5], left = 1, right = 1
输出:[5]

提示:

链表中节点数目为 n
1 <= n <= 500
-500 <= Node.val <= 500
1 <= left <= right <= n

进阶: 你可以使用一趟扫描完成反转吗?

题解思路:两次遍历、一次遍历

两次遍历:先断链,然后反转中间部分,再接上链表

/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
// 使用额外头节点
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode; // 得到left的前一个节点
for (int i = 0; i < left-1; i ++) {
pre = pre.next;
} // 得到right节点
ListNode rightNode = pre;
for (int i = 0; i < right-left+1; i ++) {
rightNode = rightNode.next;
} // 截断链表
ListNode leftNode = pre.next; // 保存rightNode后续节点
ListNode curr = rightNode.next; // 截断链接
pre.next = null;
rightNode.next = null; // 反转链表
reverseLinkedList(leftNode); // 恢复连接
pre.next = rightNode;
leftNode.next = curr;
return dummyNode.next;
}
// 反转链表
private void reverseLinkedList(ListNode head) {
ListNode curr = head;
ListNode pre = null;
while (curr != null) {
ListNode next = curr.next;
curr.next = pre;
pre = curr;
curr = next;
}
}
}

复杂度: 时间 O(n) 空间 O(1)

一次遍历:头插法建立链表的方式完成反转

/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
ListNode pre = dummyNode;
for (int i = 0; i < left - 1; i ++) {
pre = pre.next;
}
ListNode curr = pre.next;
// 步骤记熟即可
for (int i = 0; i < right-left; i ++) {
// 保存后续节点
ListNode next = curr.next;
// 改变当前节点指向
curr.next = next.next;
// 改变next节点指向
next.next = pre.next;
// 改变pre节点指向
pre.next = next;
}
return dummyNode.next; }
}

复杂度: 时间 O(n) 空间 O(1)

25. K 个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

进阶:

你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

示例 3:

输入:head = [1,2,3,4,5], k = 1
输出:[1,2,3,4,5]

示例 4:

输入:head = [1], k = 1
输出:[1]

提示:

列表中节点的数量在范围 sz
1 <= sz <= 5000
0 <= Node.val <= 1000
1 <= k <= sz

题解思路:多次区间反转

多次区间反转:

/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head;
// pre 代表待翻转链表的前驱
ListNode pre = dummyNode;
// end 代表待翻转链表的末尾
ListNode end = dummyNode;
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 dummyNode.next;
}
private ListNode reverse(ListNode head) {
ListNode pre = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = pre;
pre = curr;
curr = next;
}
return pre;
} }

复杂度:时间 O(n) 空间 O(1)

剑指 Offer 35. 复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。

提示:

-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。

解题思路:哈希表复制、节点拆分

哈希表复制:利用HashMap先存储一次全部节点,再将其取出重建链表即可

/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random; public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
if (head == null) {
return head;
}
// key存放原节点,value存放待复制节点
Map<Node, Node> map = new HashMap<>();
// 节点复制val
for (Node cur = head; cur != null; cur = cur.next) {
map.put(cur, new Node(cur.val));
}
// 遍历链表,利用key中原节点信息填充value节点中的next和random
for (Node cur = head; cur != null; cur = cur.next) {
// 取出并赋值
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
}
return map.get(head);
}
}

复杂度:时间 O(n) 空间 O(n)

节点拆分:在每个节点相邻位置创建一个相同节点,先复制 next 和 val 域,再遍历一次复制random域,最后拆分两条链表

/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random; public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
if (head == null) {
return null;
} Node cur = head;
// 1. 复制各节点,并构建拼接链表
while(cur != null) {
Node tmp = new Node(cur.val);
tmp.next = cur.next;
cur.next = tmp;
cur = tmp.next;
}
// 2. 构建各新节点的 random 指向
cur = head;
while(cur != null) {
if(cur.random != null)
cur.next.random = cur.random.next;
cur = cur.next.next;
}
// 3. 拆分两链表
cur = head.next;
Node pre = head, res = head.next;
while(cur.next != null) {
pre.next = pre.next.next;
cur.next = cur.next.next;
pre = pre.next;
cur = cur.next;
}
pre.next = null; // 单独处理原链表尾节点
return res; // 返回新链表头节点
}
}

复杂度:时间 O(n) 空间 O(1)

剑指 offer 第 2 天的相关教程结束。

《剑指 offer 第 2 天.doc》

下载本文的Word格式文档,以方便收藏与打印。