暴力递归
- 把问题转化为规模缩小了的同类问题的子问题
- 有明确的不需要继续进行递归的条件(base case)
- 有当得到了子问题的结果之后的决策过程
- 不记录每一个子问题的解
那么,怎么最终确定它能不能用“递归”做呢?
看当N取1或2之类最简情况时,问题是否可以解决——然后写程序解决它
动态规划
- 从暴力递归中来
- 将每一个子问题的解记录下来,避免重复计算
- 把暴力递归的过程,抽象成了状态表达
- 并且存在化简状态表达,使其更加简洁的可能
P和NP
P指的是我明确地知道怎么算,计算的流程很清楚;而NP问题指的是我不知道怎么算,但我知道怎么尝试(暴力递归)
暴力递归相关
我们知道n!的定义,,可以根据定义直接求解:
int getFactorial_1(int n){
int res=1;
for(int i = 1 ; i <= n ; n++){
res*=i;
}
return res;
}
但我们可以这样想,如果知道(n-1)!,那通过(n-1)! * n不就得出n!了吗?于是我们就有了如下的尝试:
int getFactorial_2(int n){
if(n=1)
return 1;
return getFactorial_2(n-1) * n;
}
n!的状态依赖(n-1)!,(n-1)!依赖(n-2)!,就这样依赖下去,直到n=1这个突破口,然后回溯,你会发现整个过程就回到了1 * 2 * 3 * …… * (n-1) * n的计算过程。
汉诺塔问题
该问题最基础的一个模型就是,一个竹竿上放了2个圆盘,需要先将最上面的那个移到辅助竹竿上,然后将最底下的圆盘移到目标竹竿,最后把辅助竹竿上的圆盘移回目标竹竿。即必须将小的圆盘放在大的圆盘上面
public class Hanoi {
public static void process(String source,String target,String auxiliary,int n){
if (n == 1) {
System.out.println("move 1 disk from " + source + " to " + target);
return;
}
//尝试把前n-1个圆盘暂时放到辅助竹竿->子问题
process(source, auxiliary, target, n - 1);
//将底下最大的圆盘移到目标竹竿
System.out.println("move 1 disk from "+source+" to "+target);
//再尝试将辅助竹竿上的圆盘移回到目标竹竿->子问题
process(auxiliary,target,source,n-1);
}
public static void main(String[] args) {
process("Left", "Right", "Help", 3);
}
}
打印一个字符串的子序列
子串和子序列并不是一个意思
例如:一个字符串 awbcdewgh
他的子串: awbc. awbcd awbcde …很多个子串 但是都是连续在一起
他的子序列: abc . abcd abcde … 很多个子序列 但是子序列中的字符在字符串中不一定是连在一起的, 但是 子序列一定是单调的, (即字符之间ASCII单调递增或单调递减)
/**
* 本级任务:将index之后(包括index)位置上的字符和index上的字符交换,将产生的所有结果扔给下一级
* @param str
* @param index
*/
public static void printAllPermutations(char[] chs,int index) {
//base case
if(index == chs.length-1) {
System.out.println(chs);
return;
}
for (int j = index; j < chs.length; j++) {
swap(chs,inde***rintAllPermutations(chs, index+1);
}
}
public static void swap(char[] chs,int i,int j) {
char temp = chs[i];
chs[i] = chs[j];
chs[j] = temp;
}
public static void main(String[] args) {
printAllPermutations("abc".toCharArray(), 0);
}
母牛生牛问题
母牛每年生一只母牛,新出生的母牛成长三年后也能每年生一只母牛,假设不会死。求N年后,母牛的数量。
那么求第n年母牛的数量,按照此公式顺序计算即可。
暴力递归改为动态规划
动态规划由暴力递归而来,是对暴力递归中的重复计算的一个优化,策略是空间换时间
最小路径和
给你一个二维数组,二维数组中的每个数都是正数,要求从左上角走到右下角,每一步只能向右或者向下。沿途经过的数字要累加起来。返回最小的路径和。
/**
* 从矩阵matrix的(i,j)位置走到右下角元素,返回最小沿途元素和。每个位置只能向右或向下
*
* @param matrix
* @param i
* @param j
* @return 最小路径和
*/
public static int minPathSum(int matrix[][], int i, int j) {
// 如果(i,j)就是右下角的元素
//终止条件
if (i == matrix.length - 1 && j == matrix[0].length - 1) {
return matrix[i][j];
}
// 如果(i,j)在右边界上,只能向下走
if (j == matrix[0].length - 1) {
return matrix[i][j] + minPathSum(matrix, i + 1, j);
}
// 如果(i,j)在下边界上,只能向右走
if (i == matrix.length - 1) {
return matrix[i][j] + minPathSum(matrix, i, j + 1);
}
// 不是上述三种情况,那么(i,j)就有向下和向右两种决策,取决策结果最小的那个
int left = minPathSum(matrix, i, j + 1);
int down = minPathSum(matrix, i + 1, j);
return matrix[i][j] + Math.min(left,down );
}
public static void main(String[] args) {
int matrix[][] = {
{ 9, 1, 0, 1 },
{ 4, 8, 1, 0 },
{ 1, 4, 2, 3 }
};
System.out.println(minPathSum(matrix, 0, 0)); //14
}