[Leetcode]动态规划之游戏&贪心问题——python版本

本篇文章根据labuladong的算法小抄汇总动态规划(游戏和贪心问题)的常见算法,采用python3实现

文章目录

  • 一、用动态规划玩游戏
    • 最小路径和
    • K站中转内最便宜的航班
    • 高楼扔鸡蛋
    • 打家劫舍1
    • 打家劫舍2
    • 打家劫舍3
    • 实现strStr()
    • 四键键盘
    • 石子游戏
    • 构造回文的最小插入次数
    • 买卖股票的最佳时机只买卖一次
    • 股票买卖的最佳时机不限次数
    • 股票买卖的最佳时机含冷冻期不限次数
    • 股票买卖的最近时机含手续费不限次数
    • 股票买卖问题的最佳时机最多买卖两次
    • 股票买卖的最佳时机限制k次
  • 二、贪心类型问题
    • 区间调度问题
      • 无重叠区间,435
      • 用最少数量的箭引爆气球,452
      • 安排会议室,253
      • 视频拼接
    • 跳跃游戏一,55
    • 跳跃游戏二,45
    • 加油站

一、用动态规划玩游戏

最小路径和

#dp函数:从左上角(0,0)到(i,j)都最小路径和为dp(grid,i,j)
def minPathSum(grid):
	def dp(grid,i,j):
        if (i == 0) and (j == 0):
            return grid[0][0]
        if (i < 0) or (j < 0):
            return float("inf")
        return min(
            dp(grid,i-1,j),
            dp(grid,i,j-1)
        ) + grid[i][j]
    m = len(grid)
    n = len(grid[0])
    return dp(grid,m-1,n-1)

备忘录改进:

#时间O(MN),空间O(MN)
def minPathSum(grid):
    def dp(grid,i,j):
        if (i == 0) and (j == 0):
            return grid[0][0]
        if (i < 0) or (j < 0):
            return float("inf")
        if memo[i][j] != -1:
            return memo[i][j]
        memo[i][j] = min(dp(grid,i-1,j),dp(grid,i,j-1)) + grid[i][j]
        return memo[i][j]
    m = len(grid)
    n = len(grid[0])
    memo = [[-1 for i in range(n)] for j in range(m)]
    return dp(grid,m-1,n-1)

dp数组:

#dp[i][j]:从(0,0)到(i,j)的最小路径和
def minPathSum(grid):
    m = len(grid)
    n = len(grid[0])
    dp = [[0 for i in range(n)] for j in range(m)]
    dp[0][0] = grid[0][0]
    for i in range(1,m):
        dp[i][0] = dp[i-1][0] + grid[i][0]
    for j in range(1,n):
        dp[0][j] = dp[0][j-1] + grid[0][j]
    for i in range(1,m):
        for j in range(1,n):
            dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j]
    return dp[m-1][n-1]

K站中转内最便宜的航班

  • 求最短路径,肯定可以用BFS解决
  • 对于加权图的场景,需要优先级队列[自动排序]的特性,将路径权重较小的节点排在队列前面
  • 下面用动态规划解决
def findCheapestPrice(n,flights,src,dst,k):
    #dp函数:从src出发,k步之内到达s的最小路径权重为dp(s,k)
    def dp(s,k):
        if s == src:
            return 0
        if k == 0:
            return -1
        res = float("inf")
        if s in indegree:
            for v in indegree[s]:
                from_ = v[0]
                price = v[1]
                subProblem = dp(from,k-1)
                if subProblem != -1:
                    res = min(res,subProblem + price)
        return res if res != float("inf") else -1
        
    k += 1
    indegree = dict() #to -> [from,price]
    for f in flights:
        from_ = f[0]
        to = f[1]
        price = f[2]
        if to in indegree:
            indegree[to].append([from_,price])
        else:
            indegree[to] = [from_,price]
    return dp(dst,k)
  • dp数组
class Solution:
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:

        #dp[i][j]=x表示从src到i,j步之内的最便宜的价格(行表示n个城市对应索引,列表示k步之内)
        k += 1
        dp = [[float("inf") for i in range(k+1)] for j in range(n)]
        for f,t,p in flights:
            if f == src:
                dp[t][1] = p
        for x in range(2,k+1):
            for f,t,p in flights:
                dp[t][x] = min(dp[t][x],dp[t][x-1],dp[f][x-1]+p)

        return dp[dst][k] if dp[dst][k] != float("inf") else -1

高楼扔鸡蛋

