当前位置:   article > 正文

数据结构 之 链表 高频面试题_双向链表面试题

双向链表面试题

一:面试时链表解题的方法论

1、对于笔试,不用太在乎空间复杂度,一切为了时间复杂度

2、对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法

二:链表面试题常用数据结构和技巧

1、使用容器(哈希表、数组等)

2、快慢指针

使用快慢指针的面试题:
  • 1、输入链表头结点,奇数长度返回中点,偶数长度返回上中点
    *在这里插入图片描述
//输入链表头结点,奇数长度返回中点,偶数长度返回上中点
	public static <T> Node<T> midOrUpMidNode(Node<T> head) {
		
		//如果当前结点为空 或者 结点的下一个结点为空 或者结点的下一个结点的下一个结点为空
		if(head == null || head.next == null || head.next.next == null) {
			return head;
		}
		//此时链表至少3个结点
		Node<T> slow  = head.next;
		Node<T> fast =  head.next.next;
		/*
			如果快指针后面还有两个结点
			慢指针一次走一步
			快指针一次走两步
		*/
		while(fast.next != null && fast.next.next != null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		return slow;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 2、输入链表头结点,奇数长度返回中点,偶数长度返回下中点
//输入链表头结点,奇数长度返回中点,偶数长度返回下中点
	public static <T> Node<T> midOrDownMidNode(Node<T> head) {
		// 如果当前结点为空 或 结点的下一个结点为空
		if(head == null || head.next == null) {
			return head;
		}
		//此时链表至少2个结点
		Node<T> slow = head.next;
		Node<T> fast = head.next;
		/*
		如果快指针后面还有两个结点
		慢指针一次走一步
		快指针一次走两步
		 */
		while(fast.next != null && fast.next.next != null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		return slow;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 3、输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
//输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
	public static <T> Node<T> midOrUpMidPreNode(Node<T> head) {
		//如果当前结点为空 或者 结点的下一个结点为空 或者结点的下一个结点的下一个结点为空
		if(head == null || head.next == null || head.next.next == null) {
			return null;
		}
		//此时链表至少3个结点
		Node<T> slow = head;
		Node<T> fast = head.next.next;
		/*
		如果快指针后面还有两个结点
		慢指针一次走一步
		快指针一次走两步
		 */
		while(fast.next != null && fast.next.next != null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		return slow;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 4、输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
//输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
	public static <T> Node<T> midOrDownMidPreNode(Node<T> head){
		// 如果当前结点为空 或 结点的下一个结点为空
		if(head == null || head.next == null) {
			return null;
		}
		//如果结点的下一个结点的下一个结点为空
		if(head.next.next == null) {
			return head;
		}
		//此时链表至少3个结点
		Node slow = head;
		Node fast = head.next;
		/*
		如果快指针后面还有两个结点
		慢指针一次走一步
		快指针一次走两步
		 */
		while(fast.next != null && fast.next.next != null) {
			slow = slow.next;
			fast = fast.next.next;
		}
		return slow;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

面试题三:反转单链表

思路⇒建立两个结点分别用于存head 和 head.next结点顺序

在这里插入图片描述

//反转单向链表
	public  Node<T> reverseLinkList(Node<T> head){
		Node<T> pre = null;
		Node<T> next = null;
		while(head != null) {
			next = head.next;
			head.next = pre;
			pre = head;
			head = next;
		}
		return pre;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

面试题四:反转双向链表

public  Node<T> reverseDoubleLinked(Node<T> head){
		head = head.next;		//将head指向第一个结点
		Node<T> pre = null;
		Node<T> next = null;
		while(head != null) {
			next = head.next; 		//next存放当前结点的下一个
			
			head.next = pre;		//当前结点的下一个指向前面
			head.previous = next;	//当前结点的前一个指向后面
			pre = head;				//当前结点变成前结点
			
			head = next;			//结点后移:从头到尾 next充当临时变量 
		}
		return pre;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

面试题五:从单链表中删除指定元素

//从单链表中删除指定元素
	public Node<T> deleteTargetNode(Node<T> head,T target){
		//首先 先找出第一个不是目标的结点作为head
		while(head.getData() != null) {
			if(head.getData() == target) {
				head = head.next;
			}else {
				break;
			}
		}
		Node<T> pre = head;
		Node<T> cur = head;
		//此时head 肯定不是target
		while(cur != null) {
			if(cur.getData() == target) {
				pre.next = cur.next;//跳过
			}else {
				pre = cur;
			}
			cur = cur.next;	//最后再将cur后移
		}
		return head;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

面试题六:从双向链表中删除指定元素

//从双向链表中删除指定元素
	public Node<T> deleteTargetNode(Node<T> head,T target){
		//找到第一个不是target的元素作为head
		while(head!=null) {
			if(head.getData()!=target) {
				break;
			}
			head = head.next;
		}
		head.previous = null;
		Node<T> pre = head;
		Node<T> cur = head;
		while(cur!=null) {
			if(cur.getData().equals(target)) {
				pre.next = cur.next;
				//判断是否为最后一个元素
				if(cur.next!=null) {
					cur.next.previous = pre;
				}
			}else {
				pre = cur;
			}
			cur = cur.next;
		}
		return head;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

面试题七:判断一个链表是否为回文结构

例子: 1 → 2 → 1 返回true 1 → 2 → 2 → 1 返回true 1 → 2 → 3 返回false

如果链表长度为N,时间复杂度为O(N),额外空间复杂度O(1)

笔试用栈,面试用改链表方法(利用有限几个变量 空间O(1))

版本一:利用栈 空间O(N)

//利用栈 空间O(N)
	public static <T> boolean checkByStack(Node<T> head) {
		//判断链表是否为空 或 长度为1
		if(head == null || head.next == null) {
			return true;
		}
		Stack<Node<T>> stack = new Stack<>();
		Node<T> p = head;
		Node<T> q = head;
		while(p!=null) {
			stack.add(p);
			p = p.next;
		}
		while(!stack.isEmpty()) {
			if(!stack.pop().getData().equals(q.getData())) {
				return false;
			}
			q = q.next;
		}
		return true;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

版本二:利用栈 空间O(N/2)

//利用栈 空间O(N/2)
	public static <T> boolean checkByStackPlus(Node<T> head) {
		//判断链表是否为空 或 长度为1
		if(head == null || head.next == null) {
			return true;
		}
		//快慢指针优化 只存一半的内容进栈
		Node<T> fast = head;	// 一次走两步
		Node<T> slow = head;	// 一次走一步
		Node<T> q 	 = head;	
		while(fast.next!=null && fast.next.next != null) {
				slow = slow.next;
				fast = fast.next.next;
		}
		Stack<Node<T>> stack = new Stack<>();
		while(slow.next != null) {
			stack.add(slow.next);
			slow = slow.next;
		}
		while(!stack.isEmpty()) {
			if(!stack.pop().getData().equals(q.getData())) {
				return false;
			}
			q = q.next;
		}
		return true;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

版本三:利用有限几个变量 空间O(1)

/*利用有限几个变量 空间O(1)*/
	public static <T> boolean checkByVar(Node<T> head) {
		//判断链表是否为空 或 长度为1
		if(head == null || head.next == null) {
			return true;
		}
		//快慢指针优化 
		Node<T> fast = head;	// 一次走两步
		Node<T> slow = head;	// 一次走一步
		while(fast.next != null && fast.next.next != null) {//找中点
				slow = slow.next;		// mid
				fast = fast.next.next;	// end
		}
		fast = slow.next;			//当前slow是中点 将fast变成是右半部分的第一个结点
		slow.next = null;			//将慢指针(当前指向mid)指向null 前半段结束
		Node<T> rightPartFirstNode = null;	
		//开始将后半段逆序 连到中点上
		while(fast != null) {
			rightPartFirstNode = fast.next;		//保存 下一个结点
			fast.next = slow;					//修改fast的下一个结点
			slow = fast;						//slow移动
			fast = rightPartFirstNode;			//fast移动
		}
		//当前只有slow指针指在链表最后一个元素的位置
		rightPartFirstNode = slow;				//用结点保存 最后结点
		//然后slow跟fast指针才能往mid走
		fast = head;							//让fast成为头结点
		boolean res = true;
		while(slow != null && fast != null) {	//检查 判断条件
			if(slow.getData() != fast.getData()) {
				res = false;	//这里不着急return 是因为还得将链表变为原样
				break;
			}
			slow = slow.next;					//slow 移动至 mid
			fast = fast.next;					//fast 移动至 mid
		}
		//退出此while循环时,fast指向null,slow指向mid
		
		//下面开始复原链表
		slow = rightPartFirstNode.next;
		rightPartFirstNode.next = null;
		while(slow != null) {
			//当slow到达mid的时候 此时他的next就为null
			fast = slow.next;
			slow.next = rightPartFirstNode;
			rightPartFirstNode = slow;
			slow = fast;
		}
		
		return res;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

面试题八:把单向链表按某值划分成左边小、中间相等、右边大的形式

1、把链表放入数组里面,在数组上做partition(笔试用)时间复杂度O(N)

//1、把链表放入数组里面,在数组上做partition(笔试用)
	public static <T> Node<T> listPartition(Node<T> head, int target){
		if(head == null) {
			return head;
		}
		Node<T> cur = head;//cur设为头结点
		//求链表元素个数 为了生成数组
		int i = 0;
		while(cur != null) {
			i++;
			cur = cur.next;
		}
		Node<T>[] nodeArr = new Node[i];
		
		i = 0;
		cur = head;
		//遍历往数组中存值
		for(i = 0; i != nodeArr.length; i++) {
			nodeArr[i] = cur;
			cur = cur.next;
		}
		//做partition 
		arrPartition(nodeArr,target);
		//此时数组已经按预期排好了 下面开始连接结点
		for(i = 1; i != nodeArr.length; i++) {
			nodeArr[i -1].next = nodeArr[i];
		}
		//最后一个结点的next指针 指向null
		nodeArr[i -1].next = null;
		//返回头结点
		return nodeArr[0];
		
	}
	
	public static <T> void arrPartition(Node<T>[] nodeArr,int target) {
		int small = -1;
		int big   = nodeArr.length;
		int index = 0 ;
		while(index != big) {
			if((int)nodeArr[index].data < target) {
				swap(nodeArr,++small,index++);				//自己和自己交换
			}else if((int)nodeArr[index].data == target) {
				index++;									//index++ 直接后移
			}else {
				swap(nodeArr,--big,index);					//当前数和最后的数做交换
			}
		}
	}
	
	public static <T> void swap(Node<T>[] arr,int x,int y) {
		Node<T> node = arr[x];
		arr[x] = arr[y];
		arr[y] = node;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

2、分成小、中、大 三部分,再把各个部分之间串起来(面试用)

时间复杂度O(N)空间复杂度O(1)
public static <T> Node<T> linkPartition(Node<T> head, int target){
		Node<T> sH = null;		//小于部分的头指针
		Node<T> sT = null;		//小于部分的尾指针
		
		Node<T> eH = null;		//等于部分的头指针
		Node<T> eT = null;		//等于部分的尾指针
		
		Node<T> mH = null;		//大于部分的头指针
		Node<T> mT = null;		//大于部分的尾指针
		
		Node<T> next = null;	//额外变量
		
		while(head != null) {
			next = head.next;	
			if((int)head.data < target) {
				if(sH == null) {		//如果当前小于部分的头指针为空 
					sH = head;			//小于部分的头尾指针都指向此结点
					sT = head;
				}else {					//如果当前小于部分的头指针不为空 
					sT.next = head;		//小于部分的头尾指针的next指向此结点
					sT 		= head;		//尾指针后移
				}
			}else if((int)head.data == target) {
				if(eH == null) {		//如果当前等于部分的头指针为空 
					eH = head;			//等于部分的头尾指针都指向此结点
					eT = head;
				}else {					//如果当前等于部分的头指针不为空 
					eT.next = head;		//等于部分的头尾指针的next指向此结点
					eT 		= head;		//尾指针后移
				}
			}else {
				if(mH == null) {		//如果当前大于部分的头指针为空 
					mH = head;			//大于部分的头尾指针都指向此结点
					mT = head;
				}else {					//如果当前大于部分的头指针不为空 
					mT.next = head;		//大于部分的头尾指针的next指向此结点
					mT 		= head;		//尾指针后移
				}
			}
			head = next;
		}
		//开始连接各部分
		if(sT != null) {	//如果有小于区域
			sT.next = eH;
			eT = eT == null ? sT : eT;	// 下一步,谁去连大于区域的头,谁就变成eT
		}
		if(eT != null) {	//如果有等于区域
			eT.next = mH;
		}
		//最后返回所以链表串起来的最左结点
		return sH != null ? sH : (eH != null ? eH : mH);
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

面试题九:给定一个由Node结点类型组成的无环单链表的头结点head,请实现一个函数完成这个链表的复制

一种特殊的单链表结点类描述如下

public static class Node{
              int value;
              Node next;
              Node rand;
         public Node(int val){
                  value = val;
                 }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

rand指针是单链表结点结构中新增的指针,rand 可能指向链表中的任意一个结点,也可能指向null
给定一个由Node结点类型组成的无环单链表的头结点head

请实现一个函数完成这个链表的复制,并返回复制的新链表的头结点
要求:空间复杂度O(N)、额外空间复杂度O(1)

在这里插入图片描述

笔试:用HashMap解决

//1、使用HashMap存储
	public static Node copyListWithRandByHashMap(Node head) {
		Map<Node,Node> map = new HashMap<>();
		Node cur = head;
		//第一次循环 存入新Node
		while(cur != null) {
			map.put(cur, new Node(cur.value));
			cur = cur.next;
		}
		cur = head;
		//第二次循环 为新Node串指针
		while(cur != null) {
			// cur 是老结点 map.get(cur)为新结点
			map.get(cur).next = map.get(cur.next);	//新结点的next指针
			map.get(cur).rand = map.get(cur.rand);	//新结点的rand指针
			cur = cur.next;
		}
		//返回	head` 结点
		return map.get(head);
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

面试:直接在每个结点后面新增该结点 用位置来解决指针指向问题

//2、直接在每个结点后面新增该结点 用位置来解决指针指向问题
	public static Node copyListWithRand(Node head) {
		if(head == null) {
			return null;
		}
		Node cur  = head;
		Node next = null;
		//第一次循环 对链表中每个结点进行复制 1 -> 2  ==> 1 -> 1` -> 2 -> 2`
		while(cur != null) {
			next = cur.next;					//2
			cur.next = new Node(cur.value);	//1 -> 1`
			cur.next.next = next;				//1 -> 1` -> 2
			cur = next;							//cur = 2
		}
		cur = head;
		Node curCopy = null;
		//第二次循环 设置复制结点的rand 1 -> 1` -> 2 -> 2`
		while(cur != null) {
			next = cur.next.next;		// 2
			curCopy = cur.next;			// 1`
			curCopy.rand = cur.rand != null? cur.rand.next : null;
			cur = next;
		}
		//开始分离node`结点
		Node resHead = head.next;		//此时就是head`结点
		cur = head;
		while(cur != null) {			//1 -> 1` -> 2 -> 2`
			next = cur.next.next;		//2
			curCopy = cur.next;			//1`
			cur.next = next;			//1.next = 2
			curCopy.next = next!=null? next.next : null;
			cur = next;
		}
		return resHead;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

面试题十:两个单链表相交的一系列问题

给定两个可能有环也可能无环的单链表,头结点head1 和 head2。

请实现一个函数,如果两个链表相交,请返回相交的 第一个结点。如果不相交,返回null

要求:如果两个链表长度之和为N,时间复杂度语法到O(N),额外空间复杂度,请达到O(1)

分成三种情况

  • 1、情况一:两个链表都无环,如果相交,返回第一个相交结点,如果不相交,返回null
    在这里插入图片描述

  • 2、情况二:两个链表 如果一个有环 一个没有 这是不可能相交的 return null

  • 3、情况三:两个链表都有环

    • 3-1:如果不相交,返回null

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hsBtVGih-1633959972336)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/79fec707-aa4d-490c-86b6-2172e2adf9ff/Untitled.png)]

    • 3-2:如果相交,他们肯定是共用环的

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ccYdXhw2-1633959972337)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/12158dd2-a415-4616-8d5a-ba9341d0d646/Untitled.png)]

判断单链表是否有环?

找到链表的第一个入环结点,如果无环,返回null

public static Node getLoopNode(Node head) {
		
		if(head == null || head.next == null || head.next.next == null) {
			return null;
		}
		//一开始让慢指针先走一步 快指针先走两步
		Node slow = head.next;
		Node fast = head.next.next;
		//下次相遇时肯定已经走过入环结点了
		while(slow != fast) {
			//如果fast指针走到null 证明链表是一条单线
			if(fast.next == null || fast.next.next == null) {
				return null;
			}
			slow = slow.next;
			fast = fast.next.next;
		}
		//此时两个指针已经过了入环结点 让快指针回到head
		fast = head;	//让慢指针停留在原地 快指针回到head
		//让他们每次走一步 再相遇则是入环点
		while(slow != fast) {
			slow = slow.next;
			fast = fast.next;
		}
		return slow;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

目前就三种情况:

1、两个链表都无环

2、两个链表 一个有环 一个无环 这种情况 两个链表是不可能相交的

3、两个链表都有环

  • 1、两个链表 各自独立 无相交结点
  • 2、他俩的入环结点是一个,转变为无环链表的相交问题 base改变
  • 3、让loop1循环 如果过程中没遇到loo2,那么就是情况3-1(两个链表 各自独立),反之就是情况3-3

在这里插入图片描述

//主函数 如果两个链表相交,返回相交的 第一个结点
	public static Node getInterNode(Node head1,Node head2) {
		if(head1 == null || head2 == null) {
			return null;
		}
		
		Node loop1 = getLoopNode(head1);//找到链表head1 的第一个入环结点
		Node loop2 = getLoopNode(head2);//找到链表head2 的第一个入环结点
		//如果两个链表各自的入环结点都为空 证明 情况一
		if(loop1 == null && loop2 == null) {
			return noLoop(head1,head2);
		}
		//如果两个链表各自的入环结点都不为空 证明 情况三
		if(loop1 != null && loop2 != null) {
			return bothLoop(head1,loop1,head2,loop2);
		}
		//情况二:两个链表 如果一个有环 一个没有  这是不可能相交的 return null
		return null;
	}
	
	//找到链表的第一个入环结点,如果无环,返回null
	public static Node getLoopNode(Node head) {
		
		if(head == null || head.next == null || head.next.next == null) {
			return null;
		}
		//一开始让慢指针先走一步 快指针先走两步
		Node slow = head.next;
		Node fast = head.next.next;
		//下次相遇时肯定已经走过入环结点了
		while(slow != fast) {
			//如果fast指针走到null 证明链表是一条单线
			if(fast.next == null || fast.next.next == null) {
				return null;
			}
			slow = slow.next;
			fast = fast.next.next;
		}
		//此时两个指针已经过了入环结点 让快指针回到head
		fast = head;	//让慢指针停留在原地 快指针回到head
		//让他们每次走一步 再相遇则是入环点
		while(slow != fast) {
			slow = slow.next;
			fast = fast.next;
		}
		return slow;
	}

	//情况一:如果两个链表都无环,如果相交,返回第一个相交结点,如果不相交,返回null
	public static Node noLoop(Node head1,Node head2) {
		if(head1 == null || head2 == null) {
			return null;
		}
		Node cur1 = head1;
		Node cur2 = head2;
		int len = 0;		//记录两个链表的差值
		//注意 cur1是走到最后一个结点 而不是 null
		while( cur1.next != null) {
			len ++;
			cur1 = cur1.next;
		}
		//注意 cur2是走到最后一个结点 而不是 null
		while(cur2.next != null) {
			len --;
			cur2 = cur2.next;
		}
		//此时len 就是 链表1和链表2 之间的差值
		
		//如果此时 链表1的最后一个结点的内存地址 不等于 链表2的最后一个结点的话 证明 俩链表不相交
		if(cur1 != cur2) {
			return null;
		}
		cur1 = len > 0 ? head1 : head2;			//如果len大于0 证明head1长度大于head2 谁长谁cur1
		cur2 = (cur1 == head1) ? head2 : head1;	//谁短,谁cur2
		len = Math.abs(len);					//len取绝对值
		//长链表 先走len个长度
		while(len != 0) {
			len --;
			cur1 = cur1.next;
		}
		//长链表和短链表一起走 再次相遇时 第一个相交结点就找到了 
		while(cur1 != cur2) {
			cur1 = cur1.next;
			cur2 = cur2.next;
		}
		return cur1;
	}
	//情况二:两个链表 如果一个有环 一个没有  这是不可能相交的
	
	//情况三:如果两个链表都有环,如果相交,他们肯定是共用环的 返回第一个相交结点,如果不相交,返回null
	public static Node bothLoop(Node head1,Node loop1,Node head2,Node loop2) {
		Node cur1 = null;
		Node cur2 = null;
		if(loop1 == loop2) {	//如果两个入环结点相同 证明是情况3-2:如果相交,他们肯定是共用环的
			cur1 = head1;
			cur2 = head2;
			int len = 0;		//两个链表的长度差值
			//开始计算链表1的长度 注意此时条件是不等于 loop1 因为可以转换为无环链表相交问题
			while(cur1 != loop1) {
				len ++;
				cur1 = cur1.next;
			}
			while(cur2 != loop2) {
				len --;
				cur2 = cur2.next;
			}
			cur1 = len > 0 ? head1 : head2;	//谁长谁cur1
			cur2 = (cur1 == head1) ? head2 : head1;//谁短谁cur2
			
			len = Math.abs(len);
			//长的链表先走len步
			while(len != 0) {
				len --;
				cur1 = cur1.next;
			}
			//两个链表一起走
			while(cur1 != cur2) {
				cur1 = cur1.next;
				cur2 = cur2.next;
			}
			//相遇则相交
			return cur1;
		}else {	//证明是情况3-1 或 情况3-3
			cur1 = loop1.next;	
			//往下走 如果遇到loop2证明3-3 相交 如果没遇到证明两个链表有环但没相交3-1
			while(cur1 != loop1) {
				if(cur1 == loop2) {
					return loop1;//两个loop都算是相交的第一个结点
				}
				cur1 = cur1.next;
			}
			return null;		//情况3-1 俩链表有环没相交
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134

面试题十一:能不能 不给单链表的头结点,只给想要删除的结点,就能做到在链表上把这个点删掉?

不能,如果没有头结点,无法准确的连接好指针,有一些抖机灵的做法但是有弊端(对内存的理解)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/422094
推荐阅读
相关标签
  

闽ICP备14008679号