关于贪心算法的一点理解 2019-10-07(未经允许禁止转载)

首先了解一下什么是贪心算法

贪心算法

指在寻求一个问题的解决时,将问题1)分解为多个阶段,每个阶段都基于当前问题所处的状态2)寻求该阶段的最优解,然后将3)各阶段的最优解组合形成整个问题的一个解决
通俗地说,贪心 = 走一步,看一步, 只顾眼前

贪心算法的特点 - 局部最优

因为每一步只关心眼前的利益,因此产生的是局部最优;而往往局部最优的组合并不是全局最优。贪心只能产生局部最优解

贪心算法的应用 - 求 局部解和是否解

  • 求某些问题的局部解。面对NP问题(Non-deterministic Polynomial,即多项式复杂度的非确定性问题关于NP问题可参考这里)时,由于求全局最优解的时间复杂度为非多项式级,会相当耗费资源,因此往往使用高效省资源的贪心算法求得一个局部最优解去逼近或者说替代全局最优解。人生其实就是一个不断使用贪心算法的过程,因为我们无法预知一生中的种种情况,不可能穷举出所有的情况然后寻找一条最优的路径,显然,求人生最优解的复杂度是非多项式级的;因此我们不断地贪心,选择走对现在的我们而言最好的路
  • 求某些问题的是否解。即我们不关心我们的解是否是最优,而只关心我们能否找到问题的一个解,问题能够得到解决究竟是“是”还是“否”,经常可以使用贪心算法,通过找出问题的一个解,证明问题是可解的

贪心算法的例子

    1. 集合覆盖问题(局部解问题)
      存在如下表所示的广播台,以及广播台信号可以覆盖的地区。 选择最少的广播台,让所有的地区都可以接收到信号
广播台 覆盖地区 备注
K1 ID,NV,UT
K2 WA,ID,MT
K3 OR,NV,CA
K4 NV,UT
K5 CA,AZ

统计出来,所有的地区是ID,NV,UT,WA,MT,OR,CA,AZ共8个。如何找出覆盖所有地区的广播台的集合?
常规的想法也是最暴力的想法就是穷举,列出所有可能的广播台集合,当存在n个广播台时,容易知道这一共有 2n个这样的集合,这显然是一个幂级而非多项式级的问题,即NP问题。当n为20时,就需要计算220 = 1024 * 1024 次,这是相当可怕的。因此,当面对NP问题时,我们常常使用贪心算法,求局部最优,减少指数级的计算量

基于贪心的思想,我们可以
A)明确当前未覆盖的地区 和 可选择的广播台
B)基于未覆盖地区,从可选广播台中,选取覆盖地区最多的广播台

如此循环,直到未覆盖地区为空
这样得到的解或许不是全局最优的,但是从最坏的情况来看,时间复杂度也降低到O(n2) (即进行n次循环,每一次循环都要扫描一次当前可选择的广播台)......代码就不贴了,思路都比较简单

给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 从位置 0 到 1 跳 1 步, 然后跳 3 步到达最后一个位置

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

注意到题目求“是否”可达,那么可以尝试使用贪心算法,快速找到一条可达路径求解

贪心:
从后往前遍历数组,每次寻找上一个最近的可达元素

  • 如果遇到的元素可以到达最后一个元素,则截断后边的元素,因为从该元素总能跳到最后;将当前元素作为新数组的最后一个元素,从而不断减小问题规模
  • 否则继续往前,如果最后剩下的元素大于1个,则判断为假,否则为真。时间复杂度O(n)
'''
伪代码:

用指针j指向当前阶段的末位元素,用指针i从j-1的位置开始从后向前扫描,不断更新j直到j为0时停止

(循环) 当 j > 0 时:
    (循环) 指针i从j-1的位置开始从后向前扫描:
    如果找到一个i能够跳到j,那么停止扫描并且更新j的值,结束本层循环
    如果找不到这样的i,即i为负数,向前溢出,则返回False

如果跳出了j的循环,说明j为0,返回True
'''

