编程题总结 链表问题常用解决方法

前言

链表问题因为涉及指针,出现错误时通常为段错误(由于访问越界引起的错误)如果没有外用编译器或者GDB是很难debug的,由于野指针,悬浮指针等问题,导致在C/C++中尤其难以debug。而如果对于链表操作不熟练,如节点连接,节点删除那就更难以处理出错。

链表基本操作

以单链表为例

链表创建

链表创建有头插法和尾插法两种方法,题做多了,就能根据不同的情境使用不同的方法。
下述操作基于此结构体节点:

struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

头插法

/* head开始有n个节点
 *
 * 头插法 每新建一个节点都接到头节点head之后
 * 连接的方法为 把头节点head之后的节点接到新节点后面即 node->next=head->next;
 *             把新节点接到头节点head之后即 head->next=node;
*/
ListNode *headInsert(int n)
{
    ListNode *head=new ListNode(1);
    for(int i=2;i<=n;++i){
        ListNode *node=new ListNode(i);
        node->next=head->next;
        head->next=node;
    }
    return head;
}

尾插法

/*head开始有n个节点
 *
 * 尾插法 每新建一个节点都接到尾部tail之后
 * 连接的方法为 把新节点接到尾部tail即 tail->next=node;
 *            修改尾部tail为新节点node即 tail=node;
 */
ListNode *tailInsert(int n)
{
    ListNode *head=new ListNode(1);
    ListNode *tail=head;
    for(int i=2;i<=n;++i){
        ListNode *node=new ListNode(i);
        tail->next=node;
        tail=node;
    }
    return head;
}

节点遍历

/*传入一个头节点执行遍历
 * cur为遍历时的当前节点
 * 访问完一个节点就指针cur后移即 cur=cur->next;
 * 边界条件为尾节点为空 跳出
 */
void travel(ListNode *head){
  ListNode *cur =head;
  while(cur){
      cout<<cur->val<<" ";
      cur=cur->next;
  }
  cout<<endl;
}

节点删除

/*传入一个头节点和要删除的节点序号
 * 遍历找到该节点位置
 * 删除方法为:找到待删除节点的前驱,接上其后继节点
 * 假设删除节点为 3 则找到2接上4。
 *
 * 特别的 n<=0的的情况应该排除
 *       超出链表范围的也应该排除
 *       头节点没有前驱,只需 head=head->next;
 */
ListNode* del(ListNode *head , int n){
    if(n<=0) return head;
    ListNode *cur=head;
    if(n==1){
        head=head->next;
        delete cur;
    }
    else {
        for (int i=2;i<=n-1&&cur;++i) {
            cur=cur->next;
        }
        if(!cur||!cur->next)return head;//超出链表长度
        ListNode *p=cur->next;
        cur->next=cur->next->next;
        delete p;
    }
    return head;
}

双指针法

这个方法是以快慢指针的方法,达到定序的作用。如:

链表中的倒数k个结点

在这里插入图片描述
原题地址

解题思路

让快指针提前走k步,慢指针才开始走。

解题代码

class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        ListNode *front=pListHead,*back=pListHead;
        unsigned i;
        for(i=0;i<k&&front;++i){
            front=front->next;
        }
        if(i<k)return nullptr;
        while(front){
            front=front->next;
            back=back->next;
        }
        return back;
    }
};

分割法

类快排的分割法,对链表进行分割,序列重排。

链表分割

在这里插入图片描述
原题地址

解题思路

新起两个指针,分别连接 <x 和 >=x 的两条链表,最后把大链表接在小链表后面。

特别要注意的是:由于采用在原链表的节点,通过调整其连接序列来新建两个链表,不需要额外空间开销,但需要注意bigHead的尾部可能连接着 <x 的节点所以需要将其置空否则将导致链表成环。

解题代码

//这里没有先建头节点,采用尾插法创建链表
//需要先判断头节点是否建立
//且需要注意边界条件 如:没有 <x或者没有 >=x 的节点
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
     // write code here
        
           ListNode *smallHead=nullptr,*bigHead=nullptr;
     ListNode *p=pHead,*smallp=nullptr,*bigp=nullptr;
     while(p){
         if(p->val<x){
             if(!smallHead){
                 smallHead=p;
                 smallp=smallHead;
             }else {
                     smallp->next=p;
                     smallp=smallp->next;
             }
         }
         else {
             if(!bigHead){
                 bigHead=p;
                 bigp=bigHead;
             }
             else{
                 bigp->next=p;
                 bigp=bigp->next;
             }
         }
         p=p->next;
     }
        if(bigp)
        bigp->next=nullptr;
        if(smallp)
        smallp->next=bigHead;
      return smallHead?smallHead:bigHead ;
 }
};

遍历与创建链表结合

链式A+B

在这里插入图片描述

