• Leetcode链表问题汇总


    2. 两数相加

    比较简单的题目,头结点标示的个位数字,因此直接模拟就可以了。注意链表相关的题目可以添加一个虚拟头节点,用来简化一些边界情况的处理。

    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     ListNode *next;
     *     ListNode() : val(0), next(nullptr) {}
     *     ListNode(int x) : val(x), next(nullptr) {}
     *     ListNode(int x, ListNode *next) : val(x), next(next) {}
     * };
     */
    class Solution {
    public:
        ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
            ListNode* res = new ListNode(-1);
            ListNode* cur = res;
            int carry = 0; //进位标示
            while (l1 || l2) {
                int v1 = l1 ? l1 -> val : 0;
                int v2 = l2 ? l2 -> val : 0;
                int sum = v1 + v2 + carry;
                carry = sum / 10;
                cur -> next = new ListNode(sum % 10);
                cur = cur -> next;
                if (l1) l1 = l1 -> next;
                if (l2) l2 = l2 -> next;
            }
            if (carry) cur -> next = new ListNode(1);
            return res -> next;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    206. 反转链表

    翻转即将所有节点的next指针指向前驱节点。由于是单链表,我们在迭代时不能直接找到前驱节点,所以我们需要一个额外的指针保存前驱节点。同时在改变当前节点的next指针前,不要忘记保存它的后继节点。
    其实相当于头插法!

    class Solution {
    public:
        ListNode* reverseList(ListNode* head) {
            ListNode* prev = nullptr;
            ListNode* cur = head;
            while (cur) {
                ListNode* nxt = cur -> next;
                cur -> next = prev;
                prev = cur;
                cur = nxt;
            }
            return prev;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    还有一种递归的写法:
    首先我们先考虑 reverseList 函数能做什么,它可以翻转一个链表,并返回新链表的头节点,也就是原链表的尾节点。
    所以我们可以先递归处理 reverseList(head->next),这样我们可以将以head->next为头节点的链表翻转,并得到原链表的尾节点tail,此时head->next是新链表的尾节点,我们令它的next指针指向head,并将head->next指向空即可将整个链表翻转,且新链表的头节点是tail。

    class Solution {
    public:
        ListNode* reverseList(ListNode* head) {
            if (!head || !head->next) return head;
            ListNode *tail = reverseList(head->next);
            head->next->next = head;
            head->next = nullptr;
            return tail;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    92. 反转链表 II

    class Solution {
    public:
        ListNode* reverseBetween(ListNode* head, int left, int right) {
            ListNode* dummy = new ListNode(-1); // 建立一个虚拟头节点,因为有可能left==1,避免处理边界情况
            dummy -> next = head;
            auto a = dummy;
            for (int i = 0; i < left - 1; i++) a = a -> next; //找到left的前一个节点
            auto b = a -> next, c = b -> next;
            //接下来是反转链表的逻辑,要做right - left次
            for (int i = 0; i < right - left; i++) {
                auto d = c -> next;
                c -> next = b;
                b = c, c = d;
            }
            //两头的指针搞正确
            a -> next -> next = c;
            a -> next = b;
            return dummy -> next;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    找到前面的那个点,改掉指针即可。

    class Solution {
    public:
        ListNode* removeNthFromEnd(ListNode* head, int n) {
            ListNode* dummy = new ListNode(-1);
            dummy -> next = head;
            // sz: 包含虚拟头结点的整个链表的长度
            int sz = 0;
            for (auto p = dummy; p; p = p -> next) sz++;
            // 倒数第N个节点,也就是正数的(sz+1-n)个点.
            // 要删除这个点,要先到它前面的那个点,也就是第(sz-n)个点,从dummy开始跳,需要跳(sz-n-1)步
            auto p = dummy;
            for (int i = 0; i < sz - n - 1; i++) p = p -> next;
            p -> next = p -> next -> next;
            return dummy -> next;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    21. 合并两个有序链表

    判断大小,直接模拟即可,注意代码的简洁性。

    class Solution {
    public:
        ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
            ListNode* dummy=  new ListNode(-1);
            ListNode* tail = dummy;
            while (l1 && l2) {
                if (l1 -> val < l2 -> val) tail = tail -> next = l1, l1 = l1 -> next;
                else tail = tail -> next = l2, l2 = l2 -> next;
            }
            if (l1) tail -> next = l1;
            if (l2) tail -> next = l2;
            return dummy -> next;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    23. 合并 K 个升序链表

    自定义比较函数的优先队列即可

    class Solution {
    public:
        struct Cmp { bool operator() (ListNode* a, ListNode* b) {return a -> val > b -> val;} };
        ListNode* mergeKLists(vector<ListNode*>& lists) {
            priority_queue<ListNode*, vector<ListNode*>, Cmp> heap;
            auto dummy = new ListNode(-1), tail = dummy;
            for (auto l: lists) if (l) heap.push(l);
    
            while (heap.size()) {
                auto t = heap.top();
                heap.pop();
                tail = tail -> next = t;
                if (t -> next) heap.push(t -> next);
            }
            return dummy -> next;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    24. 两两交换链表中的节点

    p永远指向要交换的节点的前一个节点

    class Solution {
    public:
        ListNode* swapPairs(ListNode* head) {
            auto dummy = new ListNode(-1, head);
            for (auto p = dummy; p -> next && p -> next -> next;) {
                auto a = p -> next, b = a -> next;
                p -> next = b;
                a -> next = b -> next;
                b -> next = a;
                p = a;
            }
            return dummy -> next;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    25. K 个一组翻转链表

    1. 由于头结点可能会翻转,我们建议一个虚拟头结点,避免处理边界情况
    2. 由于要翻转链表,我们需要找到要翻转的链表的前一个节点,翻转后要要处理这一段的指针指向问题
    3. 需要判断长度,不足k个的不需要翻转
    class Solution {
    public:
        ListNode* reverseKGroup(ListNode* head, int k) {
            ListNode* dummy = new ListNode(-1);
            dummy -> next = head;
            for (auto p = dummy;;) {
                auto q = p;
                for (int i = 0; q && i < k; i++) q = q -> next;// 判断接下来是否有k个节点
                if (!q) break; // 不足k个节点
                auto b = p -> next, c = b -> next;
                // k个一段,需要翻转k-1次
                for (int i = 0; i < k - 1; i++) {
                    auto d = c -> next;
                    c -> next = b;
                    b = c, c = d;
                }
                // e是翻转后的这一段的尾结点
                auto e = p -> next;
                // 连接这一段和下一段
                e -> next = c;
                // 连接上一段和这一段
                p -> next = b;
                // p永远指向下一段的前一个节点
                p = e;
            }
            return dummy -> next;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    61. 旋转链表

    相当于把后面一部分的链表拼接到前面

    class Solution {
    public:
        ListNode* rotateRight(ListNode* head, int k) {
            if (!head) return head;
    
            int sz = 0; //链表长度
            ListNode* tail; //当前链表的尾节点
            for (auto p = head; p; p = p -> next) {
                tail = p;
                sz++;
            }
            k %= sz;
            if (!k) return head;
    
            auto p = head;
            //找到前半部分的最后的元素
            for (int i = 0; i < sz - k - 1; i++) p = p -> next;
    
            //把后半部分拼到前面即可
            //1. 前半部分的尾结点的next指向空
            //2. 后半部分的尾结点的next指向链表的头结点
            tail -> next = head;
            head = p -> next; // 这个是最新的头结点
            p -> next = nullptr;
            return head;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    82. 删除排序链表中的重复元素 II

    类似于双指针,判断一段相等值的个数是只有一个,还是至少两个,来选择不同的策略。注意看代码里的条件判断的部分。

    class Solution {
    public:
        ListNode* deleteDuplicates(ListNode* head) {
            auto dummy = new ListNode(-1);
            dummy -> next = head;
            auto p = dummy;
            while (p -> next) {
                auto first = p -> next;
                auto end = first -> next;
                // 让end走到和first不等的节点
                while (end && end -> val == first -> val) end = end -> next; 
                // 说明相等这一段只有一个
                if (first -> next == end) p = p -> next;
                // 至少有两个,那么跳过这一段
                else p -> next = end;
            }
            return dummy -> next;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    83. 删除排序链表中的重复元素

    其实把上面题的else里面改一下就可以了

    class Solution {
    public:
        ListNode* deleteDuplicates(ListNode* head) {
            auto dummy = new ListNode(-1);
            dummy -> next = head;
            auto p = dummy;
            while (p -> next) {
                auto first = p -> next;
                auto end = first -> next;
                while (end && end -> val == first -> val) end = end -> next; 
                if (first -> next == end) p = p -> next;
                else p -> next -> next = end;
            }
            return dummy -> next;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    当然也有更简单的写法,思路就是判断一下枚举到的值和当前的值是否相等,只保留不相等的节点

    class Solution {
    public:
        ListNode* deleteDuplicates(ListNode* head) {
            if (!head) return head;
            auto cur = head;
            for (auto p = head -> next; p; p = p -> next) {
                if (p -> val != cur -> val) cur = cur -> next = p;
            }
            cur -> next = nullptr;
            return head;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    86. 分隔链表

    建立两个链表,分别存储 v a l < x val \lt x val<x v a l ≥ x val \ge x valx的节点,最后拼接起来即可

    class Solution {
    public:
        ListNode* partition(ListNode* head, int x) {
            ListNode* sx = new ListNode(-1), *p = sx;
            ListNode* lx = new ListNode(-1), *q = lx;
            for (auto c = head; c; c = c -> next) {
                if (c -> val < x) p = p -> next = c;
                else q = q -> next = c;
            }
            p -> next = lx -> next;
            q -> next = nullptr; //注意这里
            return sx -> next;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    138. 随机链表的复制

    class Solution {
    public:
        Node* copyRandomList(Node* head) {
            unordered_map<Node*, Node*> h;
            for (auto p = head; p; p = p -> next) {
                h[p] = new Node(p -> val);
            }
            for (auto& [k, v]: h) {
                if (k -> next) v -> next = h[k -> next];
                if (k -> random) v -> random = h[k -> random];
                
            }
            return h[head];
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    141. 环形链表

    用两个指针从头开始扫描,第一个指针每次走一步,第二个指针每次走两步。如果走到 null,说明不存在环;否则如果两个指针相遇,则说明存在环。
    原因:
    假设链表存在环,则当第一个指针走到环入口时,第二个指针已经走到环上的某个位置,距离环入口还差 x 步。由于第二个指针每次比第一个指针多走一步,所以第一个指针再走 x步,两个指针就相遇了。
    复杂度分析,由于慢指针走的总步数小于链表总长度,复杂度为 O ( N ) O(N) O(N).

    class Solution {
    public:
        bool hasCycle(ListNode *head) {
            if (!head || !head -> next) return false;
            ListNode* slow = head;
            ListNode* fast = head;
            while(slow && fast) {
                slow = slow -> next;
                fast = fast -> next;
                if (fast) fast = fast -> next;
                if (slow == fast) return true;
            }
            return false;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    142. 环形链表 II

    介绍做法:
    slow和fast两个指针从head开始走,slow每次都一步,fast每次走两步。如果fast走到了null,则无环。
    否则,当相遇后,令slow置为head,fast不变,两个每次各走一步,相遇时即为环的起点。(证明)(https://www.acwing.com/solution/content/241/)

    class Solution {
    public:
        ListNode *detectCycle(ListNode *head) {
            if (!head || !head->next) return 0;
            ListNode *first = head, *second = head;
    
            while (first && second)
            {
                first = first->next;
                second = second->next;
                if (second) second = second->next;
                else return nullptr;
    
                if (first == second)
                {
                    first = head;
                    while (first != second)
                    {
                        first = first->next;
                        second = second->next;
                    }
                    return first;
                }
            }
    
            return nullptr;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    143. 重排链表

    1. 先把后半部分翻转,前半部分尾节点的next置为null,这样就得到了两个链表
    2. 把后半部分的链表插入到前半部分的链表即可
    class Solution {
    public:
        void reorderList(ListNode* head) {
            int n = 0;
            for (ListNode *p = head; p; p = p->next) n ++ ;
            if (n <= 2) return;
            ListNode *later = head;
            // later是前一段的最后一个节点 
            for (int i = 0; i + 1 < (n + 1) / 2; i ++ ) later = later->next;
    
            //后半段翻转
            ListNode *b = nullptr, *c = later -> next;
            later->next = nullptr; //前半段末尾节点的next置为空
            while (c)
            {
                ListNode *d = c->next;
                c->next = b;
                b = c;
                c = d;
            }
           
            //后半段插入到前半段
            while (b)
            {
                c = b->next;
                b->next = head->next;
                head->next = b;
                head = head->next->next;
                b = c;
            }
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    160. 相交链表

    A链表每次向后走一步,走到null的话把指针指向B链表的head。
    B链表同理。
    如果不相交,两个指针相等的时候为nullptr
    如果相交,相等的时候即为交点。

    class Solution {
    public:
        ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
            auto p = headA, q = headB;
            while (p != q) {
                p = p ? p->next : headB;
                q = q ? q->next : headA;
            }
            return p;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    234. 回文链表

    翻转后半部分,对比,然后要恢复链表。注意链表长度是奇数的时候,我们保持前半部分比后半部分多1,代码是这么来实现的。

    class Solution {
    public:
        bool isPalindrome(ListNode* head) {
            int n = 0;
            for (auto p = head; p; p = p -> next) n++;
            if (n <= 1) return true;
            int half = n / 2;
    
            //找到后半部分的头结点,如果是奇数个的话,那么后半部分比前半部分少一个
            auto a = head;
            for (int i = 0; i < n - half; i++) a = a -> next;
            auto b = a -> next;
            
            //翻转后半部分链表 一共half个结点,做half-1次翻转操作
            for (int i = 0; i < half - 1; i++) {
                auto c = b -> next;
                b -> next = a;
                a = b, b = c;
            }
    
            // 对比结点的值 p和q分别为前半部分的头结点和后半部分的头结点(也即是翻转前的尾结点)
            auto p = head, q = a;
            bool success = true;
            for (int i = 0; i < half; i++) {
                if (p -> val != q -> val) {
                    success = false;
                    break;
                }
                p = p -> next;
                q = q -> next;
            }
    
            auto tail = a;
            // 把后半部分的链表恢复原状
            b = a -> next;
            for (int i = 0; i < half - 1; i++) {
                auto c = b -> next;
                b -> next = a;
                a = b, b = c;
            }
    
            // 要把尾结点指向NULL,因为我们上面17-20行翻转了这部分,它是指向了倒数第二个节点
            a -> next = NULL;
            return success;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
  • 相关阅读:
    MediaPlayer使用以及常见问题
    linux升级glibc-2.28
    Jmeter接口测试, 快速完成一个单接口请求
    【视频处理】通过调用图像来重建新影片及计算颜色通道的平均灰度值,并检测帧与前一帧之间的差异(Matlab代码实现)
    容联云入选IDC生成式AI图谱,多个案例被评典型应用
    Java实现一棵二叉树,并完成二叉树的层次遍历,两种中序遍历 递归 &非递归
    接入Twitter和Facebook分享踩坑记录
    16 最长回文串
    品牌方发行NFT时,应如何考量实用性?
    调试工具记录
  • 原文地址:https://blog.csdn.net/dezhonger/article/details/134023764