力扣刷题总结(链表)

1,链表的删除

1.1 Q237 删除链表中的结点

有一个单链表的 head,我们想删除它其中的一个节点 node。

给你一个需要删除的节点 node 。你将 无法访问 第一个节点  head。

链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点。

删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:

给定节点的值不应该存在于链表中。
链表中的节点数应该减少 1。
node 前面的所有值顺序相同。
node 后面的所有值顺序相同。

示例:

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

将待删除结点的下一个结点的值node->next->val赋值给待删除结点node->val,此时变成4-1-1-9,待删除结点的值已经不复存在,再通过将node结点的下一结点删除,实现链表中结点的删除,此时变成4-1-9.

class Solution {
public:
    void deleteNode(ListNode* node) {
        node->val = node->next->val; //赋值
        node->next = node->next->next; //删除下一个结点
    }
};

1.2 Q19 删除链表的倒数第n个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例:

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

这种类型的题目往往采用快慢指针的方法,快指针fast比慢指针slow多走n步,之后再一起朝后移动。当fast走到链表最后一个节点时,slow移动到待删除结点的前一个结点,通过和上面一样的操作,slow->next = slow->next->next,实现结点的删除。

注意这种往往需要哑节点的使用,以防只有一个结点时程序fast->next或者slow->next = slow->next->next;部分报错。

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, nullptr); //哑节点的使用,当链表只存在一个结点时,哑节点的存在不会使程序报错
        ListNode* fast = dummy;
        ListNode* slow = dummy;
        dummy->next = head;
        for(int i = 0; i < n; i++)
        {
            fast = fast->next;
        }
        while(fast->next) //fast运动到最后一个结点,slow运动到待删除结点的前一个结点
        {
            fast = fast->next; 
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return dummy->next;
    }
};

1.3 Q203 移除链表中的元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

进行数组的遍历,找到和val值相同的结点的前一个结点,通过1.1,1.2中的方法进行此结点的删除(将下下个结点赋值给下个节点)。也需要哑节点的使用。

注意p的移动条件,在删除了结点后指针p不能进行移动,因为此时p->next已经更新,若此时也移动,会造成结点漏掉判断。

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* dummy = new ListNode(val, nullptr);
        dummy->next = head;
        ListNode* p = dummy;
        while(p->next)
        {
            if(p->next->val == val)
            {
                p->next = p->next->next;
            }
            else   //注意在找到一个和目标值相等的val时,p不能移动,因为此时p->next的指向换了新的值。
                p = p->next;
        }
        return dummy->next;
    }
};

2,链表的旋转与反转

2.1 Q61 旋转链表

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置

示例:

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

先实现每个节点向右移动一个位置,不需要一步一步移动,通过将最后一个结点变成头节点,即先将head赋给最后一个结点的下一个结点,变成环形链表,再在倒数第二位置断开。

之后进行k次循环,这样会导致如果k很大会做很多无用功。所以应该求出实际移动的次数,即k=k%len。

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if(head == nullptr || head->next == nullptr) //当链表只包含一个节点或者没有节点时,返回原链表
        {
            return head;
        }
        ListNode* p = head;
        int len = 0;
        while(p) //求出链表长度
        {
            len++;
            p = p->next;
        }
        k = k % len; //由于k很大时,会做出很多没有意义的移动步,所以求出真实移动步数
        for(int i = 0; i < k; i++)
        {
            p = head;
            while(p->next->next) //此时p在倒数第三个结点
            {
                p = p->next; //p移动到倒数第二个结点
            }
            p->next->next = head; //注意,此步使链表变成环形链表
            head = p->next; //记录更新链表的头节点,便于输出时找到
            p->next = nullptr; //将环形链表断开
        }
        return head;
    }
};

2.2 Q24 两两交换链表中的节点

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

示例:

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