题目:给定k枚鸡蛋,1…n层楼。已知存在楼层f(0…n),任何高于f的楼层落下的鸡蛋都会碎,从f楼层或比它低的楼层落下的鸡蛋都不会破。如果鸡蛋碎了就不能用,如果没碎就可以用。请你计算并返回要确定f值的最小操作次数。

#时间O(K*N^2),空间O(KN)
def superEggDrop(K,N):
    memo = dict()
    def dp(K,N):
        if K == 1:
            return N
        if N == 0:
            return 0
        if (K,N) in memo:
            return memo[(K,N)]
        res = float("inf")
        for i in range(1,N+1):
            res = min(res,max(dp(K,N-i),dp(K-1,i-1))+1)
        memo[(K,N)] = res
        return res
    return dp(K,N)
  • 二分查找优化
#时间O(K,N,logN),空间O(KN)
class Solution:
    def superEggDrop(self, k: int, n: int) -> int:

        def dp(k,n):
            if k == 1:
                return n
            if n == 0:
                return 0
            if (k,n) in memo:
                return memo[(k,n)]
            res = float("inf")
            # 用二分搜索代替线性搜索
            lo, hi = 1, n
            while lo <= hi:
                mid = (lo + hi) // 2
                broken = dp(k - 1, mid - 1) # 碎
                not_broken = dp(k, n - mid) # 没碎
                # res = min(max(碎,没碎) + 1)
                if broken > not_broken:
                    hi = mid - 1
                    res = min(res, broken + 1)
                else:
                    lo = mid + 1
                    res = min(res, not_broken + 1)
            memo[(k,n)] = res
            return res

打家劫舍1

备忘录:

def rob(nums):
    def dp(nums,start):
        if start >= len(nums):
            return 0
        if memo[start] != -1:
            return memo[start]
        memo[start] = max(dp(nums,start+1),dp(nums,start+2)+nums[start])
        return memo[start]
        
    memo = [-1 for i in range(nums)]
    return dp(nums,0)

dp数组

def rob(nums):
    n = len(nums)
    dp = [0 for i in range(n+2)]
    for i in range(n-1,-1,-1):
        dp[i] = max(dp[i+1],dp[i+2]+nums[i])
    
    return dp[0]

状态压缩

def rob(nums):
    n = len(nums)
    dp_i_1 = 0
    dp_i_2 = 0
    for i in range(n-1,-1,-1):
        dp_i = max(dp_i_1,dp_i_2+nums[i])
        dp_i_2 = dp_i_1
        dp_i_1 = dp_i
    return dp_i

打家劫舍2

def rob(nums):
    def robRange(nums,start,end):
        n = len(nums)
        dp_1 = 0
        dp_2 = 0
        for i in range(end,start-1,-1):
            dp_i = max(dp_1,dp_2+nums[i])
            dp_2 = dp_1
            dp_1 = dp_i
        return dp_i
    
    n = len(nums)
    if n == 1:
        return nums[0]
    return max(robRange(nums,0,n-2),robRange(nums,1,n-1))

打家劫舍3

class Solution:
    def rob(self, root: TreeNode) -> int:
        def dp(root):
            if root is None:
                return 0
            if root in memo:
                return memo.get(root)
            do_it = root.val + (0 if root.left is None else dp(root.left.left)+dp(root.left.right))+(0 if root.right is None else dp(root.right.left) + dp(root.right.right))
            not_do = dp(root.left) + dp(root.right)
            res = max(do_it,not_do)
            memo[root] = res
            return res
        memo = dict()
        return dp(root)

实现strStr()

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。

暴力解法

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        M = len(needle)
        N = len(haystack)
        if M == 0:
            return 0
        if M > N:
            return -1
        for i in range(N-M+1):
            j = 0
            while j < M:
                if haystack[i+j] != needle[j]:
                    break
                else:
                    j += 1
            if j == M:
                return i
        return -1

KMP算法

#dp[j][c]=next表示状态j碰到字符c转移到状态next
def KMP(pat):
    M = len(pat)
    dp = [[0 for i in range(256)] for j in range(M)]
    dp[0][ord(pat[0])] = 1
    X = 0
    for j in range(1,M):
        for c in range(256):
            if ord(pat[j]) == c:
                dp[j][c] = j + 1
            else:
                dp[j][c] = dp[X][c]
            X = dp[X][ord(pat[j])]
    
def search(txt):
    M = len(pat)
    N = len(txt)
    j = 0
    for i in range(N):
        j = dp[j][txt[i]]
        if j == M:
            return i-M+1
    return -1

