小白也能学会的链表 | C++ | 算法与数据结构 (新手村)

这篇具有很好参考价值的文章主要介绍了小白也能学会的链表 | C++ | 算法与数据结构 (新手村)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

第一关 小白也能学会的链表

1 剑指Offer 52 两个链表的第一个公共节点

本质:找到两个有序数据段中的第一个相同数据

1.1 Set解决

解题:利用set的不重复性,首先把headA都塞到set中,再遍历headB找有没有已经出现在set中的节点即可。
注意! set的数据是ListNode* 不是 int。如果是int可能出现节点不同但是var相同的情况,而ListNode* 就不会。

#include<set>
using namespace std;
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        set<ListNode*> my_set;
        while(headA!=NULL)
        {
            my_set.insert(headA);
            headA = headA->next;
        }
        while(headB!=NULL)
        {
            if(my_set.find(headB) != my_set.end())
                return headB;
            else
                headB = headB->next;
        }
        return NULL;
    }
};

时间复杂度:O(2n)
空间复杂度:O(n)

1.2 stack解决

创建两个stack,将节点全部压入stack中然后同时出栈,等到出栈的元素不相等时,其上一个出栈的节点就是目标节点。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
//栈解决
#include<stack>
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        stack<ListNode*> stack_A;
        stack<ListNode*> stack_B;
        while(headA!=NULL)
        {
            stack_A.push(headA);
            headA = headA->next;
        }
        while(headB!=NULL)
        {
            stack_B.push(headB);
            headB = headB->next;
        }
        ListNode* pre = NULL;
        while(stack_A.size()!=0 && stack_B.size()!=0)
        {
            if(stack_A.top() == stack_B.top())
            {
                pre = stack_A.top();
                stack_A.pop();
                stack_B.pop();
            }
            else
                break;
        }
        return pre;

    }
};

有一些细节需要注意:

  1. stack的pop()函数没有返回值,所以需要用stack的top()来保存栈顶元素。
  2. 记得用pre来保存上一个pop出来的节点。否则找到了不一样的那一个节点后找不到上一个节点了。
    时间复杂度:O(3n)
    空间复杂度:O(2n)

1.3 双指针法

这个思路比较难想,回归到本质,两个链表的末段相同,那么尝试把这A,B两个链表按照AB和BA的方式拼接,会发现末段依旧相同。那么双指针同时遍历直到相等即可。

/**
 * 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;
        if(p1 == NULL || p2 ==NULL)
            return NULL;
        while(p1!=p2)
        {
            p1 = p1->next;
            p2 = p2->next;
            if(p1!=p2)  //@1
            {
                if(p1==NULL)
                    p1 = headB;
                if(p2==NULL)
                    p2 = headA;
            }
        }
        return p1;
    }
};

而链表的拼接,其实可以用虚拟的拼接:当指针遍历到链表结尾后,赋值为另一个表的表头。
不要忘记当存在空链表时,直接返回NULL,否则AB == BA 返回的就是第一个节点值,出错。
@1:这里要用一个if进行判断,因为如果两者相等,其实可以直接跳出循环,没必要再进行判断了。

这里会产生一个疑问,就是p1和p2同时到达节点末尾怎么办?
其实如果两个链表长度相同,那么比较的时候在结尾之前就可以找到相等的节点了,比原问题简单多了呢
时间复杂度:O(n)
空间复杂度:O(1)

2 Leetcode 234 回文链表

本质上是判断从头开始和从尾开始读是否相等,从数学上来说只需要判断一半即可。
显然想到Stack的数据结构。
那么开始操作:
这是我一开始写的一段错误代码,原因是stack中存储的是链表节点而不是链表值。很显然的错误!但是我就是没注意呀!

/**
 * 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) {}
 * };
 */
#include<stack>
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        stack<ListNode*> my_stack;
        ListNode* tmp = head;
        int size = 0;
        while(tmp!=NULL)
        {
            size++;
            my_stack.push(tmp);
            tmp = tmp->next;
        }
        int count = 0;
        while(count < (size/2+1))
        {
            if(head != my_stack.top())
                return false;
            else
            {
                my_stack.pop();
                head = head->next;
                count++;
            }
        }
        return true;
    }
};

修改一下,存储并且比较val值的代码如下

/**
 * 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) {}
 * };
 */
#include<stack>
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        stack<int> my_stack;
        ListNode* tmp = head;
        int size = 0;
        while(tmp!=NULL)
        {
            size++;
            my_stack.push(tmp->val);
            tmp = tmp->next;
        }
        int count = 0;
        while(count < (size/2))
        {
            if(head->val != my_stack.top())
                return false;
            else
            {
                my_stack.pop();
                head = head->next;
                count++;
            }
        }
        return true;
    }
};

结果正确
时间复杂度:O(n)
空间复杂度:O(n)
这道题目体现了算法的本质是减少重复。——吴军《计算之魂》

3 合并有序链表问题

3.1 合并两个有序链表

