【链表面试题】解决环形链表和相交链表问题

简介: 【链表面试题】解决环形链表和相交链表问题

一、环形链表

1.定义(概念)

所谓的环形链表,无非是这一种,如图所示

这样的链表,就可以称为环形链表。

2.如何判断是否为环形链表

给定一链表,我们怎么判断是否为环形链表呢?

接下来我们认识一下快慢指针的概念

1.快慢指针

快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。比如:陪女朋友到操作跑步减肥。

就是以表头为初始位置,我们定义fast、slow两个结点,指向head头节点,fast每一次,走两个结点,slow每一次走一个结点,如果有环,必然会相遇(fast==slow),注意哦,是指针地址相等哦!如果无环的话,就会fast更快的走到链表末尾,指向NULL

2.为什么快指针一次走两个结点

假设链表带环,两个指针最后都会进入环里面,快指针先进入环,慢指针后进入环、当慢指针刚计入环的时候,可能就和快指针相遇了,最差的情况是相距环的长度。此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况(无法相遇),因此,在慢指针走到一圈之前,快指针肯定能追上慢指针的,即相遇!

如图所示:

如图所示:

3.例题分析

这是例题链接,环形链表

题目如下:

AC代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode *head) {
    struct ListNode *fast=head;
    struct ListNode *slow=head;
    // if(fast!=NULL||fast->next!=NULL){
    //     return false;
    // }
    while(fast!=NULL&&fast->next!=NULL){
        slow=slow->next;
        fast=fast->next->next;
        if(fast==slow){
            return true;
        }
    }
    return false;
}

1.while循环,因为head为头节点,head为NULL,说明为空链表,head->next为NULL,说明只有一个结点,无法构成环。

2.我们使用while循环,使用快慢指针,fast步长为2,slow步长为1,如果fast==slow返回为true(有环),循环结束之后,返回false(无环)

还有一种比较暴力的解法:

代码如下:

while(head!=NULL){
        if(head->val==100000){
            return true;
        }
        head->val=100000;
        head=head->next;
    }
    return false;

第二个可以用快慢指针的题目

链表中倒数第k个结点_牛客题霸_牛客网

如图:

我们使用快慢指针的思路是,fast如果与slow同时移动的话,移动一次,fast与slow指向的指针差距都会加一,所以,倒数第k给结点,只需要,fast与slow距离为k的时候,两者再以都以步长为1移动,fast,移动到NULL时,slow指向的就是倒数第k个节点

代码如下:

/**
 * struct ListNode {
 *  int val;
 *  struct ListNode *next;
 * };
 */
/**
 * 
 * @param pListHead ListNode类 
 * @param k int整型 
 * @return ListNode类
 */
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // // write code here
    // if(pListHead==NULL){
    //     return NULL; //判断是否为NULL
    // }
    // struct ListNode*cur=pListHead;
    // int count=0;
    // while(cur!=NULL){
    //     count++;
    //     cur=cur->next;
    // }
    // //得到了结点个数
    // if(count==0){
    //     return NULL;  //如果空链表,自然返回NULL
    // }
    // int num=count+1-k;//这是num得到倒数第k个结点  count-k+1
    // if(k>count||k<=0){    //如果k大于count,当然是不行的,小于0也不行,都返回NULL
    //     return NULL;
    // }
    // while(--num){     //然后进行循环即可
    //     pListHead=pListHead->next;
    // }
    // return pListHead;
//快慢指针问题,设计两个指针,fast和slow两种,然后先移动fast指针k步,然后一起移动两个指针,直到fast移动到NULL的时候,slow就是倒数第k个结点
    struct ListNode*fast=pListHead;
    struct ListNode*slow=pListHead;
    while(k--){
        if(fast==NULL){
            return NULL;//如果为空链表,那么就返回NULL
        }
        fast=fast->next;
    }
    while(fast){
        fast=fast->next;
        slow=slow->next;
    }
    return slow;
}

while循环移动k次,然后同时步长为1移动,直到fast为NULL,这就是使用快慢指针的思路。

当然被//的是普通的做法,一并附上

二、相交链表

如图:

1.例题分析

2.解法分析

第一种如图所示:

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA==NULL||headB==NULL){
        return NULL;
    }
   struct ListNode*p=headA;
   struct ListNode*q=headB;
    while(p!=q){
        p=p==NULL?headB:p->next;
        q=q==NULL?headA:q->next;//因为比较的是指针的地址,所以如果指向NULL之后,还可以继续找到头节点
        //1.结点数相同的时候,一次遍历就能得到是否有相交,这个结点就是p指向的位置,如果没有,p正好指向null
        //2.如果结点数不同的时候,那么因为不同,所以当结点数小的那个遍历一边的时候,结点数大的还没完成,这样永远差一个结点数之差的位置,然后继续接上上两个头节点,这样第二轮,就是在尾部对齐的情况下,起始位置相同,这样一轮必然能得到答案
    }
    return p;
}

