【优选算法之链表】No.10--- 经典链表算法


前言

在这里插入图片描述

👧个人主页:@小沈YO.
😚小编介绍:欢迎来到我的乱七八糟小星球🌝
📋专栏:优选算法
🔑本章内容:链表
记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~


一、链表常用技巧和操作总结:

1.1 技巧

  • 画图(最重要的形象直观不易出错)
  • 引入虚拟"头"节点 ListNode* newhead=new ListNode(0)
  • 快慢双指针

1.2 常用操作

  • 创建一个新的节点(new)
  • 尾插
  • 头插(逆序)

二、链表示例:

2.1 两数相加

  1. 题⽬链接:2. 两数相加
  2. 题⽬描述:
    在这里插入图片描述
  3. 解法(模拟):
    算法思路:
    两个链表都是逆序存储数字的,即两个链表的个位数、⼗位数等都已经对应,可以直接相加。在相加过程中,我们要注意是否产⽣进位,产⽣进位时需要将进位和链表数字⼀同相加。如果产⽣进位的位置在链表尾部,即答案位数⽐原链表位数⻓⼀位,还需要再 new ⼀个结点储存最⾼位。
  4. C++ 代码
/**
 * 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* addTwoNumbers(ListNode* l1, ListNode* l2) 
    {
       	ListNode*cur1=l1,*cur2=l2;
        ListNode* newhead=new ListNode(0);
        ListNode* prev=newhead;
        int tmp=0;
        while(cur1||cur2||tmp)
        {
            if(cur1)
            {
                tmp+=cur1->val;
                cur1=cur1->next;
            }
            if(cur2)
            {
                tmp+=cur2->val;
                cur2=cur2->next;
            }
            prev->next=new ListNode(tmp%10);
            prev=prev->next;
            tmp/=10;
        }
        prev=newhead->next;
        delete newhead;
        return prev;
    }
};

2.2 两两交换链表中的节点

  1. 题⽬链接:24. 两两交换链表中的节点
  2. 题⽬描述:
    在这里插入图片描述
  3. 解法(模拟):
    算法思路:
    画图画图画图,重要的事情说三遍~
  4. C++代码
/**
 * 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* swapPairs(ListNode* head) 
    {
        if(head==nullptr||head->next==nullptr)return head;
        ListNode* newhead=new ListNode(0);
        newhead->next=head;
        ListNode*prev=newhead,*cur=prev->next,*dest=cur->next;
        while(cur&&dest)
        {
            cur->next=dest->next;
            dest->next=cur;
            prev->next=dest;
            prev=cur;
            cur=cur->next;
            if(cur!=nullptr)
            dest=cur->next;
        }
        return newhead->next;
    }
};

2.3 重排链表

  1. 题⽬链接:143. 重排链表
  2. 题⽬描述:
    在这里插入图片描述
  3. 解法:
    算法思路:
    画图画图画图,重要的事情说三遍~
  • 找中间节点;
  • 中间部分往后的逆序;
  • 合并两个链表
  1. C++代码
/**
 * 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:
    void reorderList(ListNode* head) 
    {
        if(head==nullptr||head->next==nullptr||head->next->next==nullptr)return;
        //找到中间节点---快慢指针
        ListNode*slow=head,*fast=head;
        while(fast&&fast->next)
        {
            slow=slow->next;
            fast=fast->next->next;
        }
        //对中间节点之后的节点进行逆序---分割成两个链表+头插
        ListNode* cur=slow->next;
        slow->next=nullptr;// 注意把两个链表给断开
        ListNode* newhead=new ListNode(0);
        ListNode* prev=nullptr;
        while(cur)
        {
            ListNode* next=cur->next;
            cur->next=prev;
            newhead->next=cur;
            prev=cur;
            cur=next;
        }
        //合并两个链表
        ListNode* cur1=head,*cur2=newhead->next;
        ListNode* ret=new ListNode(0);
        ListNode* cur3=ret;
        while(cur1)
        {
            cur3->next=cur1;
            cur1=cur1->next;
            cur3=cur3->next;
            if(cur2)
            {
                cur3->next=cur2;
                cur2=cur2->next;
                cur3=cur3->next;
            }
        }
        head=ret->next;
        delete newhead;
        delete ret;
    }
};

2.4 合并 K 个升序链表

  1. 题⽬链接:23. 合并 K 个升序链表

  2. 题⽬描述:
    在这里插入图片描述

  3. 解法⼀(利⽤堆):
    算法思路:
    合并两个有序链表是⽐较简单且做过的,就是⽤双指针依次⽐较链表 1 、链表 2 未排序的最⼩元素,选择更⼩的那⼀个加⼊有序的答案链表中。
    合并 K 个升序链表时,我们依旧可以选择 K 个链表中,头结点值最⼩的那⼀个。那么如何快速的得到头结点最⼩的是哪⼀个呢?⽤堆这个数据结构就好啦~
    我们可以把所有的头结点放进⼀个⼩根堆中,这样就能快速的找到每次 K 个链表中,最⼩的元素是哪个。

  4. 解法⼆(递归/分治):
    算法思路:
    逐⼀⽐较时,答案链表越来越⻓,每个跟它合并的⼩链表的元素都需要⽐较很多次才可以成功排序。
    ⽐如,我们有 8 个链表,每个链表⻓为 100。 逐⼀合并时,我们合并链表的⻓度分别为(0, 100), (100, 100), (200, 100), (300, 100), (400, 100), (500, 100), (600, 100), (700, 100)。所有链表的总⻓度共计 3600。
    如果尽可能让⻓度相同的链表进⾏两两合并呢?这时合并链表的⻓度分别是(100, 100) x 4, (200, 200) x 2, (400, 400),共计 2400。⽐上⼀种的计算量整整少了 1/3。
    迭代的做法代码细节会稍多⼀些,这⾥给出递归的实现,代码相对简洁,不易写错。

算法流程:

  • 特判,如果题⽬给出空链表,⽆需合并,直接返回;
  • 返回递归结果。

递归函数设计:

  • 递归出⼝:如果当前要合并的链表编号范围左右值相等,⽆需合并,直接返回当前链表;
  • 应⽤⼆分思想,等额划分左右两段需要合并的链表,使这两段合并后的⻓度尽可能相等;
  • 对左右两段分别递归,合并[l, r]范围内的链表;
  • 再调⽤ mergeTwoLists 函数进⾏合并(就是合并两个有序链表)
  1. C++代码
/**
 * 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:
    struct cmp
    {
        bool operator()(const ListNode* l1,const ListNode* l2)
        {
            return l1->val>l2->val;
        }
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) 
    {
        ListNode* newhead=new ListNode(0);
        priority_queue<ListNode*,vector<ListNode*>,cmp> pq;
        for(auto&e:lists)
            if(e) pq.push(e);//这里要判断一下每个分组里面节点是否为空
        ListNode* cur=newhead;
        while(!pq.empty())
        {
            ListNode* tmp=pq.top();
            pq.pop();
            cur->next=tmp;
            cur=tmp;
            if(tmp->next)
                pq.push(tmp->next);
        }
        ListNode* ret=newhead->next;
        delete newhead;
        return ret;
    }
};
-------------------------------------------------------------------------------------------
/**
 * 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* mergeSort(vector<ListNode*>&lists,int left,int right)
    {
        //递归出口
        if(left>right)return nullptr;
        if(left==right)return lists[left];
        //平分数组
        int mid=left+((right-left)>>1);
        //处理左右区间
        ListNode* l1=mergeSort(lists,left,mid);
        ListNode* l2=mergeSort(lists,mid+1,right);
        //合并两个有序链表
        return mergeTwoList(l1,l2);
    }
    ListNode* mergeTwoList(ListNode* l1,ListNode* l2)
    {
        if(l1==nullptr)return l2;
        if(l2==nullptr)return l1;
        ListNode head;
        ListNode*cur=&head;
        while(l1&&l2)
        {
            if(l1->val<=l2->val)
            {
                cur->next=l1;
                l1=l1->next;
                cur=cur->next;
            }
            else 
            {
                cur->next=l2;
                l2=l2->next;
                cur=cur->next;
            }
        }
        if(l1)cur->next=l1;
        if(l2)cur->next=l2;
        return head.next;
    }
    ListNode* mergeKLists(vector<ListNode*>& lists) 
    {
        return mergeSort(lists,0,lists.size()-1);
    }
};

2.5 K 个⼀组翻转链表

  1. 题⽬链接:25. K 个⼀组翻转链表
  2. 题⽬描述:
    在这里插入图片描述
  3. 解法(模拟):
    算法思路:
    本题的⽬标⾮常清晰易懂,不涉及复杂的算法,只是实现过程中需要考虑的细节⽐较多。
    我们可以把链表按 K 个为⼀组进⾏分组,组内进⾏反转,并且记录反转后的头尾结点,使其可以和前、后连接起来。思路⽐较简单,但是实现起来是⽐较复杂的。
    我们可以先求出⼀共需要逆序多少组(假设逆序 n 组),然后重复 n 次⻓度为 k 的链表的逆序即可
  4. C++代码
/**
 * 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* reverseKGroup(ListNode* head, int k) 
    {
        ListNode* cur=head;
        int cnt=0;
        while(cur)
        {
            cur=cur->next;
            cnt++;
        }
        int ret=cnt/k;
        ListNode* newhead=new ListNode(0);
        ListNode* prev=newhead,*cur1=head;
        for(int i=0;i<ret;i++)
        {
            ListNode* tmp=cur1;
            for(int j=0;j<k;j++)
            {
                ListNode* cur2=cur1->next;
                cur1->next=prev->next;
                prev->next=cur1;
                cur1=cur2;
            }
            prev=tmp;
        }
        prev->next=cur1;
        ListNode* dest=newhead->next;
        delete newhead;
        return dest;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小沈YO.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值