链表LeetCode

链表⛓

1. 合并两个有序链表🥑

👉LeetCode21. 合并两个有序链表

👽思路解析:

  • 双指针 p1 p2 分别指向两个链表头结点
  • 创建新链表指针 p 维护(技巧:虚拟头结点 dummy
  • 遍历两个链表,较小的结点优先给 p 指针,终止条件存在链表(任意一个或两个都)遍历完成
  • 判断 p1 p2 是否非空,非空则追加给 p 指针
/**
 * 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* mergeTwoLists(ListNode* list1, ListNode* list2) {
        // 虚拟头结点
        ListNode* dummy = new ListNode(-1);
        ListNode* p = dummy;
        ListNode* p1 = list1; 
        ListNode* p2 = list2;
        
        while(p1!=nullptr && p2!=nullptr){
            // 比较 p1 和 p2 两个指针
        	// 将值较小的的节点接到 p 指针
            if (p1->val <= p2->val){
                p->next = p1;
                p1 = p1->next;
            }
            else{
                p->next = p2;
                p2 = p2->next;
            }
            p = p->next;
        }
        if (p1!=nullptr){
            p->next = p1;
        }
        if (p2!=nullptr){
            p->next = p2;
        }
        return dummy->next;
    }
};
  • 「虚拟头结点」技巧,也就是 dummy 节点 :
    如果不使用 dummy 虚拟节点,代码会复杂很多,而有了 dummy 节点这个占位符,可以避免处理空指针的情况,降低代码的复杂性。
    当你需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理

2. 链表的分解🥓

👉Leetcode86. 分隔链表

👽思路解析:

  • p 指针遍历原链表,比较分区,终止条件原链表遍历完成
  • 双指针 p1 p2 分别维护两个分区新链表(技巧:虚拟头结点 dummy1 dummy2
  • 断开原链表中每个节点
/**
 * 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* partition(ListNode* head, int x) {
        // 存放小于 x 的链表的虚拟头结点
        ListNode* dummy1 = new ListNode(-1);
        // 存放大于等于 x 的链表的虚拟头结点
        ListNode* dummy2 = new ListNode(-1);
        // p1, p2 指针负责生成结果链表
        ListNode* p1 = dummy1;
        ListNode* p2 = dummy2;
        // p 负责遍历原链表,类似合并两个有序链表的逻辑
    	// 这里是将一个链表分解成两个链表
        ListNode* p = head;
        while (p!=nullptr){
            if (p->val < x){
                p1->next = p;
                p1 = p1->next;
            }
            else{
                p2->next = p;
                p2 = p2->next;
            }
            // 断开原链表中的每个节点的 next 指针
            ListNode* tmp = p->next;
            p->next = nullptr;
            p = tmp;
        }
        // 连接两个链表
        p1->next = dummy2->next;
        return dummy1->next;
    }
};

3. 合并 k 个有序链表🍒

👉Leetcode23. 合并K个升序链表

👽思路解析:

  • 创建新链表指针 p 维护(技巧:虚拟头结点 dummy
  • 优先级队列 priority_queue (最小堆)实现较小节点优先给 p 指针
/**
 * 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* mergeKLists(vector<ListNode*>& lists) {
        if (lists.size() == 0) return nullptr;
		// 虚拟头结点
        ListNode* dummy = new ListNode(-1);
        ListNode* p = dummy;
		// 优先级队列,最小堆
        auto cmp = [](ListNode* a, ListNode* b) { return (a->val) > (b->val); };
        priority_queue < ListNode*, vector<ListNode*>,decltype(cmp)>  pq(cmp);
        // 将 k 个链表的头结点加入最小堆
        for (ListNode* head : lists) {
            if (head != nullptr)
                pq.push(head);
        }

        while (!pq.empty()) {
            // 获取最小节点,接到结果链表中
            ListNode* node = pq.top();
            pq.pop();
            p->next = node;
            if (node->next != nullptr) {
                pq.push(node->next);
            }
            // p 指针不断前进
            p = p->next;
        }
        return dummy->next;
    }
};
  • 优先队列 pq 中的元素个数最多是 k,所以一次 pop 或者 push 方法的时间复杂度是 O(logk)
    所有的链表节点都会被加入和弹出 pq所以算法整体的时间复杂度是 O(Nlogk),其中 k 是链表的条数,N 是这些链表的节点总数

4. 链表的倒数第 k 个节点🍩

先让一个指针 p1 指向链表的头节点 head,然后走 k

再用一个指针 p2 指向链表头节点 head

p1p2 同时向前走,p1 走到链表末尾的空指针时前进了 n - k 步,p2 也从 head 开始前进了 n - k 步,停留在第 n - k + 1 个节点上,即恰好停链表的倒数第 k 个节点上

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

👽思路解析:

  • 封装>>>链表的倒数第 k 个节点方法
  • 建立虚拟头结点
  • 找到倒数第 n+1 个节点,删除第 n 个节点
/**
 * 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* FindNthFromEnd(ListNode* head, int n) {
                ListNode* p1 = head;
        		// p1 先走 k 步
                for(int i=0;i<n;i++){
                    p1 = p1->next;
                }
                ListNode* p2 = head;
        		// p1 和 p2 同时走 n - k 步
                while(p1!=nullptr){
                    p1 = p1->next;
                    p2 = p2->next;
                }
        		// p2 现在指向第 n - k + 1 个节点,即倒数第 k 个节点
                return p2;
            }
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        	// 虚拟头结点
            ListNode* dummy = new ListNode(-1);
            dummy->next = head;
        	// 删除倒数第 n 个,要先找倒数第 n + 1 个节点
            ListNode* x = FindNthFromEnd(dummy, n+1);
        	// 删掉倒数第 n 个节点
            x->next = x->next->next;
            return dummy->next;
    }
};
  • 虚拟头结点技巧,防止删除头结点时出错,因为需要找到倒数第 n+1 个节点

5. 链表的中点🥚

👉Leetcode876. 链表的中间结点

👽思路解析:

【🎫】 快慢指针
每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点

/**
 * 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* middleNode(ListNode* head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast!=nullptr && fast->next!=nullptr){
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
};
  • 当链表长度为偶数时,返回中点两个节点靠后的节点

6. 链表是否包含环🍟

👉Leetcode141. 环形链表

👽思路解析:

【🎫】 快慢指针
每当慢指针 slow 前进一步,快指针 fast 就前进两步
如果 fast 最终遇到空指针,说明链表中没有环
如果 fast 最终和 slow 相遇,那肯定是 fast 超过了 slow 一圈,说明链表中含有环

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast!=nullptr && fast->next!=nullptr){
            fast = fast->next->next;
            slow = slow->next;
            // 快慢指针相遇,说明含有环
            if (fast==slow)
                return true;
        }
        return false;
    }
};

👉Leetcode142. 环形链表 II

👽思路解析:

【🎫】 快慢指针
当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast!=nullptr && fast->next!=nullptr){
            fast = fast->next->next;
            slow = slow->next;
            // 快慢指针相遇,说明含有环
            if (fast==slow)
                break;
        }
        if (fast==nullptr || fast->next==nullptr){
            return nullptr;
        }
        // 重新指向头结点
        slow = head;
        // 快慢指针同步前进,相交点就是环起点
        while(fast!=slow){
            fast = fast->next;
            slow = slow->next;
        }
        return fast;
    }
};

7. 链表是否相交🍏

👉Leetcode160. 相交链表

👽思路解析:

【📌】双指针
p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A
最后让 p1p2 能够同时到达相交节点 c1

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* p1 = headA;
        ListNode* p2 = headB;
        while(p1!=p2){
            if (p1==nullptr)
                p1 = headB;
            else
                p1 = p1->next;
            if (p2==nullptr)
                p2 = headA;
            else
                p2 = p2->next;
        }
        return p1;
    }
};
  • 链表不相交最后会停止在"拼接"链表尾部 nullptr ,包含返回 null 的情况

✍️ 小方块xfk

📅 写于 2023年1月

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值