今天复习递归后面的习题,依然是跟着小成同学,参考《算法笔记》这本书。
To Do List:
1、模板代码在挑战模式下自己敲一遍
2、复习一遍比较难的递归题目(费解的开关、翻硬币、飞行员兄弟):敲一遍,注重理解
3、二分问题先掌握一下整数这种稍微好理解的。
4、背会儿单词,准备睡觉!
递归模板题再复习!
19:44开始 20: 14复习完毕,耗时30min,其中AcWing 93. 递归实现组合型枚举在做题的过程中卡壳了。优化剪枝那里突然忘记该从哪考虑,这里再加强一下记忆。
import java.util.Scanner;
public class Main {
static int n, m, N = 26;
static int[] num = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
dfs(1, 1);
}
private static void dfs(int u, int start) {
if(u + n - start < m) return;
if(u == m + 1) {
for(int i = 1; i <= m; i++) {
System.out.print(num[i] + " ");
}
System.out.println();
return;
}
for(int i = start; i <= n; i++) {
num[u] = i;
dfs(u + 1, i + 1);
num[u] = 0;
}
}
}
u :当前正在枚举第几个位置
start :当前最小可以从哪个数开始枚举
m - start 其实就是尚未枚举的位置还剩下几个数可以选,当已经枚举到的位置u+剩下可选数目 < m
的时候,其实不符合条件,就return即可,即 if (u + n - start < m) return;
前面两道题没什么问题了,这道题明天再复习一下。
接下来开始复习递归中比较难的题目:
AcWing 95. 费解的开关
20:20开始
分析:这道题的题目需求是要将所有的灯变亮,但是有一个比较难考虑到的点:只需要枚举第一行的左右初始状态,剩下行的操作均可确定,最后再判断最后一行是否符合条件即可。
上述动图可以帮助更好理解整个操作的流程,黄色表示灯处于亮的状态。
顺序可以任意,每个格子最多按一次。
所以解法就是枚举第一行,之后的所有操作就都确定了。
最后一行的状态不能改了,需要特判一下,如果有灭着的说明方案不合法,如果全亮说明方案ok。
代码如下:
import java.util.Scanner;
public class Main {
static final int N = 6;
static char[][] g = new char[N][N];
static char[][] backup = new char[N][N];
static int[] dx = {-1, 0, 1, 0, 0}; //从十字架的上方开始是第一个
static int[] dy = {0, 1, 0, -1, 0};
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
while (n-- != 0) { //这里这个写法我感觉还是比较好的,需要着重记忆
for(int i = 0; i < 5; i++) {
g[i] = sc.next().toCharArray();
}
int res = Integer.MAX_VALUE; //这句话主要是用来初始化res
for(int op = 0; op < 32; op++) { //因为是用0和1表示的数,每一种情况都可以对应一个十进制数字
//先把数组给备份一下,后面还原的时候需要用到
for(int i = 0; i < 5; i++) {
backup[i] = g[i].clone();
}
int step = 0;
//下面是对第一行状态的判断和操作
for(int i = 0; i < 5; i++) {
if((op >> i & 1) == 0) {
//这里!!对我来说位运算的一切都很新颖,mark一下
//这里的意思是来判断i的二进制的第几位是不是1
step++;
turn(0, 4 - i);//这道题明显比前几道模板题难得多得多得多,涉及到很多的函数
}
}
for(int i = 0; i < 4; i++) {
for(int j = 0; j < 5; j++) {
if(g[i][j] == '0') {
step++;
turn(i + 1, j);
}
}
}
boolean dark = false;
for(int i = 0; i < 5; i++) {
if(g[4][i] == '0') {
dark = true;
break;
}
}
if(!dark) res = Math.min(res, step);
for(int i = 0; i < 5; i++) {
g[i] = backup[i].clone();
}
}
if(res > 6) res = -1;
System.out.println(res);
}
}
// 利用偏移量改变5个位置的值
private static void turn(int x, int y) {
for (int i = 0; i < 5; i++) {
int a = x + dx[i];
int b = y + dy[i];
if (a < 0 || a >= 5 || b < 0 || b >= 5) continue; // 在边界外,直接忽略即可
g[a][b] ^= 1; // 异或运算
}
}
}
以上代码对我来说比较新的地方有:
1、偏移量
2、巧妙运用二进制与十进制之间的转换
3、特判
今天再做一道跟这个十分类似的简化版题目:
AcWing 1208. 翻硬币
import java.util.Scanner;
public class Main {
static final int N = 101;
static char[] s1 = new char[N];
static char[] s2 = new char[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
s1 = sc.next().toCharArray();
s2 = sc.next().toCharArray();
int res = 0;
for (int i = 0; i < s1.length - 1; i++) {
if (s1[i] != s2[i]) {
turn(i);
turn(i + 1);
res++;
}
}
System.out.print(res);
}
private static void turn(int i) {
if (s1[i] == '*') s1[i] ='o';
else s1[i] = '*';
}
}
21:20了,今天有点累。。任务没完成,想睡觉了。。明天见。。