第二种解法分析:

主要是,先遍历得到两条链表的长度,pq分别指向链表头节点headA、headB然后将长的那个链表的头节点给p结点,然后得到两链表结点个数之差,num=lenA-lenB,然后将p结点移动num次,while循环,然后再同时移动p和q结点,进行判断是否有相交结点

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA==NULL||headB==NULL){
        return NULL;
    }
//    struct ListNode*p=headA;
//    struct ListNode*q=headB;
//     while(p!=q){
//         p=p==NULL?headB:p->next;
//         q=q==NULL?headA:q->next;//因为比较的是指针的地址,所以如果指向NULL之后,还可以继续找到头节点
//         //1.结点数相同的时候,一次遍历就能得到是否有相交,这个结点就是p指向的位置,如果没有,p正好指向null
//         //2.如果结点数不同的时候,那么因为不同,所以当结点数小的那个遍历一边的时候,结点数大的还没完成,这样永远差一个结点数之差的位置,然后继续接上上两个头节点,这样第二轮,就是在尾部对齐的情况下,起始位置相同,这样一轮必然能得到答案
//     }
//     return p;
//第二种做法
//目的是,我们想让他们尾部对齐,然后在同长度的情况下,遍历,这样就能找到相交结点,或者NULL
    struct ListNode*p=headA;
    struct ListNode*q=headB;
    if(headA==NULL||headB==NULL){
        return NULL;
    }
    //取得第一个A链表的长度
    int lenA=0;
    while(p!=NULL){
        lenA++;
        p=p->next;
    }
    int lenB=0;
    while(q!=NULL){
        lenB++;
        q=q->next;
    }
    //找到之后,将最大的结点数都给A  就是交换位置
    p=headA;
    q=headB;
    if(lenA<lenB){
        //交换结点长度
        int temp=lenA;
        lenA=lenB;
        lenB=temp;
        //交换链表头节点
        struct ListNode*node=p;
        p=q;
        q=node;
    }
    //这样得到的是A为最长结点
    //两个结点相互减去
    int count=lenA-lenB;
    while(count--){
        p=p->next;       
    }
    while(p!=q){
        p=p->next;
        q=q->next;
    }
    return p;
}

总结

了解了快慢指针这一概念,对于环形链表会进行判断了,相交链表的问题,也可以实现了,不过值得一提的是,力扣讨论区里面,真的不少好玩有趣的解法。

比如经典的 return true; (50%答案)真的好逗!!!

相关文章
|
28天前
|
索引
【力扣刷题】两数求和、移动零、相交链表、反转链表
【力扣刷题】两数求和、移动零、相交链表、反转链表
23 2
【力扣刷题】两数求和、移动零、相交链表、反转链表
|
14天前
|
存储 算法 数据可视化
深入解析力扣160题:相交链表的解决方法(哈希表法与双指针法详细图解)
深入解析力扣160题:相交链表的解决方法(哈希表法与双指针法详细图解)
|
19天前
|
存储 算法 Java
【经典算法】Leetcode 141. 环形链表(Java/C/Python3实现含注释说明,Easy)
【经典算法】Leetcode 141. 环形链表(Java/C/Python3实现含注释说明,Easy)
9 2
|
28天前
<数据结构>五道LeetCode链表题分析.环形链表,反转链表,合并链表,找中间节点.
<数据结构>五道LeetCode链表题分析.环形链表,反转链表,合并链表,找中间节点
23 1
|
28天前
|
存储 算法 索引
【力扣刷题】只出现一次的数字、多数元素、环形链表 II、两数相加
【力扣刷题】只出现一次的数字、多数元素、环形链表 II、两数相加
30 1
|
1月前
|
存储 算法 索引
链表面试题
链表面试题
链表面试题
|
14天前
|
存储 SQL 算法
LeetCode 83题:删除排序链表中的重复元素【面试】
LeetCode 83题:删除排序链表中的重复元素【面试】
|
28天前
|
索引
【力扣刷题】回文链表、环形链表、合并两个有序链表
【力扣刷题】回文链表、环形链表、合并两个有序链表
21 0
|
1月前
|
存储
环形链表题
环形链表题
26 0
|
1月前
【一刷《剑指Offer》】面试题 17:合并两个排序的链表
【一刷《剑指Offer》】面试题 17:合并两个排序的链表