当前位置: 首页 > article >正文

Leetcode链表问题汇总

目录

      • [2. 两数相加](https://leetcode.cn/problems/add-two-numbers/)
      • [206. 反转链表](https://leetcode.cn/problems/reverse-linked-list/)
      • [92. 反转链表 II](https://leetcode.cn/problems/reverse-linked-list-ii/)
      • [19. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)
      • [21. 合并两个有序链表](https://leetcode.cn/problems/merge-two-sorted-lists/)
      • [23. 合并 K 个升序链表](https://leetcode.cn/problems/merge-k-sorted-lists/)
      • [24. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/)
      • [25. K 个一组翻转链表](https://leetcode.cn/problems/reverse-nodes-in-k-group/)
      • [61. 旋转链表](https://leetcode.cn/problems/rotate-list/)
      • [82. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/)
      • [83. 删除排序链表中的重复元素](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/)
      • [86. 分隔链表](https://leetcode.cn/problems/partition-list/)
      • [138. 随机链表的复制](https://leetcode.cn/problems/copy-list-with-random-pointer/)
      • [141. 环形链表](https://leetcode.cn/problems/linked-list-cycle/)
      • [142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)
      • [143. 重排链表](https://leetcode.cn/problems/reorder-list/)

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;
    }
};

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;
    }
};

还有一种递归的写法:
首先我们先考虑 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;
    }
};

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;
    }
};

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;
    }
};

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;
    }
};

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;
    }
};

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;
    }
};

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;
    }
};

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;
    }
};

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;
    }
};

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;
    }
};

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

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;
    }
};

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;
    }
};

138. 随机链表的复制

在这里插入代码片

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 -> next;
        while (slow && fast) {
            if (slow == fast) return true;
            slow = slow -> next;
            fast = fast -> next;
            if (fast) fast = fast -> next;
        }
        return false;
    }
};

142. 环形链表 II

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;
        }
    }
};

http://www.kler.cn/a/105811.html

相关文章:

  • PYH与MAC的桥梁MII/MIIM
  • 1.27刷题记录
  • vue框架技术相关概述以及前端框架整合
  • Pandas进行MongoDB数据库CRUD
  • hdfs:介绍三个脚本
  • zookeeper-3.8.3-基于ACL的访问控制
  • AI口语APP的实现
  • 嵌入式面试3(C++相关)
  • 前端入门(一)JavaScript语法、数据类型、运算、函数
  • openEuler 22.03 LTS 环境使用 Docker Compose 一键部署 JumpServer (all-in-one 模式)
  • 前端性别判断
  • SpringBoot集成Redis Cluster集群(附带Linux部署Redis Cluster高可用集群)
  • Runner GoUI自动化测试发布
  • Talk | 纽约州立宾汉姆顿大学博士生丁琰:开放环境中机器人的任务与动作规划
  • 一致性hash算法
  • 搜维尔科技:【应用】配备MTi-3的轻便型ROV,在水下进行地理标记视觉检测
  • Vue3-小兔鲜项目
  • 【Linux】安装部署Redis
  • 思维导图软件 ConceptDraw MINDMAP mac中文特色介绍
  • 时序预测 | Python实现ARIMA-LSTM自回归移动差分模型结合长短期记忆神经网络时间序列预测
  • linux 音视频架构 linux音视频开发
  • 程序包com.sun.xml.internal.bind.marshaller不存在
  • 什么是框架和库?
  • Java注解及自定义注解
  • 什么是IO多路复用?Redis中对于IO多路复用的应用?
  • GIT在window是 配置SSHKEY