其他分享
首页 > 其他分享> > week07_task_贪心动规

week07_task_贪心动规

作者:互联网

七、贪心动规

在这里插入图片描述


目录


来源

极客时间2021算法训练营

作者: 李煜东


1 贪心

1.1 基本思想

贪心算法(GreedyAlgorithm)是一种
(1)在每一步选柽中都采取在当前状态下的最优决策(局部最优)
(2)并希望由此导致的最终结果也是全局最优
的算法



在这里插入图片描述


1.2 相关例题

1.2.1 860 . 柠檬水找零

class Solution:
    def lemonadeChange(self, bills: List[int]) -> bool:
        dic = defaultdict(int)
        for bill in bills:
            dic[bill] += 1
            curr = bill - 5
            for money in [10, 5]:  #从找10元开始考虑
                while curr >= money and dic[money] > 0:
                    dic[money] -= 1
                    curr -= money
            if curr != 0:
                return False
        return True

1.2.2 455 . 分发饼干

决策包容性证明:贪心局部最佳>>全局最佳:一块饼干总是想要满足一个孩子的,满足胃口更大的孩子,未来的可能性包含了满足 胃口更小孩子的可能性

class Solution:
    def findContentChildren(self, g: List[int], s: List[int]) -> int:
        g.sort()
        s.sort()
        count,ans = 0, 0
        for child in g:
            while count < len(s) and s[count] < child:
                count += 1
            if count < len(s):
                ans += 1
                count += 1
        return ans

1.2.3 122 . 买卖股票的最佳时机 II

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        ans = 0
        for i in range(1, len(prices)):
            ans += max(prices[i] - prices[i-1], 0)
        return ans

1.2.4 45 . 跳跃游戏 II

class Solution:
    def jump(self, nums: List[int]) -> int:
        ans, now = 0, 0
        while now < len(nums) - 1:
            right = now + nums[now]  # right是now位置能达到最大位置
            if right >= len(nums) - 1:
                return ans +1
            nextNow = now
            nextRight = right
            for i in range(now + 1, right + 1): # now 可以达到范围[now + 1, right]
                if i + nums[i] > nextRight:        #使得 now下下个可达位置最远
                    nextNow = i
                    nextRight = i + nums[i]
            now = nextNow
            ans += 1
        return ans

1.2.5 1665 . 完成所有任务的最少初始能量

邻项交换
经常用于以某种顺序"排序"为贪心策略的证明
证明在任意局面下,任何局部的逆序改变都会造成整体结果变差

证明:
task[0]actual, task[1]minimum;
设做完第i+2n个任务所需的初始能量最少为S ;
对于两个相邻任务:设第i个和第i+l个完成的任务分别是pq:

在这里插入图片描述
考虑 p , q p, q p,q中先做者所需要的最低能量 S p , S q S_p, S_q Sp​,Sq​:
先做p, 所需要能量:
S p = m a x ( m a x ( m i n i m u m [ q ] , S + a c t u a l [ q ] ) + a c t u a l [ p ] , m i n i m u m [ p ] ) S_p =max(max(minimum[q], S+actual[q])+actual[p], minimum[p]) Sp​=max(max(minimum[q],S+actual[q])+actual[p],minimum[p])
= m a x ( m i n i m u m [ q ] + a c t u a l [ p ] , S + a c t u a l [ q ] + a c t u a l [ p ] , m i n i m u m [ p ] ) =max(minimum[q] + actual[p], S+actual[q] + actual[p], minimum[p]) =max(minimum[q]+actual[p],S+actual[q]+actual[p],minimum[p])
先做q, 所需要能量:
S q = m a x ( m a x ( m i n i m u m [ p ] , S + a c t u a l [ p ] ) + a c t u a l [ q ] , m i n i m u m [ q ] ) S_q =max(max(minimum[p], S+actual[p])+actual[q], minimum[q]) Sq​=max(max(minimum[p],S+actual[p])+actual[q],minimum[q])
= m a x ( m i n i m u m [ p ] + a c t u a l [ q ] , S + a c t u a l [ p ] + a c t u a l [ q ] , m i n i m u m [ q ] ) =max(minimum[p] + actual[q], S+actual[p] + actual[q], minimum[q]) =max(minimum[p]+actual[q],S+actual[p]+actual[q],minimum[q])

P优先则 >>> 满足 S p < S q S_p<S_q Sp​<Sq​ 即:
m a x ( m i n i m u m [ q ] + a c t u a l [ p ] , m i n i m u m [ p ] ) < m a x ( m i n i m u m [ p ] + a c t u a l [ q ] , m i n i m u m [ q ] ) max(minimum[q] + actual[p], minimum[p]) < max(minimum[p] + actual[q], minimum[q]) max(minimum[q]+actual[p],minimum[p])<max(minimum[p]+actual[q],minimum[q])

