剑指offer(C++)——链表合集

本文记录了使用C++解决的链表相关算法问题,包括从尾到头输出链表、删除链表节点、找到链表倒数第K个节点、反转链表、合并排序链表、复制复杂链表、找两链表的公共节点以及处理链表中的环和重复节点等。提供了详细的解题思路和代码实现。
摘要由CSDN通过智能技术生成

本系列笔记供自学算法用,仅记载题解代码和部分题解思路,所有题目版权归LeeCode及《剑指offer》所有;推荐一超棒的刷题笔记系列博客,本系列的题型分类均有所参考:剑指Offer系列刷题笔记汇总

注:题目序号以现官网中排序为准,或和其他参考有所出入;

6.从尾到头输出链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

输入:head = [1,3,2]
输出:[2,3,1]

限制:

0 <= 链表长度 <= 10000

解答:

class Solution {    //递归
    vector<int> res;
public:
    vector<int>& reversePrint(ListNode* head) {     //注意:返回值为引用,减少大量内存拷贝
        if(head == NULL)    return res;
        reversePrint(head->next);
        res.push_back(head->val);
        return res;
    }
};

class Solution {    //数组翻转
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> res;
        while(head)
        {
            res.push_back(head->val);
            head = head->next;
        }
        reverse(res.begin(), res.end());
        return res;
    }
};

class Solution {    //使用栈
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> res;
        stack<int> s;
        ListNode* node = head;//不改变原头指针
        while(node)
        {
            s.push(node->val);
            node = node->next;
        }
        while(!s.empty())
        {
            res.push_back(s.top());
            s.pop();
        }
        return res;
    }
};
//作者:zrita
//链接:https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/solution/c-san-chong-fang-fa-z-by-zrita-7ctq/
18.删除链表节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

解答:

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(head->val == val) return head->next;//删除首节点的情况
        ListNode *pre = head, *cur = head->next;
        while(cur != nullptr && cur->val != val) {
            pre = cur;
            cur = cur->next;//遍历被删除节点位置
        }
        if(cur != nullptr) pre->next = cur->next;//删除该节点
        return head;
    }
};

//作者:jyd
//链接:https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/solution/mian-shi-ti-18-shan-chu-lian-biao-de-jie-dian-sh-2/
22链表倒数第K个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.

解答:

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        // 排除输入是空指针和k<=0无意义
        if (head == nullptr || k <= 0) 
            return nullptr;
        ListNode* fast = head, * slow = head;
        for (int i = 0; i < k; i ++){
            // 排除k值大于链表长的情况
            if (fast == nullptr)
                return nullptr;
            fast = fast->next;
        }
        while (fast != nullptr){
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

//作者:master_xue
//链接:https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/solution/kao-lu-dai-ma-de-lu-bang-xing-cai-shi-be-7c5b/
24.反转链表

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

解答:

class Solution {    //每次改变两个相邻节点的指向
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 保存cur的下一个节点
        ListNode* pre = NULL;
        ListNode* cur = head;
        while(cur) {
            temp = cur->next;  // 保存一下 cur的下一个节点,接下来改变cur->next
            cur->next = pre; // 翻转链表方向操作
            // 更新pre 和 cur指针
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

//作者:carlsun-2
//链接:https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/solution/fan-zhuan-lian-biao-shuang-zhi-zhen-fa-d-7stu/
25.合并排序链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:
0 <= 链表长度 <= 1000

解答:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {    //非递归解法
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* head = new ListNode(0);  //新建链表
        ListNode* cur = head;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                cur->next = l1;
                l1 = l1->next;
            } else {
                cur->next = l2;
                l2 = l2->next;
            }
            cur = cur->next;  //每次合并一位
        }
        if (l1) cur->next = l1;  //最后l2为空的情况
        else cur->next = l2;  //最后l1为空的情况
        return head->next;
    }
};
class Solution {    //递归解法
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (!l1 || !l2) return !l1 ? l2 : l1;  //其中一条链表为空的情况
        if (l1->val < l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        } else {
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};
//作者:moreality
//链接:https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/solution/c-di-gui-he-fei-di-gui-jie-fa-by-moreali-7f4b/

class Solution {    //另一种递归合并法
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        //判断指针是否为空
        if(pHead1 == NULL){
            return pHead2;
        }
        else if(pHead2 == NULL){
            return pHead1;
        }
        ListNode* pMergedHead = NULL;
        if(pHead1->val < pHead2->val){
            pMergedHead = pHead1;
               pMergedHead->next = Merge(pHead1->next, pHead2);
        }
        else{
            pMergedHead = pHead2;
               pMergedHead->next = Merge(pHead1, pHead2->next);
        }
        return pMergedHead;
    }
};
//来自:https://cuijiahua.com/blog/2017/12/basis_16.html
35.复杂链表的复制

请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

提示:
-10000 <= Node.val <= 10000
Node.random 为空(null)或指向链表中的节点。
节点数目不超过 1000 。

解答:

在原链表上复制新节点,在将偶数位节点拆分成新链
在这里插入图片描述

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == nullptr) return nullptr;
        Node* cur = head;
        // 1. 复制各节点,并构建拼接链表
        while(cur != nullptr) {
            Node* tmp = new Node(cur->val);
            tmp->next = cur->next;
            cur->next = tmp;
            cur = tmp->next;
        }
        // 2. 构建各新节点的 random 指向
        cur = head;
        while(cur != nullptr) {
            if(cur->random != nullptr)
                cur->next->random = cur->random->next;
            cur = cur->next->next;
        }
        // 3. 拆分两链表
        cur = head->next;
        Node* pre = head, *res = head->next;
        while(cur->next != nullptr) {
            pre->next = pre->next->next;
            cur->next = cur->next->next;
            pre = pre->next;
            cur = cur->next;
        }
        pre->next = nullptr; // 单独处理原链表尾节点
        return res;      // 返回新链表头节点
    }
};

//作者:jyd
//链接:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/jian-zhi-offer-35-fu-za-lian-biao-de-fu-zhi-ha-xi-/
52.两链表的公共节点

输入两个链表,找出它们的第一个公共节点。

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

解答:

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0;
        while (curA != NULL) { // 求链表A的长度
            lenA++;
            curA = curA->next;
        }
        while (curB != NULL) { // 求链表B的长度
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        // 让curA为最长链表的头,lenA为其长度
        if (lenB > lenA) {
            swap (lenA, lenB);
            swap (curA, curB);
        }
        // 求长度差
        int gap = lenA - lenB;
        // 让curA和curB在同一起点上(末尾位置对齐)
        while (gap--) {
            curA = curA->next;
        }
        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != NULL) {
            if (curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    }
};

//作者:carlsun-2
//链接:https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/solution/dai-ma-sui-xiang-lu-liang-ge-lian-biao-d-i0tx/
附1-链表中环的入口结点

一个链表中包含环,请找出该链表的环的入口结点。

解答:

确定环中有几个结点
可以使用快慢指针,一个每次走一步,一个每次走两步。如果两个指针相遇,表明链表中存在环,并且两个指针相遇的结点一定在环中。
随后,我们就从相遇的这个环中结点出发,一边继续向前移动一边计数,当再次回到这个结点时,就可以得到环中结点数目了。

如果链表中的环有n个结点,指针P1先在链表上向前移动n步,然后两个指针以相同的速度向前移动。直到它们相遇,当第二个指针指向的入口结点时,第一个指针已经围绕着揍了一圈又回到了入口结点。。它们相遇的结点正好是环的入口结点

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead == NULL){
            return NULL;
        }
        ListNode* meetingnode = MeetingNode(pHead);
        if(meetingnode == NULL){
            return NULL;
        }
        // 回环链表结点个数
        int nodesloop = 1;
        // 找到环中结点个数
        ListNode* pNode1 = meetingnode;
        while(pNode1->next != meetingnode){
            pNode1 = pNode1->next;
            nodesloop++;
        }
        pNode1 = pHead;
        // 第一个指针向前移动nodesloop步
        for(int i = 0; i < nodesloop; i++){
            pNode1 = pNode1->next;
        }
        // 两个指针同时移动,找到环入口
        ListNode* pNode2 = pHead;
        while(pNode1 != pNode2){
            pNode1 = pNode1->next;
            pNode2 = pNode2->next;
        }
        return pNode1;
    }
private:
    // 使用快慢指针,找到任意的一个环中结点
    ListNode* MeetingNode(ListNode* pHead){
        ListNode* pSlow = pHead->next;
        if(pSlow == NULL){
            return NULL;
        }
        ListNode* pFast = pSlow->next;
        while(pFast != NULL && pSlow != NULL){
            if(pFast == pSlow){
                return pFast;
            }
            pSlow = pSlow->next;
            pFast = pFast->next;
            if(pFast != NULL){
                pFast = pFast->next;
            }
        }
        return NULL;
    }
};
//来自:https://cuijiahua.com/blog/2018/01/basis_55.html
附2-删除链表中重复的结点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        if(pHead == NULL){
            return NULL;
        }
        // 指向当前结点前最晚访问过的不重复结点
        ListNode* pPre = NULL;
        // 指向当前处理的结点
        ListNode* pCur = pHead;
        // 指向当前结点后面的结点
        ListNode* pNext = NULL;
        
        while(pCur != NULL){
            // 如果当前结点与下一个结点相同
            if(pCur->next != NULL && pCur->val == pCur->next->val){
                pNext = pCur->next;
                // 找到不重复的最后一个结点位置
                while(pNext->next != NULL && pNext->next->val == pCur->val){
                    pNext = pNext->next;
                }
                // 如果pCur指向链表中第一个元素,pCur -> ... -> pNext ->... 
                // 要删除pCur到pNext, 将指向链表第一个元素的指针pHead指向pNext->next。
                if(pCur == pHead){
                    pHead = pNext->next;
                }
                // 如果pCur不指向链表中第一个元素,pPre -> pCur ->...->pNext ->... 
                // 要删除pCur到pNext,即pPre->next = pNext->next
                else{
                    pPre->next = pNext->next;
                }
                // 向前移动
                pCur = pNext->next;
            }
            // 如果当前结点与下一个结点不相同
            else{
                pPre = pCur;
                pCur = pCur->next;
            }
        }
        return pHead;
    }
};
//来自:https://cuijiahua.com/blog/2018/01/basis_56.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值