题目
问题一是只有0位置才做决策,后续都是单分支,一条撸到死
问题二是只有0位置和1位置才做决策,后续也都是单分支…
代码
package com.harrison.followup;
/*
* @author:Harrison
* @Times:2022年9月8日 上午10:22:52
* @motto:众里寻他千百度,蓦然回首,那人却在灯火阑珊处。
*/
/**
* 给定一个数组arr,长度为N,arr中的值不是0就是1。
* arr[i]表示第i栈灯的状态,0代表灭灯,1代表亮灯
* 每一栈灯都有开关,但是按下i号灯的开关,会同时改变i-1、i、i+1栈灯的状态
*
* 问题一: 如果N栈灯排成一条直线,请问最少按下多少次开关,能让灯都亮起来
* 排成一条直线说明:
* i为中间位置时,i号灯的开关能影响i-1、i和i+1
* 0号灯的开关,只能影响0和1位置的灯
* N-1号灯的开关只能影响N-2和N-1位置的灯
*
* 问题二: 如果N栈灯排成一个圈,请问最少按下多少次开关,能让灯都亮起来
* 排成一个圈说明:
* i为中间位置时,i号灯的开关能影响i-1、i和i+1
* 0号灯的开关能影响N-1、0和1位置的灯
* N-1号灯的开关能影响N-2、N-1和0位置的灯
*
* 1:代表亮;0:代表不亮
*/
public class Code01_20220908_LightProblem {
// 无环改灯问题的暴力递归版本
public static int noLoop1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return arr[0] == 0 ? 1 : 0;
}
if (arr.length == 2) {
return arr[0] != arr[1] ? Integer.MAX_VALUE : arr[0] ^ 1;
}
return process1(arr, 0);
}
// arr[0...i-1]位置的灯不能做出改变
// arr[i......](i及其往后位置的灯)可以做出改变
public static int process1(int[] arr, int i) {
if (i == arr.length) {
return valid(arr) ? 0 : Integer.MAX_VALUE;
}
// 选择1:不改变i位置灯的状态
int p1 = process1(arr, i + 1);
// 选择2:改变i位置灯的状态
change1(arr, i);
int p2 = process1(arr, i + 1);
change1(arr, i);// 深度优先遍历清理现场!!!
// 如果选择2使得任何一步返回了系统最大值,
// 说明后续情况无论如何都不会使得所有灯都变亮
p2 = (p2 == Integer.MAX_VALUE) ? Integer.MAX_VALUE : p2 + 1;
return Math.min(p1, p2);
}
public static void change1(int[] arr, int i) {
if (i == 0) {
arr[0] ^= 1;
arr[1] ^= 1;
} else if (i == arr.length - 1) {
arr[i - 1] ^= 1;
arr[i] ^= 1;
} else {
arr[i - 1] ^= 1;
arr[i] ^= 1;
arr[i + 1] ^= 1;
}
}
public static boolean valid(int[] arr) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] == 0) {
return false;
}
}
return true;
}
// 无环改灯问题的优良递归版本(单决策)
public static int noLoop2(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return arr[0] == 0 ? 1 : 0;
}
if (arr.length == 2) {
return arr[0] != arr[1] ? Integer.MAX_VALUE : arr[0] ^ 1;
}
// 不改变0位置灯的状态
int p1 = process2(arr, arr[0], arr[1], 2);
// 改变0位置灯的状态
int p2 = process2(arr, arr[0] ^ 1, arr[1] ^ 1, 2);
if (p2 != Integer.MAX_VALUE) {
p2++;
}
return Math.min(p1, p2);
}
// index:当前在index位置上,但是不能在这个位置上做改变
// pre:index-1位置,在这个位置上做决定
// prepre:index-2位置,[0...prepre](0...prepre位置上的灯是全亮的)
// int prepre,int pre,int index
// 前两个参数都是灯的状态(非0及1),第三个参数才是位置
public static int process2(int[] arr, int prepre, int pre, int index) {
if (index == arr.length) {// pre正好是最后一盏灯
return pre != prepre ? Integer.MAX_VALUE : pre ^ 1;
}
// pre还没到最后一盏灯(index还没越界)
if (prepre == 0) {
// 前前位置的灯不是亮的,要想变亮,
// 必须在pre位置做改变从而使prepre位置的灯变亮
// 否则后面的位置将再也无法使prepre位置的灯变亮
pre ^= 1;
int cur = arr[index] ^ 1;// 当前位置的灯也会受影响
int next = process2(arr, pre, cur, index + 1);
// 可能会导致后续过程无效
return next = next == Integer.MAX_VALUE ? Integer.MAX_VALUE : next + 1;
} else {// prepre位置的灯是亮的,所以一定不能在pre位置的灯做改变
// arr[index] :数组中index位置的灯的状态
return process2(arr, pre, arr[index], index + 1);
}
}
// 无环改灯问题的优良递归改迭代版本(单决策递归 -> 一个循环)
public static int noLoop3(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return arr[0] == 0 ? 1 : 0;
}
if (arr.length == 2) {
return arr[0] != arr[1] ? Integer.MAX_VALUE : arr[0] ^ 1;
}
int p1 = process3(arr, arr[0], arr[1]);
int p2 = process3(arr, arr[0] ^ 1, arr[1] ^ 1);
p2 = (p2 == Integer.MAX_VALUE) ? Integer.MAX_VALUE : (p2 + 1);
return Math.min(p1, p2);
}
public static int process3(int[] arr, int prepre, int pre) {
int i = 2;
int cnt = 0;
while (i != arr.length) {
if (prepre == 0) {// 必须改变i位置灯的状态
cnt++;
prepre = pre ^ 1;
pre = arr[i++] ^ 1;
// 其实写法是这样的,但是上面的为了省代码
// pre^=1;
// prepre=pre;
// arr[i]^=1;
// pre=arr[i++];
} else {
prepre = pre;
pre = arr[i++];
}
}
return (prepre != pre) ? Integer.MAX_VALUE : (cnt + (pre ^ 1));
}
// 有环改灯问题的暴力递归版本,总体思路跟无环的一样
// 唯一的区别在于有环的话,每个位置的灯会影响三个位置
public static int loop1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return arr[0] == 0 ? 1 : 0;
}
if (arr.length == 2) {
return arr[0] != arr[1] ? Integer.MAX_VALUE : arr[0] ^ 1;
}
return fun1(arr, 0);
}
// arr[0...i-1]位置的灯不能做出改变
// arr[i......](i及其往后位置的灯)可以做出改变
public static int fun1(int[] arr, int i) {
if (i == arr.length) {
return valid(arr) ? 0 : Integer.MAX_VALUE;
}
// 选择1:不改变i位置灯的状态
int p1 = fun1(arr, i + 1);
// 选择2:改变i位置灯的状态
change2(arr, i);
int p2 = fun1(arr, i + 1);
change2(arr, i);// 深度优先遍历清理现场!!!
// 如果选择2使得任何一步返回了系统最大值,
// 说明后续情况无论如何都不会使得所有灯都变亮
p2 = (p2 == Integer.MAX_VALUE) ? Integer.MAX_VALUE : p2 + 1;
return Math.min(p1, p2);
}
public static void change2(int[] arr, int i) {
arr[lastIndex(i, arr.length)] ^= 1;
arr[i] ^= 1;
arr[nextIndex(i, arr.length)] ^= 1;
}
public static int lastIndex(int i, int N) {
return (i == 0) ? (N - 1) : (i - 1);
}
public static int nextIndex(int i, int N) {
return (i == N - 1) ? 0 : (i + 1);
}
// 有环改灯问题的优良递归版本
public static int loop2(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return arr[0] == 0 ? 1 : 0;
}
if (arr.length == 2) {
return arr[0] != arr[1] ? Integer.MAX_VALUE : arr[0] ^ 1;
}
if (arr.length == 3) {
return arr[0] != arr[1] || arr[1] != arr[2] ? Integer.MAX_VALUE : arr[0] ^ 1;
}
// 0位置不变,1位置不变
int p1 = fun2(arr, arr[1], arr[2], arr[arr.length - 1], arr[0], 3);
// 0位置不变,1位置变
int p2 = fun2(arr, arr[1] ^ 1, arr[2] ^ 1, arr[arr.length - 1], arr[0] ^ 1, 3);
// 0位置变,1位置不变
int p3 = fun2(arr, arr[1] ^ 1, arr[2], arr[arr.length - 1] ^ 1, arr[0] ^ 1, 3);
// 0位置变,1位置变
int p4 = fun2(arr, arr[1], arr[2] ^ 1, arr[arr.length - 1] ^ 1, arr[0], 3);
p2 = p2 == Integer.MAX_VALUE ? Integer.MAX_VALUE : p2 + 1;
p3 = p3 == Integer.MAX_VALUE ? Integer.MAX_VALUE : p3 + 1;
// 因为第四个分支是0位置和1位置都做了改变
p4 = p4 == Integer.MAX_VALUE ? Integer.MAX_VALUE : p4 + 2;
return Math.min(Math.min(p1, p2), Math.min(p3, p4));
}
// index:当前位置
// pre:index-1位置,在这个位置上做决定
// prepre:index-2位置
// endStatus:N-1位置灯的状态
// firstStatus:0位置灯的状态
// 返回让所有灯都亮,至少要按几次按钮
// 当前来到的是index-1位置,也就是pre位置
// index>=3,才有可能走递归
public static int fun2(int[] arr, int prepre, int pre, int endStatus, int firstStatus, int index) {
if (index == arr.length) {
return (endStatus != prepre || endStatus != firstStatus) ? Integer.MAX_VALUE : endStatus ^ 1;
}
// curStay:当前位置的灯是什么状态
// 如果index来到了最后一盏灯的位置(N-1),因为N-1位置是被0位置做决策影响了的,
// 所以我此时的状态应该是事先记录好的endStatus;
// 如果index在一个平凡范围内(index<arr.length-1),那就直接从数组中取状态
int curStay = (index == arr.length - 1) ? endStatus : arr[index];
// curChange:当前位置的灯要变成什么状态
// 平凡位置(从数组中取出状态来再异或1)
// N-1位置(事先记录好的endStatus变量再异或1)
int curChange = curStay ^ 1;
int endChange = (index == arr.length - 1) ? curChange : endStatus;
if (prepre == 0) {// pre位置要变
int next = fun2(arr, pre ^ 1, curChange, endChange, firstStatus, index + 1);
return next == Integer.MAX_VALUE ? (Integer.MAX_VALUE) : next + 1;
} else {
return fun2(arr, pre, curStay, endStatus, firstStatus, index + 1);
}
}
// 有环改灯问题的迭代版本
public static int loop3(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return arr[0] == 1 ? 0 : 1;
}
if (arr.length == 2) {
return arr[0] != arr[1] ? Integer.MAX_VALUE : (arr[0] ^ 1);
}
if (arr.length == 3) {
return (arr[0] != arr[1] || arr[0] != arr[2]) ? Integer.MAX_VALUE : (arr[0] ^ 1);
}
// 0不变,1不变
int p1 = fun3(arr, arr[1], arr[2], arr[arr.length - 1], arr[0]);
// 0不变,1改变
int p3 = fun3(arr, arr[1] ^ 1, arr[2] ^ 1, arr[arr.length - 1], arr[0] ^ 1);
// 0改变,1不变
int p2 = fun3(arr, arr[1] ^ 1, arr[2], arr[arr.length - 1] ^ 1, arr[0] ^ 1);
// 0改变,1改变
int p4 = fun3(arr, arr[1], arr[2] ^ 1, arr[arr.length - 1] ^ 1, arr[0]);
p2 = p2 != Integer.MAX_VALUE ? (p2 + 1) : p2;
p3 = p3 != Integer.MAX_VALUE ? (p3 + 1) : p3;
p4 = p4 != Integer.MAX_VALUE ? (p4 + 2) : p4;
return Math.min(Math.min(p1, p2), Math.min(p3, p4));
}
public static int fun3(int[] arr, int prepre, int pre, int endStatus, int firstStatus) {
int i = 3;
int op = 0;
while (i < arr.length - 1) {
if (prepre == 0) {
op++;
prepre = pre ^ 1;
pre = (arr[i++] ^ 1);
} else {
prepre = pre;
pre = arr[i++];
}
}
if (prepre == 0) {
op++;
prepre = pre ^ 1;
endStatus ^= 1;
pre = endStatus;
} else {
prepre = pre;
pre = endStatus;
}
return (endStatus != firstStatus || endStatus != prepre) ? Integer.MAX_VALUE : (op + (endStatus ^ 1));
}
// 生成长度为len的随机数组,值只有0和1两种值
public static int[] randomArray(int len) {
int[] arr = new int[len];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * 2);
}
return arr;
}
public static void main(String[] args) {
System.out.println("如果没有任何Oops打印,说明所有方法都正确");
System.out.println("test begin");
int testTime = 20000;
int lenMax = 12;
for (int i = 0; i < testTime; i++) {
int len = (int) (Math.random() * lenMax);
int[] arr = randomArray(len);
int ans1 = noLoop1(arr);
int ans2 = noLoop2(arr);
int ans3 = noLoop3(arr);
if (ans1 != ans2 || ans1 != ans3) {
System.out.println("1 Oops!");
}
}
for (int i = 0; i < testTime; i++) {
int len = (int) (Math.random() * lenMax);
int[] arr = randomArray(len);
int ans1 = loop1(arr);
int ans2 = loop2(arr);
int ans3 = loop3(arr);
if (ans1 != ans2 || ans1 != ans3) {
System.out.println("2 Oops!");
}
}
System.out.println("test end");
int len = 100000000;
System.out.println("性能测试");
System.out.println("数组大小:" + len);
int[] arr = randomArray(len);
long start = 0;
long end = 0;
start = System.currentTimeMillis();
noLoop3(arr);
end = System.currentTimeMillis();
System.out.println("noLoop3 run time: " + (end - start) + "(ms)");
start = System.currentTimeMillis();
loop3(arr);
end = System.currentTimeMillis();
System.out.println("loop3 run time: " + (end - start) + "(ms)");
}
}