因为必定有 m i n i m u m [ q ] + a c t u a l [ p ] > m i n i m u m [ q ] minimum[q] + actual[p] > minimum[q] minimum[q]+actual[p]>minimum[q]
所以上式等价于 m i n i m u m [ q ] + a c t u a l [ p ] < m i n i m u m [ p ] + a c t u a l [ q ] minimum[q] + actual[p] < minimum[p] + actual[q] minimum[q]+actual[p]<minimum[p]+actual[q]
即 a c t u a l [ p ] − m i n i m u m [ p ] < a c t u a l [ q ] − m i n i m u m [ q ] actual[p] - minimum[p] < actual[q] - minimum[q] actual[p]−minimum[p]<actual[q]−minimum[q]

于是有: >>>贪心策略:按照actual - minimum升序排序,以此顺序完成任务

class Solution:
    def minimumEffort(self, tasks: List[List[int]]) -> int:
        ans = 0
        tasks.sort(key = lambda x: x[0] - x[1])
        for task in tasks[::-1]:   #倒序考虑完成所需要能量
            ans = max(task[1], ans + task[0])
        return ans

2 线性动规

动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。


动态规划(DP, dynamic programming)是一种对问题的状态空间进行分阶段、有顺序、不重复、 决策性遍历的算法

在这里插入图片描述

2.1 相关题目

2.1.1 63 . 不同路径 II

Bottom-up
f[i,j]表示从(i,j)End的路径数, 如果(i,j)是空地,f[i,j] = f[i + l,j] + f[i,j + 1]
否则f[i,j] = 0
在这里插入图片描述

反过来类似的:

Top-down
f[i,j]表示从Start到(i,j)的路径数, 如果(i,j)是空地,f[i,j] = f[i - 1,j] + f[i,j - 1]

否则f[i,j] = 0

在这里插入图片描述

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        f = [[0]*n for _ in range(m)]
        for i in range(m):
            for j in range(n):
                if obstacleGrid[i][j] == 1:
                    f[i][j] = 0
                elif i == 0 and j == 0:
                    f[i][j] = 1
                elif i == 0:
                    f[i][j] = f[i][j - 1] 
                elif j == 0:
                    f[i][j] = f[i - 1][j]
                else:
                    f[i][j] = f[i - 1][j] + f[i][j - 1]
        return f[-1][-1]

2.1.2 1143 . 最长公共子序列

f[i][j]表示text1的前i个字符和text2的前j个字符能组成的LCS的长度
如果 text1[i] = text2[j]: f[i][j] = f[i - 1][j-1] + 1
如果 text1[i] != text2[j]: f[i][j] = max(f[i - 1][j], f[i][j-1] )

在这里插入图片描述

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        m, n = len(text1), len(text2)
        f = [[0]*(n + 1) for _ in range(m + 1)]
        text1 = " " + text1   #防止i-1, j-1越界
        text2 = " " + text2
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if text1[i] == text2[j]:
                    f[i][j] = f[i - 1][j - 1] + 1
                else:
                    f[i][j] = max(f[i - 1][j], f[i][j - 1])
        return f[-1][-1]

2.1.3 300 . 最长递增子序列

f[i]表示前i个数构成的a[i]为结尾的最长上升子序列的长度

f [ i ] = max ⁡ j < i , a [ j ] < a [ i ] { f [ j ] + 1 } f[i]=\max _{j<i, a[j]<a[i]}\{f[j]+1\} f[i]=j<i,a[j]<a[i]max​{f[j]+1}

边界:f[i] = 1 (0 <= i < n)
目标: max ⁡ 0 ≤ i < n { f [ i ] } \max _{0 \leq i<n}\{f[i]\} max0≤i<n​{f[i]}

在这里插入图片描述

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        n, ans = len(nums), 0
        f = [1 for _ in range(n)]
        for i in range(1, n):
            for j in range(i):
                if nums[i] > nums[j]:
                    f[i] = max(f[j] + 1, f[i])
        for k in range(n):
            ans = max(ans, f[k])
        return ans
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        n, ans,curr,end = len(nums), [], 0, -1
        f = [1 for _ in range(n)]
        pre = [-1 for _ in range(n)]
        def p(i):
            if pre[i] != -1:
                p(pre[i])
            ans.append(nums[i])
        for i in range(1, n):
            for j in range(i):
                if nums[i] > nums[j] and f[i] < f[j] + 1 :
                    f[i] = f[j] + 1
                    pre[i] = j
        for k in range(n):
            if f[k] > curr:
                end = k
        p(end)
        return ans

在这里插入图片描述

2.1.4 53 . 最大子数组和

标签:task,actual,week07,nums,int,max,range,ans,动规
来源: https://blog.csdn.net/yuliuchenyin/article/details/122207793