LeetCode算法题6:贪心 - 跳跃游戏


前言

      贪心算法系列:(之前还有一篇文章描述的也是贪心算法:https://blog.csdn.net/Little_ant_/article/details/116098188

贪心算法:

      以下摘自百度百科:

      贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,算法得到的是在某种意义上的局部最优解。
      贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择。

      利用贪心法求解的问题应具备如下2个特征 。

      1、贪心选择性质
      一个问题的整体最优解可通过一系列局部的最优解的选择达到,并且每次的选择可以依赖以前作出的选择,但不依赖于后面要作出的选择。这就是贪心选择性质。对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解 。

      2、最优子结构性质
      当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用贪心法求解的关键所在。在实际应用中,至于什么问题具有什么样的贪心选择性质是不确定的,需要具体问题具体分析(这里指的是采用何种贪心策略) 。

      贪心算法不从整体最优上加以考虑,所做出的仅是在某种意义上的局部最优选择。使用贪心策略要注意局部最优与全局最优的关系,选择当前的局部最优并不一定能推导出问题的全局最优。贪心策略解题需要解决以下两个问题:
      1、该问题是否适合使用贪心策略求解,也就是该问题是否具有贪心选择性质;
      2、制定贪心策略,以达到问题的最优解或较优解。

一、跳跃游戏

      题目链接:https://leetcode-cn.com/problems/jump-game/

      题目描述:给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标。

思路

      判断是否能够到达最后一个下标,那如果每次我们都在当前可选的位置区间内选择一个位置,满足从这个位置出发能够走的距离更远(覆盖其他位置的可达处),那么这个位置就是当前的最优选择。而我们尽最大努力一步步跳跃,如果最后也不能到达最后一个下标,那无论如何也到不了了,这个便是最优子结构性质。

      如何选择当前最优的位置?以序列 3、5、4、2、9、6、1…作为示例简单说明。初始时所在位置为 0,当前值为 3,所以下一步可跳跃的区间为[1,3];
      然后分别在区间下标 1、 2、 3 处判断,如果下一步跳到 1 处,下下一步的区间[2,6],如果下一步跳到 2 处,下下一步的区间为[3,6],如果下一步跳到 3 处,下下一步的区间为[4,5];
      此时,因为 3 处能够到达的最远距离只能到 5,它所能到达的位置,1 和 2 都可以到达,那我们在 1 或 2 中随便选一个即可,不会影响最终结果。
      假设选 1:那么接下来就会选择下标 4 处… 。选 2 的话接下来也是选择下标 4 …

      参考算法如下:

	public boolean canJump(int[] nums) {
        int value,nextI=0; //nextI 表示在当前区间内选择下一跳的位置

        for(int i=0;i<nums.length;){
        	if(i==nums.length-1)//边界条件1 已经到达最后一个位置时返回true
        		return true;
            value=-1; // 表示 nextI 能够到达的最远位置

            int tmp=nums[i];//tmp 表示当前可选区间长度

            if(tmp==0&&i!=nums.length-1)//边界条件2 如果长度为 0,并且当前 i 不是最后一个位置时返回false
                return false;

            for(int j=i+1;tmp!=0;j++,tmp--){//求区间内的 top 1
                if(j>=nums.length-1)//边界条件3 当前区间内有下标超出了最后一个位置时返回true
                    return true;
                if((nums[j]+j)>value){ //如果有多个相同的最远距离时取第一个, 改为 >= 时取最后一个
                    value=nums[j]+j;
                    nextI=j;
                } 
            }
            i=nextI;
        }
        return false; //这个语句不会被执行。
	}

      上面那个算法过于复杂了,本题其实只需要更新保存最远跳跃距离即可。参考代码如下:

	//贪心:维护最远可到达距离,如果它>=最后一个位置,返回true;否则返回false
    public boolean canJump(int[] nums) {
        int value=0;// 维护最远能够到达的位置,初始值只能为0.

        for(int i=0;i<nums.length;i++){
            if(i<=value){
                value=Math.max(value,i+nums[i]);
                if(value>=nums.length-1)
                    return true;
            }
            else
                return false;
        }
        return false;//这个语句不会被执行
    }

二、跳跃游戏II

      题目链接:https://leetcode-cn.com/problems/jump-game-ii/submissions/

      题目描述:给你一个非负整数数组 nums ,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

假设你总是可以到达数组的最后一个位置。

思路

      上一道题的解法 1 的功能不就是在判断最小的跳跃次数(最优的跳跃位置)能否到达终点嘛,所以将它改一下就好了。边界条件 2 需要去掉,因为本题已经告诉我们一定可以到达最后一个位置。

      参考算法如下:

	public int jump(int[] nums) {
        int value,nextI=0,ans=0; //nextI 表示在当前区间内选择下一跳的位置

        for(int i=0;i<nums.length;){
        	if(i==nums.length-1)//边界条件1 已经到达最后一个位置时返回true
        		return ans;
            value=-1; // 表示 nextI 能够到达的最远位置

            int tmp=nums[i];//tmp 表示当前可选区间长度
            for(int j=i+1;tmp!=0;j++,tmp--){//求区间内的 top 1
                if(j>=nums.length-1)//边界条件3 当前区间内有下标超出了最后一个位置时返回true
                    return ans+1;
                if((nums[j]+j)>value){ //如果有多个相同的最远距离时取第一个(改为 >= 时取最后一个)
                    value=nums[j]+j;
                    nextI=j;
                } 
            }
            i=nextI;
            ans++;
        }
        return 1; //这个语句不会被执行。
	}

      有一个超时的回溯算法了解一下:

   int mn=Integer.MAX_VALUE;
    public int jump(int[] nums) {
        solve(nums,0,0);
        return mn;
    }
    public void solve(int[] nums,int start,int count){
        if(start>=nums.length-1){
            mn=Math.min(mn,count);
            return;
        }
        for(int i=start+1,c=nums[start];c>0;i++,c--)
            solve(nums,i,count+1);
    } 

总结

      深度优先遍历是回溯算法使用的策略。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值