思路大概是创建一个新链表,谁大就把谁接入到新链表上面去。
思路很简单,实操的时候有许多细节和分类讨论,同时如何优化代码也是很重要的。
先上代码

/**
 * 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* new_head = new ListNode();
        ListNode* res = new_head;

        while(list1!=NULL && list2!= NULL)
        {
            if(list1->val >= list2->val)
            {
                res->next = list2;
                list2 = list2->next;
            }
            else
            {
                res->next = list1;
                list1 = list1->next;
            }
            res = res->next;
        }

        res->next = list1 == NULL ? list2 : list1;
        return new_head->next;
    }
};

首先是遍历两个链表,逐个插入,相等的情况下用else可以在下一次循环中成功处理。而最后某个链表遍历到头之后,可以直接接上未遍历完的链表。

3.2 合并k个有序链表

承接上面的问题,简化为多次合并两个链表即可解决问题。

/**
 * 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) {
        ListNode* res = NULL;
        for(auto it = lists.begin(); it != lists.end(); it++)
        {
            res = mergeTwoLists(res, *it);
        }
        return res;

    }
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* new_head = new ListNode();
        ListNode* res = new_head;

        while(list1!=NULL && list2!= NULL)
        {
            if(list1->val >= list2->val)
            {
                res->next = list2;
                list2 = list2->next;
            }
            else
            {
                res->next = list1;
                list1 = list1->next;
            }
            res = res->next;
        }

        res->next = list1 == NULL ? list2 : list1;
        return new_head->next;
    }
};

4 双指针问题

4.1 寻找中间节点

876. 链表的中间结点 - 力扣(LeetCode)

只需要双指针一快一慢,快指针每次走2个,慢指针每次走1个就行。

具体代码如下

/**
 * 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!=NULL && fast->next!=NULL)
        {
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
};

4.2 链表中倒数第k个节点

剑指 Offer 22. 链表中倒数第k个节点 - 力扣(LeetCode)

思路就是快指针先往前k次,然后再快慢指针同时向前,直到快指针到结尾,返回慢指针。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode* slow = head;
        ListNode* fast = head;
        
        while( fast!=NULL && k>0 )
        {
            k--;
            fast = fast->next;
        }
        // if(fast == NULL)
        //     return NULL;
        // else
        {
            while(fast!=NULL)
            {
                fast = fast->next;
                slow = slow->next;
            }
        }
        return slow;
    }
};

需要注意的是,有可能k大于链表长度,所以要考虑:当链表长度小于k的时候是返回什么?返回NULL还是第一个节点?

还有一点就是,这里第一个while直接用k—进行计数更加简介噢!

4.3 旋转链表

61. 旋转链表 - 力扣(LeetCode)

旋转链表比较好想到的一个思路就是,先找到要分割链表的两个链表尾,然后重新拼接。

需要注意的是如何保存最后拼接的顺序,如何保存新的链表头?

/**
 * 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* rotateRight(ListNode* head, int k) {
        int size = 0;
        for(ListNode* tmp = head; tmp!=NULL; tmp = tmp->next)
            size++;
        if(size == 0)
            return head;
        k = k%size;
        if(k == 0)
            return head;
        
        ListNode* pre = head;
        ListNode* post = head;
        
        while(k>0 && post!=NULL)
        {
            post = post->next;
            k--;
        }
        while(post->next!=NULL)
        {
            post = post->next;
            pre = pre->next;
        }
        post->next = head;
        head = pre->next;
        pre->next = NULL;
        return head;
    }
};

还有一个办法就是反转链表,先反转全部的链表,然后分割之后单独反转两个小链表,最后完成拼接。

似乎有点小麻烦……

5 删除链表元素专题

5.1 移除链表元素

203. 移除链表元素 - 力扣(LeetCode)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tlH1MekS-1689776120738)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/91e59d2c-560e-4f77-8861-d98ba4e4f312/image.png)]

删除链表中所有值为val的节点。

需要注意的是头节点的删除和中间的节点不同,所以创建了一个 dummy_head

dummy_head→next = head;

然后遍历的时候从dummy_head开始遍历就可以了。

👉🏻 注意!删除的时候,要特意考虑删除之后节点是否移动!
/**
 * 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* removeElements(ListNode* head, int val) {
        if(head == NULL)
            return head;
        ListNode* dommy_head = new ListNode(0, head);
        ListNode* cur = dommy_head;

        while(cur->next != NULL)
        {
            if(cur->next->val == val)
            {
                cur->next = cur->next->next;
            }
            else
                cur = cur->next;
        }
        return dommy_head->next;
    }
};

5.1.1 删除链表中倒数第k个元素

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3bfGIPpc-1689776120739)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/084f4f31-0e2e-4bf8-b0ff-ca55545e184b/remove_ex1.jpg)]

双指针法遍历一次,找到待删除节点的前一个节点。

尤其要注意的是如果删除的是第一个节点时,直接删。

/**
 * 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* removeNthFromEnd(ListNode* head, int n) {
        

        ListNode* fast = head;
        ListNode* slow = head;
        while(n>0)
        {
            fast = fast->next;
            n--;
        }
        if(fast == NULL)
            return head->next;
        
        while(fast->next!=NULL)
        {
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return head;
    }
};

5.2 删除重复元素

83. 删除排序链表中的重复元素 - 力扣(LeetCode)

删除要考虑的情况无非下面几种:

  1. 删除头节点怎么办?
  2. 链表为空怎么办?
  3. 删除与否,指针怎么移动?
  4. 终点条件是什么?
👉🏻 解决上面这四个问题,就解决了这一类题目。
/**
 * 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* deleteDuplicates(ListNode* head) {
        ListNode* cur = head;
        if(head == NULL)
            return head;
        while(cur->next != NULL)
        {
            if(cur->val == cur->next->val)
            {
                cur->next = cur->next->next;
            }
            else
            {
                cur = cur->next;
            }
        }
        return head;
    }
};

82. 删除排序链表中的重复元素 II - 力扣(LeetCode)

这回是把所有的重复元素都删除了。

那么可能出现删除头节点的情况,那么我们先创建一个虚拟头。后续的情况只需要用一个cur即可完成。

但是因为自己第一次写没写出来,所以对下面的标准代码进行逐行批注。文章来源地址https://www.toymoban.com/news/detail-630169.html

到了这里,关于小白也能学会的链表 | C++ | 算法与数据结构 (新手村)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】两两交换链表 && 复制带随机指针的链表

    给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 使用一个栈S来存储相邻两个节点即可 给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以

    2024年04月15日
    浏览(30)
  • 【数据结构】[LeetCode138. 复制带随机指针的链表]

    给你一个长度为  n  的链表,每个节点包含一个额外增加的随机指针  random  ,该指针可以指向链表中的任何节点或空节点。 构造这个链表的  深拷贝 。 深拷贝应该正好由  n  个  全新  节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的  next  指针和

    2024年02月04日
    浏览(30)
  • 【数据结构OJ题】复制带随机指针的链表

    原题链接:https://leetcode.cn/problems/copy-list-with-random-pointer/description/ 目录 1. 题目描述 2. 思路分析 3. 代码实现 此题可以分三步进行: 1. 拷贝链表的每一个结点,拷贝的结点先链接到被拷贝结点的后面。 2. 复制随机指针的链接:拷贝结点的随机指针指向被拷贝结点随机指针的下

    2024年02月12日
    浏览(29)
  • 【数据结构初阶】四、线性表里的链表(带头+双向+循环 链表 -- C语言实现)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【数据结构初阶】三、 线性表里的链表(无头+单向+非循环链表 -- C语言实现)-CSDN博客  ====

    2024年02月08日
    浏览(34)
  • 【数据结构】LeetCode升级版的环形链表,复制带随机指针的链表

              1、题目说明           2、题目解析           1、题目说明           2、题目解析      1、题目说明 题目链接: 升级版的环形链表  给定一个链表的头节点 head ,返回链表开始入环的第一个节点。  如果链表无环,则返回NULL。 如果链表中有某个节点,可以通

    2024年01月16日
    浏览(40)
  • 【LeetCode】数据结构题解(9)[复制带随机指针的链表]

    所属专栏:玩转数据结构题型❤️ 🚀 博主首页:初阳785❤️ 🚀 代码托管:chuyang785❤️ 🚀 感谢大家的支持,您的点赞和关注是对我最大的支持!!!❤️ 🚀 博主也会更加的努力,创作出更优质的博文!!❤️ 🚀 关注我,关注我,关注我,重要的事情说三遍!!!!!

    2024年02月11日
    浏览(37)
  • 数据结构:队列的链表结构(含完整代码,可复制)

    1.输出队列 2.入队一个元素 3.出队一个元素 5.建立链表队列 6.完整代码

    2024年01月16日
    浏览(33)
  • 贪吃蛇项目(基于C语言和数据结构中的链表)

    首先先建立3个文件。 Snake.h 函数的声明 Snake.c 函数的定义 Test.c     贪吃蛇的测试    我们分析这整个项目 首先在我们实现游戏开始的部分之前,我们要先创建贪吃蛇的节点,再由此创建整个贪吃蛇所包含的一些信息: 我们枚举一下这个贪吃蛇中所有的一些状态: 然后我们

    2024年02月20日
    浏览(44)
  • 【数据结构初阶】三、 线性表里的链表(无头+单向+非循环链表 -- C语言实现)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【数据结构初阶】二、 线性表里的顺序表(C语言实现顺序表)-CSDN博客  =========================

    2024年02月08日
    浏览(42)
  • 链表综合算法设计(c++数据结构)

      一、设计内容 已知简单的人事信息系统中职工记录包含职工编号(no)、职工姓名(name)、部门名称(depname)、职称(title)和工资数(salary)等信息(可以增加其他信息),设计并完成一个简单的人事信息管理系统,要求完成但不限于以下功能: (1)    增加一个职工信息

    2024年02月02日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包