LeetCode高频题刷题笔记(二)链表

本文介绍了LeetCode中涉及链表操作的多个问题,包括寻找链表中间节点、排序链表、重排链表、旋转链表、两两交换节点、合并K个升序链表、删除重复元素、环形链表检测、回文链表判断、相交链表查找、奇偶链表重组、K个一组翻转链表、反转链表II以及复制带随机指针的链表。解题策略涵盖迭代、递归、哈希表和双指针等技巧。
摘要由CSDN通过智能技术生成

题目

1.链表的中间结点( LeetCode 876

难度: 简单
题目表述:
给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
代码(C++):

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

题解: 快慢指针


2.排序链表( LeetCode 148

难度: 中等
题目表述:
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
代码(C++):

class Solution {
public:
    ListNode* sortList(ListNode* head) {
        if (head == nullptr) return nullptr;
        ListNode* p = head;
        int length = 0;
        while (p != nullptr) {
            p = p->next;
            length++;
        }
        ListNode* dummyHead = new ListNode(0, head);
        for (int sublength = 1; sublength < length; sublength <<= 1) {
            ListNode* prev = dummyHead;
            ListNode* curr = dummyHead->next;
            while (curr != nullptr) {
                ListNode* head1 = curr;
                for (int i = 1; i < sublength && curr->next != nullptr; i++) {
                    curr = curr->next;
                }
                ListNode* head2 = curr->next;
                curr->next = nullptr;
                curr = head2;
                for (int i = 1; i < sublength && curr !=nullptr && curr->next != nullptr; i++) {
                    curr = curr->next;
                }
                ListNode* next = nullptr;
                if (curr != nullptr) {
                    next = curr->next;
                    curr->next = nullptr;
                }
                prev->next = merge(head1, head2);
                while (prev->next != nullptr) {
                    prev= prev->next;
                }
                curr = next;
            }
        }
        return dummyHead->next;
    }
    ListNode* merge(ListNode* head1, ListNode* head2) {
        ListNode* dummyHead = new ListNode();
        ListNode* tmp =dummyHead;
        ListNode* tmp1 = head1;
        ListNode* tmp2 = head2;
        while (tmp1 != nullptr && tmp2 != nullptr) {
            if (tmp1->val < tmp2->val) {
                tmp->next = tmp1;
                tmp1 = tmp1->next;
            } else {
                tmp->next = tmp2;
                tmp2 = tmp2->next;
            }
            tmp = tmp->next;
        }
        if (tmp1 != nullptr) {
            tmp->next = tmp1;
        } else if (tmp2 != nullptr) {
            tmp->next = tmp2;
        }
        return dummyHead->next;
    }
};

题解:
时间复杂度是O(nlogn) 的排序算法包括 归并排序、堆排序 和 快速排序(快速排序的最差时间复杂度是 O(n^2)),其中最适合链表的排序算法是归并排序。
使用自底向上的方法实现归并排序,则可以达到 O(1) 的空间复杂度。


3.重排链表( LeetCode 143

难度: 中等
题目表述:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
代码(C++):

class Solution {
public:
    void reorderList(ListNode* head) {
        if (head == nullptr) return;
        ListNode* mid = middleNode(head);
        ListNode* head2 = reverseList(mid->next);
        mid->next = nullptr;
        mergeList(head, head2);
    }
    ListNode* middleNode(ListNode* head) {
        ListNode* slow = head;
        ListNode* fast = head;
        while (fast->next != nullptr && fast->next->next != nullptr) {
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while (cur != nullptr) {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
    void mergeList(ListNode* head1, ListNode* head2) {
        ListNode* p1 = head1;
        ListNode* p2 = head2;
        while (p1 != nullptr && p2 != nullptr) {
            ListNode* tmp_p1 = p1->next;
            ListNode* tmp_p2 = p2->next;
            p1->next = p2;
            p1 = tmp_p1;
            p2->next = p1;
            p2 = tmp_p2;
        }
    }
};

题解:
寻找链表中点 + 链表逆序 + 合并链表
寻找链表中点:若是偶数个节点,则
fast->next != nullptr && fast->next->next != nullptr找的是前半段的尾
fast != nullptr && fast->next != nullptr找的是后半段的首


4.旋转链表( LeetCode 61

难度: 中等
题目表述:
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
代码(C++):

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if (head == nullptr || head->next == nullptr || k == 0) return head;
        int length = 1;
        ListNode* tail = head;
        while (tail->next != nullptr) {
            tail = tail->next;
            length++;
        }
        int add = length - (k % length);
        if (add == length) return head;
        tail->next = head;
        while (add--) {
            tail = tail->next;
        }
        head = tail->next;
        tail->next = nullptr;
        return head;
    }
};

题解:
闭合为环


5.两两交换链表中的节点( LeetCode 24

难度: 中等
题目表述:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
代码(C++):

class Solution {
public:
	// 迭代
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0, head);
        ListNode* pre = dummyHead;
        while (pre->next && pre->next->next) {
            ListNode* node1 = pre->next;
            ListNode* node2 = pre->next->next;
            node1->next = node2->next;
            node2->next = node1;
            pre->next = node2;
            pre = node1;
        }
        return dummyHead->next;
    }
    // 递归
    ListNode* swapPairs(ListNode* head) {
        if (head == nullptr || head->next == nullptr) return head;
        ListNode* newHead = head->next;
        head->next = swapPairs(newHead->next);
        newHead->next = head;
        return newHead;
    }
};

题解: 迭代 / 递归


6.合并K个升序链表( LeetCode 23

难度: 困难
题目表述:
给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
代码(C++):

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        if (lists.size() == 0) return nullptr;
        return merge(lists, 0, lists.size() - 1);
    }
    ListNode* merge(vector<ListNode*>& lists, int l, int r) {
        if (l == r) {
            return lists[l];
        }
        int mid = (l + r) >> 1;
        return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
    }
    ListNode* mergeTwoLists(ListNode* head1, ListNode* head2) {
        ListNode* dummyHead = new ListNode();
        ListNode *p = dummyHead, *p1 = head1, *p2 = head2;
        while (p1 != nullptr && p2 != nullptr ) {
            if (p1->val < p2->val) {
                p->next = p1;
                p1 = p1->next;
            } else {
                p->next = p2;
                p2 = p2->next;
            }
            p = p->next;
        }
        p->next = p1 ? p1 : p2;
        return dummyHead->next;
    }
};

题解: 分支合并


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

难度: 中等
题目表述:
给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
代码(C++):

class Solution {
public:
	// 迭代
    ListNode* deleteDuplicates(ListNode* head) {
        if (!head) return head;
        ListNode* node = head;
        ListNode* dummyHead = new ListNode(0, head);
        ListNode* pre = dummyHead;
        while (pre->next && pre->next->next) {
            if (pre->next->val == pre->next->next->val) {
                int x = pre->next->val;
                while (pre->next && pre->next->val == x) {
                    pre->next = pre->next->next;
                }
            } else {
                pre = pre->next;
            }
        }
        return dummyHead->next;
    }
    // 递归
    ListNode* deleteDuplicates(ListNode* head) {
        if (!head || !head->next) return head;
        if (head->val == head->next->val) {
            int x = head->val;
            while (head && head->val == x) {
                head = head->next;
            }
            head = deleteDuplicates(head);
        } else {
            head->next = deleteDuplicates(head->next);
        }
        return head;
    }
};

题解: 头节点不一定会保留
链表和树的问题,一般都可以有递归和迭代两种写法。


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

难度: 简单
题目表述:
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
代码(C++):

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if (!head) {
            return head;
        }
        ListNode* cur = head;
        while (cur->next) {
            if (cur->val == cur->next->val) {
                cur->next = cur->next->next;
            }
            else {
                cur = cur->next;
            }
        }
        return head;
    }
};

题解: 头节点一定会保留


9.环形链表 II ( LeetCode 142

难度: 中等
题目描述:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
代码(C++):

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

题解: 快慢指针
fast += 2, slow += 1,fast 和 slow 相差一步最合理 ,fast 跑的越慢,越能在环中少跑几圈等到还在外面直线跑的 slow
外循环结束条件:fast != NULL && fast->next != NULL
慢指针第一圈走不完一定会和快指针相遇。设链表中环外部分的长度为 a,slow 指针进入环后,又走了 b 的距离与 fast 相遇,此时,fast 指针已经走完了环的 n 圈。任意时刻,fast 指针走过的距离都为slow 指针的 2 倍,故可得
a + n(b + c) + b = 2(a + b) ⟹ a = c + (n - 1)(b + c)
在这里插入图片描述


10.回文链表( LeetCode 234

难度: 简单
题目描述:
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表,如 [1,2,2,1]
代码(C++):

/** 快慢指针
1.找到前半部分链表的尾节点。
2.反转后半部分链表。
3.判断是否回文。
4.恢复链表。
5.返回结果。
**/
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        ListNode* lEnd = getMiddleNode(head);
        ListNode* reverseRHead = reverseList(lEnd->next);
        ListNode* p1 = head, *p2 = reverseRHead;
        bool res = true;
        while (res && p2 != nullptr) {
            if (p1->val != p2->val) {
                res = false;
            }
            p1 = p1->next;
            p2 = p2->next;
        }
        lEnd->next= reverseList(reverseRHead);
        return res;
    }
    ListNode* getMiddleNode(ListNode* head) {
       ListNode* slow = head;
       ListNode* fast = head; 
       while (fast->next && fast->next->next) {
           slow = slow->next;
           fast = fast->next->next;
       }
       return slow;
    }
    ListNode* reverseList(ListNode* head) {
        ListNode *pre = nullptr, *cur = head;
        while (cur) {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};
// 利用递归的特性:从后往前
class Solution {
    ListNode* firstNode;
public:
    bool isPalindrome(ListNode* head) {
        firstNode = head;
        return recursivelyCheck(head);
    }
    bool recursivelyCheck(ListNode* currentNode) {
        if (currentNode != nullptr) {
            if (!recursivelyCheck(currentNode->next)) {
                return false;
            }
            if (currentNode->val != firstNode->val) {
                return false;
            }
            firstNode = firstNode->next;
        } 
        return true;
    }
};

题解: 快慢指针 / 递归


11.相交链表( LeetCode 160

难度: 简单
代码(C++):

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == NULL || headB == NULL)
            return NULL;
        ListNode *pA = headA;
        ListNode *pB = headB;
        while (pA != pB){
            if (pA == NULL)
                pA = headB; 
            else
                pA = pA->next;
            if (pB == NULL)
                pB = headA;
            else
                pB = pB->next;
            
        }
        return pA;
    }
};

题解: 双指针交叉循环遍历
无论 A、B 两个链表是否有相交点,最终都会指向一个相同的节点,要么是它们的公共尾部,要么是 NULL。


12.奇偶链表( LeetCode 328

难度: 中等
代码(C++):

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if (!head) return head;
        ListNode* oddP = head;
        ListNode* evenHead = head->next;
        ListNode* evenP = evenHead;
        while (evenP && evenP->next) {
            oddP->next = evenP->next;
            oddP = oddP->next;
            evenP->next = oddP->next;
            evenP = evenP->next;
        }
        oddP->next = evenHead;
        return head;
    }
};