1.暴力解法,通过记录链表结点的位置,在奇数位置时实现此结点与后一个结点的交换。注意在第一次进行交换的时候要重新记录新的头节点。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == nullptr || head->next == nullptr) //链表为空或者只有一个结点时,直接返回原链表
        {
            return head;
        }
        ListNode* p = head;
        ListNode* newhead = new ListNode(0, nullptr);
        int n = 0;
        while(p->next)
        {
            n++;
            if(n == 1) //首先交换头两个结点的值,此时需要记录新的头结点,便于后期返回
            {
                int temp = p->val;
                p->val = p->next->val;
                p->next->val = temp;
                newhead = p;
            }
            else if(n % 2 == 1) //实现后面每两个结点的交换
            {
                int temp = p->val;
                p->val = p->next->val;
                p->next->val = temp;
            } 
            p = p->next;   
        }
        return newhead;
    }
};

2.递归,分清楚第一,第二,第三个结点的具体位置,先将前两个交换。再将第三个节点作为参数传到函数中递归,得到的节点作为交换后的第二个节点的下一个节点。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        if(head == nullptr || head->next == nullptr) //链表为空或者只有一个结点时,直接返回原链表
        {
            return head;
        }
        ListNode* one = head;
        ListNode* two = one->next;
        ListNode* three = two->next;
        two->next = one; //先实现前两个结点的交换之后将第三个结点作为参数传到
        one->next = swapPairs(three); //将第三个结点作为参数传到函数里进行递归,作为交换后的第二个结点的下一个节点
        return two;
    }
};

2.3 Q206  反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例:

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

1,暴力解法,求出链表长度,将链表中的每一个值都存入数组中,再将数组中的值反向放置在新的链表中。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        //链表只存在一个节点或者为空时,直接返回原链表
        if(!head || !head->next){
            return head;
        }
        ListNode* p = head;
        int n = 0;
        while(p){
            n++;
            p = p->next;
        }  //n为链表总长度
        int arr[n]; //新建一个数组用于存储链表中的值
        int i = 0;
        p = head;
        while(p){
            arr[i] = p->val;
            i++;
            p = p->next;
        } //将链表中的值存储到数组中
        ListNode* newhead = new ListNode(arr[n-1], nullptr);
        ListNode* p2 = newhead;
        for(int j = 1; j < n; j++) //将数组中的值反向放置在新的链表中
        {
            p2->next = new ListNode(arr[n-j-1], nullptr);
            p2 = p2->next;
        }
        return newhead;
    }
};

2,头节点插入法,将p结点的下一个结点存储起来,删除p结点的下一个结点,将存储起来的结点指向头节点,并更新为新的头节点,直到p结点移到最后一个结点。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(!head)
        {
            return head;
        }
        ListNode* p = head;
        while(p->next) //一遍遍历,利用头节点逐个插入更新头节点的方法,直到p节点成为链表的尾节点
        {
            ListNode* temp = p->next; //存储p节点的下一个节点
            p->next = p->next->next; //将当前节点的下一个节点删除
            temp->next = head; //将已经存储起来的p节点的下一个节点指向头节点,即让其成为新的头节点
            head = temp; //更新目前的头节点
        }
        return head;
    }
};

3,新建链表实现头节点插入法,使用dummy结点一直记录头节点的位置。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (!head) return head;
        ListNode* dummy = new ListNode(0, nullptr);
        while (head) {
            dummy->next = new ListNode(head->val, dummy->next); //新建链表进行头节点插入,将新的头结点逐个插入dummy结点后面,并且将新插入的头节点指向dummy结点的原来的下一个结点,实现插入。此步画图比较好理解。
            head = head->next;
        }
        return dummy->next;
    }
};

2.4 Q92 反转链表Ⅱ

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

示例:

输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]

1,头节点插入法,与上面不同的是需要多记录反转首结点的前一个结点。定义变量必须在函数的开头进行定义,如果在if或者for或者while循环里定义,那在循环外面将找不到,此题也是采用头节点插入法,注意一定要先保存下一个结点,再删除,再插入。

