队列+矩阵+链表问题

队列+矩阵+链表问题

  1. 猫狗队列
  2. 转圈打印矩阵(旋转矩阵)
  3. . “之”字形打印矩阵
  4. 在行列都排好序的矩阵中找数
  5. 打印有两个有序链表的公共部分
  6. 判断一个链表是否为回文机构
  7. 将单向链表按某值划分左边小,中间相等,右边大的形式
  8. 复制含有随机指针节点的链表
  9. 两个单链表相交的一系列问题
    (from左神算法初级班第四节)

1.猫狗队列

题目:实现一种狗猫队列的结构,要求如下: 用户可以调用add方法将cat类或dog类的 实例放入队列中; 用户可以调用pollAll方法,将队列中所有的实例按照进队列 的先后顺序依次弹出; 用户可以调用pollDog方法,将队列中dog类的实例按照 进队列的先后顺序依次弹出; 用户可以调用pollCat方法,将队列中cat类的实 例按照进队列的先后顺序依次弹出; 用户可以调用isEmpty方法,检查队列中是 否还有dog或cat的实例; 用户可以调用isDogEmpty方法,检查队列中是否有dog 类的实例; 用户可以调用isCatEmpty方法,检查队列中是否有cat类的实例。

解题思路:每一次进入的时候就打上一个时间戳
代码:

public static class DogCatQueue {
		private Queue<PetEnterQueue> dogQ;
		private Queue<PetEnterQueue> catQ;
		private long count;//时间戳

		public DogCatQueue() {
			this.dogQ = new LinkedList<PetEnterQueue>();
			this.catQ = new LinkedList<PetEnterQueue>();
			this.count = 0;
		}

		public void add(Pet pet) {
			if (pet.getPetType().equals("dog")) {
				this.dogQ.add(new PetEnterQueue(pet, this.count++));
			} else if (pet.getPetType().equals("cat")) {
				this.catQ.add(new PetEnterQueue(pet, this.count++));
			} else {
				throw new RuntimeException("err, not dog or cat");
			}
		}
		public Pet pollAll() {
			if (!this.dogQ.isEmpty() && !this.catQ.isEmpty()) {
				if (this.dogQ.peek().getCount() < this.catQ.peek().getCount()) {
					return this.dogQ.poll().getPet();
				} else {
					return this.catQ.poll().getPet();
				}
			} else if (!this.dogQ.isEmpty()) {
				return this.dogQ.poll().getPet();
			} else if (!this.catQ.isEmpty()) {
				return this.catQ.poll().getPet();
			} else {
				throw new RuntimeException("err, queue is empty!");
			}
		}
		public Dog pollDog() {
			if (!this.isDogQueueEmpty()) {
				return (Dog) this.dogQ.poll().getPet();
			} else {
				throw new RuntimeException("Dog queue is empty!");
			}
		}
		public Cat pollCat() {
			if (!this.isCatQueueEmpty()) {
				return (Cat) this.catQ.poll().getPet();
			} else
				throw new RuntimeException("Cat queue is empty!");
		}
		public boolean isEmpty() {
			return this.dogQ.isEmpty() && this.catQ.isEmpty();
		}
		public boolean isDogQueueEmpty() {
			return this.dogQ.isEmpty();
		}
		public boolean isCatQueueEmpty() {
			return this.catQ.isEmpty();
		}
	}

2.转圈打印矩阵、旋转矩阵

1)【题目】 给定一个整型矩阵matrix,请按照转圈的方式打印它。 例如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 打印结果为:1,2,3,4,8,12,16,15,14,13,9, 5,6,7,11, 10

需要宏观思维:分圈解决

  • 两个控制节点

控制节点1(0,0)→0行0列
控制节点2(matrix.length - 1,matrix[0].length - 1)→最后一行最后一列的节点位置

  • 打印外圈
    如果是棒状结构(a=c或者b=d),就直接打印一行或者一列。
    否则:
    先从控制节点1开始,打印一行(除了最后一点以外,并且来到行的最后一个点)
    然后从这一行的最后一个点开始,打印一列(除了最后一点以外,来到行的最后一个点)
    接着从这一列的最后一个点开始,从尾到头打印这一行(除了第一个点以外,来到这一列的第一点)
    最后从这一行的第一点开始,从下往上打印这一列(除了一点点意外,回到控制节点1位置)
    过程如图所示
    在这里插入图片描述
    代码:
public class Code_06_PrintMatrixSpiralOrder {

