92. 反转链表 II
我的思路
-
把大象放进冰箱需要三步:1.打开冰箱;2.放入大象;3.关闭冰箱。
-
同样,反转链表也需要三步:1.找到需要反转的区间;2.反转该区间;3.将该区间放回原位。
-
开辟一个
vector<ListNode *> tmp
,存放该链表,然后反转tmp[left:right]
,最后,将tmp
中的元素 连 起来,返回tmp[0]
。
根据上述思路,写出AC代码:
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
vector<ListNode *> tmp;
for (ListNode* node = head; node != nullptr; node = node->next){
tmp.push_back(node);
}
int l = left-1;
int r = right-1;
while (l < r){
ListNode *t = tmp[l];
tmp[l] = tmp[r];
tmp[r] = t;
l ++;
r --;
}
for (int i = 0; i < tmp.size(); i ++){
if (i == tmp.size() - 1){
tmp[i]->next = nullptr;
}else{
tmp[i]->next = tmp[i+1];
}
}
return tmp[0];
}
};
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:7.4 MB, 在所有 C++ 提交中击败了5.77%的用户
- 复杂度分析:
- 时间复杂度:O(n)。至多进行3次遍历链表。
- 空间复杂度:O(n)。开辟了tmp数组。
思路二:优化空间复杂度
- 显然,此题的空间复杂度是可以优化的。
- 假设当前的链表是这个样子的:
1 -> 2 -> 3 -> 4 -> 5 -> 6 ↑ ↑ left right
- 首先,取出链表的left~right区间。为了能够重新拼接到原来的链表上,我们需要知道两个位置
pre
和next
:pre next ↓ ↓ 1 -> 2 -> 3 -> 4 -> 5 -> 6 ↑ ↑ left right
- 1.确定pre和next
- 2.left~right断开连接
- 3.接下来,反转left~right链表
- 现在们得到了一个链表:
2 -> 3 -> 4 -> 5 ↑ ↑ left right
- 如何反转该链表?
- 1.栈。2.使用容器存放。这两种方式的空间复杂度都是
O(n)
.
- 空间复杂度
O(1)
反转上述链表:- 定义两个指针:
last
和cur
。分别代表 已经完成反转的链表的头部 和 未完成反转的链表的头部 。
显然,这个状态下,应该执行的代码是:3 -> 2 4 -> 5 ↑ ↑ last cur
1、记录cur的下一个位置(5)。 2、cur->next = last. 3、last更新为cur. 4、cur更新为下一个位置(5)。
- 定义两个指针:
- 显然,反转该链表的代码呼之欲出:
void reverseLinkList(ListNode* left, ListNode* right){ ListNode* last = nullptr; ListNode* cur = left; while (last != right){ ListNode* next = cur->next; cur->next = last; last = cur; cur = next; } }
- 反转完毕以后,重新拼接链表即可。
pre next ↓ ↓ 1 5 -> 4 -> 3 -> 2 6 ↑ ↑ right left
pre -> next = right; left -> next = next;
AC代码如下:
class Solution {
public:
void reverseLinkList(ListNode* left, ListNode* right){
ListNode* last = nullptr;
ListNode* cur = left;
while (last != right){
ListNode* next = cur->next;
cur->next = last;
last = cur;
cur = next;
}
}
ListNode* reverseBetween(ListNode* head, int left, int right) {
if (left == right){
return head;
}
ListNode* dummy = new ListNode(-1); // 哨兵
dummy->next = head;
ListNode *leftNode, *rightNode, *preNode, *nextNode;
// 寻找leftNode和preNode
preNode = dummy;
for (int i = 0; i < left - 1; i ++){
preNode = preNode->next;
}
leftNode = preNode->next;
// 寻找rightNode和nextNode
rightNode = preNode;
for (int i = 0; i < right - left + 1; i ++){
rightNode = rightNode->next;
}
nextNode = rightNode->next;
// leftNode~rightNode断开连接
preNode->next = nullptr;
rightNode->next = nullptr;
// 反转leftNode~rightNode
reverseLinkList(leftNode, rightNode);
// 重新拼接
preNode->next = rightNode;
leftNode->next = nextNode;
// 返回
return dummy->next;
}
};
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:7.3 MB, 在所有 C++ 提交中击败了20.84%的用户
-
注意:上述代码中使用了哨兵
dummy
。这样可以简化操作。 -
复杂度分析:
- 时间复杂度:
O(n)
。最坏情况下,可能进行2次扫描。 - 空间复杂度:
O(1)
。
- 时间复杂度:
进阶:使用一趟扫描
头插法
- 具体思路,不可言传,只能意会。
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
ListNode *dummy = new ListNode(-1);
dummy->next = head;
ListNode *pre = dummy;
for (int i = 0; i < left - 1; i ++){
pre = pre->next;
}
ListNode* cur = pre->next;
ListNode* next;
for (int i = 0; i < right - left; i ++){
next = cur->next;
cur->next = next->next;
next->next = pre->next;
pre->next = next;
}
return dummy->next;
}
};
执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:7.1 MB, 在所有 C++ 提交中击败了67.58%的用户
- 复杂度分析:
- 时间复杂度:
O(n)
- 空间复杂度:
O(1)
- 时间复杂度:
2021.3.18 23:33