算法笔记——左神初级(6)判断链表回文、复制链表、链表相交或成环

链表

注意:链表的题在笔试和面试过程中,考虑的重点不同。 链表问题的最优解往往都在额外空间复杂度上下功夫,时间复杂度一般都为O(N)。

笔试中,以最快方式来做,使用辅助空间来做。
面试中,主要是以减少辅助空间,维持时间复杂度为O(N)来思考


题目10:打印两个有序链表的公共部分

【题目】 给定两个有序链表的头指针head1和head2,打印两个 链表的公共部分
思路:类似于merge归并排序,从小的位置开始,两个比较,如果相等,则打印。


题目11:判断一个链表是否为回文结构

在这里插入图片描述
笔试思路
思路1:
用栈,把链表依次输入到栈里去,然后链表头结点开始与栈出栈的每一个数比较,比较一半即可
额外空间复杂度O(N)
思路 2:
用快慢的两个指针,快指针一次走两步,慢指针一次走一步;读完整个链表。快的跳完之后,慢的在中间,然后从中间开始放入栈,再和原来的链表比较。 额外空间复杂度少一半,但是仍未O(N)。

面试思路:
用快慢的两个指针,快指针一次走两步,慢指针一次走一步; 快的跳完之后,慢的在中间,然后把后半部分逆序,然后从两头开始比较,即不用栈,这样比完之后还需要将链表恢复回来。注意逆序时,中间那个点需要指到空,这样当比较过程中,有一个链到空时,停止。

具体代码如下:

public class Code_11_IsPalindromeList {

	public static class Node {
		public int value;
		public Node next;

		public Node(int data) {
			this.value = data;
		}
	}

	// need n extra space 笔试思路1
	public static boolean isPalindrome1(Node head) {
		Stack<Node> stack = new Stack<Node>();
		Node cur = head;
		while (cur != null) {
			stack.push(cur);
			cur = cur.next;
		}
		while (head != null) {
			if (head.value != stack.pop().value) {
				return false;
			}
			head = head.next;
		}
		return true;
	}

	// need n/2 extra space  笔试思路2
	public static boolean isPalindrome2(Node head) {
		if (head == null || head.next == null) {
			return true;
		}
		Node right = head.next;
		Node cur = head;
		while (cur.next != null && cur.next.next != null) {
			right = right.next;
			cur = cur.next.next;
		}
		Stack<Node> stack = new Stack<Node>();
		while (right != null) {
			stack.push(right);
			right = right.next;
		}
		while (!stack.isEmpty()) {
			if (head.value != stack.pop().value) {
				return false;
			}
			head = head.next;
		}
		return true;
	}

	// need O(1) extra space  面试思路
	public static boolean isPalindrome3(Node head) {
		if (head == null || head.next == null) {
			return true;
		}
		Node n1 = head;
		Node n2 = head;
		while (n2.next != null && n2.next.next != null) { // find mid node
			n1 = n1.next; // n1 -> mid
			n2 = n2.next.next; // n2 -> end  n2是快指针、n1是慢指针
		}
		n2 = n1.next; // n2 -> right part first node
		n1.next = null; // mid.next -> null
		Node n3 = null;
		while (n2 != null) { // right part convert   这个while部分好好理解,是后半段逆序的核心!!!
			n3 = n2.next; // n3 -> save next node
			n2.next = n1; // next of right node convert
			n1 = n2; // n1 move
			n2 = n3; // n2 move
		}
		n3 = n1; // n3 -> save last node
		n2 = head;// n2 -> left first node
		boolean res = true;
		while (n1 != null && n2 != null) { // check palindrome  判断比较回文
			if (n1.value != n2.value) {
				res = false;
				break;
			}
			n1 = n1.next; // left to mid
			n2 = n2.next; // right to mid
		}
		n1 = n3.next;
		n3.next = null;
		while (n1 != null) { // recover list  恢复正常顺序
			n2 = n1.next;
			n1.next = n3;
			n3 = n1;
			n1 = n2;
		}
		return res;
	}

