暴力递归:
1,把问题转化为规模缩小了的同类问题的子问题
2,有明确的不需要继续进行递归的条件(base case)
3,有当得到了子问题的结果之后的决策过程
4,不记录每一个子问题的解
动态规划
1,从暴力递归中来
2,将每一个子问题的解记录下来,避免重复计算
3,把暴力递归的过程,抽象成了状态表达
4,并且存在化简状态表达,使其更加简洁的可能
public class Code_01_Factorial {
public static int getFactorial1(int n){
if (n <= 0){
return 0;
}
int res = 1;
for(int i = 1;i <= n;i++){
res *= i;
}
return res;
}
public static int getFactorial2(int n){
if(n == 1){
return 1;
}
return n * getFactorial2(n - 1);
}
public static void main(String[] args) {
System.out.println(getFactorial1(10));
System.out.println(getFactorial2(10));
}
}
打印n层汉诺塔从最左边移动到最右边的全部过程
【解题】
1)1到n-1个从from租组移动到help组
2)单独地把from上的n移动到to上去
3)把1到n-1从help上移动到to上去
三层的图解:
代码:
public class Code_02_Hannoi {
public static void process(int N,String from,String help,String to){
if(N == 1){
System.out.println("Move " + N + " from " + from + " to "+ to);
}else {
process(N-1,from,to,help);//第一步:把1到n-1从from移动到help组上去
System.out.println("Move " + N + " from " + from + " to "+ to);
process(N-1,help,from,to);//第三步:把1到n-1从help上移动到to上去
}
}
public static void main(String[] args) {
int N = 3;
process(N,"左边","中间","右边");
}
}
结果:
需要的时间复杂度分析:
T(n) = T(n-1)+1+T(n-1)=2T(n-1)+1 ==>
T(n) + 1= 2*(T(n-1)+1) ==>
(T(n) + 1)/(T(n-1)+1) = 2 ==>T(n) + 1是一个以2为底的等比数列,T(n) = 2^n-1
也就是说利用上边的递归解决汉诺塔问题需要2^n-1步 ==>该算法的时间复杂度为O(2 ^ n)
public class Code_03_PrintSubString {
public static void printSubStr(char[] chs,int i,String res){
if(i == chs.length){
System.out.println(res);
return;
}
printSubStr(chs,i + 1,res);//不需要当前的字符的
printSubStr(chs,i + 1,res + chs[i]);//需要当前的字符的
}
public static void main(String[] args) {
String s = "abc";
printSubStr(s.toCharArray(),0,"");
}
}
进阶:打印一个字符串的全部排列,要求不要出现重复的排列
两个的代码:
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年后,母牛的数量。
增长的规律是:1, 2 , 3 , 4 , 6 , 9
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;
}
public static void main(String[] args) {
int n = 20;
System.out.println(cowNumber1(n));
System.out.println(cowNumber2(n));
}
}
实现得到栈中最后一个元素并返回的递归调用过程:
实现逆序当前栈的功能:
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;
}
}
public static void main(String[] args) {
Stack<Integer> test = new Stack<Integer>();
test.push(1);
test.push(2);
test.push(3);
test.push(4);
test.push(5);
reverse(test);
while (!test.isEmpty()) {
System.out.println(test.pop());
}
}
}
一般先列出暴力递归的方法.
当暴力递归中出现可以重复的状态的时候,这种情况都可以改成动态规划的。
有递归实现动态规划的几个点如下(以“数组与目标数”为例):
1.首先写出尝试版本,也就是暴力递归实现的版本
2.列出可变参数范围,在最小路径和中可变参数就是i,j i,ji,j,所以空间表为二维,这里变换参数为i,sum i,sumi,sum,也是两个值,所以空间记录表也是两维的。范围,i就是arr的长度,sum最大范围就是所有数字加起来的和
3.标记终止位置,也就是最后要得到的哪个位置的值
4.根据base case整理出空间表的哪些位置可以提前填好
5.最后普遍位置的值依赖哪些位置,将普通位置的结果填到空间表中
给你一个二维数组,二维数组中的每个数都是正数,要求从左上 角走到右下角,每一步只能向右或者向下。沿途经过的数字要累 加起来。返回最小的路径和。
先使用暴力递归解:
public static int getMinPath(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] + getMinPath(matrix,i,j + 1);
}
if(j == matrix[0].length - 1){
//如果已经到了最后的一列
return matrix[i][j] + getMinPath(matrix,i + 1,j);
}
int right = getMinPath(matrix,i,j + 1);//当前位置向右边走的路径和
int down = getMinPath(matrix,i + 1,j);//当前位置向下边走的路径和
return matrix[i][j] + Math.min(right,down);
}
public static int getMinPath2(int[][] matrix){
if(matrix == null || matrix.length < 1 || matrix[0] == null || matrix[0].length < 1){
return 0;
}
int row = matrix.length;
int col = matrix[0].length;
int[][] dp = new int[row][col];
dp[0][0] = matrix[0][0];
for(int i = 1;i < row;i++){
//填满第一列
dp[i][0] = dp[i - 1][0] + matrix[i][0];
}
for(int j = 1;j < col;j++){
//填满第一行
dp[0][j] = dp[0][j - 1] + matrix[0][j];
}
for(int i = 1;i < row;i++){
for(int j = 1;j < col;j++){
dp[i][j] = Math.min(dp[i][j - 1],dp[i - 1][j]) + matrix[i][j];
}
}
return dp[row - 1][col - 1];
}
//test
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(getMinPath(m,0,0));//12
System.out.println(getMinPath2(m));//12
}
给你一个数组arr,和一个整数aim。如果可以任意选择arr中的 数字,能不能累加得到aim,返回true或者false。(aim与arr中都是正数)
上述图的Demo:
public class Code_08_GetTargetNum {
public static boolean isSum(int[] arr,int target){
return getTarget(arr,0,0,target);
}
public static boolean getTarget(int[] arr,int i,int sum,int target){
if(sum == target){
return true;
}
//sum!=target
if(i == arr.length){
return false;
}
return getTarget(arr,i + 1,sum,target)
|| getTarget(arr,i + 1,sum + arr[i],target);
}
public static void main(String[] args) {
int[] arr = {
1,2,3};
int target = 6;
System.out.println(isSum(arr,target));
}
}
下边使用动态规划来解决这个问题:
上边的代码已经实现。
数组和aim不可变,i,sum可变,本题为后效性问题
return isSum1(arr, i + 1, sum, aim) || isSum1(arr, i + 1, sum + arr[i], aim);
要知道一个普遍位置的值,就得知道它的下一行的值和下一行,向右加arr[i]列的值。
最后一列和最后一行知道了,反过来就能填完了整张表。
使用动态规划的代码:
public static boolean isSum2(int[] arr,int target){
boolean[][] dp = new boolean[arr.length + 1][target + 1];
for(int i = arr.length;i >= 0;i--){
dp[i][target] = true;
}
for(int i = arr.length - 1;i >= 0;i--){
for (int j = target - 1;j >= 0;j--){
dp[i][j] = dp[i + 1][j];
if(j + arr[i] <= target){
dp[i][j] = dp[i][j] || dp[i + 1][j + arr[i]];
}
}
}
return dp[0][0];
}
给定两个数组w和v,两个数组长度相等,w[i]表示第i件商品的 重量,v[i]表示第i件商品的价值。 再给定一个整数bag,要求你挑选商品的重量加起来一定不能超 过bag,返回满足这个条件下,你能获得的最大价值。
【思路】和上边的题目分析思路基本是一样的。
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 0;
}
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];
}
public static void main(String[] args) {
int[] c = {
3, 2, 4, 7 };
int[] p = {
5, 6, 3, 19 };
int bag = 11;
System.out.println(maxValue1(c, p, bag));
System.out.println(maxValue2(c, p, bag));
}
}