暴力递归到动态规划
题目—》找到暴力递归写法(尝试)
—》把可变参数,不讲究组织的形式,做缓存,那就是记忆化搜索的方法(拥有重复解的前提下)
—》精细化组织----》那就是动态规划
如果暴力过程中没有枚举行为(即通过循环来求得值)
则记忆化搜索和动态规划的时间复杂度一致,没有必要从记忆化搜索再优化为动态规划
什么暴力递归可以继续优化?
有重复调用同一个子问题的解,这种递归可以优化 如果每一个子问题都是不同的解,无法优化也不用优化
暴力递归和动态规划的关系
某一个暴力递归,有解的重复调用,就可以把这个暴力递归优化成动态规划 任何动态规划问题,都一定对应着某-个有解的重复调用的暴力递归 但不是所有的暴力递归,都一定对应着动态规划
面试题和动态规划的关系
解决一个问题,可能有很多尝试方法 可能在很多尝试方法中,又有若干个尝试方法有动态规划的方式 一个问题可能有若干种动态规划的解法
如何找到某个问题的动态规划方式?
1)设计暴力递归:重要原则+4种常见尝试模型!重点! 2)分析有没有重复解:套路解决 3)用记忆化搜索->用严格表结构实现动态规划:套路解决 4)看看能否继续优化:套路解决
面试中设计暴力递归过程的原则
1)每一个可变参数的类型,一定不要比int类型更加复杂 2)原则1)可以违反,让类型突破到一维线性结构,那必须是唯一-可变参数 3)如果发现原则1)被违反,但不违反原则2),只需要做到记忆化搜索即可 4)可变参数的个数,能少则少
常见的4种尝试模型
1)从左往右的尝试模型. 2)范围上的尝试模型 3)多样本位置全对应的尝试模型 4)寻找业务限制的尝试模型
机器人路线问题
假设有排成-行的N个位置,记为1~N, N一定大于或等于2 开始时机器人在其中的M位置上(M -定是1~N中的一个) 如果机器人来到1位置,那么下一步只能往右来到2位置; 如果机器人来到N位置,那么下一步只能往左来到N-1位置; 如果机器人来到中间位置,那么下一步可以往左走或者往右走; 规定机器人必须走K步,最终能来到P位置(P也是1 ~N中的一个)的方法有多少种 给定四个参数N、M、K、P,返回方法数。
暴力递归
public static int ways1(int N, int M, int K, int P) {
if(N<2||K<1||M<1||M>N||P<1||P>N) {
return 0;
}
return walk(N, M, K, P);
}
public static int walk(int N, int cur, int rest, int P) {
if (rest == 0) {
returncur==P?1:0;
}
if(cur==1){
return walk(N, 2, rest - 1, P);
}
if(cur==N){
return walk(N, N .1, rest - 1, P);
}
return walk(N, cur + 1, rest - 1, P) + walk(N, cur - 1, rest - 1, P);
}
动态规划
public static int waysCache(int N, int M, int K, int P) {
if (N<2||K<1||M<1||M>N||P<1||P>N){
return 0;
}
int[][] dp = new int [N+1][K+1];
for(int row = 0; row <= N; row++) {
for(int col = 0; col <= K; col++) {
dp[row][co1] = -1;
}
}
return walkCache(N, M, K, P,dp);
}
public static int walkCache(int N,int cur, int rest, int P, int[][] dp) {
if(dp[cur][rest] != -1) {
return dp[cur][rest];
}
if (rest == 0) {
dp[cur][rest] = cur==P?1: 0;
return dp[cur][rest];
}
if (cur== 1) {
dp[cur][rest] = walkCache(N, 2, rest - 1, P, dp);
return dp[cur][rest];
}
if (cur= N) {
dp[cur][rest] =walkCache(N, N - 1, rest .1, P,dp);
return dp[cur][rest];
}
dp[cur][rest] = walkCache(N, cur + 1, rest - 1, P,dp)
+ waLkCache(N, cur - 1, rest - 1, P, dp);
return dp[cur][rest];
}
计划搜索
动态规划 = 暴力递归+缓存
背包问题递归到动态规划
public static int dpWay(int[] W, int[] V, int bag) {
int N = W. length;
int[][] dp = new int[N + 1][bag + 1];
for (int index = N - 1; index >= 0; index--) {
for (int rest = 0; rest <= bag; rest++) {
int p1 = dp[index+1][rest];
int p2 = -1;
if(rest - w[index] >= 0) {
p2 = v[index] + dp[index + 1][rest - w[index]];
}
}
}
return dp[e][bag];
}
字符串转化问题递归到动态规划
原方法:
public static int number(String str) {
if (str == null II str.1ength() == 0) {
return 0;
}
return process(str .toCharArray(), 0);
}
public static int process(char[] str, int i) {
if (i == str.length) {
return 1;
}
if (str[i] == '0') {
return 0;
}
if (str[i] == '1') {
int res = process(str, i + 1);
if (i + 1<str.1ength) {
res += process(str, i + 2);
}
return res;
}
if (str[i] == '2') {
int res = process(str, i + 1);
if (i + 1< str.1ength && (str[i + 1] >= '0' && str[i + 1] <= '6')) {
res += process(str, i + 2);
}
return res;
}
return process(str, i + 1);
}
动态规划
public static int number(String str) {
if (str == null II str.1ength() == 0) {
return 0;
}
return process(str .toCharArray(), 0);
}
public static int process(char[] s, int i) {
if (str == null II str.1ength() == 0) {
return 0;
}
char[] str =s.toCharArray();
int N =str.length;
int []dp=new int[N+1];
dp[N]=1;
for(int i=N-1;i>=0;i--){
if (str[i] == '0') {
dp[i]=0;
}
if (str[i] == '1') {
dp[i]=dp[i+1];
if (i + 1<str.1ength) {
dp[i]+=dp[i+2];
}
}
if (str[i] == '2') {
dp[i]=dp[i+1];
if (i + 1< str.1ength && (str[i + 1] >= '0' && str[i + 1] <= '6')) {
dp[i]=dp[i+2];
}
}
}
return dp[0];
}
拿牌问题递归到动态规划
范围上的模型
暴力递归
public static int win1(int[] arr) {
if (arr == nu11|I arr.length == 0) {
return 0;
}
return Math . max(f(arr,0, arr.length - 1), s(arr, 0, arr .1ength - 1));
}
public static int f(int[] arr, int L, int R) {
if(L==R){
return arr[4];
}
return Math. max(
arr[L]+ s(arr, L + 1, R),arr[R] + s(arr, L, R - 1));
}
public static int s(int[] arr, int i, int j) {
if (i=j) {
return 0;
}
return Math .min(f(arr, i + 1, j)
,f(arr, i, j - 1));
}
动规
f作为一张表缓存
s作为一张表缓存
L>R时,数据无效,即数组左下半区无效
/pic:mw://2c14ddb958445ac6716418d6047774b8
public static int win2(int[] arr) {
if (arr == nu11|I arr.length == 0) {
return 0;
}
int N=arr.length;
int [][]f=new int[N][N];
int [][]f=new int[N][N];
for(int i=0;i<N;i++){
f[i][i]=arr[i];
s[i][i]=0;
}
for(inti=1;i<N;i++){
int L =0;
int R =i;
while(L<N&&R<N){
f[L][R] = Math . max(
arr[L] + s[L + 1][ R],
arr[R] + s[L][R - 1]
);
s[L][R] = Math.min(
f[L + 1][R],
f[L][R - 1]
);
L++;
R++;
}
}
return Math.max(f[0][arr.length - 1]
,s[0][arr.length - 1]
);
}
拿钞票问题递归到动态规划
一个数组,里面的元素代表钞票面额,每种钞票都可以无穷次的拿,数组中无重复值、均为正数
给一个目标值,求用数组中有多少种办法将目标值凑出来?
public static int ways(int[] arr, int aim) {
if(arr==nu1l||arr.1ength=0|1aim<0){
return 0;
}
return process(arr, 0, aim);
}
public static int process(int[] arr, int index, int rest) {
if(index == arr.1ength) {
return rest ==0?1 :0 ;
}
int ways = 0;
for(int zhang = 0;zhang * arr[index] <= rest ;zhang++) {
ways += process(arr, index + 1, rest - (zhang * arr[index]) );
}
return ways;
}
有重复过程,所以有必要优化
/pic:mw://47af1d23675ae4e0b08835876cfe587d
public static int ways2(int[] arr, int aim) {
if (arr == nu1l || arr .1ength == 0|1 aim < 0) {
return 0;
}
int[][] dp = new int[arr .1ength+1][aim+1];
= -1
for(int i = 0 ; i < dp.1ength; i++) {
for(int j = 0 ; j < dp[8].1ength; j++) {
dp[i][j] = -1;
}
}
return process2(arr, 0,aim,dp);
}
public static int process(int[] arr, int index, int rest,int [][]dp) {
if(dp[index][rest] != -1) {
return dp[index][rest];
}
if(index == arr.1ength) {
dp[index][rest]=rest ==0?1 :0 ;
return dp[index][rest];
}
int ways = 0;
for(int zhang = 0;zhang * arr[index] <= rest ;zhang++) {
ways += process(arr, index + 1, rest - (zhang * arr[index]),dp );
}
dp[index][rest]=ways;
return ways;
}
动态规划
由下到上进行计算,每一行从左往右
public static int ways2(int[] arr, int aim) {
if (arr == nu1l || arr .1ength == 0|| aim < 0) {
return 0;
}
int N=arr.length;
int[][] dp = new int[N+1][aim+1];
dp[N][0]=1;
for(int index = N - 1; index >= 0; index--) {
for(int rest = 0; rest <= aim; rest++) {
int ways = 0;
for(int zhang = 0;zhang * arr[index] <= rest ;zhang++) {
ways += dp[index + 1][rest - (zhang * arr[index])];
}
dp[index][rest] = ways;
}
}
return dp[0][aim];
}
因为有枚举行为,可以进行优化
比如,f(3,100) 其实是依赖 f(3,97)的
/pic:mw://55c6b92ec4c67512854029c1d24dab3a
public static int ways2(int[] arr, int aim) {
if (arr == nu1l || arr .1ength == 0|1 aim < 0) {
return 0;
}
int N=arr.length;
int[][] dp = new int[N+1][aim+1];
dp[N][0]=1;
for(int index = N - 1; index >= 0; index--) {
for(int rest = 0; rest <= aim; rest++) {
dp[index][rest] = dp[index+1][rest];
if(rest-arr[index]>=0){
dp[index][rest]+=dp[index][rest-]
}
}
}
return dp[0][aim];
}
字符贴纸问题
给定一个字符串str,给定一个字符串类型的数组arr。 arr里的每一-个字符串, 代表一张贴纸, 你可以把单个字符剪开使用,目的是 拼出str来。 返回需要至少多少张贴纸可以完成这个任务。 例子: str= “babac”,; a arr = {“a”,",“abcd”} 至少需要两张贴纸"ba"和"abcd",因为使用这两张贴纸,把每-个字符单独剪 开,含有2个a、2个b、1个c。是可以拼出str的。所以返回2。
public static int minStickers1(String[] stickers, String target) {
int n = stickers. length;
int[][] map = new int[n][26];
for(inti=0;i<n;i++){
char[] str = stickers[i]. toCharArray();
for (char C_ : str) {
map[i][c - 'a]++;
}
}
HashMap<String, Integer> dp = new HashMap<>();
dp.put("", 0);
return process1(dp, map, target);
}
public static int process1(
HashMap<String, Integer> dp,
int[][] map,
String rest) {
if (dp. containsKey(rest)) {
return dp. get(rest);
}
int ans = Integer .MAX_ VALUE;
int n = map.1ength;
int[] tmap = new int[26];
char[] target = rest. toCharArray();
for (char C : target) {
tmap[c - 'a']++;
}
for(inti=0;i<n;i++){.
if (map[i][target[0] - 'a'] == 0) {
continue;
}
StringBuilder sb = new StringBuilder();
for(intj=0;j<26;j++){
if (tmap[j] > 0) {
for (int k = 0; k < Math.max(0, tmap[j] - map[i][j]); k++) {
sb. append((char) ('a'+ j));
}
}
String s = sb. toString();
int tmp = process1(dp, map, s);
if (tmp != -1) {
ans = Math.min(ans, 1 + tmp);
}
dp. put(rest, ans == Integer .MAX_ .VALUE ? -1 : ans);
return dp.get(rest);
}
最长公共子序列问题
两个样本问题模型
/pic:mw://c4ac17e6e1dee6bb4eeff0a89ced8f90
情况1
最长公共子序列 不以str1 的最后一个字符结尾,也不以str2的最后一个字符结尾
情况2
最长公共子序列 以str1 。。。结尾,不以str2。。。结尾
情况3
情况2取反
情况4
情况1取反
public static int lcse(char[] str1, char[] str2) {
int[][] dp = new int[str1. length][str2.1ength];①
dp[0][0] = str1[0] == str2[0] ? 1 : 0;
for (int i = 1; i < str1.length; 1++) {
dp[i][0] = Math.max(dp[i - 1][0], str1[i] = str2[0] ? 1 : 0);
for (int j = 1; j < str2.1ength; j++) {
dp[0][j] = Math.max(dp[0][j - 1], str1[0] == str2[j] ? 1 : 0);
}
for (int i = 1; i < str1.1ength; i++) {
for (int j = 1; j < str2.1ength; j++) {
dp[i][j] = Math.max(dp[i - 1][i], dp[i][j - 1]);
if (str1[i] = str2[j]) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1);
}
}
}
return dp[str1.1ength - 1][str2.1ength - 1];
}
业务限制的尝试模型
给定一个数组,代表每个人喝完咖啡准备刷杯子的时间 只有一台咖啡机,一次只能洗一个杯子,时间耗费a,洗完才能洗下一杯 每个咖啡杯也可以自己挥发干净,时间耗费b,咖啡杯可以并行挥发 返回让所有咖啡杯变干净的最早完成时间 三个参数: int[] arr、 int a 、 int b
public static int process(int[] drinks, int a, int b,int index, int washLine) {
if (index == drinks.1ength - 1) {
return Math . min(
Math . max(washLine, drinks[index]) + a
, drinks[index] + b
);
int wash = Math. max(washLine, drinks[index]) + a;
int next1 = process(drinks, a, b,index + 1, wash);
int p1 = Math. max(wash, next1);
int wash = Math. max(washLine, drinks[index]) + a;
int next1 = process(drinks, a, b, index + 1, wash);
int p1 = Math . max(wash, next1);
int dry = drinks[index] + b;
int next2 = process(drinks, a, b, index + 1, washLine);
int p2 = Math . max(dry, next2);
}
return Math.min(p1, p2);
}
|