编程导航算法通关村第一关|白银 | 回文与公共结点问题

两个链表第一个公共子节点

有两个链表,它们头结点是已知的,相交之后是单链表,但是相交的结点位置和结点数未知,求出剩下相交的单链表

类似于这种题目有很多方法。

首先可以想到它们刚开始是未相交的,有两个链表。但是后面是相交成一个链表,所以结点是一样的,因此可以从后往前,找到即将分叉的地方,这是可以采取先进后出的栈来解决。

先将他们都遍历一遍,分别放入两个栈内,然后弹栈,直到两个结点不同时停止,弹栈的同时可以新建一个链表,使用头插法来存储弹出的结点。同样的思路还有好多方法

链表拼接

再换个思路,可以先将两个链表拼接起来,上图可以看到,无论是A连B还是B连A,他们最后的三个结点都是一样的,都是6,7,8,所以只要找到最后一样的结点即可。

将链表以公共结点分为左右,可以知道right_a=right_b,如上图,将两个链表拼接,最后一段就是所要求的。代码如下

struct ListNode *getIntersectionNode(struct ListNode *pHead1, struct ListNode *pHead2)
{
    //若两个头结点都为空则退出
    if(pHead1==NULL||pHead2==NULL)
        return NULL;

    //用两个指针分别指向两个链表
    struct ListNode *p1 = pHead1;
    struct ListNode *p2 = pHead2;

    // 当两个结点不同时就循环,直到两个结点相同
    while(p1!=p2)
    {
        p1=p1->next;
        p2=p2->next;
        //这里if(p1!=p2)是为了防止死循环,如果两个链表没有公共结点,则他们同时指向为null时就会退出
        //这里条件还可以写成 if(p1==NULL||p2==NULL)
        if(p1!=p2)
        {
            if(p1==NULL)
                p1=pHead2;
            if(p2==NULL)
                p2=pHead1;
        }
    }
    return p1;
}
差和双指针

还有一种利用快慢双指针的方法来做

如图,链表a长度为6,链表b长度为4,求出两者差值,使链表a的头指针前移2次,这样a和b的头指针就会从同一起跑线开始遍历后面的链表,方便找到相同的结点。

大体思路就是先求出两个链表长度,求出两个长度差值sub,长的链表的头指针就先移动sub个结点,然后在和短的链表同时遍历,访问到相同结点即可。

struct ListNode *getIntersectionNode(struct ListNode *pHead1, struct ListNode *pHead2)
{
    if(pHead1==NULL||pHead2==NULL)
        return NULL;

    //先求出两个链表的长度
    int l1=getLength(pHead1);
    int l2=getLength(pHead2);

    struct ListNode *p1=pHead1;
    struct ListNode *p2=pHead2;

    //求出两个链表的差值
    int sub=l1>l2?l1-l2:l2-l1;

    //先让长的先走sub步
    if(l1>l2)
        for(int i=1;i<=sub;i++)
            p1=p1->next;
    if(l1<=l2)
        for(int i=1;i<=sub;i++)
            p2=p2->next;

    //此时两个指针处于同一起点
    while(p1!=p2)
    {
        p1=p1->next;
        p2=p2->next;
    }
    return p1;
}

注:上面getLength函数是自己定义的

这样一个问题就可以举一反三,锻炼多个角度思考问题。 

判断链表是否是回文序列

看见这种问题仍然是可以想想常用的数据结构和算法。

首先可以想到利用栈先进后出的特点来做,将整个链表放入栈中,然后一边弹栈,一边从头遍历链表,判断两个结点是否相等。

再此基础上还可以优化一下,就是先获取链表长度,再压入一半的元素到栈中,接着一边遍历后续元素,一边弹栈,判断两者是否相等。

反转链表

可以将整个链表反转,反转后和原链表比较。注意反转时候要利用头插法

优化一下,可以先遍历一遍,获取链表长度,然后反转一半时候,开始比较两个链表

还可以继续优化一下,遍历一遍即可。利用快慢指针,slow->next一下,fast->next->next一下,然后反转前面的链表。当fast走到表尾时,slow走到一半,此时一边遍历反转后的,一边移动slow

先将slow指向结点保存,再移动fast和slow指针,再将slow指向的结点保存到反转链表中

注意顺序不能返,如果顺序反了,如先保存到反转链表中,再移动指针,那么fast和slow指针会指向反转链表,而不是原链表

 fast指针先指向第1个结点,每次移动两个位置,也就是第二次会指向第3个结点,第三次指向第5个结点,也就是1,3,5,7,9类似这样的规律,而不是2,4,6,8,10这种。

所以当结点个数为奇数和偶数时,fast指向不同,如果为偶数时,fast会指向NULL,否则会指向最后一个尾结点。此时循环条件要两者兼顾,即fast != NULL && fast->next !=NULL

还要注意,当偶数个结点时,fast指向NULL时,slow指向一半+1个结点

而当奇数个结点时,slow指向的是中间的结点,为了统一处理,这里要做个判断,判断节点个数为奇数时(也就是fast->next==NULL时),slow要再移1位

bool isPalindrome(struct ListNode *head)
{
    if(head==NULL||head->next==NULL)
        return true;
    
    struct ListNode*slow=head;
    struct ListNode*fast=head;
    struct ListNode*pre=head;
    struct ListNode*newhead=NULL;
    
    
    while(fast!=NULL&&fast->next!=NULL)
    {
        
        pre=slow;
        //下面两行代码不要放到最后面,要提前前移,否则pre会将链表指向null,使链表打断
        slow=slow->next;
        fast=fast->next->next;

        pre->next=newhead;
        newhead=pre;
        
        
    }
    
    if(fast==NULL)
        slow=slow->next;
    
    while(slow)
    {
        if(slow->val!=newhead->val)
            return false;
        slow=slow->next;
        newhead=newhead->next;
    }
    return true;

    
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值