0%

递归和动态规划

介绍递归和动态规划。

  • 暴力递归:
    1. 把问题转化为规模缩小了的同类问题的子问题
    2. 有明确的不需要继续进行递归的条件(base case)
    3. 有当得到了子问题的结果之后的决策过程
    4. 不记录每一个子问题的解
  • 动态规划:
    1. 从暴力递归中来
    2. 将每一个子问题的解记录下来,避免重复计算
    3. 把暴力递归的过程,抽象成了状态表达
    4. 并且存在化简状态表达,使其更加简洁的可能

求n!的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Code_01_Factorial {
public static long getFactorial1(int n){
if(n==1) {
return 1L;
}
return (long) n*getFactorial1(n-1);
}
public static long getFactorial2(int n){
long result=1;
for (int i = 1; i <= n; i++) {
result*=i;
}
return result;
}
}

汉诺塔问题

  • 打印n层汉诺塔从最左边移动到最右边的全部过程
1
2
3
4
5
6
7
8
9
10
11
public class Code_02_Hanoi {
public static void process(int N, String from, String to, String help) {
if (N == 1) {
System.out.println("Move 1 from " + from + " to " + to);
} else {
process(N - 1, from, help, to);
System.out.println("Move " + N + " from " + from + " to " + to);
process(N - 1, help, to, from);
}
}
}
  • T(N) = 2T(N-1)+1

打印一个字符串全部的子序列,包括空字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Code_03_Print_All_Subsequences {
public static void printAllsub(char[] str, int i, String res) {
if (i == str.length) {
System.out.println(res);
return;
}
printAllsub(str, i + 1, res);
printAllsub(str, i + 1, res + String.valueOf(str[i]));
}

public static void main(String[] args) {
String test = "abc";
printAllsub(test.toCharArray(), 0, "");
}
}

打印一个字符串的全排列

  • 打印一个字符串的全部排列,要求不要出现重复的排列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.util.HashSet;

public class Code_04_Print_All_Permutations {

public static void printAllPermutations1(String str) {
char[] chs = str.toCharArray();
process1(chs, 0);
}

public static void process1(char[] chs, int i) {
if (i == chs.length) {
System.out.println(String.valueOf(chs));
}
for (int j = i; j < chs.length; j++) {
swap(chs, i, j);
process1(chs, i + 1);
swap(chs, i, j);
}
}

public static void printAllPermutations2(String str) {
char[] chs = str.toCharArray();
process2(chs, 0);
}

public static void process2(char[] chs, int i) {
if (i == chs.length) {
System.out.println(String.valueOf(chs));
}
HashSet<Character> set = new HashSet<>();
for (int j = i; j < chs.length; j++) {
if (!set.contains(chs[j])) {
set.add(chs[j]);
swap(chs, i, j);
process2(chs, i + 1);
swap(chs, i, j);
}
}
}

public static void swap(char[] chs, int i, int j) {
char tmp = chs[i];
chs[i] = chs[j];
chs[j] = tmp;
}

public static void main(String[] args) {
String test1 = "abc";
printAllPermutations1(test1);
System.out.println("======");
printAllPermutations2(test1);
System.out.println("======");

String test2 = "acc";
printAllPermutations1(test2);
System.out.println("======");
printAllPermutations2(test2);
System.out.println("======");
}
}

牛生牛

  • 母牛每年生一只母牛,新出生的母牛成长三年后也能每年生已知母牛,假设不会死,求N年后,母牛的数量。
  • 进阶:如果每只母牛只能活10年,求N年后,母牛的数量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Code_05_Cow {
public static int cowNumber1(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2 || n == 3) {
return n;
}
return cowNumber1(n - 1) + cowNumber1(n - 3);
}

public static int cowNumber2(int n) {
if (n < 1) {
return 0;
}
if (n == 1 || n == 2 || n == 3) {
return n;
}
int res = 3;
int pre = 2;
int prepre = 1;
int tmp1 = 0;
int tmp2 = 0;
for (int i = 4; i <= n; i++) {
tmp1 = res;
tmp2 = pre;
res = res + prepre;
pre = tmp1;
prepre = tmp2;
}
return res;
}
}
  • O(N) ,所有递推式,比如斐波那契数列,都有O(logN)的解。

递归逆序栈

给你一个栈,请你逆序这个栈,不能申请额外的数据结构,只能 使用递归函数。如何实现?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Stack;
public class Code_06_ReverseStackUsingRecursive {
public static void reverse(Stack<Integer> stack) {
if (stack.isEmpty()) {
return;
}
int i = getAndRemoveLastElement(stack);
reverse(stack);
stack.push(i);
}

public static int getAndRemoveLastElement(Stack<Integer> stack) {
int result = stack.pop();
if (stack.isEmpty()) {
return result;
} else {
int last = getAndRemoveLastElement(stack);
stack.push(result);
return last;
}
}
}

