【数据结构与算法-Java】从暴力递归到动态规划

本文详细介绍了机器人步行问题的动态规划解决方案,从暴力递归到使用缓存优化,再到完全动态规划的表格填充过程。同时,讲解了01背包问题,包括二维和一维动态规划数组的应用,探讨了遍历顺序和初始化逻辑的重要性。
摘要由CSDN通过智能技术生成

主要是参考【左程云】和【代码随想录】的讲解,两者都有更丰富的题解可供入门后练习
 

01 从暴力递归到动态规划(机器人步行问题)

参考:


假设有排成一行的N个位置记为1~N,N一定大于或等于2
开始时机器人在其中的M位置上(M一定是1~N中的一个)

  • 如果机器人来到1位置,那么下一步只能往右来到2位置;
  • 如果机器人来到N位置,那么下一步只能往左来到N-1位置;
  • 如果机器人来到中间位置,那么下一步可以往左走或者往右走;

规定机器人必须走K步,最终能来到P位置(P也是1~N中的一个)的方法有多少种
给定四个参数 N、M、K、P,返回方法数量

1.1 暴力递归

思路:

  • 如果机器人来到1位置,那么下一步只能往右来到2位置(下一步cur=2,并且rest-1);
  • 如果机器人来到N位置,那么下一步只能往左来到N-1位置(下一步cur=2,并且rest-1);
  • 如果机器人再其他位置,那么下一步可以往左走或者往右走(下一步有两种选择:①向左走: cur=cur-1,并且rest-1;②向右走: cur=cur+1,并且rest-1);
public class Code01_RobotWalk {

	public static int ways1(int N, int start, int aim, int K) {
		if (N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1) {
			return -1;
		}
		return process1(start, K, aim, N);
	}

	// 机器人当前来到的位置是cur,
	// 机器人还有rest步需要去走,
	// 最终的目标是aim,
	// 有哪些位置?1~N
	// 返回:机器人从cur出发,走过rest步之后,最终停在aim的方法数,是多少?
	public static int process1(int cur, int rest, int aim, int N) {
		if (rest == 0) { // 如果已经不需要走了,走完了!
			return cur == aim ? 1 : 0;
		}
		// (cur, rest)
		if (cur == 1) { // 1 -> 2
			return process1(2, rest - 1, aim, N);
		}
		// (cur, rest)
		if (cur == N) { // N-1 <- N
			return process1(N - 1, rest - 1, aim, N);
		}
		// (cur, rest)
		return process1(cur - 1, rest - 1, aim, N) + process1(cur + 1, rest - 1, aim, N);
	}

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

}

1.2 缓存

思路:

  • 递归的过程会有重复计算的问题
  • 所以建立一个缓存表dp[][],如果结果在dp里有,就直接取出,否则递归
public class Code01_RobotWalk {

	public static int ways2(int N, int start, int aim, int K) {
		if (N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1) {
			return -1;
		}
		int[][] dp = new int[N + 1][K + 1];
		for (int i = 0; i <= N; i++) {
			for (int j = 0; j <= K; j++) {
				dp[i][j] = -1;
			}
		}
		// dp就是缓存表
		// dp[cur][rest] == -1 -> process1(cur, rest)之前没算过!
		// dp[cur][rest] != -1 -> process1(cur, rest)之前算过!返回值,dp[cur][rest]
		// N+1 * K+1
		return process2(start, K, aim, N, dp);
	}

	// cur 范: 1 ~ N
	// rest 范:0 ~ K
	public static int process2(int cur, int rest, int aim, int N, int[][] dp) {
		if (dp[cur][rest] != -1) {
			return dp[cur][rest];
		}
		// 之前没算过!
		int ans = 0;
		if (rest == 0) {
			ans = cur == aim ? 1 : 0;
		} else if (cur == 1) {
			ans = process2(2, rest - 1, aim, N, dp);
		} else if (cur == N) {
			ans = process2(N - 1, rest - 1, aim, N, dp);
		} else {
			ans = process2(cur - 1, rest - 1, aim, N, dp) + process2(cur + 1, rest - 1, aim, N, dp);
		}
		dp[cur][rest] = ans;
		return ans;

	}

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

}

1.3 动态规划

思路:

  • dp[][]可以不通过递归获得,而是找到建立表格的规律,通过for循环填满这个表格
  • dp[cur][rest],如下图:
    • 第一列根据aim确定为0还是为1
    • 后面的每一列
      • 第一行cur=1,下一步是cur=2rest-1,即第一行等于上一列的第二行dp[1][rest] = dp[2][rest - 1];
      • 最后一行cur=N,下一步是cur=N-1rest-1,即最后一行等于上一列的倒数第二行dp[N][rest] = dp[N - 1][rest - 1];
      • 中间的行cur=x,下一步是cur=x+1也可以是cur=x-1,即上一列的 上一行和下一行 相加dp[cur][rest] = dp[cur - 1][rest - 1] + dp[cur + 1][rest - 1]

在这里插入图片描述

public class Code01_RobotWalk {

	public static int ways3(int N, int start, int aim, int K) {
		if (N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1) {
			return -1;
		}
		int[][] dp = new int[N + 1][K + 1];
		dp[aim][0] = 1;
		for (int rest = 1; rest <= K; rest++) {
			dp[1][rest] = dp[2][rest - 1];
			for (int cur = 2; cur < N; cur++) {
				dp[cur][rest] = dp[cur - 1][rest - 1] + dp[cur + 1][rest - 1];
			}
			dp[N][rest] = dp[N - 1][rest - 1];
		}
		return dp[start][K];
	}

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

}

02 背包问题

背包问题感觉参考【代码随想录】的讲解更清楚一点


主要的流程就是:

  1. dp数组含义
  2. 递推公式
  3. 初始化
  4. 遍历顺序

可能会遇到的面试题:
 
要求先实现一个纯二维的01背包
然后再问为什么两个for循环的嵌套顺序这么写? 反过来写行不行?(可以)
再讲一讲初始化的逻辑。
 
然后要求实现一个一维数组的01背包
一维数组的01背包,两个for循环的顺序反过来写行不行?为什么?(不可以,因为这是一个压缩后的,所以必须先遍历物品,再遍历背包)
为什么要倒序遍历?(保证物品只放入一次)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值