92. 反转链表 II

92. 反转链表 II

LeetCode题目: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区间。为了能够重新拼接到原来的链表上,我们需要知道两个位置prenext
    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)反转上述链表:
    • 定义两个指针:lastcur。分别代表 已经完成反转的链表的头部未完成反转的链表的头部
      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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值