class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* p = dummy;
        ListNode* p1 = nullptr, *p2 = nullptr, *front = nullptr;
        int i = 1;
        while(p)
        {           
            if(i == left+1) p1 = p; //记录反转首位置
            else if(i == right+2) p2 = p;//记录反转尾部结点的下一个结点
            else if(i == left) front = p;//记录反转首部结点的前一个结点                          
            p = p->next;
            i++;
        }
        ListNode* newhead = p1;
        while(p1->next != p2) //头节点插入法(必须在下个结点删除后再进行头节点插入)
        {
            ListNode* temp = p1->next;
            p1->next = p1->next->next; //注意,此删除下一个结点的语句必须写在以下语句前面,否则会造成逻辑错乱!!!           
            front->next = temp;
            temp->next = newhead;
            newhead = temp;        
        }
        return dummy->next;
    }
};

3,链表高精度加法

3.1 Q2 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

两个链表对应位置的数字相加,注意进位,尤其是链表循环完成后最后一步的进位。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0, nullptr);
        ListNode* l3 = dummy;
        int count = 0;
        while(l1 || l2) //当链表的指针没有移动到最后时,进行前面每个值的逐个对应相加
        {
            int n1 = l1 ? l1->val : 0; //指针非空,将指针指向的值给n1,否则给0
            int n2 = l2 ? l2->val : 0;
            int sum = n1 + n2 + count; //对应位置指针和相加
            if (sum >= 10) //和大于十需要有进位
            {
                sum = sum % 10;
                count = 1;
            }
            else //和小于10,进位为0
            {
                count = 0;
            }
            l3->next = new ListNode(sum, nullptr); //将加的和放到新建的链表中
            l3 = l3->next;
            if(l1) //链表指针如果没移动到最后,就继续朝下移动
            {
                l1 = l1->next;
            }
            if(l2)
            {
                l2 = l2->next;
            }         
        }
        if(count > 0) //遍历完成后,如果仍有进位,需要在最后加一
        {
            l3->next = new ListNode(1,nullptr);
        }
        return dummy->next;
    }
};

3.2 Q445 两数相加Ⅱ

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例:

输入:l1 = [7,2,4,3], l2 = [5,6,4]
输出:[7,8,0,7]

此题是反转列表与两数之和的结合,注意在用到头插法反转列表时,将一个结点指向链表的头节点,语法顺序的写法,temp->next = head!!!!!

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* p1 = l1, * head1 = l1;
        ListNode* p2 = l2, * head2 = l2;
        ListNode* dummy = new ListNode(0, nullptr);
        ListNode* p3 = dummy;
        while(p1->next) //使用头节点插入法将链表进行反转
        {
            ListNode* temp = p1->next;
            p1->next = p1->next->next;
            temp->next = head1;
            head1 = temp;
        }
        while(p2->next) //使用头节点插入法将链表进行反转
        {
            ListNode* temp = p2->next;
            p2->next = p2->next->next;
            temp->next = head2; //一定需要注意此句等于号两边的顺序,将temp指向头节点语法是temp->next=head!!!!!!
            head2 = temp;
        }
        int carry = 0;
        while(head1 || head2) //逐个结点的位置的值相加,注意进位
        {
            int n1 = head1 ? head1->val : 0;
            int n2 = head2 ? head2->val : 0;
            int sum = n1 + n2 + carry;
            if(sum >= 10){
                sum = sum % 10;
                carry = 1;
            }
            else if(sum < 10){
                carry = 0;
            }
            p3->next = new ListNode(sum, nullptr);
            p3 = p3->next;
            if(head1) head1 = head1->next;
            if(head2) head2 = head2->next;
        }
        if(carry == 1) p3->next = new ListNode(1, nullptr);
        ListNode* l3 = dummy->next;
        ListNode* head3 = dummy->next;
        while(l3->next)
        {
            ListNode* temp = l3->next;
            l3->next = l3->next->next;
            temp->next = head3;
            head3 = temp;
        }
        return head3;
    }
};

4,链表的合并

4.1 Q21合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

