从leetcode45题简单聊起

前言

看到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}] k1k+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,jmax1]我们知道 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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值