四键键盘

def maxA(n):
	def dp(n,a_num,copy):
        if n <= 0:
            return a_num
        if (n,a_num,copy) in memo:
            return memo[(n,a_num,copy)]
        memo[(n,a_num,copy)] = max(dp(n-1.a_num+1,copy),
                                   dp(n-1,a_num+copy,copy),
                                   dp(n-2,a_num,a_num)
                                  )
        return memo[(n,a_num,copy)]
    memo = dict()
    return dp(n,0,0)
def maxA(N):
    dp = [0 for i in range(N+1)]
    dp[0] = 0
    for i in range(N+1):
        #按A
        dp[i] = dp[i-1] + 1
        #第j个是ctrl+v
        for j in range(2,i):
            dp[i] = max(dp[i],dp[j-2]*(i-j+1))
    return dp[N]

石子游戏

def stoneGame(piles):
    n = len(piles)
    dp = [[[0 for i in range(n)] for j in range(n)] for k in range(2)]
    for i in range(n):
        dp[i][i][0] = piles[i]
        dp[i][i][1] = 0
    for i in range(n-2,-1,-1):
        for j in range(i+1,n):
            left = piles[i] + dp[i+1][j][1]
            right = piles[j] + dp[i][j-1][1]
            if left > right:
                dp[i][j][0] = left
                dp[i][j][1] = dp[i+1][j][0]
            else:
                dp[i][j][0] = right
                dp[i][j][1] = dp[i][j-1][0]
    return dp[0][i-1][0] - dp[0][i-1][1]

构造回文的最小插入次数

def minInsertions(s):
    n = len(s)
    dp = [[0 for i in range(n)] for j in range(n)]
    for i in range(n-2,-1,-1):
        for j in range(i+1,n):
            if s[i] == s[j]:
                dp[i][j] = dp[i+1][j-1]
            else:
                dp[i][j] = min(dp[i+1][j],dp[i][j-1]) + 1
    return dp[0][n-1]

买卖股票的最佳时机只买卖一次

给定一个数组,第i个元素是一支给定股票在第i天的价格,设计一个算法来计算你所能获取的最大利润。你最多可以完成k笔交易。注意:你不能同时参与多笔交易。

#原始版本
def maxProfits(prices):
    n = len(prices)
    dp = [[0 for i in range(2)] for j in range(n)]
    dp[0][0] = 0
    dp[0][1] = -prices[0]
    for i in range(1,n):
        dp[i][0] = max(dp[i-1][0],dp[i-1][1] + prices[i])
        dp[i][1] = max(dp[i-1][1],- prices[i])
    return dp[n-1][0]
#空间复杂度优化版本
def maxProfits(prices):
    n = len(prices)
    dp_i_0 = 0
    dp_i_1 = -prices[0]
    for i in range(1,n):
        dp_i_0 = max(dp_i_0,dp_i_1 + prices[i])
        dp_i_1 = max(dp_i_1,- prices[i])
    return dp_i_0

股票买卖的最佳时机不限次数

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

股票买卖的最佳时机含冷冻期不限次数

def maxProfit(prices):
    n = len(prices)
    dp = [[0 for i in range(2)] for j in range(n)]
    dp[0][0] = 0
    dp[0][1] = -prices[0]
    for i in range(1,n):
        dp[i][0] = max(dp[i-1][0],dp[i-1][1] + prices[i])
        dp[i][1] = max(dp[i-1][1],dp[i-2][0] - prices[i])
    return dp[n-1][0]

股票买卖的最近时机含手续费不限次数

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

股票买卖问题的最佳时机最多买卖两次

def maxProfit(prices):
    max_k = 2
    n = len(prices)
    dp = [[[0 for i in range(max_k+1)] for j in range(3)] for k in range(n)]
    for i in range(n):
        for k in range(max_k,0,-1):#遍历顺序?
            if i - 1 == -1:
                dp[i][k][0] = 0
                dp[i][k][1] = - prices[0] #不消耗交易次数?
                continue
            dp[i][k][0] = max(dp[i-1][k][0],dp[i-1][k][1] + prices[i])
            dp[i][k][1] = max(dp[i-1][k][1],dp[i-1][k-1][0] - prices[i])
    return dp[n-1][max_k][0]
            

股票买卖的最佳时机限制k次

