算法_贪心算法篇

Posted 葡萄籽-June

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法_贪心算法篇相关的知识,希望对你有一定的参考价值。

LeetCode_跳跃游戏

本文通过跳跃游戏的几个算法例题总结一下相关贪心算法,开始学习的阶段,借鉴好的算法思路并学习,也巩固一下最近学习的算法。(后续遇到贪心算法会续更~~)



前言

贪心算法——在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅仅是在某种意义上的局部最优解。

贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性(即某个状态以后的过程不会影响以前的状态,只与当前状态有关。)

贪心算法的基本思路:

  • 建立数学模型来描述问题
  • 把求解的问题分成若干个子问题
  • 对每个子问题求解,得到子问题的局部最优解
  • 把子问题的解局部最优解合成原来问题的一个解

该算法存在的问题:

  • 不能保证求得的最后解是最佳的
  • 不能用来求最大值或最小值的问题
  • 只能求满足某些约束条件的可行解的范围

贪心策略适用的前提是:局部最优策略能导致产生全局最优解。


一、跳跃游戏I

=>题目:
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。

示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2: 输入:nums = [3,2,1,0,4]输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。


提示:
1 <= nums.length <= 3 * 104
0 <= nums[i] <= 105

大致思路:
分析:
由题目得出,要想到达最后一个下标,得满足两个条件:

1、假设每个位置都能跳到,那么只需要遍历数组,看看有没有位置能直接通过这个位置上的数字跳到结尾。
比如[2,3,1,1,4],我们遍历数字,看看哪个位置可以跳到最后,可以发现第三个位置的数字是2,所以可以通过第三个位置跳到最后的下标,数组成立。

2、上述假设成立的还有个条件就是每个位置是否都能跳到。
比如[3,2,1,0,4],按照上面的逻辑,第三个位置可以跳跃1步到第4个位置,但第4个位置不可跳跃,因此不能到达最后位置。


public class CanJumpGames {
//方法1
    public boolean canJump(int[] nums) {
    //n为步数,初始为1
        int step = 1;
        //从倒数第2位开始检验
        for (int i = nums.length - 2; i >= 0; i--) {
        //nums[i]大于或等于step,说明从此位置可以跳到最后一位
            if (nums[i] >= step) {
                step = 1;
            } else {
            //若nums[i]小于step,说明从此位置不能跳到最后一位,则step++,向前遍历查看其它位置是否能跳到最后一位
                step++;
            }
            //遍历到位置0时,且跳跃的步数大于1,则返回false
            if (i == 0 && step > 1) {
                return false;
            }
        }
        return true;
    }
    
//方法2
public boolean canJump(int[] nums) { 
        //能到达的最大位置k 
        int k =0; 
        //获取数组长度 
        int len = nums.length; 
        //遍历数组 
        for(int i=0;i<len ;i++){ 
            if(k<i) return false; 
            k=Math.max(k,i+nums[i]); 
            if (k >= len -1) { 
            //减少循环次数
             return true; 
            } 
        } 
        return false; 
    } 
}

二、跳跃游戏II

题目:
给你一个非负整数数组 nums ,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
假设你总是可以到达数组的最后一个位置。

示例 1:
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:
输入: nums = [2,3,0,1,4]
输出: 2

大致思路:
简单分析:[2,3,1,1,4]中,第一步数字是2,可以跳到数字是3和数字是1的两个地方,贪心选择大的,即选择跳到3那步;从3即下标1的位置可以跳到数字分别是1,1,4的三个地方,贪心选择4;而4即为最后一位,最终结果是需要跳2次。


public class CanJump2 {
//  贪心
    public int jump(int[] nums) {
//        若数组长度为1,则不需要跳跃
        if(nums.length == 1) {return 0;}
//        每次跳跃的开始位置
        int reach = 0;
//        跳跃后到达的位置的数值,开始在下标为0的位置
        int nextreach = nums[0];
//        跳跃步数
        int step = 0;
        for(int i = 0;i<nums.length;i++){
//            取目前最大的跳跃
            nextreach = Math.max(i+nums[i],nextreach);
            if(nextreach >= nums.length-1) {return (step+1);}
            if(i == reach){
//              当nextreach下标的值不能跳跃到最后一位时,step++且让reach到达nextreach下标的位置
                step++;
                reach = nextreach;
            }
        }
        return step;
    }

    //动态规划 较占用内存,消耗时间较长
    public int jump01(int[] nums) {
        int[] dp = new int[nums.length];//dp[i] 为到达 i 位置的最小跳数
        dp[0] = 0;//到达下标0的最小跳数是0
        for (int i = 1; i < nums.length; i++) {
            dp[i] = Integer.MAX_VALUE;//先把到达此步数的设置为无限大,作为下面取最小步数时不会有误的临界值
            for (int j = 0; j < i; j++) {
                if (i - j <= nums[j]) {
//                    选择 i 之前的能跳到 i 的所有位置 j 中, dp[j] 值最小的位置 j 作为上一步要跳到 i 的位置
                    dp[i] = Math.min(dp[i], dp[j] + 1);//dp[i]=dp[j]+1 ,从 j 跳到 i ,步数加1
                }
            }
        }
//        dp[nums.length-1] 即到达最后一个位置的最小跳数
        return dp[nums.length - 1];
    }
}

以上是关于算法_贪心算法篇的主要内容,如果未能解决你的问题,请参考以下文章

贪心算法:总结篇!(每逢总结必经典)

贪心算法:划分字母区间

贪心算法练习

763. 划分字母区间-贪心算法

算法篇贪心算法介绍——基于Python实现的爬山算法

贪心算法学习手册开放下载!!