题解: 分离后合并


13.K 个一组翻转链表( LeetCode 25

难度: 困难
题目描述:
给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。
代码(C++):

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode* dummyHead = new ListNode(0, head);
        ListNode* pre = dummyHead;
        while (pre->next) {
            ListNode* h = pre->next;
            ListNode* t = h;
            int i = 1;
            while (i++ < k && t) {
                t = t->next;
            }
            if (t) {
                ListNode* next = t->next;
                t->next = nullptr;
                pre->next = reverseList(h);
                h->next = next;
                pre = h;
            } else {
                break;
            }
        }
        return dummyHead->next;
    }
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* cur = head;
        while (cur) {
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

题解: 反转链表


14.反转链表 II ( LeetCode 92

难度: 中等
题目表述:
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回反转后的链表 。
代码(C++):

class Solution {
public:
	// 递归 O(n) O(n)
    ListNode* reverseList(ListNode* head) {
        if (head == NULL || head->next == NULL) {
            return head;
        }
        ListNode* ret = reverseList(head->next);
        head->next->next = head;
        head->next = NULL;
        return ret;
    }
    // 迭代 O(n) O(1)
    void reverseList(ListNode* head) {
        ListNode *pre = nullptr;
        ListNode *cur = head;
        while (cur != nullptr) {
            ListNode *next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        head = pre;
    }
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode *dummyNode = new ListNode();
        dummyNode->next = head;
        ListNode *pre = dummyNode;
        for (int i = 1; i < left; i++) {
            pre = pre->next;
        }
        ListNode *leftNode = pre->next;
        ListNode *rightNode = pre;
        for (int i = 0; i < right - left + 1; i++) {
            rightNode = rightNode->next;
        }
        ListNode *tail = rightNode->next;
        pre->next = nullptr;
        rightNode->next = nullptr;
        reverseList(leftNode);
        leftNode->next = tail;
        pre->next = rightNode;
        return dummyNode->next;
    }
};

题解:
因为头节点有可能发生变化,使用虚拟头节点dummyNode可以避免复杂的分类讨论


15.复制带随机指针的链表( LeetCode 138

难度: 中等
题目表述:
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
代码(C++):

class Solution {
private:
    unordered_map<Node*, Node*> map;
public:
    Node* copyRandomList(Node* head) {
        if (head == NULL) {
            return head;
        }
        if (map.count(head) == 0) {
            Node* newNode = new Node(head->val);
            map[head] = newNode;
            newNode->next = copyRandomList(head->next);
            newNode->random = copyRandomList(head->random);
        }
        return map[head];
    }
};

题解: 回溯 + 哈希表 / 迭代 + 节点拆分
利用回溯的方式,让每个节点的拷贝操作相互独立


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

难度: 中等
题目表述:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
代码(C++):

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyNode = new ListNode(0, head);
        ListNode* fast = head, *slow = dummyNode;
        for (int i = 0; i < n; i++) {
            fast = fast->next;
        }
        while (fast) {
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* tmp = slow->next;
        slow->next = slow->next->next;
        delete tmp;
        return dummyNode->next;
    }
};

题解: 快慢指针


小结

迭代、递归、哈希表、双指针、快慢指针


参考链接

玩转 LeetCode 高频 100 题
LeetCode 刷题攻略

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值