【算法】链表相关

【ps】本篇有 5 道 leetcode OJ。 

一、算法简介

        链表是一种常见的线性数据结构,是一种在物理结构上非连续、非顺序的存储结构,其中的数据元素的逻辑顺序由其中的指针链接次序实现,指针链接的每一个结构体都是一个节点。

        链表的结构多种多样,有单向或双向、带头或不带头、循环或非循环之分。但实际中,最常用的是以下两种组合:

  • 无头单向非循环链表(或称单链表)

  • 带头双向循环链表(或称双向链表)

 【Tips】链表的常用技巧

  • 一定要画图!
  • 引入一个虚拟的头节点,利于处理边界情况、对链表进行各种操作。

  • 不要吝啬空间,大胆定义变量去使用,尤其涉及到插入操作的时候。

  • 快慢双指针,可以对链表判环、找环的入口、找链表中倒数第 n 个节点。

【Tips】链表的常见操作

  •  创建一个新节点
  • 尾插
  • 头插

二、相关例题

1)两数相加

2. 两数相加

.1- 题目解析

        要将两个链表的每个节点中的数相加并放在一个新的链表中,那一定就要构建一个链表,我们可以对这个新链表引入一个头节点,然后直接遍历两个链表进行求和,然后将结果构建出一个新节点尾插入头节点即可。

        特别的,我们单独定义一个变量 t,既保存相加的结果,又记录进位。

.2- 代码编写

