DP问题最主要的解题思路在于将给定的问题分解为层层递归的子问题,通过子问题与子问题之间的先后关系来依次求解直到获得原始问题的最终答案。因此如何定义子问题与子问题之间的排列顺序也是DP解题过程中很重要的一个方面。
下面我们来看一下跳跃游戏这个题目。
给定一个非负整数数组 nums,最初位于数组的第一个下标 。
数组中的每个元素代表在该位置时可以跳跃的最大长度。
判断是否能跳到最后一个下标。
实例如下,
int[] nums = {3, 1, 2, 0, 4}
其答案是我们可以从1号位置跳到终点, 因为可以从1号位置跳两步到达3号位置,然后再跳两步到达终点5号位置。
使用DP的解题思路如下:
首先,我们知道从1号位置出发,最多可以跳三步,也就是说,可以到达2号,3号和4号位置中的一个,那么我们就可以将原问题"能否从1号位置跳到终点"转化为"能否从2号,3号或4号位置跳到终点"。
同样的,"能否从2号位置跳到终点"这个子问题可以转化为"能否从3号位置跳到终点"。
由此,这个问题就可以表达成如下的逻辑表达式
F(k) =
True (k >= N)
F(k + 1) OR F(k + 2) OR ... F(k + nums[k])
这里N是nums数组的长度,k是数组下标,F(k)是一个返回True/False的函数,表示能否从k号位置跳到终点。
从上面的表达式中,我们不难看出要得到F(k)的结果,必须也得到F(k + 1), F(k + 2) 等等的结果,也就是说,求解顺序是根据数组坐标从大到小的排列顺序。这点确定了之后,编程实现就轻而易举了。
Java 代码如下
class BottomUp {
public boolean canJump(int[] nums) {
int N = nums.length;
if (N == 1) {
return true;
}
boolean[] dp = new boolean[N + 1];
//终点
dp[N - 1] = true;
//外循环,数组下标从N-2到0
for (int i = N - 2; i >= 0; i --) {
int jump = nums[i];
boolean canJump = false;
//内循环,数组下标从nums[i]到1
for (int j = jump; j >= 1; j --) {
if ((i + j >= N) || dp[i + j]) {
canJump = true;
break;
}
}
dp[i] = canJump;
}
//能否从起点跳到终点"
return dp[0];
}
}