@Author:Runsen
@Date:2020/7/5
人生最重要的不是所站的位置,而是内心所朝的方向。只要我在每篇博文中写得自己体会,修炼身心;在每天的不断重复学习中,耐住寂寞,练就真功,不畏艰难,奋勇前行,不忘初心,砥砺前行,人生定会有所收获,不留遗憾 (作者:Runsen )
作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件。导致翘课严重,专业排名中下。.在大学60%的时间,都在CSDN。决定今天比昨天要更加努力。 前面文章,点击下面链接
我的Python教程,不断整理,反复学习
今日,我决定继续更新Python教程,今天就开始了八十三、Python | Leetcode贪心算法系列。
贪心算法
贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的是在某种意义上的局部最优解。
假设我们有一个100kg的背包,可以装飞中物品,如何将所装的物品总价值最大
![10aeda8ec4df20015b9ead13c97d730e.png](https://i-blog.csdnimg.cn/blog_migrate/91a39414f932f6377683716ebd3288c9.jpeg)
答案 :20kg 黑豆 ,30kg 绿豆 ,50kg 红豆
贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止
贪心算法的解题步骤
- 建立数学模型来描述问题;
- 把求解的问题分成若干个的问题;
- 对每一子问题求解,得到子问题的局部最优解;
- 把子问题的解局部最优解合成原来解问题的一个解。
再比如,有一个有权图,从顶点S开始,照样一条到顶点T的最短路径
贪心算法的解决思路
每次选择一条跟当前相连的圈最小的边,直到找出顶点T。
求出的最短路径S ->A->E->T,路径长度1+4+4 =9
![2ca64df5a90b7f96c1a4fa66a2a1779f.png](https://i-blog.csdnimg.cn/blog_migrate/6a41bf668388e01d55cff5f1a28dccfa.jpeg)
但是贪心选择的方法,求的路径并不是最短路径 S- >B ->D->T 才是最短路径
因为前面的选择会影响后面的选择,导致每一步的选择都很糟糕,最终无法得到全局最优解
生活中常见的贪心算法就是钱币付钱
肯定先用最大的100来付钱,如果不够,就用50,最后剩下的用1元来补齐
1.贪婪算法可以寻找局部最优解,并尝试以这种方式获得全局最优解
2.得到的可能是近似最优解,但也可能便是最优解(区间调度问题,最短路径问题(广度优先、狄克斯特拉))
3.对于完全NP问题,目前并没有快速得到最优解的解决方案
4.面临NP完全问题,最佳的做法就是使用近似算法
5.贪婪算法(近似算法)在大部分情况下易于实现,并且效率不错
举个简单的例子,比如给定某个数字的金额(如 250)与 100, 50, 10, 5, 1 这些纸币(不限量),怎么能用最少张的纸币来兑换这张金额呢,显然每次兑换应该先从大额的纸币兑换起,第一次选 100, 第二次还是选 100, 第三次选第二大的 50 元,每次都选小于剩余金额中的最大面额的纸币,这样得出的解一定是全局最优解!时间复杂度无疑是线性的。
最大和的连续子数组
我不知道这题是不是Leetcode,但出现的频率很高。好像是牛课的,反正是一个面试题。
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。示例:输入: [-2,1,-3,4,-1,2,1,-5,4],输出: 6解释: 连续子数组[4,-1,2,1] 的和最大,为 6。
第一时间想到的当然是暴力解决,基本思路就是遍历一遍,用两个变量,一个记录最大的和,一个记录当前的和。后面发现可以用贪心算法来解比较简单其基本思路是正在访问的节点值+此节点之前的最大值如果大于当前节点,则更新最大值为和,否则更新最大值为当前节点。
nums=[-2,1,-3,4,-1,2,1,-5,4]tmp = nums[0]max_ = tmpn = len(nums)for i in range(1,n): # 当当前序列加上此时的元素的值大于tmp的值,说明最大序列和可能出现在后续序列中,记录此时的最大值 if tmp + nums[i]>nums[i]: max_ = max(max_, tmp+nums[i]) tmp = tmp + nums[i] else: #当tmp(当前和)小于下一个元素时,当前最长序列到此为止。以该元素为起点继续找最大子序列, # 并记录此时的最大值 max_ = max(max_, tmp, tmp+nums[i], nums[i]) tmp = nums[i]print(max_)
贪心算法的代码
nums=[-2,1,-3,4,-1,2,1,-5,4]lenth = len(nums)cur_sum=max_sum = nums[0]for i in range(1,lenth): cur_sum = max(cur_sum+nums[i], nums[i]) max_sum = max(cur_sum, max_sum)print(max_sum)
我们再看一个场景,这是一个求得最优解的贪心算法例子。
场景:一名收银员,需要找零 88元。怎么找零,所需要的纸/硬币的数量最少。
思路:依次找最大的纸币, 例如:找零88元, ¥88 = ¥50 + ¥20 + ¥10 + ¥5 + ¥1 * 3
# arr的每一位:分别对应100块、50块、20块、10块、5块、1块纸币。arr = [0,0,0,0,0,0]def change(money): while money >= 1: if money >= 100: money -= 100 arr[0] += 1 elif money >= 50: money -= 50 arr[1] += 1 elif money >= 20: money -= 20 arr[2] += 1 elif money >= 10: money -= 10 arr[3] += 1 elif money >= 5: money -= 5 arr[4] += 1 elif money >= 1: money -= 1 arr[5] += 1change(88)print(arr)
![d1776be9aa3b60f7be4c05876b32a6ce.png](https://i-blog.csdnimg.cn/blog_migrate/8cc3a967ac44535f6cfe889152e11ac5.jpeg)
LeetCode 第 55题:跳跃游戏
#给定一个非负整数数组,你最初位于数组的第一个位置。 # 数组中的每个元素代表你在该位置可以跳跃的最大长度。 # 判断你是否能够到达最后一个位置。 # 示例 1:# 输入: [2,3,1,1,4]#输出: true#解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。# 示例 2: # 输入: [3,2,1,0,4]#输出: false#解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。# Related Topics 贪心算法 数组
从第i个位置,最远可跳nums[i]步:nums=[2,3,1,1,4,...];我们假设从第i个位置,最远可跳至第index[i]个位置,则index[i] = i + nums[i];
若从第0个位置最远可跳至第i个位置;则从第0个位置也一定可以跳至:第1个位置、第2个位置、...、第i-1个位置。从第0个位置,应该跳至第1、第2、...、第i-1个位置中的哪一个呢?
应该调至第1、2、...、i-1、i位置中,又可向前跳至最远位置(即index[1]、index[2]、...、index[i-1] 和 index[i] 最大的那个)(贪心算法的思想)
算法步骤:
- 求从第i个位置最远可跳至第index[i]位置;
- 根据从第i位置最远可跳nums[i]步:index[i] = nums[i] + i;
- 初始化:设置变量i代表当前所处的位置,初始化为0;设置变量max_index代表从第0位置至第i位置的这个过程中,最远可到达的位置,初始化为index[0]。
- 利用i扫描index数组,直到i到达index数组尾部或i超过max_index,扫描过程中,更新max_index.
- 若最终 i 为数组长度,则返回true,否则返回false。
class Solution: def canJump(self, nums: List[int]) -> bool: farthest = nums[0] for i in range(len(nums)): if i <= farthest: farthest = max(i+nums[i], farthest) return len(nums)-1 <= farthest
LeetCode 第 122题: 买卖股票的最佳时机 II
#给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 # 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 # 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 # 示例 1: # 输入: [7,1,5,3,6,4]#输出: 7#解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。# 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。# 示例 2: # 输入: [1,2,3,4,5]#输出: 4#解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。# 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。# 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。# 示例 3: # 输入: [7,6,4,3,1]#输出: 0#解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 # 提示: # 1 <= prices.length <= 3 * 10 ^ 4 # 0 <= prices[i] <= 10 ^ 4 # Related Topics 贪心算法 数组
虽然题目看似复杂, 但真正完成的时候你会发现这道题是纸老虎。于是题目的思路就是遍历整个数组, 预测 i+1 个交易日比 i 高即可卖出, 累加利润; 否则跳过这个交易日. 什么?你觉得不可能这么简单? 事实就是这么简单!
class Solution: def maxProfit(self, prices: List[int]) -> int: # return sum([max(prices[i+1] - prices[i],0) for i in range(len(prices)-1)]) return sum([prices[i+1]-prices[i] if prices[i+1]-prices[i] > 0 else 0 for i in range(len(prices)-1)]) # return sum([y - x for x, y in zip(prices[:-1], prices[1:]) if x