一、递归与递推
1.Acwing 92.递归实现指数型枚举
也就是考虑选不选这个数(高中数学集合问题)
实现代码:
import java.util.Scanner;
public class Main {
static int n, N = 16; //数组从1开始
static int[] st = new int[N]; //状态 0:未考虑 1:选 2 :不选
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
dfs(1);
}
private static void dfs(int u) {
if( u > n) {
for(int i = 1; i <= n; i ++) {
if(st[i] == 1) {
System.out.print(i + " ");
}
}
System.out.println();
return;
}
st[u] = 2;
dfs(u + 1); // 第一个分支:不选
// 这两行恢复现场加不加都一样的
// 因为递归时会被下面的值给覆盖掉 所以不用手动恢复 这里加上是让代码看起来更加圆滑 更加还原算法本身
st[u] = 0; // 恢复现场
st[u] = 1;
dfs(u + 1); // 第二个分支:选
st[u] = 0;
}
}
法1:考虑依次枚举哪个数放在哪个位置
法2:依次枚举每个位置,去看放哪个数(本题做法)
import java.util.Scanner;
public class Main {
static int n, N = 10;
static int[] state = new int[N]; // 0表示还没放数,1~n表示放了哪个数
static boolean[] used = new boolean[N]; // true表示用过,false表示还未用过
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
dfs(1);
}
private static void dfs(int u) {
if (u > n) { // 边界
for (int i = 1; i <= n; i++) {
System.out.print(state[i] + " "); // 打印方案
}
System.out.println();
return;
}
// 依次枚举每个分支,即当前位置可以填哪些数
for (int i = 1; i <= n; i++) {
if (!used[i]) {
state[u] = i;
used[i] = true;
dfs(u + 1);
// 恢复现场
state[u] = 0;
used[i] = false;
}
}
}
}
思想:把指数型符合长度的结果挑出来
优化:剪枝儿~
import java.util.Scanner;
public class Main {
static int n, m, N = 26;
static Scanner sc = new Scanner(System.in);
static int[] way = new int[N];
public static void main(String[] args) {
n = sc.nextInt();
m = sc.nextInt();
dfs(1, 1);
}
private static void dfs(int u, int start) {
if (u + n - start < m) return; // 剪枝 优化dfs 如果把后面所有数全选上,都不够m个,当前分支就一定无解
if (u == m + 1) { // 边界
for (int i = 1; i <= m; i++) {
System.out.print(way[i] + " "); // 打印方案
}
System.out.println();
return;
}
for (int i = start; i <= n; i++) {
way[u] = i;
dfs(u + 1, i + 1);
way[u] = 0; // 恢复现场
}
}
}
Java B组 第9题 先列出一个表达式: n = a + b / c , 得到c · n = c · a + b, n已知,只需要枚举a和c , b直接可以算出
import java.util.Scanner;
public class Main {
static final int N = 10;
static int n; // 输入的目标数
static int cnt; // 最后的结果数
static int[] num = new int[N]; // 保存全排列的结果
static boolean[] used = new boolean[N]; // 标记数字状态 true表示已使用,false表示未使用
static Scanner sc = new Scanner(System.in);
public static void main(String[] args) {
n = sc.nextInt();
dfs(0);
System.out.print(cnt);
}
private static void dfs(int u) {
if (u == 9) {
// 两层循环将数组分成三段
for (int i = 0; i < 7; i++) {
for(int j = i + 1; j < 8; j++) {
int a = calc(0, i);
if (a >= n) return; // 优化:如果a比n还大 说明无解 直接return
int b = calc(i + 1, j);
int c = calc(j + 1, 8);
if (a * c + b == c * n) { // n = a + b / c 化为 c·n = c·a + b
cnt++;
}
}
}
return;
}
// 全排列模板
for (int i = 1; i <= 9; i++) {
if (!used[i]) {
used[i] = true;
num[u] = i;
dfs(u + 1);
used[i] = false; // 恢复现场
}
}
}
// 在数组中计算某一区间的数
private static int calc(int l, int r) {
int res = 0;
for (int i = l; i <= r; i++) {
res = res * 10 + num[i];
}
return res;
}
}
上面的方案其实我们还是需要进行对b进行枚举的,y总进行了优化,利用公式变换得到了b = c * n - c * a
import java.util.Scanner;
import java.util.Arrays;
public class Main {
static final int N = 10;
static int n; // 输入的目标数
static int ans; // 最后的结果数
static boolean[] st = new boolean[N]; // 标记数字状态 true表示已使用,false表示未使用
static boolean[] backup = new boolean[N]; // 判重数组,备份
static Scanner sc = new Scanner(System.in);
public static void main(String[] args) {
n = sc.nextInt();
dfs_a(0, 0);
System.out.print(ans);
}
private static void dfs_a(int u, int a) {
if (a >= n) return;
if (a != 0) dfs_c(u, a, 0);
for (int i = 1; i <= 9; i++) {
if (!st[i]) {
st[i] = true;
dfs_a(u + 1, a * 10 + i);
st[i] = false; // 恢复现场
}
}
}
private static void dfs_c(int u, int a, int c) {
if (u > 9) return;
if (check(a, c)) ans++;
for (int i = 1; i <= 9; i++) {
if (!st[i]) {
st[i] = true;
dfs_c(u + 1, a, c * 10 + i);
st[i] = false; // 恢复现场
}
}
}
private static boolean check(int a, int c) {
long b = n * (long)c - a * c; // 这里要定义为long,要不然可能会溢出
if (a == 0 || b == 0 || c == 0) return false; // 不包含0,直接返回
backup = Arrays.copyOf(st, st.length); // 将st数组的值copy到backup里,保持st原样,更改backup里面的值
while (b != 0) {
int x = (int)(b % 10); // 取个位
b /= 10; // 把个位删掉
if (x == 0 || backup[x]) return false;
backup[x] = true;
}
for (int i = 1; i <= 9; i++) {
if (!backup[i]) return false;
}
return true;
}
}
???但是运行时间比上一个还要长?what
第二次课
递归和递推的区别
递推写法
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] f = new int[46];
f[1] = 0;
f[2] = 1;
for (int i = 3; i <= n; i++) f[i] = f[i - 1] + f[i - 2];
for (int i = 1; i <= n; i++) System.out.print(f[i] + " ");
}
}
优化写法(dp滚动数组的雏形)
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int a = 0, b = 1;
for (int i = 1; i <= n; i++) {
System.out.print(a + " ");
int fn = a + b;
a = b;
b = fn;
}
}
}
题目梳理:
需要将所有的灯变亮 (将所有的数字变成1)。
思想:
每一行开关的操作完全被上一行灯的亮灭状态所唯一决定。顺序可以任意,每个格子最多按一次。所以解法就是枚举第一行,之后的所有操作就都确定了。最后一行的状态不能改了,需要特判一下,如果有灭着的说明方案不合法,如果全亮说明方案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}; // 坐标x的偏移量
static int[] dy = {0, 1, 0, -1, 0}; // 坐标y的偏移量
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;
for (int op = 0; op < 32; op++) { // 5位数 转换成2进制最大的数是32
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) { // 判断i的二进制的第几位是不是1
step++;
turn(0, 4 - i);
}
}
// 对2,3,4行判断
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; // 异或运算
}
}
}