「这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战」
先来看一个场景
- 打开力扣刷算法
- 一道链表简单题,信心满满,直接开始撸代码
- 反手一个提交,直接报错
- 然后开始找bug,找了老半天还是报错
- 最后心态就崩了,关闭力扣继续摸鱼。。。 以上是我一开始刷题碰到的情况,其实在做链表题的时候最好是通过画图来理清思路,否则很容易把自己绕晕。接下来看几道反转链表相关的算法题。
从简单题开始
206. 反转链表
分析
- 以题目给出的
示例1为例,如果要得到反转后的链表那么1节点最终将指向null - 因此先新建一个指针
p指向null - 再新建一个
q指针指向head方便之后的反转操作
- 如果想将
1节点指向null,不能直接修改q.next = p,这样将断开链表,失去了从2节点开始的引用。需要用一个变量去接收,因此将head向后移动拿到2节点的引用,head = head.next
- 现在就可以将
1节点的next指向null,q.next = p
-
然后将
p和q指针向后移动,p = q,q = head,切忌赋值顺序不能反,这样就完成了1节点的反转 -
重复以上的步骤就能完成所有的反转,来看一下动图
- 最后返回
p指针就是反转后的链表
代码实现
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var reverseList = function(head) {
var p = null, q = head
while(q) { // 顺序不能颠倒,否则将会失去引用
head = head.next
q.next = p
p = q
q = head
}
return p
};
- 根据上面的图片分析就能很好写出代码,接下来增加一下难度
进阶中等题
92. 反转链表 II
分析
- 有了上一道题的经验,这道题只是多加了反转的位置和数量,不需要整个链表都反转,主要的焦点在于,如何将链表拆开部分反转,然后再连接回原链表
- 以
示例1为例,首先定义一个虚拟头节点ret指向head,目的是方便返回,因为可能从头结点开始就反转,最终只需要返回ret.next即可
- 接着定义一个
pre指针一开始指向ret,依次向后移动找到待反转区域的前一个节点,即1节点,然后操作他的下一个节点进行区域反转
-
为什么是找到前一个节点,而不是在待反转节点的第一个节点直接进行反转,如果
pre指针指向待反转开始的2节点,区域反转后会变成4->3->2->5,此时1节点的next依然指向2节点,最终的结果就会变成1->2->5,然而我们需要的是1节点的next指向4->3->2->5 -
在
2节点的位置进行操作,反转的步骤和上一题类似,不同的是需要加一个变量用于接收反转后的结果,一开始也是指向2节点 -
反转结束后将这个变量的
next指向反转区域的下一个节点5 -
最后把这一个反转后的
头节点p返回拼上原链表 -
整体动画演示
代码实现
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} left
* @param {number} right
* @return {ListNode}
*/
var reverseBetween = function (head, left, right) {
var ret = new ListNode(null, head)
var cnt = right - left + 1 // 需要反转区间的个数
var pre = ret
while (--left) {
pre = pre.next
}
pre.next = reverse(pre.next, cnt) // 必须反转p.next,如果直接反转p,p的前一个元素仍然指向p,中间反转的部分都被略过了
return ret.next
};
function reverse(head, cnt) {
var p = null,
q = ret = head
while (cnt--) {
head = head.next
q.next = p
p = q
q = head
}
ret.next = q // 原来的头节点已经指向了null,再连接上反转后的下一个元素
return p
}
困难题也可迎刃而解
25. K 个一组翻转链表
分析
- 这道题其实就是上两道题的合体版本,如果你不知道如何反转链表,直接看这道题可能有思路,但是直接上手可能会定义一大堆变量,到最后把自己绕进去了
- 在92. 反转链表 II的基础上,需要判断是否符合反转的条件,如果剩下的节点数量小于待反转的数量
k则不反转 - 一开始依然是定义一个
pre指针一开始指向head - 再定义一个指针
pre一开始指向ret,每次在反转完k个节点后,向后移动k步,直到走到末尾或者剩余数量小于k - 最终返回
ret.next即可
参考代码
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} k
* @return {ListNode}
*/
var reverseKGroup = function (head, k) {
if (k === 1) return head
var ret = new ListNode(0, head)
var pre = ret
while (1) {
pre.next = reverse(pre.next, k)
var n = k
while (n-- && pre) { // 反转完以后向后走
pre = pre.next
}
if (!pre) break // 待反转数量不够跳出循环
}
return ret.next
};
function reverse(head, k) {
var p = ret = q = head,
n = k
while (--n && p) {
p = p.next
}
if (!p) return head //需要反转的节点数量小于k则不反转
p = null
while (k--) {
head = head.next
q.next = p
p = q
q = head
}
ret.next = q
return p
}
- 如果还不是很清楚,可以动手画一下
-- end --