昨日回顾
昨天,我们针对链表中环与交点的题目,进行了总结。其中主要使用到的解题方法,一个是快慢指针,另外一个就是画图画图画图(重要的事情说三遍)。针对链表题,前期最重要的就是通过画图帮助理解。
今天,我们来学习链表的另外一种题型: 反转链表专题。
反转链表
链表不同于数组,可以通过获取数组的长度然后递减完成逆序。
算法题目中多数的链表都是单向的,我们想获取到链表的最后一个节点就必须要挨个的遍历链表,从而得到链表尾节点。
所以链表逆序(反转)的题目就出现的理所当然了。
让我们先上一道反转链表的开胃菜,用来熟悉链表的基本操作吧。
剑指offerII024.反转链表
https://leetcode-cn.com/problems/UHnkqh/solution/shua-chuan-jian-zhi-offer-day12-lian-bia-190d/
难度:简单
题目:
给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。
提示:
- 链表中节点的数目范围是 [0, 5000]
- -5000 <= Node.val <= 5000
示例:
![](https://i-blog.csdnimg.cn/blog_migrate/87a345457901ef641f419cf9dde0e51d.png)
分析
链表不同于数组,可以通过获取数组的长度然后递减完成逆序。
日常的算法题目中链表多是单向的,我们想获取到最后一个节点就必须要挨个的遍历直到获取到链表末尾。
所以,链表逆序(反转)的题目就出现的理所当然了。
这道题是一个简单的链表逆序,主要就是考察链表的截断与重定向问题。
具体来说就是创建一个空节点,然后从链表头逐个反转。
千言万语不及画几张图,我们循环2-3的操作,即可完成链表反转...
![](https://i-blog.csdnimg.cn/blog_migrate/7dbd4274dc49153e36bab4b919e2a31d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/a77b682633881d0e0cce99e506545d8d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0955425a8c57b3248f567746145fb79a.png)
解题:
Python:
def reverseList(self, head):
pre, cur = None, head
while cur:
tmp = cur.next
cur.next = pre
pre, cur = cur, tmp
return pre
Java:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
有了这道基础的反转链表题目打底,接下来的进阶题目就有一战之力了。
相信按顺序刷力扣的朋友,都有做过第二题,两数相加。那么今天这道题是两数相加的进阶版,一起来看下吧。
剑指offerII025.链表中的两数相加
https://leetcode-cn.com/problems/lMSNwu/solution/shua-chuan-jian-zhi-offer-day13-lian-bia-cl27/
难度:中等
题目:
给定两个 非空链表 l1和 l2 来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
可以假设除了数字 0 之外,这两个数字都不会以零开头。
提示:
- 链表的长度范围为 [1, 100]
- 0 <= node.val <= 9
- 输入数据保证链表代表的数字无前导 0
示例:
![](https://i-blog.csdnimg.cn/blog_migrate/cee5497bb479c5b7d21335143fb22e67.png)
示例1:
输入:l1 = [7,2,4,3], l2 = [5,6,4]
输出:[7,8,0,7]
示例2:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[8,0,7]
示例3:
输入:l1 = [0], l2 = [0]
输出:[0]
分析
这道题目是力扣的第二题两数相加的进阶版本。
第二题是整数从低位向高位保存求加和, 但是这道题却是整数从高位到低位求加和的操作。
由于是单向链表,当低位存在进位时,我们没办法返回到之前的节点进行进位操作。
所以这道题,我们需要先反转链表后,在进行加法操作,最后在将加和的结果反转后输出。
由于上一道题目:
我们已经写好了反转链表的方法,所以在这里就可以直接复用了!
具体解法如下:
解题:
Python:
class Solution:
def reverseList(self, head):
pre, cur = None, head
while cur:
tmp = cur.next
cur.next = pre
pre, cur = cur, tmp
return pre
def addTwoNumbers(self, l1, l2):
rev_l1 = self.reverseList(l1)
rev_l2 = self.reverseList(l2)
count = 0
ret = ListNode()
tmp = ret
while rev_l1 or rev_l2 or count:
num = 0
if rev_l1:
num += rev_l1.val
rev_l1 = rev_l1.next
if rev_l2:
num += rev_l2.val
rev_l2 = rev_l2.next
count, num = divmod(num + count, 10)
tmp.next = ListNode(num)
tmp = tmp.next
return self.reverseList(ret.next)
Java:
class Solution {
private ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode rev_l1 = this.reverseList(l1);
ListNode rev_l2 = this.reverseList(l2);
int count = 0;
ListNode ret = new ListNode(0);
ListNode cur = ret;
while (rev_l1 != null || rev_l2 != null || count != 0) {
int num = 0;
if (rev_l1 != null) {
num += rev_l1.val;
rev_l1 = rev_l1.next;
}
if (rev_l2 != null) {
num += rev_l2.val;
rev_l2 = rev_l2.next;
}
num += count;
count = num >= 10 ? 1 : 0;
cur.next = new ListNode(num - count * 10);
cur = cur.next;
}
return this.reverseList(ret.next);
}
}
刚才两道题,我们都是全量反转链表的,下来这道题目,可就用到了我们之前学习到的快慢指针,配合反转链表的操作,快来看看吧。
剑指OfferII026.重排链表
https://leetcode-cn.com/problems/LGjMqU/solution/shua-chuan-jian-zhi-offer-day13-lian-bia-chs7/
难度:中等
题目
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0→ L1→ … → Ln-1→ Ln
请将其重新排列后变为:
L0→Ln→L1→Ln-1→L2→Ln-2→ …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
提示:
- 链表的长度范围为 [1, 5 * 104]
- 1 <= node.val <= 1000
示例
示例 1:
输入: head = [1,2,3,4]
输出: [1,4,2,3]
示例 2:
输入: head = [1,2,3,4,5]
输出: [1,5,2,4,3]
分析
如果是数组来做这道题,那真的是很简单,但偏偏它是链表你说气不气。
然而,这道题还能无脑的去使用024的整体反转链表么?答案是不能,why...
因为如果我们无脑的去反转了链表那么会将链表从1->2->3->4,转化为4->3->2->1并未解决任何问题。
不知道大家在做链表题时,是否有和我一样的感觉: 这解法就摆在眼前,一看便知,但就是写不出来.
写不出来怎么办?
- 先考虑下咱们之前做过的链表题目有没有类似的。注意题目关键点,将链表对半分后进行了首尾添加。
- 关于获取链表的一半,是不和之前咱们做过的快慢指针查找链表中间节点有关系?
- 找到中间节点后,如果我们将后半部分链表逆序,然后把前半部分的链表与后半部分断开。
-
这不就成了针对两个链表的合并题目了吗?
让我们画图整理下思路:
![](https://i-blog.csdnimg.cn/blog_migrate/89380756ca952390b9346cd64d524278.png)
![](https://i-blog.csdnimg.cn/blog_migrate/30c4f95ea208f02fbafcd6b1be5ab67e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/fb5ff6c04564c73b845dfe2807d6a583.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7b90c3bd7c28df3a8577b8072f717e90.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1ce87791e2003b4182b64f53026fc071.png)
最终代码如下:
解题
Python:
class Solution:
def reverseList(self, head):
pre, cur = None, head
while cur:
tmp = cur.next
cur.next = pre
pre, cur = cur, tmp
return pre
def reorderList(self, head: ListNode) -> None:
pre = ListNode()
pre.next = head
slow = fast = pre
while fast and fast.next:
slow = slow.next
fast = fast.next.next
half = slow.next
slow.next = None
rev_half = self.reverseList(half)
cur = pre.next
while slow and rev_half:
tmp = cur.next
cur.next = rev_half
cur = cur.next
rev_half = rev_half.next
cur.next = tmp
cur = cur.next
Java:
class Solution {
private ListNode reverseList(ListNode head) {
ListNode pre = null;
ListNode cur = head;
while (cur != null) {
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
public void reorderList(ListNode head) {
ListNode pre = new ListNode();
ListNode slow = pre;
ListNode fast = pre;
pre.next = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
ListNode half = slow.next;
slow.next = null;
ListNode rev_half = this.reverseList(half);
ListNode cur = pre.next;
while (rev_half != null) {
ListNode tmp = cur.next;
cur.next = rev_half;
cur = cur.next;
rev_half = rev_half.next;
cur.next = tmp;
cur = cur.next;
}
}
}
好了,今天的链表题目整理就到这儿了。链表的题目千万不能偷懒,多在草稿纸上画画,题目就迎刃而解了,千万别偷懒啊!
![](https://i-blog.csdnimg.cn/blog_migrate/5bb6358ad2eb3cc68628a8bf7378aa08.png)
加油干饭人....
欢迎关注我的公众号: 清风Python,带你每日学习Python算法刷题的同时,了解更多python小知识。
我的个人博客:https://qingfengpython.cn