最小路径和

  • 给你一个二维数组,二维数组中的每个数都是正数,要求从左上角走到右下角,每一步只能向右或者向下。沿途经过的数字要累 加起来。返回最小的路径和。

有重复计算且是无后效性问题,(即可变参数固定,返回值一定)可以改成动态规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class Code_07_MinPath {
public static int process1(int[][] matrix, int i, int j) {
if (i == matrix.length - 1 && j == matrix[0].length - 1) {
return matrix[i][j];
}
if (i == matrix.length - 1) {
return matrix[i][j] + process1(matrix, i, j + 1);
}
if (j == matrix[0].length - 1) {
return matrix[i][j] + process1(matrix, i + 1, j);
}
int right = process1(matrix, i, j + 1);
int down = process1(matrix, i + 1, j);
return matrix[i][j] + Math.min(right, down);
}

public static int minPath2(int[][] m) {
if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) {
return 0;
}
int row = m.length;
int col = m[0].length;
int[][] dp = new int[row][col];
dp[0][0] = m[0][0];
for (int i = 1; i < row; i++) {
dp[i][0] = dp[i - 1][0] + m[i][0];
}
for (int j = 1; j < col; j++) {
dp[0][j] = dp[0][j - 1] + m[0][j];
}
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + m[i][j];
}
}
return dp[row - 1][col - 1];
}

// for test
public static int[][] generateRandomMatrix(int rowSize, int colSize) {
if (rowSize < 0 || colSize < 0) {
return null;
}
int[][] result = new int[rowSize][colSize];
for (int i = 0; i != result.length; i++) {
for (int j = 0; j != result[0].length; j++) {
result[i][j] = (int) (Math.random() * 10);
}
}
return result;
}

public static void main(String[] args) {
int[][] m = {{1, 3, 5, 9}, {8, 1, 3, 4}, {5, 0, 6, 1}, {8, 8, 4, 0}};
System.out.println(process1(m, 0, 0));
System.out.println(minPath2(m));

m = generateRandomMatrix(6, 7);
System.out.println(process1(m, 0, 0));
System.out.println(minPath2(m));
}
}

累加求和判断

  • 给你一个数组arr,和一个整数aim。如果可以任意选择arr中的 数字,能不能累加得到aim,返回true或者false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Code_08_Money_Problem {

public static boolean money1(int[] arr, int aim) {
return isSum(arr, 0, 0, aim);
}

public static boolean isSum(int[] arr, int i, int sum, int aim) {
if (i == arr.length) {
return sum == aim;
}
return isSum(arr, i + 1, sum, aim) || isSum(arr, i + 1, sum + arr[i], aim);
}

public static boolean money2(int[] arr, int aim) {
boolean[][] dp = new boolean[arr.length + 1][aim + 1];
for (int i = 0; i < dp.length; i++) {
dp[i][aim] = true;
}
for (int i = arr.length - 1; i >= 0; i--) {
for (int j = aim - 1; j >= 0; j--) {
dp[i][j] = dp[i + 1][j];
if (j + arr[i] <= aim) {
dp[i][j] = dp[i][j] || dp[i + 1][j + arr[i]];
}
}
}
return dp[0][0];
}

public static void main(String[] args) {
int[] arr = {1, 4, 8};
int aim = 12;
System.out.println(money1(arr, aim));
System.out.println(money2(arr, aim));
}
}

背包问题

  • 给定两个数组w和v,两个数组长度相等,w[i]表示第i件商品的重量,v[i]表示第i件商品的价值。 再给定一个整数bag,要求你挑选商品的重量加起来一定不能超过bag,返回满足这个条件 下,你能获得的最大价值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class Code_09_Knapsack {
public static int maxValue1(int[] c, int[] p, int bag) {
return process1(c, p, 0, 0, bag);
}

public static int process1(int[] weights, int[] values, int i, int alreadyweight, int bag) {
if (alreadyweight > bag) {
return Integer.MIN_VALUE;
}
if (i == weights.length) {
return 0;
}
return Math.max(
process1(weights, values, i + 1, alreadyweight, bag),
values[i] + process1(weights, values, i + 1, alreadyweight + weights[i], bag));
}

public static int maxValue2(int[] c, int[] p, int bag) {
int[][] dp = new int[c.length + 1][bag + 1];
for (int i = c.length - 1; i >= 0; i--) {
for (int j = bag; j >= 0; j--) {
dp[i][j] = dp[i + 1][j];
if (j + c[i] <= bag) {
dp[i][j] = Math.max(dp[i][j], p[i] + dp[i + 1][j + c[i]]);
}
}
}
return dp[0][0];
}
}