class Solution:
    def maxProfit(self, k: int, prices: List[int]) -> int:
        n = len(prices)
        if n == 0:
            return 0
        dp = [[[0 for i in range(2)] for j in range(k+1)] for h in range(n)]
        for h in range(1,k+1):
            dp[0][h][0] = 0
            dp[0][h][1] = - prices[0]
        for i in range(1,n):
            for j in range(1,k+1):
                dp[i][j][0] = max(dp[i-1][j][0],dp[i-1][j][1] + prices[i])
                dp[i][j][1] = max(dp[i-1][j][1],dp[i-1][j-1][0] - prices[i])
        return dp[n-1][k][0]

二、贪心类型问题

贪心算法是动态规划的一个特例,需要满足更多条件(贪心选择性质),但效率比动态规划高。

贪心选择性质:每一步都做出一个局部最优的选择,最终结果就是全局最优。

区间调度问题

def intervalSchedule(intvs):
    if len(intvs) == 0:
        return 0
    intvs.sort(key = lambda a : a[1])
    count = 1
    x_end = intvs[0][1]
    for interval in intvs:
        start = interval[0]
        if start >= x_end:
            count += 1
            x_end = interval[1]
    return count

无重叠区间,435

class Solution:
    def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
        n = len(intervals)
        if n == 0:
            return 0
        intervals.sort(key=lambda a:a[1])
        end = intervals[0][1]
        count = 1
        for interval in intervals:
            if interval[0] >= end:
                end = interval[1]
                count += 1
        return n - count

用最少数量的箭引爆气球,452

class Solution:
    def findMinArrowShots(self, points: List[List[int]]) -> int:
        n = len(points)
        if n == 0:
            return 0
        points.sort(key=lambda a:a[1])
        count = 1
        end = points[0][1]
        for point in points:
            if point[0] > end:
                end = point[1]
                count += 1
        return count

安排会议室,253

#计算最多有几个重叠区间
def minMeetingRooms(meetings):
    n = len(meetings)
    begin = [0 for i in range(n)]
    end = [0 for i in range(n)]
    for i in range(n):
        begin[i] = meetings[i][0]
        end[i] = meetings[i][1]
    begin.sort()
    end.sort()
    count = 0
    res = 0
    i = 0
    j = 0
    while (i < n) and (j < n):
        if begin[i] < end[j]:
            count += 1
            i += 1
        else:
            count -= 1
            j += 1
        res = max(res,count)
    return res

视频拼接

def videoStitching(clips,T):
    n = len(clips)
    clips.sort(key = lambda a:(a[0],-a[1]))
    curEnd = 0
    nextEnd = 0
    count = 0
    i = 0
    while (i < n) and (clips[i][0] <= curEnd):
        while (i < n) and (clips[i][0] <= curEnd):
            nextEnd = max(nextEnd,clips[i][1])
            i += 1
        curEnd = nextEnd
        count += 1
        if curEnd >= T:
            return count
    return -1

跳跃游戏一,55

def canJump(nums):
    n = len(nums)
    farthest = 0
    for i in range(n-1):
        farthest = max(farthest,i + nums[i])
        if farthest <= i:
            return False
    return farthest >= n-1

跳跃游戏二,45

动态规划:

def jump(nums):
    def dp(nums,p):
        n = len(nums)
        if p >= n-1:
            return 0
        if memo[p] != n:
            return memp[p]
        steps = nums[p]
        for i in range(1,steps+1):
            subProblem = dp(nums,p+i)
            memo[p] = min(memo[p],subProblem+1)
        return memo[p]
    n = len(nums)
    memo = [n for i in range(n)]
    return dp(nums,0)

贪心算法:

def jump(nums):
    n = len(nums)
    end = 0
    count = 0
    farthest = 0
    for i in range(n-1):
        farthest = max(nums[i] + i,farthest)
        if end == i:
            count += 1
            end = farthest
    return count

加油站

数学图像:

def canCompleteCircuit(gas,cost):
    n = len(gas)
    sum = 0
    minSum = 0
    start = 0
    for i in range(n):
        sum += gas[i] - cost[i]
        if sum < minSum:
            start = i + 1
            minSum = sum
    if sum < 0:
        return -1
    return start if start != n else 0

贪心算法:

def canCompleteCircuit(gas,cost):
    n = len(gas)
    sum = 0
    for i in range(n):
        sum += gas[i] - cost[i]
    if sum < 0:
        return -1
    tank = 0
    start = 0
    for i in range(n):
        tank += gas[i] - cost[i]
        if tank < 0:
            tank = 0
            start = i+1
    return start if start != n else 0

你可能感兴趣的:(leetcode刷题笔记,动态规划,leetcode,python)