链表专题.

========快慢指针=========

Leetcode 876. 链表的中间结点

结论:

代码:

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        ListNode* fast =head, *slow = head;
        while (fast != nullptr && fast -> next != nullptr){
            slow = slow -> next;
            fast = fast -> next -> next;
        }
        return slow;
    }
};

Leetcode 141. 环形链表

在上面一题的基础上,加上一个slow和fast的判断即可。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* fast = head, *slow = head;
        while (fast != nullptr && fast -> next != nullptr){
            slow = slow -> next;
            fast = fast -> next -> next;
            if(slow == fast)    return true;
        }
        return false;
    }
};

Leetcode 142. 环形链表 II

本题需要返回进入环形链表的第一个节点:(且要求O(1)的空间复杂度)

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head, *slow = head;
        while (fast && fast -> next){
            slow = slow -> next;
            fast = fast -> next -> next;
            if(slow == fast){
                while (slow != head){
                    slow = slow -> next;
                    head = head -> next;
                }
                return slow;
            }
        }
        return nullptr;
    }
};

上面在进行数学的推导的时候,假设了在慢指针与快指针相遇时,慢指针还没有完成环形一圈的路程,下面给出证明:

Leetcode 143. 重排链表

组合题(快慢指针+翻转链表):只要先用876题确定出中间结点,然后对中间结点后面的所有节点进行206题的翻转链表操作,然后迭代构建出答案链表即可。

实现代码:

class Solution {
public:
    void reorderList(ListNode* head) {
        // step1: 快慢指针找到中间结点
        ListNode* fast = head, *slow = head;
        while(fast && fast -> next){
            slow = slow -> next;
            fast = fast -> next -> next;
        }   // 退出循环的slow指针就是中间节点的指向
        // step2: 完成后半部分链表的翻转
        ListNode* pre = nullptr, * cur = slow, * nxt = cur;
        while (cur != nullptr){
            nxt = nxt -> next;
            cur -> next = pre;
            pre = cur, cur = nxt;
        }   // 后半部分链表翻转后, 头结点为pre, 而cur == nullptr
        // step3: 完成链表的重排
        ListNode* headnext = head -> next, * h2 = pre, * h2next = pre -> next;
        while (h2next != nullptr){  // 注意是h2的next为空的时候,退出循环(模拟一下就可以知道,)
            head -> next = h2;
            h2 -> next = headnext;
            // 移动指针, 迭代子问题
            head = headnext;
            h2 = h2next;
            headnext = head -> next;
            h2next = h2next -> next;
        }
    }
};

这里在组合重排答案链表的时候,注意终止条件是h2next == nullptr。模拟一下即可,奇数结点headnxt==nullptr 或h2next == nullptr都可以,但是偶数链表当完成组合后,只能h2next == nullptr退出循环。


Leetcode 160. 相交链表

解题思路: 

代码实现:时间复杂度O(n+m) 空间复杂度O(1)

我的实现方式

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* pa = headA, *pb = headB;
        bool f1 = false, f2 = false;
        while(pa != nullptr && pb != nullptr){
            if(pa == pb)    return pa;      // 必须放在前面,否则当两个链表第一个节点就是相交结点的话,则会被跳过
            pa = pa -> next;
            pb = pb -> next;
            if(pa == nullptr && !f1)   pa = headB, f1 = true;
            if(pb == nullptr && !f2)   pb = headA, f2 = true;
        }
        return nullptr;
    }
};

更加优雅的方式:(为什么不会死循环,因为A和B在交换链表遍历之后,一定会在同一时刻(不相交的话)变成nullptr)(即同时走了a+b长度的路)

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *A = headA, *B = headB;
        while (A != B) {
            A = A != nullptr ? A->next : headB;
            B = B != nullptr ? B->next : headA;
        }
        return A;
    }
};

========反转链表=========

Leetcode 206. 反转链表(板子)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr, * cur = head, *nxt  = head;
        while (nxt != nullptr){
            nxt = nxt -> next;
            cur -> next = pre;
            pre = cur, cur = nxt;
        }
        return pre;
    }
};

Leetcode 92. 反转链表 II

利用规律在反转结束后,从原链表上看,pre指向这一段的末尾(即反转后链表的头),cur指向这一段的下一个节点。

思路:

 蓝色线是先按照206的解法翻转链表,粉线是接下来与整个链表对接的操作。

