leecode/problems/92.reverse-linked-list-ii.md
2020-05-22 18:17:19 +08:00

290 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 题目地址
https://leetcode.com/problems/reverse-linked-list-ii/description/
## 题目描述
Reverse a linked list from position m to n. Do it in one-pass.
Note: 1 ≤ m ≤ n ≤ length of list.
Example:
Input: 1->2->3->4->5->NULL, m = 2, n = 4
Output: 1->4->3->2->5->NULL
## 思路
这道题和[206.reverse-linked-list](https://github.com/azl397985856/leetcode/blob/master/problems/206.reverse-linked-list.md) 有点类似并且这道题是206的升级版。 让我们反转某一个区间而不是整个链表我们可以将206看作本题的特殊情况special case
核心在于**取出需要反转的这一小段链表,反转完后再插入到原先的链表中。**
以本题为例:
反转的是2,3,4这三个点那么我们可以先取出2用cur指针指向2然后当取出3的时候我们将3指向2的把cur指针前移到3依次类推到4后停止这样我们得到一个新链表4->3->2, cur指针指向4。
对于原链表来说有两个点的位置很重要需要用指针记录下来分别是1和5把新链表插入的时候需要这两个点的位置。用pre指针记录1的位置当4结点被取走后5的位置需要记下来
这样我们就可以把反转后的那一小段链表加入到原链表中
![92.reverse-linked-list-ii](../assets/92.reverse-linked-list-ii.gif)
(图片来自: https://github.com/MisterBooo/LeetCodeAnimation)
首先我们直接返回head是不行的。 当 m 不等于1的时候是没有问题的但只要 m 为1就会有问题。
```python
class Solution:
def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
pre = None
cur = head
i = 0
p1 = p2 = p3 = p4 = None
# ...
if p1:
p1.next = p3
else:
dummy.next = p3
if p2:
p2.next = p4
return head
```
如上代码是不可以的我们考虑使用dummy节点。
```python
class Solution:
def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
pre = None
cur = head
i = 0
p1 = p2 = p3 = p4 = None
dummy = ListNode(0)
dummy.next = head
# ...
if p1:
p1.next = p3
else:
dummy.next = p3
if p2:
p2.next = p4
return dummy.next
```
关于链表反转部分, 顺序比较重要,我们需要:
- 先 cur.next = pre
- 再 更新p2和p2.next(其中要设置p2.next = None否则会互相应用造成无限循环)
- 最后更新 pre 和 cur
上述的顺序不能错,不然会有问题。原因就在于`p2.next = None`如果这个放在最后那么我们的cur会提前断开。
```python
while cur:
i += 1
if i == m - 1:
p1 = cur
next = cur.next
if m < i <= n:
cur.next = pre
if i == m:
p2 = cur
p2.next = None
if i == n:
p3 = cur
if i == n + 1:
p4 = cur
pre = cur
cur = next
```
## 关键点解析
- 链表的基本操作
- 考虑特殊情况 m 1 或者 n是链表长度的情况我们可以采用虚拟节点dummy 简化操作
- 用四个变量记录特殊节点 然后操作这四个节点使之按照一定方式连接即可
- 注意更新current和pre的位置 否则有可能出现溢出
## 代码
语言支持JS, C++, Python3
JavaScript Code:
```js
/*
* @lc app=leetcode id=92 lang=javascript
*
* [92] Reverse Linked List II
*
* https://leetcode.com/problems/reverse-linked-list-ii/description/
*/
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @param {number} m
* @param {number} n
* @return {ListNode}
*/
var reverseBetween = function(head, m, n) {
// 虚拟节点,简化操作
const dummyHead = {
next: head
}
let cur = dummyHead.next; // 当前遍历的节点
let pre = cur; // 因为要反转,因此我们需要记住前一个节点
let index = 0; // 链表索引,用来判断是否是特殊位置(头尾位置)
// 上面提到的四个特殊节点
let p1 = p2 = p3 = p4 = null
while(cur) {
const next = cur.next;
index++;
// 对 (m - n) 范围内的节点进行反转
if (index > m && index <= n) {
cur.next = pre;
}
// 下面四个if都是边界, 用于更新四个特殊节点的值
if (index === m - 1) {
p1 = cur;
}
if (index === m) {
p2 = cur;
}
if (index === n) {
p3 = cur;
}
if (index === n + 1) {
p4 = cur;;
}
pre = cur;
cur = next;
}
// 两个链表合并起来
(p1 || dummyHead).next = p3; // 特殊情况需要考虑
p2.next = p4;
return dummyHead.next;
};
```
C++ Code:
```c++
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int s, int e) {
if (s == e) return head;
ListNode* prev = nullptr;
auto cur = head;
for (int i = 1; i < s; ++i) {
prev = cur;
cur = cur->next;
}
// 此时各指针指向:
// x -> x -> x -> x -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> x -> x -> x ->
// ^head ^prev ^cur
ListNode* p = nullptr;
auto c = cur;
auto tail = c;
ListNode* n = nullptr;
for (int i = s; i <= e; ++i) {
n = c->next;
c->next = p;
p = c;
c = n;
}
// 此时各指针指向:
// x -> x -> x -> x 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1 x -> x -> x ->
// ^head ^prev ^p ^cur ^c
// ^tail
if (prev != nullptr) { // 若指向前一个节点的指针不为空则说明s在链表中间不是头节点
prev->next = p;
cur->next = c;
return head;
} else {
if (tail != nullptr) tail->next = c;
return p;
}
}
};
```
Python Code:
```Python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:
"""采用先翻转中间部分,之后与不变的前后部分拼接的思路"""
# 处理特殊情况
if m == n:
return head
# 例行性的先放一个开始节点,方便操作
first = ListNode(0)
first.next = head
# 通过以下两个节点记录拼接点
before_m = first # 原链表m前的部分
after_n = None # 原链表n后的部分
# 通过以下两个节点记录翻转后的链表
between_mn_head = None
between_mn_end = None
index = 0
cur_node = first
while index < n:
index += 1
cur_node = cur_node.next
if index == m - 1:
before_m = cur_node
elif index == m:
between_mn_end = ListNode(cur_node.val)
between_mn_head = between_mn_end
elif index > m:
temp = between_mn_head
between_mn_head = ListNode(cur_node.val)
between_mn_head.next = temp
if index == n:
after_n = cur_node.next
# 进行拼接
between_mn_end.next = after_n
before_m.next = between_mn_head
return first.next
```