	public static void spiralOrderPrint(int[][] matrix) {
		int a = 0;//控制节点(0,0)
		int b = 0;
		int c = matrix.length - 1;//控制节点最后一个点(matrix.length - 1,matrix[0].length - 1)
		int d = matrix[0].length - 1;
		while (a <= c && b <= d) {
			printEdge(matrix, a++, b++, c--, d--);//每次打印一个外圈,打印后,两个控制节点都往里走,直到错过才停止
		}
	}

	public static void printEdge(int[][] m, int a, int b, int c, int d) {
		if (a == c) {//棒状结构
			for (int i = b; i <= d; i++) {
				System.out.print(m[a][i] + " ");
			}
		} else if (b == d) {//棒状结构
			for (int i = a; i <= c; i++) {
				System.out.print(m[i][b] + " ");
			}
		} else {
			int curC = b;
			int curR = a;
			while (curC != d) {//打印到同一行的最后一个节点停止
				System.out.print(m[a][curC] + " ");
				curC++;
			}
			while (curR != c) {//打印到同一列的最后一个节点停止
				System.out.print(m[curR][d] + " ");
				curR++;
			}
			while (curC != b) {//打印到同一行的第一个节点停止
				System.out.print(m[c][curC] + " ");
				curC--;
			}
			while (curR != a) {//打印到同一列的第一个节点停
				System.out.print(m[curR][b] + " ");
				curR--;
			}
		}
	}

	public static void main(String[] args) {
		int[][] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 },
				{ 13, 14, 15, 16 } };
		spiralOrderPrint(matrix);

	}

}

2)旋转矩阵(长方形没法转)
同样使用分圈思想
每一次交换四个位置,重复matrix.length - 1次。
在这里插入图片描述

public class Code_05_RotateMatrix {

	public static void rotate(int[][] matrix) {
		int tR = 0;
		int tC = 0;
		int dR = matrix.length - 1;
		int dC = matrix[0].length - 1;
		while (tR < dR) {
			rotateEdge(matrix, tR++, tC++, dR--, dC--);
		}
	}

	public static void rotateEdge(int[][] m, int a, int b, int c, int d) {
		int times = d - b; 
		int tmp = 0;
		for (int i = 0; i != times; i++) {//扣边界,实现换位置
			tmp = m[a][b + i];//将第一个点保存到tmp变量上
			m[a][b + i] = m[c - i][b];//将第四个点位置换到第一个点位置上
			m[c - i][b] = m[c][d - i];//将第三个位置换到最后一个点位置上
			m[c][d - i] = m[a + i][d];//将第二个点位置换到第三个点位置上     
			m[a + i][d] = tmp;//将tmp变量的位置换到第二个点位置上
		}
	}

	public static void printMatrix(int[][] matrix) {
		for (int i = 0; i != matrix.length; i++) {
			for (int j = 0; j != matrix[0].length; j++) {
				System.out.print(matrix[i][j] + " ");
			}
			System.out.println();
		}
	}

	public static void main(String[] args) {
		int[][] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 },
				{ 13, 14, 15, 16 } };
		printMatrix(matrix);
		rotate(matrix);
		System.out.println("=========");
		printMatrix(matrix);

	}

}

3.反转单向链表、双向链表

4.“之”字形打印矩阵

【题目】 给定一个矩阵matrix,按照“之”字形的方式打印这 个矩阵,例如: 1 2 3 4 5 6 7 8 9 10 11 12 “之”字形打印的结果为:1,2,5,9,6,3,4,7,10,11, 8,12。
解题过程:

  • 用两个变量A,B控制每次打印的头尾点
  • A先往右走,到达最后一列,就开始往下走
  • B先往下走,到达最后一行,就开始往右走
  • 知道A到达最后一个点(18点位置),或者B达到最后一个点(18位置)结束。
    在这里插入图片描述
    代码:
public class Code_08_ZigZagPrintMatrix {

	public static void printMatrixZigZag(int[][] matrix) {
		int a = 0;
		int b = 0;
		int c = 0;
		int d = 0;
		int endR = matrix.length - 1;
		int endC = matrix[0].length - 1;
		boolean fromUp = false;//从左上开始打印,还是从右下开始打印
		while (a != endR + 1) {//a来到最后一行,终止
			printLevel(matrix, a, b, c, d, fromUp);//打印
			a = b == endC ? a + 1 : a;//A列数来到最后一列,才往下走
			b = b == endC ? b : b + 1;
			d = c == endR ? d + 1 : d;//B行数来到最后一行,才往右走
			c = c == endR ? c : c + 1;
			fromUp = !fromUp;//每次取反
		}
		System.out.println();
	}

	public static void printLevel(int[][] m, int tR, int tC, int dR, int dC,
			boolean f) {//打印
		if (f) {
			while (tR != dR + 1) {
				System.out.print(m[tR++][tC--] + " ");
			}
		} else {
			while (dR != tR - 1) {
				System.out.print(m[dR--][dC++] + " ");
			}
		}
	}

