开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情
有一句话叫:蓝桥杯=暴力杯。
在暴力这条路上确实很多时候有用,今天我们来聊一聊暴力递归问题。
什么是暴力递归?
一句话就是尝试。很多帖子会说:暴力递归的关键在于找到一种决策,即一种试的方法。
那么怎么找到这种决策?这对于我们初学者或者是刷题比较少的小伙伴来说,非常不友好,很多时候,就是找不到足够决策,导致后续没法进行。
其实我们大可以从最简单的代码做起,然后不停优化。
try(递归版本)-》记忆化搜索(dp)-》二维表形式的动态规划(1.分析可变参数范围,2.标出结束位置,3.推出前提和参数关系,4.确定顺序关系)-》严格动态规划。
例子:
有一个数组,里面的值表示硬币面值,且每一个值只有一个。那么我们要组合成 X 的和,至少需要几个硬币?
我们定义一个arr数组,表示硬币面值,index表示从index往后取硬币,aim表示我们目标值。
我们知道,对每个index位置,我们都可以选择要或者不要。如果要,那么接下来aim更新成,aim-arr[index],然后index++,继续选择要或者不要。那么就形成了一个递归。
首先讨论临界情况,如果aim=0,那么什么硬币都不选,输出最少0个硬币。如果aim<0,不可能有解(取硬币方法),使用返回-1,代表无解。如果index==arr.length,表示一句不能再有硬币取了,返回-1。
其他情况就是:递归调用。arr不变,index++,aim有两种情况,等于aim或者aim-arr[index]。
代码如下:
public static int f(int[] arr, int index, int res) {
if (res < 0) {
return -1;
}
if (res == 0) {
return 0;
}
if (index == arr.length) {
return -1;
}
int p1 = f(arr, index + 1, res);
int p2 = f(arr, index + 1, res - arr[index]);
if (p1 == -1 && p2 == -1) {
return -1;
} else {
if (p1 == -1) {
return p2 + 1;
}
if (p2 == -1) {
return p1;
}
return Math.min(p1, p2 + 1);
}
}
如同样式,我们知道如果在最左边出现一个f(index,aim)最右边也出现,那么我们需要计算两次,所以优化1,我们可以添加一个dp缓存。
给这个dp数组(二维数组)定义初始化值为-2.
public static int min1(int[] arr, int aim) {
int[][] dp = new int[arr.length + 1][aim + 1];
for (int i = 0; i <= arr.length; i++) {
for (int j = 0; j <= aim; j++) {
dp[i][j] = -2;
}
}
return f1(arr, 0, aim, dp);
}
f函数需要另外添加一个参数,当dp[index]【aim】!=-2时候,表示之前就遇到过,直接返回数组值就是。在返回正确,都需要修改dp数组。
优化2,我们把一个二维数组行表示index,列表示aim
按照递归条件我们可以给数组这样赋值。那么dp[1]【1】=?
我们知道根据p1,p2,可知,dp和index+1的res有关