贪心算法

贪心算法:是指在对问题进行求解的时候,总是做出在当前看来最好的选择,即不易整体为考虑,而是局部最优解。

  • 贪心算法并不能对所有问题都得出整体最优解,关键是贪心策略的选择。
  • 无后效性:某个状态以前的过程不会影响以后的状态,只与当前状态有关。
  • 一般能用贪心算法解决的问题是:在有一个限定值的情况下,使某个期望值能达到最高或者最低。
  • 无需尝试去证明贪心算法的正确性,贪心策略一般是显而易见的。

贪心是一种思想,要参透它,还是要以刷题为主。

1、买卖股票的最佳时机(简单)-122


题目

这道题的给出了连续好多天一直股票的价格,让我们能制订买卖策略获取最大收益,但是在现实中肯定是不行的。贪心的策略是不要放过任何一次涨,跌之前就卖掉(要真这样我就去炒股好了,我的手就可以拿来弹钢琴了)。

class Solution {
    public int maxProfit(int[] prices) {
        int profit = 0;
        for(int i = 1; i < prices.length; i++) {
            if(prices[i] > prices[i - 1]) {
                profit += prices[i] - prices[i - 1];
            }
        }
        return profit;
    }
}

2、跳跃游戏(中等)-55


题目

这道题说实话我没做出来,我一开始想的是从0开始每次我都跳最大,实在不行我退回来,但是无法通过,后来想想这样做,当跳一跃的时候,必定会影响到后面的一跃。
其实思想应该是:要跳跃到最后一个位置,中间的每个位置一定都能到达,如果把能到达终点的点成为好坐标,就可以从倒数第二个位置开始向前遍历,每个点要能到达终点,必须能够到达离他最近的好坐标,而后它就成了前面点的最近好坐标。最后只要判断0的位置是不是好坐标,就能解决问题了。

class Solution {
    public boolean canJump(int[] nums) {
        int len = nums.length;
        int lastPos = len - 1;
        for(int i = len - 1; i >= 0; i--) {
            if(i + nums[i] >= lastPos) {
                lastPos = i;
            }
        }
        if(lastPos == 0) {
            return true;

        } else {
            return false;
        }
    }
}

3、加油站 -134


题目

一开始我是想从开到下个加油站能剩余最多油的站开始走,但是这样是会出现到了某段路程开不过的情况。我又想,那就不要错过每一个增量,也是行不通的。
其实,因为答案是唯一的,想走完路程,只要总油量比总油耗多,肯定是能走完的。从第一个油站开始,若出现油耗不够,那么起点必须在这个加油站的后面,只要某个点能走到最后一个站,那它必须能走一圈。

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int start = 0;
        int total = 0;
        int sum = 0;
        for(int i = 0; i < gas.length; i++) {
            int rest = gas[i] - cost[i];
            total += rest;
            sum += rest;
            if(sum < 0) {
                start = i + 1;
                sum = 0;
            }
        }
        return total >= 0 ? start : -1;
    }
}

4、判断子序列 -392


题目

一开始没理解轻触题目,哦,这不是洒洒水,我用个26长度的数组统计一下,你来10亿个我也能判断,后来才发现忽略了顺序。还是得乖乖地去遍历字符串,但是比对了别人的答案,相形见绌,别人都是用字符串的方法,而我对api都不熟悉,方法实在不高明啊。力扣归类的是贪心,我没悟到。

我的:
class Solution {
    public boolean isSubsequence(String s, String t) {
        if("".equals(s)) {
            return true;
        }
        if("".equals(t)) {
            return false;
        }
        char[] a = s.toCharArray();
        char[] b = t.toCharArray();
        int j = 0;
        int len = a.length;
        for(int i = 0; i < b.length; i++) {
            if(b[i] == a[j]) {
                j++;
                if(j == len) {
                    return true;
                }
            }
        }
        return false;
    }
}

别人的:
class Solution {
    public boolean isSubsequence(String s, String t) {
        int i = -1;
        for(char c : s.toCharArray()){
           i =  t.indexOf(c,i+1);
            System.out.println(i);
           if(i == -1){
                return  false;
           }
        }
        return true;
    }
}
作者:shu-pi-2
链接:https://dev.lingkou.xyz/problems/two-sum/solution/chong-fen-li-yong-indexofmark-by-shu-pi-2/
来源:力扣(LeetCode)

5、分发糖果 -135


题目

之前做的分糖果题目是孔融让梨一样小吃小大吃大来分大小不同的题目,所以一开始切入的时候我还是在找最低分的孩子,但是这道题每个孩子要比较的只是他相邻的孩子,所以解决不了。那么,一个孩子可能有两个相邻的孩子,所以既要跟左边的相比也要跟右边的相比,就会有两条规矩:最先给每个孩子都发上一个,先从左边开始遍历,只要我比我左边的人分数高,我就要分到比他的糖果,不然我就不开心,再从右边开始遍历,每个人与他右边的孩子相比(没有就不用),最终糖果数分别用两个数组存储,每个孩子都要满足这两个约束,那当然是取多的发。