	public static void main(String[] args) {
		int[][] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };
		printMatrixZigZag(matrix);

	}

}

5.在行列都排好序的矩阵中找数

【题目】 给定一个有N*M的整型矩阵matrix和一个整数K, matrix的每一行和每一 列都是排好序的。实现一个函数,判断K 是否在matrix中。 例如: 0 1 2 5 2 3 4 7 4 4 4 8 5 7 7 9 如果K为7,返回true;如果K为6,返 回false。
【要求】 时间复杂度为O(N+M),额外空间复杂度为O(1)。

(右上角法、左下角法)

  • 一个数比数组当前值小的时候往左走,比当前值大的时候就往下走。
  • 找到就有,一直到数组边界也没有找到则没有。

过程如图所示
在这里插入图片描述
代码:

public class Code_09_FindNumInSortedMatrix {

	public static boolean isContains(int[][] matrix, int K) {
		int row = 0;
		int col = matrix[0].length - 1;
		while (row < matrix.length && col > -1) {
			if (matrix[row][col] == K) {
				return true;
			} else if (matrix[row][col] > K) {
				col--;
			} else {
				row++;
			}
		}
		return false;
	}

	public static void main(String[] args) {
		int[][] matrix = new int[][] { { 0, 1, 2, 3, 4, 5, 6 },// 0
				{ 10, 12, 13, 15, 16, 17, 18 },// 1
				{ 23, 24, 25, 26, 27, 28, 29 },// 2
				{ 44, 45, 46, 47, 48, 49, 50 },// 3
				{ 65, 66, 67, 68, 69, 70, 71 },// 4
				{ 96, 97, 98, 99, 100, 111, 122 },// 5
				{ 166, 176, 186, 187, 190, 195, 200 },// 6
				{ 233, 243, 321, 341, 356, 370, 380 } // 7
		};
		int K = 233;
		System.out.println(isContains(matrix, K));
	}

}

6.判断一个链表是否为回文结构

【题目】 给定一个链表的头节点head,请判断该链表是否为回 文结构。 例如: 1->2->1,返回true。 1->2->2->1,返回true。 15->6->15,返回true。 1->2->3,返回false。 进阶: 如果链表长度为N,时间复杂度达到O(N),额外空间复杂 度达到O(1)。

扫描二维码关注公众号,回复: 8683146 查看本文章

笔试:用辅助空间快速过掉

  • 用一个栈保存链表,再进行比较(方法一)
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;
	}
  • 用快慢指针来到中间节点,再把后面的部分装入栈中,比较栈和链表的前面部分(方法二)
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;
	}

面试(用几个变量):
方法一:不用辅助空间

  • 先快慢指针找到中间节点
  • 将后面部分逆序
  • 分别从头和尾开始走,判断是否相等
  • 无论是否为回文,最后将数据恢复回来(逆序部分再逆序)
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.next; // n2 -> right part first node
		n1.next = null; // mid.next -> null
		Node n3 = null;
		while (n2 != null) { // right part convert
			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;
	}

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

【题目】 给定一个单向链表的头节点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的节点即可。对某部分内部的节点顺序不做要求。 (荷兰国旗)

进阶问题: 在原问题的要求之上再增加如下两个要求。 在左、中、右三个部分的内部也做顺序要求,要求每部分里的节点从左 到右的 顺序与原链表中节点的先后次序一致。 例如:链表9->0->4->5->1,pivot=3。 调整后的链表是0->1->9->4->5。 在满足原问题要求的同时,左部分节点从左到 右为0、1。在原链表中也 是先出现0,后出现1;中间部分在本例中为空,不再 讨论;右部分节点 从左到右为9、4、5。在原链表中也是先出现9,然后出现4, 最后出现5。 如果链表长度为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1)。

解题过程

  • 准备6个节点变量
  • 按照顺序将小于、等于、大于的节点放在less、eq、more的链接点后,直到遍历链表结束。
  • 从less开始按照顺序将节点串起来,形成要求的链表

如图所示
在这里插入图片描述
代码:

public static Node listPartition2(Node head, int pivot) {
		Node sH = null; // small head
		Node sT = null; // small tail
		Node eH = null; // equal head
		Node eT = null; // equal tail
		Node bH = null; // big head
		Node bT = null; // big tail
		Node next = null; // save next node
		// every node distributed to three lists
		while (head != null) {
			next = head.next;
			head.next = null;
			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;
		}
		// small and equal reconnect
		if (sT != null) {
			sT.next = eH;
			eT = eT == null ? sT : eT;
		}
		// all reconnect
		if (eT != null) {
			eT.next = bH;
		}
		return sH != null ? sH : eH != null ? eH : bH;
	}

