【学习笔记】【Leetcode 分门别类讲解】——链表

本文深入解析链表算法,涵盖单链表、双链表、循环链表等类型,详解链表的增删改查操作及性能分析。通过经典题目如反转链表、合并有序链表等,展示迭代和递归方法的应用。

链表相关总结

链表问题极容易弄错,请一定要在纸上把过程先画出来,请一定手撕,请一定画示意图
虚拟头节点+迭代+递归

  1. 链表的种类主要为:单链表,双链表,循环链表
  2. 链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起。
  3. 链表是如何进行增删改查的。
  4. 数组和链表在不同场景下的性能分析。

0、一道题目考察链表五个常见操作!​

增删改查

1、链表问题——穿针引线,头插法

不懂会很烦(递归),搞懂会很爽(头插法)

【206. 反转链表】

——方法一:三指针迭代:翻转当前节点,需要保存的是:下一个节点和上一个节点

在这里插入图片描述

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *pre = nullptr;//dummyHead
        ListNode *cur = head;
        while(cur!=nullptr){
            ListNode *tmp = cur->next;//保存cur->next
            cur->next = pre;//reverse
            pre = cur;//pre往后走
            cur = tmp;//cur往后走
        }
        return pre;//最后结束循环cur到尾后了,所以return pre
    }
};
——方法二:递归

在这里插入图片描述
详细的PPT示意

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
    //递归终止条件是当前为空,或者下一个节点为空
        if(head == nullptr||head->next == nullptr){
            return head;
        }
        //这里的cur就是最后一个节点
        ListNode *cur = reverseList(head->next);//一直递归,直到触发终止条件
        head->next->next = head;//如果链表是 1->2->3->4->5,那么此时的cur就是5//而head是4,head的下一个是5,下下一个是空
        head->next = nullptr;//防止链表循环,需要将head.next设置为空
        return cur;//每层递归函数都返回cur,也就是最后一个节点
    }
};

【92. 反转链表 II】——精彩,极其精彩!

——方法一:穿针引线
class Solution {
public:
    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;
    }
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode *dummyHead = new ListNode(-1);//这里不可以是nullptr
        dummyHead->next = head;
        ListNode *pre = dummyHead;
        for(int i = 0;i<left-1;i++){pre = pre->next;}//找到left的pre
        ListNode *LeftNode = pre -> next;
        ListNode *RightNode = pre;//利用下面的循环找RightNode
        for(int i = 0;i<right-left+1;i++){RightNode = RightNode->next;}
        ListNode *curr = RightNode->next;//先记录RightNode->next!不然找不到了
        RightNode -> next = nullptr;//截断头尾
        pre->next = nullptr;
        reverseList(LeftNode);//reverse要求部分
        pre->next = RightNode;//重新续上头尾
        LeftNode->next = curr;
        return dummyHead->next;
    }
};
——方法二:头插法!妙啊妙啊!

看图看图

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode *dummyHead = new ListNode(-1);//这里不可以是nullptr,括号里是0或者-1都行
        dummyHead->next = head;
        ListNode *pre = dummyHead;
        for(int i = 0;i<left-1;i++){pre = pre->next;}
        ListNode *leftNode = pre->next;
        for(int i = 0;i<right-left;i++){//以left处的值 = 2为例
            ListNode *remove = leftNode->next;//记录好原来的下一个位置 3
            leftNode->next = leftNode->next->next;//2指向3的next  2->4
            remove->next = pre->next;//3->2
            pre->next = remove;//1->3
            //第一轮后就变成了1->3 3->2 2->4
        }
        return dummyHead->next;
    }
};

在这里插入图片描述

【203. 移除链表元素】

——删除问题的两种方法:迭代和递归

用dummy 来解决问题,这样就不需要多考虑一个头节点问题;
迭代的方式。

public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode *dummy = new ListNode(-1);
        dummy ->next = head;
        ListNode *tail = dummy;
        while(tail ->next){//就算链表的题刷少了,不熟练
            if(tail ->next ->val ==val){tail->next = tail->next->next;}
            else{tail = tail->next;}
        }
        return dummy->next;
    }
};

【82. 删除排序链表中的重复元素 II】

存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。

返回同样按升序排列的结果链表。
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

基本思路
几乎所有的链表题目,都具有相似的解题思路。
1.建一个「虚拟头节点」dummy 以减少边界判断,往后的答案链表会接在 dummy 后面
2.使用 tail 代表当前有效链表的结尾
3.通过原输入的 head 指针进行链表扫描

我们会确保「进入外层循环时 head 不会与上一节点相同」,因此插入时机:
1.head 已经没有下一个节点,head 可以被插入
2.head 有一下个节点,但是值与 head 不相同,head 可以被插入

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        auto dummy = new ListNode(-1),tail = dummy;
        while(head){
            if(head->next == nullptr or head->val != head->next->val){
                tail->next = head;
                tail = head;
            }
            while(head->next and head->val == head->next->val) head = head->next;
            head = head->next;
        }
        tail->next = nullptr;//用tail后面这个指向空就不会忘
        return dummy->next;
    }
};

【83. 删除排序链表中的重复元素】

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

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* dummy = new ListNode(109);
        ListNode* tail = dummy;
        while(head){
            if(tail->val!=head->val){
                tail->next = head;
                tail = head;
            }
            head = head->next;
        }
        tail->next = nullptr;
        return dummy->next;
    }
};

