6.面试算法-链表之高频面试题(一)

高频面试题(一)

1.1 五种方法解决两个链表第一个公共子节点

这是一道经典的链表问题先看一下题目。

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

例如下面的两个链表:
在这里插入图片描述
两个链表的头结点都是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。

1.没有思路时该怎么解题
这种问题该怎么入手呢?如果一时想不到该怎么办呢?其实这时候我们可以将常用数据结构和常用算法都想一遍,看看哪些能解决问题。

常用的数据结构有数组、链表、队、栈、Hash 、集合、树、堆。常用的算法思想有查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划等等。

我们先在脑子里快速过一下谁有可能解决问题。首先想到的是蛮力法,类似于冒泡排序的方式,将第一个链表中的 每一个结点依次与第二个链表的进行比较,当出现相等的结点指针时,即为相交结点,但是这种方法时间复杂度高,而且有可能只是部分匹配上,所以还有要处理复杂的情况。排除!

其次Hash呢?模模糊糊感觉行的, OK。

之后是集合呢?和Hash一样用,目测也能解决, OK。

队列和栈呢?貌似队列没啥用,但是栈能解决问题,于是就有了第三种方法。

其他的几种结构或者算法呢?貌似都不太好用。这时候我们可以直接和面试官说,应该可以用HashMap做,另外集合和栈应该也能解决问题。面试官很明显就问了,怎么解决?

那这时候你可以继续考虑HashMap、集合和栈具体应该怎么解决,假如错了呢?比如你说队列也行,但是后面发现根本解决不了,这时候直接对面试官说“ 队列不行,我想想其他方法” ,一般对方就不会再细究了。

算法面试本身也是一个相互交流的过程,如果有些地方你不清楚,他甚至会提醒你一下,所以不用紧张,也不用怕他盯着你写代码,努力去做就行了。

(1)HashMap法

先将一个链表全部存到Map里,然后再遍历第二个,如果有交点,那么一定能在访问到某个元素的时候检测出来如果面试官点头,就可以手写了:

import java.util.HashMap;
public class Solution {
   
   
	public ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
   
    
		if(pHead1==null || pHead2==null){
   
   
			return null;
		}
		ListNode current1=pHead1;
		ListNode current2=pHead2;

		HashMap<ListNode,Integer>hashMap=new HashMap<>();
		while(current1!=null){
   
   
			hashMap.put(current1,null);
			current1=current1.next;
		}

		while(current2!=null){
   
   
			if(hashMap.containsKey(current2))
				return current2;
			current2=current2.next;
		}
	return null;
	}
}

(2) 集合Set法

能用Hash,那能不能用Set呢?其实思路和上面的一样,

先把第一个链表的节点全部存放到集合set中,然后遍历第二个链表的每一个节点,判断在集合set中是否存在,如果存在就直接返回这个存在的结点。如果遍历完了,在集合set中还没找到,说明他们没有相交,直接返回null即 可。

public ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
   
   
	Set<ListNode> set = new HashSet<>();
	while (headA != null) {
   
   
		set.add(headA);
		headA = headA.next;
	}

	while (headB != null) {
   
   
		if (set.contains(headB))
			return headB;
		headB = headB.next;
	}
	return null;
}

(3) 使用栈

这里需要使用两个栈,分别将两个链表的结点入两个栈,然后分别出栈,如果相等就继续出栈,不相等的时候就找到了分界线了。这种方式需要两个O(n)的空间,所以在面试时不占优势,但是能够很好锻炼我们,所以花十分钟写一个吧:

import java.util.Stack;
public class Solution {
   
   
	public ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
   
   
		Stack<ListNode> stackA=new Stack();
		Stack<ListNode> stackB=new Stack();
		while(headA!=null){
   
   
			stackA.push(headA);
			headA=headA.next;
		}
		while(headB!=null){
   
   
			stackB.push(headB);
			headB=headB.next;
		}

		ListNode preNode=null;
		while(stackB.size()>0 && stackA.size()>0){
   
   
			if(stackA.peek()==stackB.peek()){
   
   
				preNode=stackA.pop();
				stackB.pop();
			}else{
   
   
				break;
			}
		}
		return preNode;
	}
}

看到了吗,从一开始没啥思路到最后搞出三种方法,熟练掌握数据结构是多么重要!!

(4)拼接两个字符串

先看下面的链表A和B:
A: 0-1-2-3-4-5
B:a-b-4-5

如果分别拼接成AB和BA会怎么样呢?
AB:0-1-2-3-4-5-a-b-4-5
BA:a-b-4-5-0-1-2-3-4-5

我们发现最后从4开始的就是公共子节点,但是建立新的链表太浪费空间了,我们只要在每个队列访问到头之后调整一下指针就行了,于是代码就出来了:

public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
   
    					 
	if(pHead1==null || pHead2==null){
   
   
		return null;
	}
	ListNode p1=pHead1;
	ListNode p2=pHead2;
	while(p1!=p2){
   
   
		p1=p1.next;
		p2=p2.next;
		if(p1!=p2){
   
   
			if(p1==null){
   
   
				p1=pHead2;
			}
			if(p2==null){
   
   
				p2=pHead1;
			}
		}
	}
	return p1;
}

(5) 拓展 差和双指针

如果你想到了这三种方法中的两个,并且顺利手写并运行出一个来,面试基本就过了,至少面试官对你的基本功是满意的。但是对方可能会再来一句:还有其他方式吗?或者说,有没有申请空间大小是O(1)的方法。

我们前面介绍过双指针,那能否用一下呢?貌似可以,但是不能直接用。

假如公共子节点一定存在第一轮遍历,假设La长度为L1, Lb长度为L2.则| L2-L1 |就是两个的差值。第二轮遍历,长的先走| L2-L1|,然后两个链表同时向前走,结点一样的时候就是公共结点了。

public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
   
   
	if(pHead1==null || pHead2==null){
   
    
		return null;
	}
	ListNode current1=pHead1;
	ListNode current2=pHead2;
	int l1=0,l2=0;
	while(current1!=null){
   
   
		current1=current1.next;
		l1++;
	}

	while(current2!=null){
   
   
		current2=current2.next;
		l2++;
	}
	current1=pHead1;
	current2=pHead2;

	int sub=l1>l2?l1-l2:l2-l1;

	if(l1>l2){
   
   
		int a=0;
		while(a<sub){
   
   
			current1=current1.next;
			a++;
		}
	}

	if(l1<l2){
   
   
		int a=0;
		while(a<sub){
   
   
			current2=current2.next;
			a++;
		}
	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值