1,暴力解法,新建一个链表,将比较小的链表值放到新的链表里,这种解法占用内存比较多,代码也比较复杂。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* dummy = new ListNode(0, nullptr);
        ListNode* p3 = dummy;
        ListNode* p1 = list1;
        ListNode* p2 = list2;
        while(p1 && p2) //当p1与p2均不为空时,分别比较两个链表中的元素大小,并创建到新的链表中
        {
            if(p1->val < p2->val){
                p3->next = new ListNode(p1->val, nullptr);
                p3 = p3->next;
                p1 = p1->next;
            }
            else if(p1->val == p2->val){
                p3->next = new ListNode(p1->val, nullptr);
                p3 = p3->next;
                p3->next = new ListNode(p2->val, nullptr);
                p3 = p3->next;
                p1 = p1->next;
                p2 = p2->next;
            }
            else{
                p3->next = new ListNode(p2->val, nullptr);
                p3 = p3->next;
                p2 = p2->next;
            }
        }
        if(!p1 && p2) p3->next = p2; //当比较完成后,如果p1到尾,p2还没结束,就再把剩下的p2加到后面
        else if(!p2 && p1) p3->next = p1; //比较完成后,如果p2到尾,p1还没结束,就再把剩下的p1加到后面
        return dummy->next;
        
    }
};

2,相较于上面的解法有了一定的改进,新建了一个节点,直接在原来的两个链表上操作,减少了内存的占用,代码方面也简单很多。直接让新节点指向较小值的结点,在原链表上进行操作。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* list3 = new ListNode(0, nullptr);
        ListNode* head = list3; //记录头节点
        while(list1 && list2)
        {
            if(list1->val < list2->val) //比较list1与list2的值,list3指向比较小的那个值
            {
                list3->next = list1;
                list1 = list1->next;
            }
            else
            {
                list3->next = list2;
                list2 = list2->next;
            }
            list3 = list3->next;
        }
        if(list1) list3->next = list1; //若比较完成后,list1非空,则接到list3后面
        if(list2) list3->next = list2; //若比较完成后,list2非空,则接到list3后面
        return head->next;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,关于力扣刷C++常用操作,我可以给你一些常见的操作和技巧: 1. 使用 STL 容器和算法库:STL(Standard Template Library)是 C++ 标准库中的一个重要组成部分,包含了许多常用的容器和算法。在力扣刷中,使用 STL 可以大大提高代码的效率和可读性。例如,vector 可以用来存储动态数组,sort 可以用来排序等等。 2. 使用 auto 关键字:auto 关键字可以自动推导变量类型,可以减少代码量和提高可读性。例如,auto x = 1; 可以自动推导出 x 的类型为 int。 3. 使用 lambda 表达式:lambda 表达式是 C++11 中引入的一种匿名函数,可以方便地定义一些简单的函数对象。在力扣刷中,使用 lambda 表达式可以简化代码,例如在 sort 函数中自定义比较函数。 4. 使用位运算:位运算是一种高效的运算方式,在力扣刷中经常会用到。例如,左移运算符 << 可以用来计算 2 的幂次方,右移运算符 >> 可以用来除以 2 等等。 5. 使用递归:递归是一种常见的算法思想,在力扣刷中也经常会用到。例如,二叉树的遍历、链表的反转等等。 6. 使用 STL 中的 priority_queue:priority_queue 是 STL 中的一个容器,可以用来实现堆。在力扣刷中,使用 priority_queue 可以方便地实现一些需要维护最大值或最小值的算法。 7. 使用 STL 中的 unordered_map:unordered_map 是 STL 中的一个容器,可以用来实现哈希表。在力扣刷中,使用 unordered_map 可以方便地实现一些需要快速查找和插入的算法。 8. 使用 STL 中的 string:string 是 STL 中的一个容器,可以用来存储字符串。在力扣刷中,使用 string 可以方便地处理字符串相关的问。 9. 注意边界条件:在力扣刷中,边界条件往往是解决问的关键。需要仔细分析目,考虑各种边界情况,避免出现错误。 10. 注意时间复杂度:在力扣刷中,时间复杂度往往是评判代码优劣的重要指标。需要仔细分析算法的时间复杂度,并尽可能优化代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值