class Solution(object):
    def canJump(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        j = len(nums) - 1
        # j == 0 是循环跳出的条件,当指针j为0时,说明已经从后向前寻找到首个元素
        while j > 0:
            i = j - 1
            while i >= 0:
                # 如果可达,
                if nums[i] + i >= j:
                    j = i
                    break
                # 不可达,继续向前搜索
                else:
                    i -= 1
                    # 如果指针i向前溢出,说明找不到j前面的元素可以跳到j
                    if i < 0:
                        return False
        return True
              

成绩:
执行用时 :88 ms, 在所有 Python 提交中击败了87.11%的用户
内存消耗 :13.3 MB, 在所有 Python 提交中击败了39.94%的用户

另解:
这是我最开始的解法。容易发现当数组里所有的数都是正数时,显然可以跳到最后,因此问题出在数字0的身上。当数组中有0时,我们要观察0前面的数是否可以越过它,如果不能越过,则返回False
需要注意的是,这种方法的边界情况需要仔细考虑,注意当末位为0时,前面位置的元素不必要越过它,到达即可。这种方法我一共提交了3次代码。我第二次提交代码时就没有考虑到这一点,导致部分测试用例通不过。下面是代码实现,除去注释其实也很短

class Solution(object):
    def canJump(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        j = len(nums)-1
        # 当数组至少有2个元素时(因为当数组长度为1时,直接满足,第一次提交时未考虑,导致[0]输出了False  
        while j > 0:
            now = nums[j]
            # 如果某位为0,检查前面的位置是否可以越过它;不为0,再往前搜索上一个0
            # 注意当末位为0时,前面位置的元素不必要越过它,到达即可,这在第二次提交时未考虑
            if now == 0:
                for i in range(j):
                    # 如果存在一个位置能越过0,将j指向i
                    if i + nums[i] > j or (i + nums[i] == j and j == len(nums)-1):
                        j = i
                        break
                    # 否则,返回False
                    else:
                        if i == j-1:
                            return False
                        continue                        
            # 该位置不为0,再往前搜索上一个0
            else:
                j -= 1
                # 考虑首个元素为0的边界情况
                if j == 0 and nums[0] == 0:
                    return False        
        # 如果顺利跳出循环,实现大会师,返回True
        return True

成绩:
执行用时 :72 ms, 在所有 Python 提交中击败了99.33%的用户
内存消耗 :13.3 MB, 在所有 Python 提交中击败了43.52%的用户

NOTE:
关于解是否问题的小体会。解是否问题,基本上循环是必须的,我们需要在循环里面去判断是否满足我们的条件
那么,return False要写在循环体内,一旦不满足条件马上返回False;而return True则写在循环体外,因为只要跳出了循环,说明成功通过了每一次循环的判断


这点东西拖了2天T_T

本文内容为作者原创,未经允许禁止转载

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

推荐阅读更多精彩内容

  • 1.part1 绪论 计算机是有限状态机 计算机本质上是一台有限状态机,只能根据已经计算过的状态集合(calcul...
    9_SooHyun阅读 2,631评论 0 5
  • 分治算法 一、基本概念 在计算机科学中,分治法是一种很重要的算法。字面上的解释是“分而治之”,就是把一个复杂的问题...
    木叶秋声阅读 5,292评论 0 3
  • 贪心算法 贪心算法总是作出在当前看来最好的选择。也就是说贪心算法并不从整体最优考虑,它所作出的选择只是在某种意义上...
    fredal阅读 9,223评论 3 52
  • 猝不及防的死亡 遍体鳞伤的伤 从今往后 秋雨潇潇,萧瑟寂寥 花开花落,无人共赏 将回忆藏在时间里 将思念寄于生活 ...
    chenxing123阅读 134评论 0 0
  • 太阳在照耀,月亮在皎洁,而你在发光。乌云来临的时候,太阳会消亡;黎明来临的时候,月亮会隐藏;而你,亘古。 ...
    思远道125阅读 279评论 0 0