前言
看到leetcode45题的时候,发现标记是困难,但是定睛一看,这不就是一个非常简单的DP问题么。抱着这种想法,我想着应该可能是int型溢出可能需要单独处理吧。所以很小心的来处理int的溢出问题。
最后运行的时候发现问题是超时。
这时候就回过头来仔细想了想:
1、已经使用了DP来解决了,而且都是一维的DP,子空间结构改进的地方不多的。
2、本问题具有最优子结构是毋庸置疑的,但是使用DP后还是超时。显然:本问题需要使用贪心算法去解决。
贪心法解决
贪心法解决代码写起来比较简单,但是有两个问题需要考虑的:
1、贪心的原则;
2、证明贪心选择性质——即做局部最优选择后能够得到全局最优选择。
贪心原则
先给出一些问题的符号说明,方便后续形式化的描述:
1、输入数组是nums[0,…,n-1]
2、子问题 S i S_i Si表示求解问题nums[i,…,n-1]
对于某个子问题 S i S_i Si,每一步的贪心选择原则如下:
在 [ i + 1 , i + n u m s [ i ] ] [i+1, i+nums[i]] [i+1,i+nums[i]]范围内,找 j m a x j_{max} jmax,有: ∀ j ∈ [ i + 1 , i + n u m s [ i ] ] , j m a x + n u m s [ j m a x ] ≥ j + n u m s [ j ] \forall j \in [i+1, i+nums[i]], jmax+nums[jmax] \geq j+nums[j] ∀j∈[i+1,i+nums[i]],jmax+nums[jmax]≥j+nums[j]
通俗的说,找一个能够跳到最远距离的下一位置。
贪心选择性质的证明
考虑子问题 S i S_i Si,假设其最优解中的某一步是跳转到 k k k位置,而在该一步根据贪心选择应该是跳转到 j m a x j_{max} jmax位置:
1、如果 k + n u m s [ k ] = = j m a x + n u m s [ j m a x ] k+nums[k] == j_{max}+nums[j_{max}] k+nums[k]==jmax+nums[jmax],那么 k k k就是我们的贪心选择选择出来的一种情况,贪心选择性质平凡成立;
2、如果 k + n u m s [ k ] < j m a x + n u m s [ j m a x ] k+nums[k] < j_{max}+nums[j_{max}] k+nums[k]<jmax+nums[jmax],那么我们知道,子问题 S i S_i Si的最优解就是:
1 + 子问题 S k S_k Sk的最优解那么对于子问题 S k S_k Sk我们假设其下一跳转位置是 k 1 k_1 k1,我们显然知道:
k 1 ≤ k + n u m s [ k ] ≤ j m a x + n u m s [ j m a x ] k_1 \leq k+nums[k] \le j_{max}+nums[j_{max}] k1≤k+nums[k]≤jmax+nums[jmax]
1、如果有 j m a x < = k 1 < j m a x + n u m s [ j m a x ] j_{max} <= k_1 < j_{max}+nums[j_{max}] jmax<=k1<jmax+nums[jmax],也就意味着,我们在前一步选择中使用 j m a x j_{max} jmax 替换 k k k的话,我们在下一步仍然可以选择 k 1 k_1 k1位置,即我们使用 j m a x j_{max} jmax 替换 k k k仍然能够得到最优解
2、如果 k 1 < j m a x k_1 < j_{max} k1<jmax的话,那么对于 k ′ ∈ [ k 1 , j m a x − 1 ] k' \in [k_1, j_{max}-1] k′∈[k1,jmax−1]我们知道 k ′ + n u m s [ k ′ ] < j m a x + n u m s [ j m a x ] k'+nums[k'] < j_{max}+nums[j_{max}] k′+nums[k′]<jmax+nums[jmax]的。也就是说,如果跳转到 k 1 k_1 k1的话,不管你怎么跳转,经过多少次跳转,跳转的位置都是小于 j m a x + n u m s [ j m a x ] j_{max}+nums[j_{max}] jmax+nums[jmax]的,也就是说,肯定会有一次跳转的位置落在 [ j m a x , j m a x + n u m s [ j m a x ] ] [j_{max}, j_{max}+nums[j_{max}]] [jmax,jmax+nums[jmax]]的。如果是这种情况的话,我们直接在开始就使用 j m a x j_{max} jmax替换 k k k的话,会得到一个更优的解,这与假设矛盾。
综上,得证。
DP的代码
int bottomUpSolution(vector<int> &nums)
{
if(nums.size() <= 1)
return 0;
int n = nums.size();
vector<long long> DP(nums.size(), INT_MAX); //-1表示无法到达
for(auto &ele : DP)
++ele;
DP[n-1] = 0;
for(int i = n-2; i >= 0; i--)
{
if(nums[i] != 0)
{
if(i+nums[i] >= n-1)
DP[i] = 1;
else
{
for(int j = i+1; j <= i+nums[i]; j++)
if(DP[j] != -1)
DP[i] = min(DP[i], 1+DP[j]);
}
}
}
return (int)DP[0];
}
贪心的代码
int jump_greedy(vector<int> &nums)
{
if(nums.size() <= 1)
return 0;
int n = nums.size();
int loc = 0;
int step = 0;
while(loc < n-1)
{
if(loc+nums[loc] >= n-1)
return (step+1);
int jmax = loc+1;
for(int j = loc+2; j <= loc+nums[loc]; j++)
if((j+nums[j]) > (jmax+nums[jmax]))
jmax = j;
loc = jmax;
++step;
}
return step;
}