算法通关村第一关——链表经典问题之相交链表笔记

相交链表

相交链表问题也就是两个链表的第一个公共子节点问题。
LeetCode题目链接 160. 相交链表

👹 题目描述

相交链表

👹 题目分析

  • 首先明确要求:我们已知两个链表的头节点,求两个链表的相交节点的第一个公共节点。
  • 分析题目:单链表的每一个节点都只能指向唯一的下一个节点next(也就是只能拥有唯一的后继),但是可以有多个指针指向同一个节点。例如上面的a2,b3同时指向c1.
  • 明确了题目要求和前提之后,我们就可以开始解题了~

👹 解题方法

很多时候我们了解链表的基本概念和题目的意思,但面对题目还是大眼瞪小眼,一脸懵啊😶~~

一脸无辜.jpg
那么我们如何解决这个问题呢?
一个屡试不爽的方法:套!!!

套什么?
当然是把常用的数据结构和算法思想都套用一遍
~

常用的数据结构,数组、队列、栈、Hash、树、堆、集合等;
常用算法思想,查找、排序、双指针、递归、迭代、分治、贪心、dp、回溯等。

  • 首先我们脑中第一想到的肯定是暴力解决法,将第一个链表中的每一个结点一次与第二个链表的进行比较,当出现相等的结点指针时,即为相交节点。虽然很简单,但是时间复杂度非常高,排除此方法!
  • 要找到相交的链表,我们可以进行比较。我们可以通过(先进后出)保存元素,然后进行比较。我们将两个链表分别压入到两个栈里面,如果栈顶元素一致则出栈,然后继续找,最晚出栈的那组一致的节点就是要找的位置。ok,第二种方法出来。
  • 通过Hash,先将一个链表的元素全部添加到Map里面,然后遍历第二个链表并检查当前元素是否在Hash中,如果找到了,则存在交点。okay,第三种方法找到了。同理,既然Hash可以,那集合呢?可想而知同Hash一样,也能解决,ok,第四种方法我们也找到了。
  • 同样双指针也适合,这个后续介绍~

👹 具体解决方案+代码(java)

🤖 法一(使用栈)

这里我们需要使用两个栈,分别将两个链表的节点压入栈,然后比较栈顶元素,相同则出栈(相同说明是公共元素),一直找到最后出栈的那一组。
🤡解释:最后出栈的那一组,即最后一组相同的元素,因为栈是一种先入后出的思想,所以最后一组相同的元素,就是相交链表的第一个节点。
这种方法,我们需要创建两个O(n)空间大小的栈

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 创建两个栈
        Stack<ListNode> stack1 = new Stack<>();
        Stack<ListNode> stack2 = new Stack<>();

		// 将两个链表的元素分别压入栈
        while (headA != null) {
            stack1.push(headA);
            headA = headA.next;
        }
        while (headB != null) {
            stack2.push(headB);
            headB = headB.next;
        }

		// 定义一个变量,用来保存相交的元素
        ListNode node = null;
        // 当两个栈都不为空的时候才进行比较判断栈顶元素
        // 解释:当一个栈为空一个栈不为空的时候,肯定不存在相交元素
        while (!stack1.empty() && !stack2.empty()) {
        	// 判断栈顶元素是否一致,一致则出栈
            if (stack1.peek() == stack2.peek()) {
                node = stack1.pop();
                stack2.pop();
            } else {
            	// 不同则退出循环
                break;
            }
        }
        // 返回一致元素的最后一组,即相交链表的第一个公共节点
        return node;
    }

在这里插入图片描述

🤖 法二(哈希和集合)

先将一个链表元素全部存到集合中,然后遍历第二个链表,并判断集合中是否存在当前节点,如果存在,即相交节点。使用Map也可以,不过本题更适合用集合。

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // set
        Set<ListNode> set = new HashSet<>();
        // 将链表A的元素全部添加到集合中
        while (headA != null) {
            set.add(headA);
            headA = headA.next;
        }

		// 遍历链表B
        while (headB != null) {
        	// 判断集合中是否包含当前节点
            if (set.contains(headB)) {
            	// 包含则直接返回
                return headB;
            }
            headB = headB.next;
        }
        return null;
    }

在这里插入图片描述

🤖 法三(差和双指针)

首先计算两链表的长度的差值k,让长的链表先走k步,然后再与短的链表一起向后走,直到找到相同节点或者结束为止。

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
		// 先分别计算两链表的长度
        int size1 = size(headA);
        int size2 = size(headB);

		// 定义两个指针,分别指向A、B链表的头节点
        ListNode cur1 = headA;
        ListNode cur2 = headB;
        
        // 计算差值
        // 保证差值为正数,并且保证 cur1 始终指向长一些的链表,cur2指向短一些的链表
        int count = size1 - size2;
        if (size1 < size2) {
        	// size1 < size2 说明链表A短
            count = size2 - size1;
            cur1 = headB;
            cur2 = headA;
        }
        
        // 让长一点的链表先走count步
        while (count > 0 ) {
            cur1 = cur1.next;
            count--;
        }
        // 此时cur1与cur2 到末尾节点长度相等
        // cur1 = cur2 退出循环
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1; // cur1即相交节点
    }
    public int size(ListNode head) {
        ListNode cur = head;
        int count = 0;
        while (cur != null) {
            cur = cur.next;
            count++;
        }
        return count;
    }

在这里插入图片描述

🤖 法四(双指针)拼接

在这里插入图片描述

listA : [4, 1, 8, 4, 5]
listB :[5, 6, 1, 8, 4, 5]
将listA与listB拼接起来:
AB [4, 1, 8, 4, 5, 5, 6, 1, 8, 4, 5]
BA [5, 6, 1, 8, 4, 5, 4, 1, 8, 4, 5]

会发现,从8开始后面的节点就一样了,显然8就是我们要找的节点~

注意:
比较的是两个节点是否相同,不是值是否相等。
也就是说1节点,两链表的值都是1,但是不是同一个节点

在这里插入图片描述
思路:

  • 定义两个指针分别指向链表的头部 p -> headA, q -> headB
  • p走到链表尾部后,回到headBq走到链表尾部后回到headA
  • 继续向后走,直到p、q相遇(相交)
  • 当两链表没有交点时,p、q会同时等于空

代码:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 定义两个指针分别指向链表的头部
        // p 指向链表A,当p走到尾部后,回到headB
        // q 指向链表B,当q走到尾部后,回到headA
        // 如果p、q相等且不为空即 相交
        
        ListNode p = headA;
        ListNode q = headB;
        while (p != q) {
            p = (p != null) ? p.next : headB;
            q = (q != null) ? q.next : headA;
        }
        return p;
    }

复杂度分析:

  • 时间复杂度 O(a+b) : 最差情况下(即 ∣a−b∣=1 , c=0 ),此时需遍历 a+b 个节点。
  • 空间复杂度 O(1) : 节点指针 A , B 使用常数大小的额外空间。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值