【86. 分隔链表】

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
在这里插入图片描述

——设立两个虚拟头节点smallHead bigHead

然后两个节点去穿针引线弄好小的和大的list

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {//要两个虚拟头节点smallHead bigHead ,然后两个节点去穿针引线弄好小的和大的list,
        ListNode* smallHead = new ListNode(0);
        ListNode* small = smallHead;
        ListNode* bigHead = new ListNode(0);
        ListNode* big = bigHead;
        while(head){
            if(head->val<x){
                small->next = head;
                small = head;
            }
            else{
                big->next = head;
                big = head;
            }
            head = head->next;
        }
        small->next = bigHead->next;//这就是bigHead的作用
        big->next = nullptr;
        return smallHead->next;
    }
};

【21. 合并两个有序链表】

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
在这里插入图片描述
思路:就是比较,注意是比较完一个list后有指向空的,这时候就要加上下面那两个while了!!!

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(-1);
        ListNode* tail = dummy;
        if(!l1 and !l2) return nullptr;
        if(!l1 and l2)  return l2;
        if(l1 and !l2)  return l1;
        while(l1 and l2){
            if(l1->val <= l2->val){
                tail->next = l1;
                tail = l1;
                l1 = l1->next;
            }
            else{
                tail->next = l2;
                tail = l2;
                l2 = l2->next;
            }
        }
        while(l1){
            tail->next = l1;
            tail = l1;
            l1 = l1->next;
        }
        while(l2){
            tail->next = l2;
            tail = l2;
            l2 = l2->next;
        }
        tail->next = nullptr; 
        return dummy->next;
    }
};

【328. 奇偶链表】

class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
        if(head==nullptr) return head;
        ListNode *dummyHead = new ListNode (-1);
        dummyHead->next = head;
        ListNode *evenHead = head->next;//标记even的节点
        ListNode *even = evenHead;//用even来移动
        while(even!=nullptr && even->next!=nullptr){//更新条件要判断好
            head ->next = even ->next;
            head = head ->next;
            even ->next = head ->next;
            even = even ->next;
        }
        head ->next = evenHead;
        return dummyHead->next;
    }
};

【2. 两数相加】

在这里插入图片描述
思路:
1.用a和b分别存l1和l2的val(要先判断存在与否)
2.用 t 存进位
3.cur来存放当前值(也许叫sum更好)
4.更新

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode *dummy = new ListNode(0);
        ListNode *cur = dummy;
        int t = 0;
        while(l1 or l2){
            int a = l1?l1->val:0;
            int b = l2?l2->val:0;
            t = a+b+t;
            cur -> next = new ListNode(t%10);
            t /= 10;
            cur = cur->next;
            if(l1) l1 = l1->next;
            if(l2) l2 = l2->next;
        }
        if(t!=0) cur ->next = new ListNode(t);
        return dummy->next;
    }
};

【445. 两数相加 II】

——stack+头插法

思路:
这里是要变个方向,要求不能反转,所以用stack是最好的。把所有数字压入栈中,再依次取出相加。计算过程中需要注意进位的情况。最后得到答案反转那里很妙,有点像头插法。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        stack<int> s1,s2;
        while(l1) {s1.push(l1->val);l1 = l1->next;}
        while(l2) {s2.push(l2->val);l2 = l2->next;}
        int t = 0;//进位
        ListNode *tail = nullptr;//用于后面反转
        while(!s1.empty() or !s2.empty() or t!=0){//注意t!=0也要放作条件
            int a = s1.empty()?0:s1.top();
            int b = s2.empty()?0:s2.top();
            if(!s1.empty()) s1.pop();
            if(!s2.empty()) s2.pop();
            int cur = (a+b+t)%10;//return链表的val
            t = (a+b+t)/10;//更新进位
            ListNode *curnode = new ListNode(cur);//开一个新curnode来放cur的val
            curnode ->next = tail;//画图理解下这里的反转,和之前头插法挺像的
            tail = curnode;
        }
        return tail;
    }
};

【24. 两两交换链表中的节点】

在这里插入图片描述
FN:node1 CN:node2 n:next

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummy = new ListNode(0);
        dummy->next = head;
        ListNode* pre = dummy;
        while(pre->next && pre->next->next){
            ListNode* FN = pre->next;
            ListNode* CN = FN ->next;
            ListNode* n = CN->next;

            CN ->next = FN;
            FN ->next = n;
            pre->next = CN;

            pre = FN;
        } 
        return dummy->next;
    }
};

2、链表排序问题(待做)

【147. 对链表进行插入排序】

【148. 排序链表】

3、不仅是穿针引线的链表问题

【237. 删除链表中的节点】

——引出C++内存管理和设计原则(不太理解)链接

4、双指针

【19.删除链表的倒数第N个节点】

在这里插入图片描述

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(-1);
        dummy ->next = head;
        ListNode* fast = dummy;
        ListNode* slow = dummy;
        while(n-- && fast!=nullptr){fast =fast->next;}
        fast = fast->next;
        while(fast!=nullptr){
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* del_node = slow->next;//C++特色,要学会释放内存
        slow->next = del_node->next;
        delete del_node;
        return dummy->next;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值