22.1.26 递归改动态规划

22.1.26 递归改动态规划

1.套路:

1)递归:根据题目写出递归版本;

2)记忆化搜索:用某种结构存储已经计算过的信息,省去重复计算的过程;

3)严格表结构:将递归套路转化为填表,注意考虑越界问题;

  • 严格表结构的构造过程:

    • 分析可变参数的范围(有几个可变参数就是几维表结构);

    • 标出待求的终止位置;

    • 根据不同的basecase标出不用通过计算就能得到答案的位置;

    • 根据递归,找依赖关系,填表。

2.问题分析:

1)机器人走路问题:
  • 描述:给出1~N,N个位置,给出最终需要到达的位置E,当前位置cur,剩余步数rest。

    • 例如有1,2,3,4四个位置,机器人现在位于位置2,需要到达位置3,还剩3步,返回方案总数。

    • 可执行的方案有:2->3->4->3,2->1->2->3,2->3->2->3 共计三种方案。

  • code(暴力递归版本):

public static int process(int N, int E, int rest, int cur)
{
   if(rest == 0)
       return cur == E ? 1 : 0;//判断方案是否可行的最终条件
   if(cur == 1)//当前是一位置,只能往二位置走
       process(N, E, rest - 1, 2);
   if(cur == N)//当前来到N位置,只能向N-1位置走
       process(N, E, rest - 1, N - 1);
   return process(N, E, rest - 1, cur - 1)
       + process(N, E, rest - 1, cur + 1);//中间位置可以向两边走
}

public static void main(String[] args)
{
   System.out.println(process(4, 3, 3, 2));
}

//结果输出   3
  • code(记忆化搜索版本)

    public static int process(int N, int E, int rest, int cur, int[][] dp)
{
if(dp[rest][cur] != -1)
return dp[rest][cur];//不等于-1说明之前已经计算过,直接返回就行
if(rest == 0)
{
dp[rest][cur] = cur == E ? 1 : 0;
return dp[rest][cur];
}
if(cur == 1)
dp[rest][cur] = process(N, E, rest - 1, 2, dp);
else if(cur == N)
dp[rest][cur] = process(N, E, rest - 1, N - 1, dp);
else
dp[rest][cur] = process(N, E, rest - 1, cur - 1, dp)
+ process(N, E, rest - 1, cur + 1, dp);
return dp[rest][cur];
}

public static void walk1(int N, int E, int rest, int cur)
{
int[][] dp = new int[rest+1][N+1];//记录表
for (int i = 0 ; i <=rest ; i++)
{
for (int j = 0 ; j <=N ; j++)
{
dp[i][j] = -1;//在java中数组全部默认初始化为0,0是我们需要的返回值,所以修改默认初始化
}
}
System.out.println(process(N, E, rest, cur, dp));
}

public static void main(String[] args)
{
walk1(4, 3, 3, 2);
}

//结果 3
  • code(严格表版本)

    public static void main(String[] args)
{
System.out.println(ways2(4, 3, 3, 2));
}

public static int ways2(int N, int E, int rest, int cur)
{
if(rest < 2 || cur < 1 || cur < 1 || cur > N || E < 1 || E > N)
{
return 0;
}
int[][] dp = new int[rest + 1][N + 1];
dp[0][E] = 1;
for (int i = 1 ; i <= rest ; i++)
{
for (int j = 1 ; j <= N ; j++)
{
if(j == 1)
{
dp[i][j] = dp[i - 1][2];
} else if(j == N)
{
dp[i][j] = dp[i - 1][N - 1];
} else
{
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
}
}
}
return dp[rest][cur];
}


//结果   3
2)组成给定硬币面值所使用的硬币个数:
  • 给定一个数组[2,7,3,5,3],表示面值为arr[i]的硬币各一枚,重复出现表示有多枚,给定一个整数10,则组成10所需要的最少的个数为2个(7+3)。

  • code(暴力递归版本)


public static int minCoins1(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim < 0) {
return -1;
}
return process(arr, 0, aim);
}

public static int process(int[] arr, int i, int rest) {
if (i == arr.length) {
return rest == 0 ? 0 : -1;
}
int res = -1;
for (int k = 0; k * arr[i] <= rest; k++) {
int next = process(arr, i + 1, rest - k * arr[i]);
if (next != -1) {
res = res == -1 ? next + k : Math.min(res, next + k);
}
}
return res;
}

public static int minCoins2(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim < 0) {
return -1;
}
int N = arr.length;
int[][] dp = new int[N + 1][aim + 1];

for (int col = 1; col <= aim; col++) {
dp[N][col] = -1;
}
for (int i = N - 1; i >= 0; i--) {
for (int rest = 0; rest <= aim; rest++) {
dp[i][rest] = -1;
if (dp[i + 1][rest] != -1) {
dp[i][rest] = dp[i + 1][rest];
}
if (rest - arr[i] >= 0 && dp[i][rest - arr[i]] != -1) {
if (dp[i][rest] == -1) {
dp[i][rest] = dp[i][rest - arr[i]] + 1;
} else {
dp[i][rest] = Math.min(dp[i][rest],
dp[i][rest - arr[i]] + 1);
}
}
}
}
return dp[0][aim];
}

// for test
public static int[] generateRandomArray(int len, int max) {
int[] arr = new int[(int) (Math.random() * len) + 1];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * max) + 1;
}
return arr;
}

public static void main(String[] args) {
int len = 10;
int max = 10;
int testTime = 10000;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(len, max);
int aim = (int) (Math.random() * 3 * max) + max;
if (minCoins1(arr, aim) != minCoins2(arr, aim)) {
System.out.println("ooops!");
break;
}
}
}

 

posted @ 2022-01-26 21:40  张满月。  阅读(24)  评论(0编辑  收藏  举报