算法通关村第一关——链表经典问题之第一个公共子节点笔记

五种方法解决链表的第一个公共子节点问题

题目:剑指 Offer 52. 两个链表的第一个公共节点

输入两个链表,找出它们的第一个公共节点。

如下面的两个链表:
在这里插入图片描述在节点 c1 开始相交。

链表节点的定义

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

小技巧:

如果题目刚拿到手的时候没有思路怎么办?

试着将常用的数据结构和常用的算法思想都想一遍,一个一个靠,看有没有能解决的。

常用的数据结构:数组,链表,队列,栈,Hash表,集合,树,堆等等

常用的算法:各种排序,双指针,递归等等

按照这个思路,想一想

(1)方法一: 首先想到的就是暴力破解,用一个双层循环遍历来一个一个对比找相同的那个节点,这样时间复杂度就会有点高,O(n^2)

(2)方法二: 既然时间复杂度比较高的话,那我们就想想用空间换时间,数组?链表?队列?栈?

欸,这个可以。既然两个链表尾巴是一致的,那将两个链表分别放到栈里面,根据栈先进后出的特性,相当于就是从尾到头地遍历链表嘛。

在出栈过程中,对比两边的节点,若相同则继续出栈比较,若不相同,则上一个出栈的节点就是公共节点。

这样时间复杂度就降到O(n),空间复杂度为O(n)

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        // 将链表A存放到栈A中
        stack<ListNode*> stackA;
        while (headA != nullptr) {
            stackA.push(headA);
            headA = headA->next;
        }
        
        // 将链表B存放到栈B中
        stack<ListNode*> stackB;
        while (headB != nullptr) {
            stackB.push(headB);
            headB = headB->next;
        }
        
        ListNode* intersectionNode = nullptr;
        ListNode* tempA = nullptr;
        ListNode* tempB = nullptr;
        
        // 同时出栈进行比较
        while(!stackA.empty() && !stackB.empty()) {
            tempA = stackA.top();
            tempB = stackB.top();
            stackA.pop();
            stackB.pop();
            
            // 当遇到不一样的节点时,证明上一个出栈的即为公共节点,就是当前节点的下一个节点
            if (tempA != tempB) {
                break;
            }
        }
        
        // 结束while的可能性有两种:
        // 1. 碰到了不同的节点
        if(tempA != tempB) {
            intersectionNode = tempA->next;
        } else { // 2. A和B所有节点都是相同的,也就是栈A和栈B同时为空
            intersectionNode = tempA;
        }
        
        return intersectionNode;
    }
};

(3)方法三: 还有没有其他的数据结构能用呢?Hash表和集合?

可以,只将一个链表中的节点存到hash表或者集合中,然后遍历另外一个链表,看有没有集合中有没有一样的节点。

时间复杂度为O(n),空间复杂度为O(n)

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
       set<ListNode*> setA;
       while(headA != nullptr) {
           setA.insert(headA);
           headA = headA->next;
       }
        
        while (headB != nullptr) {
            if (setA.find(headB) != setA.end()) {
                return headB;
            }
            headB = headB->next;
        }
        
        return nullptr;
    }
};

那还有没有时间或者空间复杂度更低的方法呢?一般这个时候,想要的方法都是稍微带一点技巧性了,多刷题的重要性在此时此刻就体现出来了。

(4)方法四: 拼接法,既然两个链表最后的部分都是一样的。那我们将两个链表拼一下,就像A和B,我们拼成AB和BA两种,然后进行遍历,最后肯定会在某一个节点达成相同的。

注意,我们是为了减少空间复杂度来跟进一步地找方法的,如果我们又去新建两个链表岂不是违背初衷了嘛?

那我们怎么办呢?

欸,我们只需要在遍历完A后,接着遍历B,不就组成AB了。同样遍历B,再遍历A,就组成BA了。而且,一边遍历一边比较,这样就OK啦!

在这里插入图片描述

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == nullptr || headB == nullptr) {
            return nullptr;
        }
        
        ListNode* pA = headA;
        ListNode* pB = headB;
        
        // 当不为公共节点时,就接着往下遍历
        while (pA != pB) {
            pA = pA->next;
            pB = pB->next;
            
            /* 考虑边界条件,那就是没有公共节点的时候
             * 在更新pA和pB之后,如果pA == pB == nullptr
             * 就意味着AB和BA对比完了,那我们就不能继续下去
             * 否则会导致ABABABAB……和BABABABA……无限下去造成死循环了
             * */
            if (pA != pB) {
                // 拼成AB,当A遍历完了,就接着遍历B
                if (pA == nullptr) { 
                	pA = headB;
            	}
                
                // 拼成BA,当B遍历完了,就接着遍历A
            	if (pB == nullptr) { 
                	pB = headA;
            	}
            }
        }
        
        return pA;
    }
};

(5)方法五: 对于链表而言,我们最常用的方法还有一个双指针的方法。

既然两个链表尾巴部分一样,那就两个指针一起开始遍历嘛,如果同时遍历到同一个节点,那不就是我们要找的第一个公共节点嘛。

在这里插入图片描述

但是要是像图中这样去遍历,因为A和B长度不一样,那pA和pB永远都不可能遍历到同一个节点上嘛!

这个简单啊,我们让他们从同一起点出发不就好了,谁的跑道长,那我们就先让谁先跑一点,保证两个指针在同一起跑线就好啦!

在这里插入图片描述

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        // 遍历A,得到A的长度
        ListNode *pA = headA;
        int lengthA = 0;
        while(pA != nullptr) {
            ++lengthA;
            pA = pA->next;
        }
        
        // 遍历B,得到B的长度
        ListNode *pB = headB;
        int lengthB = 0;
        while(pB != nullptr) {
            ++lengthB;
            pB = pB->next;
        }
        
        pA = headA;
        pB = headB;
        
        // 计算长度差并统一起跑点
        if(lengthA - lengthB > 0) {
            int sub = lengthA - lengthB;
            while(sub != 0) {
                pA = pA->next;
                --sub;
            }
        } else if (lengthB - lengthA > 0) {
            int sub = lengthB - lengthA;
            while(sub != 0) {
                pB = pB->next;
                --sub;
            }
        }
        
        while(pA != nullptr && pB != nullptr) {
            if (pA == pB) {
                break;
            }
            
            pA = pA->next;
            pB = pB->next;
        }
        
        return pA;
    }
};
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值