引言:以下内容均只是个人看法,并无严格经过核对,如有异议,欢迎交流,发现错误,欢迎您的及时指正,感谢
本文中使用的结点定义语句如下
public class listNode {
public int data;
public listNode next;
public listNode(int data) {
this.data = data;
this.next=null;
}
}
单链表的遍历一般从第一个结点依次遍历整个链表,所以第一个结点十分重要,在本文中头节点指的是第一个存放数据的结点,与其他相关文章中关于它的定义可能不同,无需深究,理解相关操作的逻辑原理即可,本质上无较大差别。
1.两个链表的第一个公共子节点
当存在两个单链表有公共结点时,可能出现如下图所示的情况,我们已经知道单链表中中间结点只能有一个后缀,对于其的前驱个数没有限制,所以下图满足单链表的要求。
对于题目没有思路怎么办?
这里的建议是把常用的数据结构和算法思想都想一遍,看看哪些可以解决问题。
常用的数据结构有数组,链表,队列,栈,HASH,集合,树,堆,常用的算法思想有查找,排序,双指针,递归,迭代,分治,贪心,回溯和动态规划等。
分析题目要求我们要找到的是两个单链中第一个公共点,且这个公共点在两个单链中的相对位置未知(即不知道在两个单链中第一个公共结点前面的结点个数)。
注意比较是否为公共结点时,如果在两个链表中某个结点的结点地址相同,即为公共结点
1.1蛮力法
首先我们可能想到的是蛮力法,既然要比较结点地址,那就从一条链表的第一个结点开始,去比较另一条链表的每一个结点,虽然简单,但是时间复杂度比较高,面试时排除,但是至少要知道如何实现。
代码如下
注意在while(node2!=null)前面要加node2=head2,以保证每次遍历第二条链表都从头遍历
/**
*
* @param head1 第一条链表的头节点
* @param head2 第二条链表的头节点
* @return
*/
public static listNode findFirstPublicNode(listNode head1,listNode head2){
//用node1和node2代替head进行遍历链表
listNode node1=head1;
listNode node2=head2;
//判断两个头节点是否为空,为空自然没有公共结点
if(head1==null||head2==null){
return null;
}
while(node1!=null){
node2=head2;
while(node2!=null){
//对于引用数据类型,==表示对其地址的比较
if(node1==node2){
return node1;
}
node2=node2.next;
}
node1=node1.next;
}
return null;
}
2.HASH和集合法
基本思路:我们遍历一条链表并将其中元素存到Map中,之后遍历另一条链表并在Map中检测当前元素是否存在,第一次出现存在的情况对应的节点就是第一个公共节点,集合的思路与之类似。
利用HashMap的代码如下:
注意利用while遍历链表时,加上node指针后移的语句
/**
*
* @param head1 第一条链表的头节点
* @param head2 第二条链表的头节点
* @return
*/
public static listNode findFirstPublicNode_hash(listNode head1,listNode head2){
//创建一个HashMap用来存放一条链表中的节点
HashMap<listNode,Integer> map=new HashMap<listNode, Integer>();
//用node1和node2代替head进行遍历链表
listNode node1=head1;
listNode node2=head2;
//判断两个头节点是否为空,为空自然没有公共结点
if(head1==null||head2==null){
return null;
}
while(node1!=null){
//将第一条链表的结点存到Map
map.put(node1,node1.data);
node1=node1.next;
}
while(node2!=null){
//判断map中是否可以检测到node2
if(map.containsKey(node2)){
return node2;
}
node2=node2.next;
}
return null;
}
利用Set的代码如下
/**
*
* @param head1 第一条链表的头节点
* @param head2 第二条链表的头节点
* @return
*/
public static listNode findFirstPublicNode_set(listNode head1,listNode head2){
//新建一个集合
Set<listNode> set=new HashSet<>();
//用node1和node2代替head进行遍历链表
listNode node1=head1;
listNode node2=head2;
//判断两个头节点是否为空,为空自然没有公共结点
if(head1==null||head2==null){
return null;
}
while(node1!=null){
//将第一条链表的结点存到Map
set.add(node1);
node1=node1.next;
}
while(node2!=null){
//判断map中是否可以检测到node2
if(set.contains(node2)){
return node2;
}
node2=node2.next;
}
return null;
}
3.栈方法
基本思路:思考两个有公共结点的链表特点,查看本文开篇处的图片,我们发现两个链表的最后一段都是相同的结点,我们其实是找到这些里面的第一个,前面的方法都是正向遍历,
/**
*
* @param head1 第一条链表的头节点
* @param head2 第二条链表的头节点
* @return
*/
public static listNode findFirstPublicNode_stack(listNode head1,listNode head2){
Stack<listNode> stack1=new Stack<listNode>();
Stack<listNode> stack2=new Stack<listNode>();
//用node1和node2代替head进行遍历链表
listNode node1=head1;
listNode node2=head2;
//判断两个头节点是否为空,为空自然没有公共结点
if(head1==null||head2==null){
return null;
}
while(node1!=null){
stack1.push(node1);
node1=node1.next;
}
while(node2!=null){
stack2.push(node2);
node2=node2.next;
}
listNode snode=null;
while(stack1.size()>0&&stack2.size()>0){
if(stack1.peek()==stack2.peek()){
snode=stack1.pop();
stack2.pop();
}
else {
break;
}
}
return snode;
}