class Solution {
    public int candy(int[] ratings) {
        int count = 0;
        int n = ratings.length;
        int[] l = new int[n];
        int[] r = new int[n];
        //初始时每个学生都分配1个糖果
        for(int i = 0; i <= n - 1; i++) {
            l[i] = 1;
            r[i] = 1;
        }
        //从左边遍历得到分配数组
        for(int i = 1; i <= n - 1; i++) {
            if(ratings[i] > ratings[i - 1]) {
                l[i] = l[i - 1] + 1;
            }
        }
        //在从右边遍历得到分配数组
        for(int i = n - 2; i >= 0; i--) {
            if(ratings[i] > ratings[i + 1]) {
                r[i] = r[i + 1] + 1;
            }
        }
        for(int i = 0; i < n; i++) {
            count += l[i] > r[i] ? l[i] : r[i];
        }
        return count;
    }
}

6、按要求补齐数组 -330


题目

欸,这道题就比较有难度了。一开始我想的居然是从n开始去减最大的数,然后再n-1继续,然后就没有然后了,怎么可能嘛,真是愚蠢的雷霆球迷。
这道题讲究的是一个覆盖,要覆盖[1, n]的区间,当然使用比n的数来相加,子区间也是同样的道理,所以从1开始,用一个miss来表示1开始的区间覆盖不到的第一个,如果miss>=某个元素nums[i]就证明区间连上了,覆盖不到的就是nums[i]+miss了,并检查下个元素nums[i+1];如果miss<nums,那肯定就要补充数了,贪心就体现在这里了,补充比miss大的数,那miss不能被覆盖到,补上比miss小的数,就有点吃亏了,那miss得变为多少呢?本来前面所有数加上最多是miss-1,又加上了miss,那miss就等于2miss了。

class Solution {
    public int minPatches(int[] nums, int n) {
        int patches = 0;
        //miss找不到的时候是要翻倍的,用Int可能会发生溢出
        long miss = 1;
        int i = 0;
        while(miss <= n) {
            if(i < nums.length && miss >= nums[i]) {
                //两个区间重叠,区间能覆盖到num[i]+miss-1,去一个区间从i+1开始
                miss += nums[i++];

            } else {
                //补上miss这个数,加上前面的数刚好能覆盖到2miss-1
                miss += miss;
                patches ++;
            }
        }
        return patches;
    }
}

7、跳跃游戏Ⅱ -45


题目

因为做过之前跳跃游戏那道题,在解这题的时候我还是用了相同的方法,就是由后往前来遍历,只要能到达终点的就是好坐标,然后遍历某个点能到达的所有好坐标,选出最短的跳数。

class Solution {
    public int jump(int[] nums) {
        int n = nums.length;
        int[] steps = new int[n];
        for(int i = 0; i < n; i++) {
            steps[i] = -1;
        }
        steps[n - 1] = 0;
        for(int i = n - 2; i >= 0; i--) {
            int least = -1;
            for(int j = i + nums[i]; j >= i; j--) {
                if(j <= n - 1 && steps[j] != -1) {
                    //选取跳数最少的
                    int step = steps[j] + 1;
                    if(least == -1) {
                        least = step;

                    } else if(least > step) {
                        least = step;
                    }
                }
            }
            steps[i] = least;
        }
        return steps[0];
    }
}
提交结果

很明显这个耗时是非常不理想的,这个解法还是不够高明。可以看到就是在代码中是嵌套了两层循环,时间复杂度会达到O(n*n)。
其实这个题目已经明确告诉我们是能到达终点的,所以我们从起点开始遍历,每次都跳到可到达点中能跳最远距离的点。

class Solution {
    public int jump(int[] nums) {
        int steps = 0;
        int end = 0;
        int maxPosition = 0;
        for(int i = 0; i < nums.length - 1; i++) {
            //maxPosition表示可达点能跳跃到的最远的距离
            maxPosition = Math.max(maxPosition, nums[i] + i);
            //end表示的是当前点一步可达的范围边界
            if(i == end) {
                end = maxPosition;
                steps ++;
            }
        }
        return steps;
    }
}
提交结果

这次就快很多了,因为只需要遍历一次数组,时间复杂度减低到O(n)。

其实像贪心、回溯、动态规划这些都是思想,主要是要从题目本身出发,剖析它的要求,多做题多实践。贪心主要的思想就是局部最优解,但是前提要是不会影响到下一次的选择。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容