8.复制含有随机指针节点的链表

【题目】 一种特殊的链表节点类描述如下: 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,请实现一个 函数完成 这个链表中所有结构的复制,并返回复制的新链表的头节点。 进阶: 不使用额外的数据结构,只用有限几个变量,且在时间复杂度为 O(N) 内完成原问题要实现的函数。

方法一:使用hash表

  • 将原链表p1作为key,放入hashmap中
  • 拷贝p1的每个节点,并作为hashmap的value值放入。
  • key为原链表节点,value为拷贝节点。
  • 可以通过hashmap查找来找到对应关系。例如p1的第一节点的next指向第二个节点key值,那么hashmap中的第一个节点的value就指向第二个节点的value

代码:

public static Node copyListWithRand1(Node head) {
		HashMap<Node, Node> map = new HashMap<Node, Node>();
		Node cur = head;
		while (cur != null) {//放入到key中
			map.put(cur, new Node(cur.value));
			cur = cur.next;
		}
		cur = head;
		while (cur != null) {//将对应关系映射到value上
			map.get(cur).next = map.get(cur.next);
			map.get(cur).rand = map.get(cur.rand);
			cur = cur.next;
		}
		return map.get(head);
	}

方法二:

  • 先遍历链表,让原链表节点的第一个位置指向拷贝的节点,拷贝节点指向原链表的第二个位置,以此类推
  • 而rand可以通过next关系找到(例如1的rand指向3,而现在3指向3’,所以1’的rand就等于3的next)
    如图所示:
    在这里插入图片描述

代码:

public static Node copyListWithRand2(Node head) {
		if (head == null) {
			return null;
		}
		Node cur = head;
		Node next = null;
		// copy node and link to every node
		while (cur != null) {//拷贝节点
			next = cur.next;
			cur.next = new Node(cur.value);
			cur.next.next = next;
			cur = next;
		}
		cur = head;
		Node curCopy = null;
		// set copy node rand
		while (cur != null) {//复制rand关系
			next = cur.next.next;
			curCopy = cur.next;
			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;
	}

9.两个单链表相交的一系列问题

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

问题分为:

  • 1)判断链表有环无环?
  • 2)两个无环链表的第一个相交节点?
  • 3)两个有环链表是否相交?

1)判断链表有环无环?
方法一:用hash表

  • 遍历时,将节点放入key中,然后查hash表是否出现过?
  • 查到第一个放进去过就是第一个相交节点

方法二:快慢指针(快指针一次走两步,慢指针一次走一步)

  • 准备两个指针
  • 快指针达到null证明无环
  • 快慢指针第一次相遇后,快指针回到起点,由一次走两步,变成一次走一步,快慢指针再次相遇时,一定是环的入口。

2)两个无环‘’链表的第一个相交节点?
方法一:hashmap

  • 遍历时,将节点放入key中,然后查hash表是否出现过?
  • 查到第一个放进去过就是第一个相交节点

方法二:先后出发

  • 先各自遍历一遍两个链表,得到长度len1,len2,end1,end2(end为最后一个节点 )
  • 首先判断end1和end2是否相等,如果不相等,则没有相交,否则相交
  • 如果len1等于len2,比较len1和len2长度,大的减小的,然后大的先出发(长度差) 步,再一起走,当第一个相等时,就是相交的第一个节点
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++;
			cur1 = cur1.next;
		}
		while (cur2.next != null) {
			n--;
			cur2 = cur2.next;
		}
		//计算差值
		if (cur1 != cur2) {
			return null;
		}
		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;
	}

3)两个有环链表是否相交?
两个链表的入环节点loop1、loop2,如果loop1=loop2,则是拓扑结构2,loop1走一遍环,看看遇不遇得到loop2,遇不到,则是拓扑结构1,如果遇到,则用hashmap检查loop1的节点是否出现过loop2中

三种拓扑结构:

  • 两个有环链表不相交
    在这里插入图片描述

  • 两个有环链表公用一个环,环只有一个入口
    在这里插入图片描述

  • 两个有环链表公用一个环,环有两个入口
    在这里插入图片描述
    代码:

public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
		Node cur1 = null;
		Node cur2 = null;
		if (loop1 == loop2) {//拓扑2
			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 {
			cur1 = loop1.next;
			while (cur1 != loop1) {
				if (cur1 == loop2) {
					return loop1;
				}
				cur1 = cur1.next;
			}
			return null;
		}
	}
发布了7 篇原创文章 · 获赞 0 · 访问量 100

猜你喜欢

转载自blog.csdn.net/weixin_41585309/article/details/103998885
今日推荐