链表专题(三)
两数相加
问题
[力扣2] 2. 两数相加 - 力扣(LeetCode)
问题描述
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例 1:
![](https://i-blog.csdnimg.cn/img_convert/2e59f6049c806824bfe08c90231d57a3.jpeg)
java
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
示例 2:
java
输入:l1 = [0], l2 = [0]
输出:[0]
示例 3:
java
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]
解决方案
定义两个链表的临时索引: curr1,curr2。
定义第三个链表的虚拟头结点: dummy,及其临时索引curr3。
![](https://i-blog.csdnimg.cn/img_convert/5b9b223a81cae6ba55cf21e49408e3c4.png)
从第一个、二个节点中拆一个节点计算值并存入第三个链表
tips: 需要记录两数相加的进位up(0/1)
![](https://i-blog.csdnimg.cn/img_convert/d84b505b56bb9f0537efc22502485e5a.png)
java
int t = 0;
if (curr1 == null) { //第一个链表运算结束
t = curr2.val + up;
curr2 = curr2.next;
} else if (curr2 == null) { //第二个链表运算结束
t = curr1.val + up;
curr1 = curr1.next;
} else { //两个链表运算都没有结束
t = curr1.val + curr2.val + up;
curr1 = curr1.next;
curr2 = curr2.next;
}
up = t / 10; //计算进位值(0,1)
curr3.next = new ListNode(t % 10); //计算新节点的值大于10则取余
curr3 = curr3.next;
如果计算完成后超出链表的长度(即计算的结果超出了链表的长度),需要额外的添加节点存储进位up(up=1)
if(up ==1){
curr3.next = new ListNode(up);
}
[参考实现]
java
/**
* 遍历两个链表将两个链表相加添加到第三个链表节点上
*/
public ListNode addTwoNumbers(ListNode head1, ListNode head2) {
int up = 0;
ListNode dummy = new ListNode(-1); //虚拟头结点记录第三个链表(head未知)
ListNode curr1 = head1;
ListNode curr2 = head2;
ListNode curr3 = dummy;
/**
* 两个链表有空则计算另一个链表,直到最后一个链表完成计算
*/
while (curr1 != null || curr2 != null) {
int t = 0;
if (curr1 == null) { //第一个链表运算结束
t = curr2.val + up;
curr2 = curr2.next;
} else if (curr2 == null) { //第二个链表运算结束
t = curr1.val + up;
curr1 = curr1.next;
} else { //两个链表运算都没有结束
t = curr1.val + curr2.val + up;
curr1 = curr1.next;
curr2 = curr2.next;
}
up = t / 10; //计算进位值(0,1)
curr3.next = new ListNode(t % 10); //计算新节点的值大于10则取余
curr3 = curr3.next;
}
//如果计算有进位值=1,则需要添加一个节点
if (up == 1) {
curr3.next = new ListNode(up);
}
return dummy.next;
}
两两交换链表中的节点
问题
问题描述
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
![](https://i-blog.csdnimg.cn/img_convert/1980c187b0835f3541a190f0fd83ec49.jpeg)
java
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
java
输入:head = []
输出:[]
示例 3:
java
输入:head = [1]
输出:[1]
解决方案
定义虚拟头结点dummy: dummy = new ListNode(-1,head);
定义临时节点tmp: tmp = dummy.next;
![](https://i-blog.csdnimg.cn/img_convert/2077607e57f3830753e7951559c3b890.png)
![](https://i-blog.csdnimg.cn/img_convert/8e3b4055f90b28a4569c247957aafc4b.png)
java
ListNode dummy = new ListNode(-1, head);
ListNode temp = dummy;
while (temp.next != null && temp.next.next != null) {
ListNode curr = temp.next; //当前节点
ListNode next = temp.next.next; //当前节点的下一个节点
//节点交换
temp.next = next;
curr.next = next.next;
next.next = curr;
temp = curr;
}
return dummy.next;
[参考实现]
java
public ListNode swapPairs(ListNode head) {
ListNode dummy = new ListNode(-1, head);
ListNode temp = dummy;
while (temp.next != null && temp.next.next != null) {
ListNode curr = temp.next; //当前节点
ListNode next = temp.next.next; //当前节点的下一个节点
//节点交换
temp.next = next;
curr.next = next.next;
next.next = curr;
temp = curr;
}
return dummy.next;
}
对链表进行插入排序
问题
[力扣147] 147. 对链表进行插入排序 - 力扣(LeetCode)
问题描述
给定单个链表的头
head
,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。插入排序 算法的步骤:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
对链表进行插入排序。
![](https://i-blog.csdnimg.cn/img_convert/090fee5053857f6a3d792c775dd897ed.gif)
示例 1:
![](https://i-blog.csdnimg.cn/img_convert/a34aedcaa3ce1d5a0fe8c7a8488a318a.png)
输入: head = [4,2,1,3]
输出: [1,2,3,4]
示例 2:
![](https://i-blog.csdnimg.cn/img_convert/0522861ecaccb0de67034f04b947e8bb.png)
输入: head = [-1,5,3,4,0]
输出: [-1,0,3,4,5]
解决方案
[初始化]
- 虚拟头结点dummy = new ListNode(-1,head);
- 记录排序后的后一个节点 sortedLast = dummy.next(先指向head位置): sortedLast记录插入点
- 待操作的节点curr = dummy.next.next。
![](https://i-blog.csdnimg.cn/img_convert/60269d3f358ac167dbdd036c02b1d631.png)
如果sortedLast.val < curr.val, 准备进入循环移动小的节点
![](https://i-blog.csdnimg.cn/img_convert/29a32bcb4685f82b25f02511bc461018.png)
如果sortedLast.val > curr
![](https://i-blog.csdnimg.cn/img_convert/b0dbfd67e05b5cf5a658e844d793be02.png)
从dummy开始查找比curr小的前一个节点位置
![](https://i-blog.csdnimg.cn/img_convert/fb2a56363536b516aa644477b870c606.png)
找到比curr小的位置,如图目前pre = dummy, 因为节点4的值大于节点2的值。
![](https://i-blog.csdnimg.cn/img_convert/f7ccedfc0345a8ed1e2a33b19b1a050c.png)
java
sortedLast.next = curr.next; // curr小于sortedLast,那么curr即为sortedLast的新记录
curr.next = pre.next;
pre.next = curr;
移动curr索引
![](https://i-blog.csdnimg.cn/img_convert/dac652ee4d3763a29df3e1804f5655ca.png)
java
curr = sortedLast.next;
[参考代码]
java
public ListNode insertionSortList(ListNode head) {
if(head == null) return null;
ListNode dummy = new ListNode(-1, head);
ListNode sortedLast = dummy.next; // 排好序后的最后一个节点,类似于插入排序已排好序的最后一个位置值
ListNode curr = dummy.next.next; // 待插入的节点
while (curr != null) {
// 当前待插入节点大于于排序序节点的最后一个节点,最后一个节点后移(tip: 已经不是最后一个了)
if (sortedLast.val <= curr.val) {
sortedLast = sortedLast.next;
} else {// curr节点值小于排序好序的最后一个节点,需要向前查找合适位置(curr.val大于的位置)
ListNode pre = dummy;
while (pre.next.val <= curr.val) { // 移动pre找到比curr.val大的位置前一个位置
pre = pre.next;
}
sortedLast.next = curr.next; // curr小于sortedLast,那么curr即为sortedLast的新记录
curr.next = pre.next;
pre.next = curr;
}
curr = sortedLast.next;
}
return dummy.next;
}
删除链表的倒数第N个节点
问题
问题描述
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
![](https://i-blog.csdnimg.cn/img_convert/2e800574ff269812abe6ea1185f9fd75.jpeg)
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
解决方案
虚拟头结点及快慢指针法
【初始化】
- 虚拟头结点 : dummy = new ListNode(-1,head);
- 快慢索引(指针): ListNode fast = dummy; ListNode slow= dummy;
![](https://i-blog.csdnimg.cn/img_convert/c5acf9c9ec742c10c3a77d548e6fb20b.png)
让快指针(fast)先走n步。
![](https://i-blog.csdnimg.cn/img_convert/9b2f82baf0f039243eb0191208f97447.png)
java
for (int i = n; i > 0; i--)
fast = fast.next;
两个指针(fast)和slow同时走到链表结尾。
如图所示,slow的末尾即与末尾的n个位置
![](https://i-blog.csdnimg.cn/img_convert/7aa1122b3a3af6f873dbd70fc4a239ca.png)
java
while (fast!=null &&fast.next != null) {
fast = fast.next;
slow = slow.next;
}
![](https://i-blog.csdnimg.cn/img_convert/0f2e24e302910a56a603ad3783f6b846.png)
slow.next = slow.next.next;
return pre.next;
[参考代码]
java
public ListNode removeNthFromEnd(ListNode node, int n) {
ListNode pre = new ListNode(-1, node); // 定义要删除节点的前一个节点
// 定义快慢指针让其距离差n,让fast先走n个节点,fast与slow之间就差n
ListNode fast = pre;
ListNode slow = pre;
// slow ----------n------------- fast
for (int i = n; i > 0; i--)
fast = fast.next;
// 两个节点同时移动,走到末尾,那么节点slow即为从末尾到n的节点
while (fast!=null &&fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return pre.next;
}
合并两个有序链表
题目
[力扣21] 21. 合并两个有序链表 - 力扣(LeetCode)
题目描述
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
java输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
示例 2:
java输入:l1 = [], l2 = [] 输出:[]
示例 3:
java输入:l1 = [], l2 = [0] 输出:[0]
解决方案
拆除重组法
![](https://i-blog.csdnimg.cn/img_convert/867dda94b93192fb727a0394ca6f3480.png)
参考实现
java
public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
ListNode dummy = new ListNode(-1);
ListNode curr1 = head1;
ListNode curr2 = head2;
ListNode curr = dummy;
while (curr1 != null || curr2 != null) {
if (curr1 == null) {
curr.next = curr2;
curr2 = curr2.next;
} else if (curr2 == null) {
curr.next = curr1;
curr1 = curr1.next;
} else if (curr1.val < curr2.val) {
curr.next = curr1;
curr1 = curr1.next;
} else {
curr.next = curr2;
curr2 = curr2.next;
}
curr = curr.next;
}
return dummy.next;
}
java
public ListNode mergeTwoLists(ListNode head1, ListNode head2) {
if (head1 == null) return head2;
if (head2 == null) return head1;
ListNode newNode = head1.val < head2.val ? head1 : head2;
newNode.next = mergeTwoLists(newNode.next, head1.val >= head2.val ? head1 : head2);
return newNode;
}
合并K个升序链表
题目
题目描述
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
java输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下: [ 1->4->5, 1->3->4, 2->6 ] 将它们合并到一个有序链表中得到。 1->1->2->3->4->4->5->6
示例 2:
java输入:lists = [] 输出:[]
示例 3:
java输入:lists = [[]] 输出:[]
解决方案
将每个节点的头结点放入优先队列,重新组装链表。
[初始化]
- 优先链表定义
- 虚拟头结点
java
PriorityQueue<ListNode> priorityQueue = new PriorityQueue<>(Comparator.comparingInt(o -> o.val));
ListNode dummy = new ListNode(-1);
ListNode curr = dummy;
将链表头节点放入优先队列(优先小队列)
java
for (ListNode head : lists) {
if(head !=null) priorityQueue.offer(head);
}
从优先队列中取出最小值,将头结点的下一个节点放入队列。
![](https://i-blog.csdnimg.cn/img_convert/95445b3261526dd23eec3daa5b822cbb.png)
java
while (!priorityQueue.isEmpty()) {
ListNode node = priorityQueue.poll();
curr.next = node;
if (node.next != null) {
priorityQueue.offer(node.next);
}
curr = curr.next;
}
[参考解答]
java
public ListNode mergeKLists(ListNode[] lists) {
if(lists == null) return null;
PriorityQueue<ListNode> priorityQueue = new PriorityQueue<>(Comparator.comparingInt(o -> o.val));
ListNode dummy = new ListNode(-1);
ListNode curr = dummy;
for (ListNode head : lists) {
if(head !=null) priorityQueue.offer(head);
}
while (!priorityQueue.isEmpty()) {
ListNode node = priorityQueue.poll();
curr.next = node;
if (node.next != null) {
priorityQueue.offer(node.next);
}
curr = curr.next;
}
return dummy.next;
}
分割链表
题目
[力扣86] 86. 分隔链表 - 力扣(LeetCode)
题目描述
给你一个链表的头节点
head
和一个特定值x
,请你对链表进行分隔,使得所有 小于x
的节点都出现在 大于或等于x
的节点之前。你应当 保留 两个分区中每个节点的初始相对位置。示例 1:
java输入:head = [1,4,3,2,5,2], x = 3 输出:[1,2,2,4,3,5]
示例 2:
java输入:head = [2,1], x = 2 输出:[1,2]
解决方案
![](https://i-blog.csdnimg.cn/img_convert/eea0ae7a4750f6025839aee05c2beb80.png)
![](https://i-blog.csdnimg.cn/img_convert/7274c6a87276ad04a31ec833f17e54d0.png)
java
ListNode<Integer> dummyLess = new ListNode<>(-1);
ListNode<Integer> dummyLarge = new ListNode<>(-2);
ListNode<Integer> curr = head;
ListNode<Integer> currLess = dummyLess;
ListNode<Integer> currLarge = dummyLarge;
while (curr != null) {
if (curr.val < x) {
currLess.next = curr;
currLess = currLess.next;
} else {
currLarge.next = curr;
currLarge = currLarge.next;
}
curr = curr.next;
}
currLess.next = dummyLarge.next;
currLarge.next =null;
return dummyLess.next;