小白也能学会的链表 | 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即可完成。

但是因为自己第一次写没写出来,所以对下面的标准代码进行逐行批注。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值