Bootstrap

相邻开关控制路灯求全亮时最少灯数

  • 题目:相邻开关控制路灯求全亮时最少灯数

    • 给定一个数组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位置的灯

  • 特殊点:因为每个开关跟左右关联,所以判断每个位置时需要其他位置的信息。

  • ^思路:每个开关的意义是为了使前一个灯打开^

  • 处理:因为当前开关前一个位置的灯只能由当前开关控制,所以要保证整条路灯都亮则i-1 位置的灯需要亮。

  • 判断:如果当前开关前一个位置为0就开灯,否则不开,再向后传递更改后的信息

  • 特殊处理:第一个开关没有前置开关所以没有意义,所以0和1都行。各自使用递归。最后一个位置的灯没有后置开关能帮它,所以如果最后的开关处理完倒二的灯后,最后的灯为0,则这种开法不行

  • 问题:怎么传递灯开关的信息(需要简化信息维度,为dp做准备)

  • 答案:赋值个数组直接在数组上改

  • 缺点:参数带二维信息难改成dp

  • 解决:因为开关只会改变左中右的值,所以只传递左中右的值,剩下的从原数组中拿

  • 升级:灯路由线改成环

  • 表面区别:之前0位只能由1位改变,len-1只能由len-2改变。现在len-1和0直接也能改变。

  • 本质区别:0位置的开关有意义,最后位置的灯也有开关可以补救

  • 问题:灯路变成环状所有灯都有可以补救它状态的开关,会导致死循环。(灯路直线时,最后一个灯没有可以补救它的开关所以最后一定会结束)

  • 解决:首先就是要结束循环。选择在结尾结束循环,就是不让可以补救结尾的开关更改状态。

  • 0位置的开关可以补救末尾的灯,所以一开始直接分开和关0位置的开关进行递归。

  • 特殊:因为0和末尾位置的灯需要特殊处理,所以传参的时候多传两个

  • coding:因为只有1位置的开关会改变0位置的灯的状态,所以在main函数就先展开0和1位置的开关情况在后面可以省略特殊判断1位置的代码。

  • basecase:末尾两个和0位置灯的状态要一致才能全亮

  • 代码

    • package class09;
      
      /*
       * 给定一个数组arr,长度为N,arr中的值不是0就是1
       * arr[i]表示第i栈灯的状态,0代表灭灯,1代表亮灯
       * 每一栈灯都有开关,但是按下i号灯的开关,会同时改变i-1、i、i+2栈灯的状态
       * 问题一:
       * 如果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位置的灯
       * 
       * */
      public class Code01_LightProblem {
      
      
      	// 无环改灯问题的暴力版本
      	public static int noLoopRight(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);
      		}
      		return f1(arr, 0);
      	}
      
      	public static int f1(int[] arr, int i) {
      		if (i == arr.length) {
      			return valid(arr) ? 0 : Integer.MAX_VALUE;
      		}
      		int p1 = f1(arr, i + 1);
      		change1(arr, i);
      		int p2 = f1(arr, i + 1);
      		change1(arr, i);
      		p2 = (p2 == Integer.MAX_VALUE) ? p2 : (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 noLoopMinStep1(int[] arr) {
      		if (arr == null || arr.length == 0) {
      			return 0;
      		}
      		if (arr.length == 1) {
      			return arr[0] ^ 1;
      		}
      		if (arr.length == 2) {
      			return arr[0] != arr[1] ? Integer.MAX_VALUE : (arr[0] ^ 1);
      		}
      		// 不变0位置的状态
      		int p1 = process1(arr, 2, arr[0], arr[1]);
      		// 改变0位置的状态
      		int p2 = process1(arr, 2, arr[0] ^ 1, arr[1] ^ 1);
      		if (p2 != Integer.MAX_VALUE) {
      			p2++;
      		}
      		return Math.min(p1, p2);
      	}
      
      	// 当前在哪个位置上,做选择,nextIndex - 1 = cur ,当前!
      	// cur - 1 preStatus
      	// cur  curStatus
      	// 0....cur-2  全亮的!
      	public static int process1(int[] arr, int nextIndex, int preStatus, int curStatus) {
      		if (nextIndex == arr.length) { // 当前来到最后一个开关的位置
      			return preStatus != curStatus ? (Integer.MAX_VALUE) : (curStatus ^ 1);
      		}
      		// 没到最后一个按钮呢!
      		// i < arr.length
      		if (preStatus == 0) { // 一定要改变
      			curStatus ^= 1;
      			int cur = arr[nextIndex] ^ 1;
      			int next = process1(arr, nextIndex + 1, curStatus, cur);
      			return next == Integer.MAX_VALUE ? next : (next + 1);
      		} else { // 一定不能改变
      			return process1(arr, nextIndex + 1, curStatus, arr[nextIndex]);
      		}
      	}
      
      	// 无环改灯问题的迭代版本
      	public static int noLoopMinStep2(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);
      		}
      		int p1 = traceNoLoop(arr, arr[0], arr[1]);
      		int p2 = traceNoLoop(arr, arr[0] ^ 1, arr[1] ^ 1);
      		p2 = (p2 == Integer.MAX_VALUE) ? p2 : (p2 + 1);
      		return Math.min(p1, p2);
      	}
      
      	public static int traceNoLoop(int[] arr, int preStatus, int curStatus) {
      		int i = 2;
      		int op = 0;
      		while (i != arr.length) {
      			if (preStatus == 0) {
      				op++;
      				preStatus = curStatus ^ 1;
      				curStatus = arr[i++] ^ 1;
      			} else {
      				preStatus = curStatus;
      				curStatus = arr[i++];
      			}
      		}
      		return (preStatus != curStatus) ? Integer.MAX_VALUE : (op + (curStatus ^ 1));
      	}
      
      	// 有环改灯问题的暴力版本
      	public static int loopRight(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);
      		}
      		return f2(arr, 0);
      	}
      
      	public static int f2(int[] arr, int i) {
      		if (i == arr.length) {
      			return valid(arr) ? 0 : Integer.MAX_VALUE;
      		}
      		int p1 = f2(arr, i + 1);
      		change2(arr, i);
      		int p2 = f2(arr, i + 1);
      		change2(arr, i);
      		p2 = (p2 == Integer.MAX_VALUE) ? p2 : (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 loopMinStep1(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 = process2(arr, 3, arr[1], arr[2], arr[arr.length - 1], arr[0]);
      		// 0改变,1不变
      		int p2 = process2(arr, 3, arr[1] ^ 1, arr[2], arr[arr.length - 1] ^ 1, arr[0] ^ 1);
      		// 0不变,1改变
      		int p3 = process2(arr, 3, arr[1] ^ 1, arr[2] ^ 1, arr[arr.length - 1], arr[0] ^ 1);
      		// 0改变,1改变
      		int p4 = process2(arr, 3, 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));
      	}
      
      	
      	// 下一个位置是,nextIndex
      	// 当前位置是,nextIndex - 1 -> curIndex
      	// 上一个位置是, nextIndex - 2 -> preIndex   preStatus
      	// 当前位置是,nextIndex - 1, curStatus
      	// endStatus, N-1位置的状态
      	// firstStatus, 0位置的状态
      	// 返回,让所有灯都亮,至少按下几次按钮
      	
      	// 当前来到的位置(nextIndex - 1),一定不能是1!至少从2开始
      	// nextIndex >= 3
      	public static int process2(int[] arr, 
      			int nextIndex, int preStatus, int curStatus, 
      			int endStatus, int firstStatus) {
      		
      		if (nextIndex == arr.length) { // 最后一按钮!
      			return (endStatus != firstStatus || endStatus != preStatus) ? Integer.MAX_VALUE : (endStatus ^ 1);
      		}
      		// 当前位置,nextIndex - 1
      		// 当前的状态,叫curStatus
      		// 如果不按下按钮,下一步的preStatus, curStatus
      		// 如果按下按钮,下一步的preStatus, curStatus ^ 1
      		// 如果不按下按钮,下一步的curStatus, arr[nextIndex]
      		// 如果按下按钮,下一步的curStatus, arr[nextIndex] ^ 1
      		int noNextPreStatus = 0;
      		int yesNextPreStatus = 0;
      		int noNextCurStatus =0;
      		int yesNextCurStatus = 0;
      		int noEndStatus = 0;
      		int yesEndStatus = 0;
      		if(nextIndex < arr.length - 1) {// 当前没来到N-2位置
      			 noNextPreStatus = curStatus;
      			 yesNextPreStatus = curStatus ^ 1;
      			 noNextCurStatus = arr[nextIndex];
      			 yesNextCurStatus = arr[nextIndex] ^ 1;
      		} else if(nextIndex == arr.length - 1) {// 当前来到的就是N-2位置
      			noNextPreStatus = curStatus;
      			yesNextPreStatus = curStatus ^ 1;
      			noNextCurStatus = endStatus;
      			yesNextCurStatus = endStatus ^ 1;
      			noEndStatus = endStatus;
      			yesEndStatus = endStatus ^ 1;
      		}
      		if(preStatus == 0) {
      			int next = process2(arr, nextIndex + 1, yesNextPreStatus, yesNextCurStatus,
      					nextIndex == arr.length - 1 ? yesEndStatus : endStatus, firstStatus);
      			return next == Integer.MAX_VALUE ? next : (next + 1);
      		}else {
      			return process2(arr, nextIndex + 1, noNextPreStatus, noNextCurStatus, 
      					nextIndex == arr.length - 1 ? noEndStatus : endStatus, firstStatus);
      					
      		}
      //		int curStay = (nextIndex == arr.length - 1) ? endStatus : arr[nextIndex];
      //		int curChange = (nextIndex == arr.length - 1) ? (endStatus ^ 1) : (arr[nextIndex] ^ 1);
      //		int endChange = (nextIndex == arr.length - 1) ? curChange : endStatus;
      //		if (preStatus == 0) {
      //			int next = process2(arr, nextIndex + 1, curStatus ^ 1, curChange, endChange, firstStatus);
      //			return next == Integer.MAX_VALUE ? next : (next + 1);
      //		} else {
      //			return process2(arr, nextIndex + 1, curStatus, curStay, endStatus, firstStatus);
      //		}
      	}
      
      	// 有环改灯问题的迭代版本
      	public static int loopMinStep2(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 = traceLoop(arr, arr[1], arr[2], arr[arr.length - 1], arr[0]);
      		// 0改变,1不变
      		int p2 = traceLoop(arr, arr[1] ^ 1, arr[2], arr[arr.length - 1] ^ 1, arr[0] ^ 1);
      		// 0不变,1改变
      		int p3 = traceLoop(arr, arr[1] ^ 1, arr[2] ^ 1, arr[arr.length - 1], arr[0] ^ 1);
      		// 0改变,1改变
      		int p4 = traceLoop(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 traceLoop(int[] arr, int preStatus, int curStatus, int endStatus, int firstStatus) {
      		int i = 3;
      		int op = 0;
      		while (i < arr.length - 1) {
      			if (preStatus == 0) {
      				op++;
      				preStatus = curStatus ^ 1;
      				curStatus = (arr[i++] ^ 1);
      			} else {
      				preStatus = curStatus;
      				curStatus = arr[i++];
      			}
      		}
      		if (preStatus == 0) {
      			op++;
      			preStatus = curStatus ^ 1;
      			endStatus ^= 1;
      			curStatus = endStatus;
      		} else {
      			preStatus = curStatus;
      			curStatus = endStatus;
      		}
      		return (endStatus != firstStatus || endStatus != preStatus) ? 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 = noLoopRight(arr);
      			int ans2 = noLoopMinStep1(arr);
      			int ans3 = noLoopMinStep2(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 = loopRight(arr);
      			int ans2 = loopMinStep1(arr);
      			int ans3 = loopMinStep2(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();
      		noLoopMinStep2(arr);
      		end = System.currentTimeMillis();
      		System.out.println("noLoopMinStep2 run time: " + (end - start) + "(ms)");
      
      		start = System.currentTimeMillis();
      		loopMinStep2(arr);
      		end = System.currentTimeMillis();
      		System.out.println("loopMinStep2 run time: " + (end - start) + "(ms)");
      	}
      
      }
      
      
;