	public static void printLinkedList(Node node) {
		System.out.print("Linked List: ");
		while (node != null) {
			System.out.print(node.value + " ");
			node = node.next;
		}
		System.out.println();
	}

题目12:将单向链表按某值划分成左边小、中间相等、右边大的形式

【题目】给定一个单向链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot 的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点。除这个要求外,对调整后的节点顺序没有更多的要求。
例如:链表9->0->4->5->1,pivot=3。调整后链表可以是1->0->4->9->5,也可以是0->1->9->5->4。总之,满足左部分都是小于3的节点,中间部分都是等于3的节点(本例中这个部分为空),右部分都是大于3的节点即可。对某部分内部的节点顺序不做要求。

思路:荷兰国旗问题,另外新建一个数组,把每个节点的地址放到数组里,再根据用快排完成数组的排序,重新链接链表

进阶版:在原问题的要求之上再增加如下两个要求。在左、中、右三个部分的内部也做顺序要求,要求每部分里的节点从左到右的顺序与原链表中节点的先后次序一致。
思路:额外新建三个节点,分别存放的是小于pivot ,等于pivot ,大于pivot 。每个节点需要保存头head和尾tail,每次增加一个节点都需要tail++,最后链接所有的头和位。注意要判断头尾是否为空,因为可能三部分有的为空。

代码如下:

public static class Node{
	public int value;
	public Node next;
	public Node(int data){
		this.value = data;
	}
}

//这是用荷兰国旗思路,数组的形式来做
public static Node listPartition1(Node head,int pivot){
	if(head == null){
		return head;
	}
	Node cur = head;
	int i = 0;
	while(cur !=null){ //获取链表的长度
		i++;
		cur = cur.next;
	}

	Node[] nodeArr = new Node[i]; //新建一个数组,然后将链表的数都存入数组中
	i = 0;
	cur = head;  
	for(i = 0;i!= nodeArr.length;i++){
		nodeArr[i] = cur;  //注意!这里存的是链表的引用,保留了链表的节点
		cur = cur.next;
	}

	arrPartition(nodeArr,pivot);
	
	for(i = 1;i != nodeArr.length;i++){   //根据数组的顺序重新生成链表
		nodeArr[i-1].next =nodeArr[i];
	}
	nodeArr[i-1].next = null;
	return nodeArr[0];
}

public static void arrpartition(Node[] nodeArr,int pivot){  //这个函数的作用就是完成快速排序
	int small = -1;
	int big = nodeArr.length;
	int index = 0;
	while(index != big){
		if(nodeArr[index].value<pivot){
			swap(nodeArr,++small,index++);
		}else if(nodeArr[index].value>pivot){
			swap(nodeArr,--big,index);
		}else{
			index++;
		}
	}
}
public static void swap(Node[] nodeArr,int a,int b){
	Node tmp = nodeArr[a];
	nodeArr[a] = nodeArr[b];
	nodeArr[b] = tmp;
}

/////////////////////////////////////////下面是额外空间复杂度低的做法
public static Node listPartition2(Node head ,int pivot){
	Node sH = null;//small H代表head
	Node sT = null;//small  tail
	Node eH = null;//equal
	Node eT = null;
	Node bH = null; //big 大于num
	Node bT = null;
	Node next = null;//下一个节点
	//每一个节点都需要进行判断,然后分别挂到三个链表上。
	while(head !=null ){
		next = head.next;  //不然下一步会丢掉head的next
		head.next = null;  //为把head放到3个链上做准备
		if(head.value<pivot){
			if(sH == null){
				sH = head;
				sT = head;
			}else{
				sT.next = head;
				sT = head;
			}
		}else if(head.value == pivot){
			if(eH == null){
				eH = head;
				eT = head;
			}else{
				eT.next = head;
				eT = head;
			}
		}else{
			if(bH == null){
				bH = head;
				bT = head;
			}else{
				bT.next = head;
				bT = head;
			}
		}
		head = next;
	}
	//将小于和等于部分相连接,判断一下等于区域是否为空
	if(sT!= null) {
		sT.next = eH;
		eT = eT == null?sT:eT;
	}
	//与大于部分相连
	if(eT != null){
		eT.next = bH;
	}
	//这里是判断小于和等于区域是否为空。
	//如果都为空,返回bh,如果小于区域为空,返回eh,如果都不为空,返回sh
	return sH != null ? sH :eH != null ? eH :bH ;
}

题目13:复制含有随机指针节点的链表

【题目】 一种特殊的链表节点类描述如下:
public class Node { public int value; public Node next; public Node rand;
public Node(int data) { this.value = data; }
}
Node类中的value是节点值,next指针和正常单链表中next指针的意义一 样,都指向下一个节点,rand指针是Node类中新增的指针,这个指 针可 能指向链表中的任意一个节点,也可能指向null。 给定一个由 Node节点类型组成的无环单链表的头节点head,请实现一个 函数完成 这个链表中所有结构的复制,并返回复制的新链表的头节点。

思路:

  1. 建立一个哈希表
  2. 遍历链表,将链表作为哈希表K-V中的K,复制的节点作为V; (注意:此时只复制节点的value,而没有链接新复制的节点)
  3. 再遍历链表,根据每个节点的next和rand,对应的节点作为K去找相应的V,完成新链表的链接。

代码如下:

public static Node copyListWithRand1(Node head) {
		HashMap<Node, Node> map = new HashMap<Node, Node>();
		Node cur = head;
		while (cur != null) {
			map.put(cur, new Node(cur.value));
			cur = cur.next;
		}
		cur = head;
		while (cur != null) {
			map.get(cur).next = map.get(cur.next);
			map.get(cur).rand = map.get(cur.rand);
			cur = cur.next;
		}
		return map.get(head);
	}

进阶: 不使用额外的数据结构,只用有限几个变量,且在时间复杂度为 O(N) 内完成原问题要实现的函数
左神思路如下,主要是要解决新的链表链接时找不到对应复制节点的问题
所以这个思路是将新复制的节点都放在每个老节点的后面,省去了哈希表的空间
在这里插入图片描述
代码如下:

public static Node copyListWithRand2(Node head) {
		if (head == null) {
			return null;
		}
		Node cur = head;
		Node next = null;
		// 把每个节点都复制,然后跟在老节点后面
		while (cur != null) {
			next = cur.next;
			cur.next = new Node(cur.value);
			cur.next.next = next;
			cur = next;
		}
		cur = head;
		Node curCopy = null;  //curCopy链就是新的链表
		// set copy node rand   先连接rand
		while (cur != null) {
			next = cur.next.next;
			curCopy = cur.next;
			//就是判断rand是否为空,不为空就要找到rand指向的节点的下个节点(也就是复制的新节点)
			curCopy.rand = cur.rand != null ? cur.rand.next : null; 
			cur = next;
		}
		Node res = head.next;
		cur = head;
		// split  按顺序重新链接
		while (cur != null) {
			next = cur.next.next;
			curCopy = cur.next;
			cur.next = next;
			curCopy.next = next != null ? next.next : null;
			cur = next;
		}
		return res;
	}

题目14:两个单链表相交的一系列问题

【题目】 在本题中,单链表可能有环,也可能无环。给定两个 单链表的头节点 head1和head2,这两个链表可能相交,也可能 不相交。请实现一个函数, 如果两个链表相交,请返回相交的 第一个节点;如果不相交,返回null 即可。
要求:如果链表1 的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外 空间复杂度请达到O(1)。

这道题算是链表中的难题。
思路: 小问题拆解
1、判断单链表有环或无环,有环则返回入环的节点,无环则返回null
两个思路
1.1 用hashset(hashset只有K,没有value) 依次存入hashset,返回成环节点
1.2 用两个指针,快指针一次两步,慢指针一次一步;如果快指针遇到空,直接无环;只要有环,一定相遇;此时,快指针回到开头,也一次走一步,两个指针必然在成环处相遇(结论,可以数学证明)

2、查找两个不成环的单链表相交的节点
两个思路
2.1 新建一个hashmap,把链表1放入map里,然后遍历链表2,看能否在map里查到,查到相交,否则不相交
2.2 不用哈希表,遍历两个链表,得到每个链表的长度len1、len2和最后的节点end1和end2;首先判断end1或者end2是否相等;相等的话代表必然相交,则将len大的链表从head处前进至小的两个len之差的位置,然后再依次往后比较,找相交节点。 若两个end不相等,则必然不相交。 注意:由于是单链表,所以不可能相交叉开

3、一个链表有环,一个链表——不可能相交 (单链表)

4、两个成环的单链表是否相交(只有三种情况)
在这里插入图片描述
第一种是不相交;第二种是先相交,再成环;第三种是各自进环。

思路:比较入环点loop1和loop2,如果相等就是第二种情况;不相等可能是1、3种情况,这时让loop1继续往下走,如果转回了loop1,则是第一种情况不相交,如果转到loop2,则相交,返回loop1或loop2都行。

对第二种,其实可以回溯到上方的两个不成环链表相交,只不过之前的end节点需要换成各自入环的loop节点即可。

代码如下:
主函数:

public static Node getIntersectNode(Node head1, Node head2) {
		if (head1 == null || head2 == null) {
			return null;
		}
		Node loop1 = getLoopNode(head1); //先判断是否成环,返回成环节点  小问题1
		Node loop2 = getLoopNode(head2);
		if (loop1 == null && loop2 == null) {
			return noLoop(head1, head2);  //无环比较  小问题2
		}
		if (loop1 != null && loop2 != null) {
			return bothLoop(head1, loop1, head2, loop2);  //有环比较 小问题4
		}
		return null; // 一个有环、一个无环  不相交
	}

判断成环函数: getLoopNode 没有用哈希表

public static Node getLoopNode(Node head) {
		if (head == null || head.next == null || head.next.next == null) {
			return null;
		}
		Node n1 = head.next; // n1 -> slow   快慢两个指针
		Node n2 = head.next.next; // n2 -> fast
		while (n1 != n2) {
			if (n2.next == null || n2.next.next == null) { //注意,每个快指针都需要对两格判空
				return null; 
			}
			n2 = n2.next.next;
			n1 = n1.next;
		}
		n2 = head; // n2 -> walk again from head  也就是说下一个循环前,快指针到了首节点、慢指针在相交点
		while (n1 != n2) {
			n1 = n1.next;
			n2 = n2.next;
		}
		return n1;
	}

无环链表判断相交函数:noLoop

public static Node noLoop(Node head1, Node head2) {
		if (head1 == null || head2 == null) {
			return null;
		}
		Node cur1 = head1;
		Node cur2 = head2;
		int n = 0;
		while (cur1.next != null) {
			n++;   //对一个变量n,表示了len1-len2   节省一个变量
			cur1 = cur1.next;
		}
		while (cur2.next != null) {
			n--;
			cur2 = cur2.next;
		}
		if (cur1 != cur2) {
			return null;
		}
		cur1 = n > 0 ? head1 : head2;  //把cur1设为长的,cur2设为短的
		cur2 = cur1 == head1 ? head2 : head1;
		n = Math.abs(n);  //n取绝对值
		while (n != 0) {  //长的先走n步
			n--;
			cur1 = cur1.next;
		}
		while (cur1 != cur2) {  //两个一起走,直到相等。
			cur1 = cur1.next;
			cur2 = cur2.next;
		}
		return cur1;
	}

判断两个成环链表是否相交的函数:bothLoop

public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
		Node cur1 = null;
		Node cur2 = null;
		if (loop1 == loop2) {  //这里就两个成环的第二种情况,和无环高度一致
			cur1 = head1;
			cur2 = head2;
			int n = 0;
			while (cur1 != loop1) {
				n++;
				cur1 = cur1.next;
			}
			while (cur2 != loop2) {
				n--;
				cur2 = cur2.next;
			}
			cur1 = n > 0 ? head1 : head2;
			cur2 = cur1 == head1 ? head2 : head1;
			n = Math.abs(n);
			while (n != 0) {
				n--;
				cur1 = cur1.next;
			}
			while (cur1 != cur2) {
				cur1 = cur1.next;
				cur2 = cur2.next;
			}
			return cur1;
		} else {  //这里代表进入成环1、3种情况  需要判断
			cur1 = loop1.next;
			while (cur1 != loop1) {
				if (cur1 == loop2) {
					return loop1;
				}
				cur1 = cur1.next;
			}
			return null;
		}
	}
发布了27 篇原创文章 · 获赞 4 · 访问量 821

猜你喜欢

转载自blog.csdn.net/qq_25414107/article/details/104545604