牛客网 4 — 宏观调度问题
宏观调度问题
这种问题不要把自己局限在一个过细节的问题上,而是从宏观上来看。
最优解!!一般从数据状况或者问法出发!!
旋转打印矩阵
1、转圈打印矩阵
public class printMatrixSpiralOrder {
public static void spiralOrderPrint(int[][] matrix) {
//前面四步骤是获得起点终点的位置
int tR = 0;
int tC = 0;
int dR = matrix.length - 1;
int dC = matrix[0].length - 1;
//循环操作
while (tR <= dR && tC <= dC) {
//移动位置
print(matrix, tR++, tC++, dR--, dC--);
}
}
public static void print(int[][] m, int tR, int tC, int dR, int dC) {
//首先判断是否位于同一行
if(tR == dR) {
//循环打印改行,注意这里循环的是列
for(int i = tC; i < dC; i++) {
System.out.println(m[tR][i] +" ");
}
}else if(tC == dC) { //同理,在同一列也是一样
//循环打印行
for(int i = tR; i < dR; i++) {
System.out.println(m[i][tC] +" ");
}
}else { //剩下一种情况就是不在同一行且不在同一列
int curC = tC;
int curR = tR;
while(curC != dC) {
//说明不在同一列,就要打印整一行 tR的那一行,从dR开始
System.out.println(m[tR][curC++] +" ");
}
while(curR != dR) {
//说明不在同一行,就要打印整一列 dC的那一列
System.out.println(m[curR++][dC] +" ");
}
while(curC != tC) {
System.out.println(m[dR][curC--] +" ");
}
while(curR != dR) {
System.out.println(m[curR--][tC] +" ");
}
}
}
}
每次循环打印都打印最外一层!然后由 spiralOrderPrint 控制函数来控制起点和终点的位置。
2、旋转正方形
依次旋转,先转最外面一圈的,然后逐步往里面旋转。每次change只改变四个点的位置,然后循环 i 次。
public class xuanzhuanzhengfangxing {
public static void rotate(int[][] matrix) {
//还是一样,先要获得起点与终点
int tR = 0, tC = 0; //起点
int dR = matrix.length - 1 , dC = matrix[0].length - 1; //终点
while(tR < dR) {
//旋转的方法,这里不存在=的情况,所以有所区别。
change(matrix, tR++, tC++, dR--, dC--);
}
}
public static void change(int[][] m, int tR, int tC, int dR, int dC) {
//每一次需要改变的一对位置的数量
int times = dC - tC;
int tmp = 0;
for (int i = 0; i != times; i++) {
tmp = m[tR][tC + i];
m[tR][tC + i] = m[dR - i][tC];
m[dR - i][tC] = m[dR][dC - i];
m[dR][dC - i] = m[tR + i][dC];
m[tR + i][dC] = 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);
}
}
反转链表
1、反转链表
2、反转双向链表
之字打印矩阵
public class Code_08_ZigZagPrintMatrix {
public static void printMatrixZigZag(int[][] matrix) {
//先初始化 A,B两个点。
int tR = 0;
int tC = 0;
int dR = 0;
int dC = 0;
//然后终止条件当然是到最后面
int endR = matrix.length - 1;
int endC = matrix[0].length - 1;
//这个布尔值用来判断是从上向下打印还是从下向上打印
boolean fromUp = false;
//进入循环
while (tR != endR + 1) {
//打印
printLevel(matrix, tR, tC, dR, dC, fromUp);
//然后是进行移动,这里是关键!!!!
//A:只往右边移动,如果到了最右边,就向下移动
//B:只向下移动,如果到了最下面,就往右边移动
tR = tC == endC ? tR + 1 : tR;
tC = tC == endC ? tC : tC + 1;
dC = dR == endR ? dC + 1 : dC;
dR = dR == endR ? dR : dR + 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);
}
}
行列有序的矩阵中找数
这里要注意好是从小到大还是从大到小的排列。
如果行和列都是从小到大排列的话,就从右上脚开始,并且如果当前数字大于number就要换到前一列。
如果是从大到小排列的话,就要从左上角开始,并且如果当前数字小于number就要换到下一列,因为这个数字下面的元素肯定大于它,就没必要遍历了。
public class FindNumInSortedMatrix {
public static boolean find(int[][] matrix, int number) {
int row = 0;
int col = matrix[0].length - 1;
while(row < matrix.length && col > -1) {
if(matrix[row][col] == number) {
return true;
}else if(matrix[row][col] > number) {
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(find(matrix, K));
}
}
有序链表的公共部分
public class lianbiaogonggong {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
//打印公共部分
public static void printcommon(Node head1, Node head2) {
while(head1 != null && head2 != null) {
if(head1.value > head2.value) {
head1 = head1.next;
}else if(head1.value < head2.value) {
head2 = head2.next;
}else {
System.out.print(head1.value + " ");
head1 = head1.next;
head2 = head2.next;
}
}
}
}
链表回文结构
1、可以先使用一个栈来存储,第一次遍历时,边遍历边入栈;第二次遍历时,边遍历边弹栈。
//用栈实现,额外空间为 O(n)
public static boolean isPalindrome1(Node node) {
Stack<Node> stack = new Stack<>();
while(node != null) {
stack.push(node);
node = node.next;
}
while(node != null) {
if(node.value != stack.pop().value) {
return false;
}
node = node.next;
}
return true;
}
2、快慢指针,找到链表中点位置,然后把后半段逆序(栈),然后再重头遍历。这个不难,就不写了。
3、
//不用额外空间
public static boolean isPalindrome2(Node node) {
Node quick = node; //快指针
Node slow = node; //慢指针
//找到中点位置
while(quick.next.next != null && quick.next != null) {
quick = quick.next.next;
slow = slow.next;
}
//逆序后半段的链表
quick = slow.next;
slow.next = null; //中点的next为null
Node tmp = null; //额外空间
while(quick != null) {
tmp = quick.next;
quick.next = slow;
slow = quick;
quick = tmp;
}
quick = tmp; //quick指向反转链表的开头
slow = node; //slow指向链表开头
//遍历链表
quick = node;
boolean res = true;
while(quick != null) {
if(quick.value != slow.value) {
res = false;
}
quick = quick.next;
slow = slow.next;
}
//最后把链表反转回来
quick = tmp.next;
tmp.next = null;
while(quick != null) {
slow = quick.next;
quick.next = tmp;
tmp = quick;
quick = slow;
}
return res;
}
链表荷兰国旗
建立三个节点类型的对象,分别时 less 区域,equal 区域,more 区域,然后每次遍历都选择三个符合区域的节点,添加到该区域上,最后把三个区域串起来。
public class SmallerEqualBigger {
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) {
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(head.value > pivot) {
//如果值属于小于区域
if(bH == null) {
//如果小于区域还没有节点
bH = head;
bT = head;
}else {
bT.next = head;
bT = head;
}
}
//移动head
head = next;
}
//分好区域以后,要把他们串起来
if(sT != null) {
sT = eH;
eT = eT == null? sT : eT;
}
if(eT != null) eT.next = bH;
//三个区域都有可能为空,因此这种方法返回是最简便的!!
return sH != null ? sH : eH != null ? eH : bH;
}
}
随即节点链表
1、哈希表,用一个 HashMap<Node,Node> 来存放原节点与拷贝节点。然后再连接好拷贝节点即可。
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);
}
2、不适用哈希表,而是把复制的节点放在原节点之后。
太聪明了!!!老链表还是指向老链表的节点,新链表指向新链表的节点。两者之间没有互相干扰!!!最后新老链表分开即可。
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) {
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;
}
两个单链表
1、判断有无环:用哈希表;不用哈希表,就用快慢指针,如果有环则一定相遇,在相遇后,快指针回到 head 节点,由一次走两步变成一次走一步,最后快慢指针一定会在第一个如环节点相遇。
2、判断完有环无环后,如果无环,可以遍历第一条链表。并放入哈希表中,然后遍历第二条链表,查找是否出现过。如果有环,参照 1。如果不用 map ,就可以遍历第一条,统计该链的长度与记录 end1 节点,遍历第二条,统计该链长度与 end2 节点,比对 end1 与 end2 ,如果地址相同(注意这里不是值相同!!!),就说明他们相交,这里不可能分叉开!!!单链表!! 接着,需要获得第一个相交的节点,因此 先比较两个链表长度大小,先到同一个起点,(也就是 :假设 head1=100,head2 = 80,那么先让 head1 先走20步,达到同一起点,然后再一起走。他们一定会一起走到第一个相交的节点数。
3、如果一个链表有环,一个无环,绝不相交。
4、两个有环相交。三中情况!
怎么区分???
1、获取两个链表的头节点 head1,head2,和 入环节点 loop1,loop2。
2、如果 loop1 == loop2 (地址相同,不是值相同),那么就是第二种情况,那么要求第一个相交的节点就直接参考上面的方法。
3、让 loop1 一直往下走,如果都走回 loop1 了,就说明不相交,第一种情况,如果走着走着走到 loop2 了,说明相交,则返回 loop1 或者 loop2 都是对的!!!
public class FindFirstIntersectNode {
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
//找到相交节点
public static Node getIntersectNode(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
//判断是否有环!
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
//如果都没有环
if (loop1 == null && loop2 == null) {
return noLoop(head1, head2);
}
//如果都有环
if (loop1 != null && loop2 != null) {
return bothLoop(head1, loop1, head2, loop2);
}
//其他情况都是错的!!
return null;
}
//判断是否有环,没有环就返回null,有环就返回入环节点
public static Node getLoopNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
//找到共同位置
Node quick = head.next.next;
Node slow = head.next;
while(quick != slow) {
if(quick.next == null || quick.next.next == null) {
return null;
}
quick = quick.next.next;
slow = slow.next;
}
//快指针指回head,重新出发,一步一个next, 再次相遇就是入环节点
quick = head;
while(quick != slow) {
quick = quick.next;
slow = slow.next;
}
return quick;
}
//两条链表都没有环
public static Node noLoop(Node head1, Node head2) {
//遍历第一条
Node cur = head1;
int length1 = 0;
while(cur.next != null) {
length1++;
cur = cur.next;
}
//遍历第二条
Node cur2 = head2;
int length2 = 0;
while(cur2.next != null) {
length2++;
cur2 = cur2.next;
}
//此时的cur 与 cur2都指向了两条链表的最后
//如果cur 与 cur2的地址不相同,就说明他们不相交了!
if(cur != cur2) {
return null;
}else {
//找到相交位置并返回
int len = length1 - length2;
cur = len > 0 ? head1 : head2;
cur2 = cur == head1 ? head2 : head1;
len = Math.abs(len);
while(len != 0) {
len--;
cur = cur.next;
}
while(cur != cur2) {
cur = cur.next;
cur2 = cur2.next;
}
}
return cur;
}
//如果两个都有环
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 {
//如果两个入环节点不相同,就让LOOP1一直走下去,如果回到loop1说明不相交,
//如果到了loop2.则返回哪一个都是正确的。
cur1 = loop1.next;
while (cur1 != loop1) {
if (cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}
}