为了统一操作(left = 1的情况),设置一个头结点。

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode* dummy = new ListNode(0, head);    // 初始化虚拟头结点(统一left=1的情况)
        ListNode* p0 = dummy, *pre = nullptr, * cur, * nxt;
        // 找出p0
        for(int i = 0; i < left - 1; i ++)  p0 = p0 -> next;
        cur = p0 -> next, nxt = cur;
        // 开始翻转(那一部分(子链表),最后再进行与整体链表的对接)
        for(int i = 0; i < right - left + 1; i ++){     // 子链表花费right - left + 1次翻转
            nxt = nxt -> next;
            cur -> next = pre;
            pre = cur, cur = nxt;
        } 
        // 翻转完毕, 此时pre是当前子链表的'head', cur指向大链表中子链表的后一个结点(拼接)
        p0 -> next -> next = cur;
        p0 -> next = pre;
        return dummy -> next;                       // dummy的next结点才是真正的头结点
    }
};

Leetcode 25. K 个一组翻转链表

思路与leetcode 92相同,92题是本问题的子问题,组合一下就好。

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        int n = 0;          // 先统计一下链表中结点的个数
        for(ListNode* p = head; p != nullptr; p = p -> next)  n ++;
        int cnt = n / k;    // 整除-->代表翻转的次数
        ListNode* dummy = new ListNode(0, head);
        ListNode* p0 = dummy;
        while (cnt --){
            ListNode* pre = nullptr, *cur = p0 -> next, * nxt = cur;
            for(int i = 0; i < k; i ++){    // 一共k结点,操作k次
                nxt = nxt -> next;
                cur -> next = pre;
                pre = cur, cur = nxt;
            }
            cout << p0 -> val << endl;
            p0 -> next -> next = cur;   // cur此时是原翻转链表后面的一个结点
            p0 -> next = pre;           // pre是翻转的那部分子链表的头部
            // 更新p0值
            for(int i = 0; i < k; i ++) p0 = p0 -> next;
        }
        return dummy -> next;
    }
};

Leetcode 234. 回文链表

方法一:利用O(n)的空间,额外存储一下链表的值,利用数组的随机访问特性即可。(略)

方法二:递归(空间仍然是O(n))

使用本方法前,可以先看下这个递归实现的“逆序打印链表的值”的伪代码:

function print_values_in_reverse(ListNode head)
    if head is NOT null
        print_values_in_reverse(head.next)
        print head.val

 利用递归函数一直拿到链表的最后一个元素,与front指针指向元素比较。然后front往前走,递归出栈相当于右指针往前走。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        ListNode* front = head;
        auto f = [&](auto&& f, ListNode* cur) -> bool{
            if(cur != nullptr){
                if(!f(f, cur -> next)) return false;
                if(cur -> val != front -> val)  return false;
                front = front -> next;
            }
            return true;    // 递归终止的base case
        };
        return f(f, head);
    }
};

方法三:快慢指针/翻转后半部分链表

实现代码:

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        int n = 0; 
        ListNode* p = head;
        while (p != nullptr)    n ++,  p = p -> next;
        p = head;
        for(int i = 0; i < (n / 2); i ++)   p = p -> next;
        if (n % 2)  p = p -> next;      // 找到下半段链表的第一个结点
        // 翻转后半部分链表
        ListNode* pre = nullptr, * cur = p, * nxt = cur;
        while (cur != nullptr){
            nxt = nxt -> next;
            cur -> next = pre;
            pre = cur, cur = nxt;
        } 
        // 开始比较
        ListNode* i = head, * j = pre;
        while (i != nullptr && j != nullptr){
            if(i -> val != j -> val)    return false;
            i = i -> next, j = j -> next;
        }
        // 恢复成原来的链表
        p = head;   // 找到上半段链表的最后一个结点
        for(int i = 0; i < (n % 2 == 0? (n / 2 - 1): n / 2); i ++)   p = p -> next;
        ListNode* p1 = nullptr, * p2 = pre, * p3 = p2;
        while (cur != nullptr){
            p3 = p3 -> next;
            p2 -> next = p1;
            p1 = p2, p2 = p3;
        } 
        p -> next = p1;
        return true;
    }
};

========删除=========

Leetcode 237. 删除链表中的节点

(脑筋急转弯): 注意题目要求:我们只需要让链表在删除前后「看起来」是一样的就行

比如示例 1,我们可以把第二个节点的值替换成第三个节点的值,然后删除第三个节点,「看起来」删除前后就是一样的。具体来说,先把 node.val 更新成 node.next.val,然后删除 node 的下一个节点,把 node.next 更新成 node.next.next。

class Solution {
public:
    void deleteNode(ListNode* node) {
        int val = node -> next -> val;
        ListNode* p = node -> next;
        node -> val = val;
        node -> next = node -> next -> next;
        delete p, p = nullptr;
    }
};

Leetcode 19. 删除链表的倒数第 N 个结点