解题思路

1) 首先需要遍历A,B两个链表获得两个值,可采用遍历累加,每一位乘上数位权重
2) 将其和值sum转换为链表。循环(对10)取余、求商,获得每一位的值。
每次获得的值都是sum的低位,且题目要求数位反向存储。那么采用头插法即可。

解题代码

class Plus {
public:
    ListNode* plusAB(ListNode* a, ListNode* b) {
        // write code here
        int A=0,B=0;
        int n=0;
        while(a){
            A+=a->val*pow(10,n);
            ++n;
            a=a->next;
        }
        n=0;
        while(b){
            B+=b->val*pow(10,n);
            ++n;
            b=b->next;
        }
        int sum=A+B;
        
        ListNode *res=new ListNode(-1);
        ListNode *p=res;
        while(sum){
            ListNode *node=new ListNode(sum%10);
            p->next=node;
            p=node;
            sum/=10;
        }
        return res->next;
    }
};

链表翻转 双指针结合

以链表翻转结合双指针来解决经典问题: 回文链表

回文链表

在这里插入图片描述
原题地址

解题思路

如果不看进阶要求,那么只需要用一个栈,来存储数据再比较即可或者转换为回文数组。

进阶:
如何判断回文链表:
找到中间结点:快慢指针,快指针速度为慢指针速度的一半。
翻转其中一半链表,则得到两段一样的链表,遍历判断即可

解题代码

1、简单模拟下链表长度为单数或者双数的情况。
得知以head为起点,如果为双数,快指针最后为空,慢指针位于中点的next。
如果为单数,快指针位于尾节点,慢指针位于中点。
2、翻转链表需要:当前节点的上一个即pre,而继续往遍历则不能丢掉当前节点的尾巴。
即以pre节点迭代记住前一个节点,同时需要个临时节点记住当前节点的尾巴,以便后移。
3、最后得到pre节点为前半部分翻转链表头,slow为后半链表头

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        ListNode *fast,*slow;
        fast=slow=head;
        ListNode *pre=nullptr;
        //快慢指针遍历,同时翻转前半部分
        while(fast&&fast->next){
            fast=fast->next->next;
            ListNode *temp=slow->next;
            slow->next=pre;
            pre=slow;
            slow=temp;
        }
        //如果为单数的情况,需要后移一位
        if(fast) slow=slow->next;
        //开始判断两段链表是否一致。
        while(slow&&pre){
            if(slow->val!=pre->val)return false;
            else slow=slow->next,pre=pre->next;
        }
        return (!slow||!pre)?true:false;
        }
};

转换路径问题

链表可以看成一条路径,在某些链表问题如:链表相交、链表成环等等 ,我们可以把链表看成路径,问题则可以变成简单的数学问题如:追及问题。

链表相交

在这里插入图片描述

原题地址

解题思路

1、如果通过暴力法写两个循环,比较A链的每个节点与B链的每个节点,那么复杂度将是O(n2)。

换个思路

2、如果把链表看成路径,在假设有两人在路径上竞走,但两人起始地点不一定相同,求其相遇的地点。这个问题怎么解?
评论区有个特别浪漫的说法:我变成你,走过你的路;你变成我,走过我的路,最后我们相遇。即:让先各自遍历链表,到达一方结尾,走另一方的路,最后路程相等:A+B=B+A
只要让两个点遍历链表,判断条件为节点指针是否相等,如果达到一方末端则走另一方的路。

解题代码

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode *A=headA, *B=headB;
        while(A!=B){
        A!=NULL?A=A->next:A=headB; 
        B!=NULL?B=B->next:B=headA;
        }
        return A;
    }
};

链表环路检测

在这里插入图片描述
原题地址

解题思路

要求:原题需要我们找到环路的入口,如果没有环则返回nullptr。
在这里插入图片描述
设环路起点距离head为X,环一圈为Y长。如果求链表的中点我们可以用快慢指针,快指针为慢指针的两倍步长。
1、这里我们如果用快慢指针,如果存在环,他们必在环上相遇。设相遇点距离入口为k,此时快指针已在环上走了n圈:存在关系2(x+k)=x+k+nY画简得X+k=nY
X=(n-1)Y+Y-k这里Y-k也是相遇点和入口的距离。
2、那么答案已经呼之欲出了;让快指针回到head按照慢指针的速度走,下次相遇的节点就是环的入口。
3、如果无环节点,快慢指针将在空指针相遇。

解题代码

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(!head||!head->next)return nullptr;
        ListNode *fast,*slow;
        fast=slow=head;
        while(fast&&fast->next){
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow) break;
        }
        if(fast!=slow)return nullptr;//无环
        fast=head;
        while(fast!=slow){
            fast=fast->next;
            slow=slow->next;
        }
        return fast;
    }
};
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值