/**
 * 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* newhead=new ListNode(0);
        ListNode*cur1=l1,*cur2=l2,*prev=newhead;
        int t=0; //保存求和结果+记录进位
        while(cur1||cur2||t)
        {
            //取两个链表的节点进行求和
            if(cur1)
            {
                t+=cur1->val;
                cur1=cur1->next;
            }
            if(cur2)
            {
                t+=cur2->val;
                cur2=cur2->next;
            }
            prev->next=new ListNode(t%10);//取结果的个位
            t/=10;                        //取进位
            prev=prev->next;
        }
        prev=newhead->next;
        delete newhead;
        return prev;
    }
};

2)两两交换链表中的节点

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

.1- 题目解析

        以示例 1 为例。我们可以引入一个头节点,并定义四个指针变量来表示不同的节点。

        由此,要实现题目要求的交换,仅需交换 cur 和 next 指向的节点即可,然后将这四个指针整体向后移动,继续完成下次交换,直到 cur 遇到了空节点,说明此时整个链表都完成了交换。

.2- 代码编写

/**
 * 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=newhead->next;
        ListNode* next=cur->next,*nnext=next->next;
        while(cur && next)
        {
            //交换 cur 和 next
            cur->next=nnext;
            next->next=cur;
            prev->next=next;
            //继续遍历链表
            prev=cur;
            cur=nnext;
            if(cur)next=cur->next;
            if(next)nnext=next->next;
        }
        cur=newhead->next;
        delete newhead;
        return cur;
    }
};

3)重排链表

重排链表. - 力扣(LeetCode)

.1- 题目解析

        以示例 1 为例。如果将原始链表从中间一分为二,将后半部分链表逆序,然后将前半部分和后半部分的链表节点依此插入道一个新链表中(前半部分先插入,后半部分后插入),就能得到题目要求的链表了。

         我们可以用快慢双指针来找到链表的中间位置,将后半部分的起始节点看作是慢指针的下一根节点,然后用头插法来对后半部分的链表进行逆序操作,这样无论原始链表的节点是单数还是复数,都不会影响操作的正确性。

         逆序完后半部分的链表之后,依此将前后两部分的链表插入到一个新的头节点之后即可。

.2- 代码编写

/**
 * 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* fast=head,*slow=head;
        while(fast&&fast->next)
        {
            slow=slow->next;
            fast=fast->next->next;
        }
        //头插法逆序后半部分
        ListNode* newhead=new ListNode(0);
        ListNode*cur=slow->next,*next=cur->next;
        slow->next=nullptr; //断开前后两个链表
        while(cur)
        {
            next=cur->next;
            cur->next=newhead->next;
            newhead->next=cur;
            cur=next;            
        }
        //合并两个链表
        ListNode* ret=new ListNode(0);
        ListNode* prev=ret;
        ListNode* cur1=head,*cur2=newhead->next;
        while(cur1)
        {
            prev->next=cur1;
            cur1=cur1->next;
            prev=prev->next;
            if(cur2)
            {
                prev->next=cur2;
                cur2=cur2->next;
                prev=prev->next;
            }
        }
        delete newhead;
        delete ret;
    }
};

4)合并 K 个升序链表

23. 合并 K 个升序链表

.1- 题目解析

        要合并 K 个升序链表,可以先合并两个升序链表,再继续两两合并剩下的链表。我们可以用优先级队列(建小根堆排升序)来优化这个过程,将所有链表的头节点依此入队,即可找到最小的那个节点,只需将其尾插入一个新的头节点,然后将后续的节点入队,重复这个过程,就可以完成 K 个升序链表的合并了。

        另外,先合并两个升序链表,再继续两两合并剩下的链表,也可以用归并的方式来解决。

.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 {
    struct cmp
    {
        bool operator()(const ListNode* l1,const ListNode* l2)
        {
            return l1->val > l2->val;
        }
    };
public: 
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        //创建一个小根堆
        priority_queue<ListNode*,vector<ListNode*>,cmp> heap;
        //让所有链表的头节点进行小根堆
        for(auto l: lists) 
            if(l)
                heap.push(l);
        //合并链表
        ListNode* ret=new ListNode(0);
        ListNode* prev=ret;
        while(!heap.empty())
        {
            auto t=heap.top();//每次取堆顶元素
            heap.pop();
            prev->next=t;//链接
            prev=t;
            if(t->next) //将后续的节点入堆
                heap.push(t->next);
        }
        prev=ret->next;
        delete ret;
        return prev;
    }
};
//法2:递归
/**
 * 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) {
        return merge(lists,0,lists.size()-1);

    }
    ListNode* merge(vector<ListNode*>&lists,int left,int right)
    {
        if(left>right)return nullptr;
        if(left==right)return lists[left];
        //平分数组
        int mid=(left+right)>>1;
        //递归处理左右区间
        ListNode* l1=merge(lists,left,mid);
        ListNode* l2=merge(lists,mid+1,right);
        //合并两个有序链表
        return mergeTowlist(l1,l2);
    }
    ListNode* mergeTowlist(ListNode* l1,ListNode* l2)
    {
        if(l1==nullptr)return l2;
        if(l2==nullptr)return l1;
        ListNode head;
        ListNode* cur1=l1,*cur2=l2,*prev=&head;
        head.next=nullptr;
        while(cur1&&cur2)
        {
            if(cur1->val<=cur2->val)
            {
                prev->next=cur1;
                prev=cur1;
                cur1=cur1->next;
            }
            else
            {
                prev->next=cur2;
                prev=cur2;
                cur2=cur2->next;
            }
        }
        if(cur1)prev->next=cur1;
        if(cur2)prev->next=cur2;
        return head.next;
    }
};

5)K 个一组翻转链表

25. K 个一组翻转链表

.1- 题目解析

.2- 代码编写

/**
 * 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) {
        //1.先求出需要逆序多少组
        int n=0;
        ListNode* cur=head;
        while(cur)
        {
            cur=cur->next;
            n++;
        }
        n/=k;
        //2.重复n次,长度为k的链表逆序
        ListNode* newHead=new ListNode(0);
        ListNode* prev=newHead;
        cur=head;

        for(int i=0;i<n;i++)
        {
            ListNode* tmp=cur;
            for(int j=0;j<k;j++)
            {
                ListNode* next=cur->next;
                cur->next=prev->next;
                prev->next=cur;
                cur=next;
            }
            prev=tmp;
        }
        prev->next=cur;
        cur=newHead->next;
        delete newHead;
        return cur;
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值