方法一:遍历链表得到总的结点数目,得到正数的位置删除即可

方法二:巧妙设置l和r指针,r先走n步,然后l和r同时走直到r走到最后一个结点,那么l的位置就是倒数第n + 1个结点。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* left = dummy, * right = dummy, *p;
        for(int i = 0; i < n; i ++)     right = right -> next;
        while(right -> next){
            left = left -> next;
            right = right -> next;
        }   // right和left始终差n个位置,right目前在最后一个结点,那么left就在倒数第n+1个结点上
        p = left -> next;
        left -> next = left -> next -> next;   
        delete p, p = nullptr;
        return dummy -> next;
    }
};

========排序链表=========

Leetcode 148. 排序链表

下面是“链表的归并排序”,包含子函数①寻找中间结点 ②合并两个有序链表

class Solution {
public:
    // func1: 快慢指针寻找中间结点(当偶数节点的时候,返回中位节点的靠前结点,注意fast的写法区别即可)
    ListNode* findmid(ListNode* head){
        if(!head)   return head;
        ListNode* fast = head -> next, *slow = head;
        while(fast && fast -> next){
            fast = fast -> next -> next;
            slow = slow -> next;
        }
        return slow;    // 当偶数节点的时候,返回中位节点的靠前结点
    }
    // func2: 合并两个有序链表
    ListNode* merge(ListNode* list1, ListNode* list2){
        ListNode* dummy = new ListNode(0);  // 虚节点
        ListNode* p1 = list1, * p2 = list2, * q = dummy;
        while(p1 && p2){
            int v1 = p1 -> val, v2 = p2 ->  val;
            if(v1 < v2)    q -> next = p1, p1 = p1 -> next;
            else           q -> next = p2, p2 = p2 -> next;
            q = q -> next;
        }
        // 扫尾
        while(p1)   q -> next = p1, p1 = p1 -> next, q = q -> next;
        while(p2)   q -> next = p2, p2 = p2 -> next, q = q -> next;
        return dummy -> next;
    }
    // func3:主函数
    ListNode* sortList(ListNode* head) {
        if(head == nullptr || head -> next == nullptr) return head;
        ListNode* midNode = findmid(head), * j = midNode -> next;
        midNode -> next = nullptr;  // 从中间断开链表
        ListNode* left = sortList(head); // 排左边的
        ListNode* right = sortList(j);    // 排右边的
        // 排完序后进行merge
        return merge(left, right);
    }
};

========其他=========

Leetcode 21. 合并两个有序链表

 方法一:迭代实现ez

class Solution {
public:
    // 迭代实现: 时间O(n) 空间O(1)
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr)    return list2;
        if(list2 == nullptr)    return list1;
        ListNode* dummy = new ListNode();
        ListNode* p1 = list1, *p2 = list2, *p = dummy;
        while (p1 && p2){
            int v1 = p1 -> val, v2 = p2 -> val;
            if(v1 < v2) p -> next = p1, p1 = p1 -> next;
            else        p -> next = p2, p2 = p2 -> next;
            p = p -> next;
        }
        if(p1 == nullptr)   p -> next = p2;
        else                p -> next = p1;
        return dummy -> next;
    }
};

方法二:递归实现

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == nullptr)    return list2;
        else if(list2 == nullptr)   return list1;
        else if(list1 -> val < list2 -> val){
            list1 -> next = mergeTwoLists(list1 -> next, list2);
            return list1;
        }else{
            list2 -> next = mergeTwoLists(list1, list2 -> next);
            return list2;
        }
    }
};

Leetcode 23. 合并 K 个升序链表

纯暴力做法,在21题的思路基础上暴力找下一个可能结点。O(n²)

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int n = lists.size();
        ListNode* dummy = new ListNode(0), * p = dummy;
        while(1){
            bool bkfg = true;
            int minnum = INT_MIN, minidx = -1;
            for(int i = 0; i < n; i ++){
                if(lists[i] != nullptr){
                    bkfg = false;
                    if(minidx == -1 || minnum > lists[i] -> val)
                        minidx = i, minnum = lists[i] -> val;
                }
            }
            if(bkfg)    break;
            p -> next = lists[minidx], lists[minidx] = lists[minidx] -> next, p = p -> next;
        }
        return dummy -> next;
    }
};

优化方法一:最小堆

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        auto cmp = [](const ListNode* a, const ListNode* b){
            return a -> val > b -> val;     // 最小堆
        };
        priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq;
        for(auto& head: lists)
            if(head)    pq.push(head);
        ListNode* dummy = new ListNode(0), *cur = dummy;  // 虚拟头结点
        while (!pq.empty()){                // 循环至heap为空
            auto node = pq.top();           // 剩余节点中最小的节点
            pq.pop();
            if(node -> next)    pq.push(node -> next);
            cur -> next = node;             // 合并到新链表中
            cur = cur -> next;
        }
        return dummy -> next;
    }
};

优化方法二:分治

class Solution {
public:
    // func: 合并两个有序链表
    ListNode* MergeTwo(ListNode* list1, ListNode* list2){
        ListNode* dummy = new ListNode(0),* cur = dummy;
        ListNode* p1 = list1, * p2 = list2;
        while (p1 && p2){
            int v1 = p1 -> val, v2 = p2 -> val;
            if(v1 < v2) cur -> next = p1, p1 = p1 -> next;
            else        cur -> next = p2, p2 = p2 -> next;
            cur = cur -> next;
        }
        // 扫尾
        if (p1)  cur -> next = p1;
        if (p2)  cur -> next = p2;
        return dummy -> next;
    }
    // func: 合并从 list[i]到lists[j - 1]的链表
    ListNode* subMerge(vector<ListNode*>& lists, int i, int j){
        int m = j - i;  // 计算需要合并的链表数目
        if(m == 0)  return nullptr;     // 输入lists为空
        if(m == 1)  return lists[i];    // 无需合并, 直接返回
        auto left = subMerge(lists, i, i + m / 2);   // 合并左半部分
        auto right = subMerge(lists, i + m / 2, j);  // 合并右半部分
        return MergeTwo(left, right);
    }
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        return subMerge(lists, 0, lists.size());
    }
};

Leetcode 2. 两数相加

class Solution {
public: // 本题直接变成int/longlong会爆整数范围, 应该直接考虑每一位相加(模拟加法)
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0), *p1 = l1, *p2 = l2, *p3 = dummy;
        int fg = 0;         // 进位标识符
        while(p1 != nullptr ||  p2 != nullptr){
            // cout << 6 << endl;
            int a, b;
            if(p1 && p2)
                a = p1 -> val, b = p2 -> val;
            else if(p1 == nullptr)  a = 0, b = p2 -> val;
            else if(p2 == nullptr)  a = p1 -> val, b = 0;   // 别忘记给另一个变量赋值
            int c = a + b + fg;
            int v = c % 10;
            fg = c / 10;
            ListNode* tmp = new ListNode(v);
            // cout << a << " " << b << " " << fg << endl;
            p3 -> next = tmp;
            p3 = p3 -> next;
            if(p1)  p1 = p1 -> next;
            if(p2)  p2 = p2 -> next;
        } 
        if(fg){     // 处理最后的进位
            ListNode* t = new ListNode(1);
            p3 -> next = t;
        }
        return dummy -> next;
    }
};

========综合应用=========

Leetcode 146. LRU 缓存

class Node{
public:
    int key, value;
    Node* prev, * next;
    Node(int k = 0, int v = 0): key(k), value(v){}
};

class LRUCache {
private:
    int capacity;
    Node* dummy;    // 虚拟结点
    unordered_map<int, Node*> key_to_node;
    // 构建双向链表
    // func1: 删除一个结点(抽出一本书)
    void remove(Node* x){
        x -> prev -> next = x -> next;
        x -> next -> prev = x -> prev;
    }
    // func2:在链表头添加一个结点(把一本书放在最上面)
    void push_front(Node* x){
        x -> prev = dummy;
        x -> next = dummy -> next;
        x -> prev -> next = x;
        x -> next -> prev = x;
    }
    // func3:
    Node* get_node(int key){
        auto it = key_to_node.find(key);
        if(it == key_to_node.end())
            return nullptr; // 没有这本书
        auto node = it -> second;   // 有 
        remove(node);   push_front(node);   // 抽出来并放在最上面
        return node;
    }
public:
    LRUCache(int capacity): capacity(capacity), dummy(new Node()) {
        dummy -> prev = dummy;
        dummy -> next = dummy;
    }
    
    int get(int key) {
        auto node = get_node(key);
        return node? node -> value: -1;
    }
    
    void put(int key, int value) {
        auto node = get_node(key);
        if(node){   // 有这本书(在getnode里面已经将书本放在最上面了)
            node -> value = value;  // 更新值
            return;
        }
        key_to_node[key] = node = new Node(key, value); // 新书
        push_front(node);   // 放在最上面
        if (key_to_node.size() > capacity){ // 书太多了,该LRU了
            auto back_node = dummy -> prev;
            key_to_node.erase(back_node -> key);
            remove(back_node);  // 去掉最后一本书
            delete back_node;   // 释放内存
        }
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Ocean__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值