MUYANG GUO / INDEX

Post

Algorithm Notes: DP

·7 min read·#Algorithm Notes#Study Notes

Chapter 18 动态规划

例题 1 数字三角形 LeetCode-120 Triangle

数字三角形 VS 二叉树

数字三角形 O(n^2) 个节点,二叉树 O(2^n) 个节点。

18.2 坐标型动态规划

例题 1. LeetCode-63 Unique Paths II

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid):
        
        ### 坐标型的DP
        
        ### 首先判断, 在右下角,上一步只可能来自, (m-1,n-1-1) or (m-1-1, n-1)
        
        ### 那么 每个f[i][j] = f[i][j-1] + f[i-1][j]
        
        ### f[i][j] represents the total path to be here
        
        ### construct the matrix 
        
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        
#         if m == 1 and n == 1:
#             if obstacleGrid[0][0] == 1:
#                 return 0
#             else:
#                 return 1
        
        if m <= 1 or n <= 1:
            for i in range(m):
                for j in range(n):
                    if obstacleGrid[i][j] == 1:
                        return 0
 
        
        f = [[1] * n for _ in range(m)]
        
        ### initial 条件
        
        ### 1. f[0][0] = 1
        ### 2. 第一行在出现block之前为1,之后都为0
        ### 3. 第一列在出现blcok之前为1,之后都为0
        ### 4. 出现block的地方为0
            
        for i in range(n):
            if obstacleGrid[0][i] == 1:
                for j in range(i,n):
                    f[0][j] = 0
                break
                
        for i in range(m):
            if obstacleGrid[i][0] == 1:
                for j in range(i,m):
                    f[j][0] = 0
                break
        
        for i in range(1,m):
            for j in range(1,n):            
                if obstacleGrid[i][j] == 1:
                    f[i][j] = 0
                else:
                ### now start the transfer function
                    f[i][j] = f[i][j-1] + f[i-1][j]
 
        
        return f[m-1][n-1]

例题 2. 630 Knight Shortest Path II

DP:

 
DIRECTIONS = [
    (-1, -2),
    (1, -2),
    (-2, -1),
    (2, -1),
]
 
 
class Solution:
    # @param {boolean[][]} grid a chessboard included 0 and 1
    # @return {int} the shortest path
    def shortestPath2(self, grid):
        if not grid or not grid[0]:
            return -1
        
        n, m = len(grid), len(grid[0])
        
        # state: f[i][j] 代表从 0,0 跳到 i,j 的最少步数
        f = [[sys.maxsize for j in range(m)] for _ in range(n)]
 
        # initialize: f[0][0] 是起点
        f[0][0] = 0
        
        # function
        for j in range(m):
            for i in range(n):
                if grid[i][j]:
                    continue
                for delta_x, delta_y in DIRECTIONS:
                    x, y = i + delta_x, j + delta_y
                    if 0 <= x < n and 0 <= y < m:
                        f[i][j] = min(f[i][j], f[x][y] + 1)
 
        if f[n - 1][m - 1] == sys.maxsize:
            return -1
 
        return f[n - 1][m - 1]
 
 
 

滚动数组DP优化:

DIRECTIONS = [
    (-1, -2),
    (1, -2),
    (-2, -1),
    (2, -1),
]
 
class Solution:
    """
    @param grid: a chessboard included 0 and 1
    @return: the shortest path
    """
    def shortestPath2(self, grid):
        # write your code here
        if not grid or not grid[0]:
            return -1
        
        n, m = len(grid), len(grid[0])
        
        # state: dp[i][j % 3] 代表从 0,0 跳到 i,j 的最少步数
        dp = [[float('inf')] * 3 for _ in range(n)]
 
        # initialize: 0,0 是起点
        dp[0][0] = 0
        
        # function
        for j in range(1, m):
            for i in range(n):
                dp[i][j % 3] = float('inf')
                if grid[i][j]:
                    continue
                for delta_x, delta_y in DIRECTIONS:
                    x, y = i + delta_x, j + delta_y
                    if 0 <= x < n and 0 <= y < m:
                        dp[i][j % 3] = min(dp[i][j % 3], dp[x][y % 3] + 1)
 
        # answer
        if dp[n - 1][(m - 1) % 3] == float('inf'):
            return -1
        return dp[n - 1][(m - 1) % 3]
 

例题 3. LeetCode-55 Jump Game

class Solution:
    """
    @param A: A list of integers
    @return: A boolean
    """
    def canJump(self, A):
        if not A:
            return False
 
        n = len(A)
        # state: dp[i] 代表能否跳到坐标 i
        dp = [False] * n
        
        # initialization: 一开始站在0这个位置
        dp[0] = True
        
        # function
        for i in range(1, n):
            for j in range(i):
                # 高效的写法:
                if dp[j] and j + A[j] >= i:
                    dp[i] = True
                    break
                # 偷懒的写法
                # dp[i] = dp[i] or dp[j] and (j + A[j] >= i)
        
        # answer
        return dp[n - 1]

18.3 背包型动态规划

注意 背包型动态规划并不一定比搜索快 DFS combination来说 O(n * 2^n) 背包型态为O(n*m), m为sum,当m值很大的时候,动态规划可能会比搜索还要慢。

dp[i][j] 表示 前 i 个物品 里挑出若干物品组成和 为j 的大小是否可行。

例题 1. 92 Backpack

class Solution:
    """
    @param m: An integer m denotes the size of a backpack
    @param A: Given n items with size A[i]
    @return: The maximum size
    """
    def backPack(self, m, A):
        # write your code here
        # Backpack type DP:
        # for n items in A:
        # Lets consider the last step of DP:
        # if last item is not picked, then the previous n - 1 items should try to fill up the size m - A[n - 1]. 
        # if last item is picked, then the all n items should try to fill up the size m. 
        
        # The status:
        # f[i][w] previous i items can or can not have weight w, w = 0, 1 , 2...,m
        n = len(A)
        if n==0:
            return 0
            
        f = [[False] * (m + 1) for _ in range(n + 1)]
        
        f[0][0] = True
 
        
        for i in range(1, n + 1):
            for w in range(m + 1):
                # case 1: not pick last item
                f[i][w] = f[i - 1][w]
                # case 2: picked last item
                if w >= A[i - 1]:
                    f[i][w] = f[i][w] or f[i - 1][w - A[i - 1]]
        
        
        for i in range(m, -1, -1):
            if f[n][i]:
                return i
        
        return 0
 

例题 2. 724 Minimum Partition

01背包问题的变种:dpi表示两个集合之差为i的构造方法是否存在,枚举数组中的每个数,依次迭代更新dp数组即可

即求使得X(较小的数组之和)尽可能接近SUM/2的最大值。

class Solution:
    """
    @param nums: the given array
    @return: the minimum difference between their sums 
    """
    def findMin(self, nums):
        # write your code here
        SUM = sum(nums)
        m = len(nums)
        limit = int(SUM/2)
        f = [[0] * (limit + 1) for _ in range(2)]
        f[0][0] = 0
 
        for i in range(1, m + 1):
            for w in range(limit + 1):
                # case 1: not pick last item
                f[i % 2][w] = f[(i - 1) % 2][w]
                # case 2: picked last item
                if w >= nums[i - 1]:
                    f[i % 2][w] = max(f[i % 2][w] , f[(i - 1) % 2 ][w - nums[i - 1]] + nums[i - 1])
                if f[i % 2][w] >= limit:
                    return abs(SUM -2 * f[i % 2][w])
        
        return SUM -2 * f[m % 2][limit]

或者用逻辑运算符 稍微更快一些:

class Solution:
    """
    @param nums: the given array
    @return: the minimum difference between their sums 
    """
    def findMin(self, nums):
        # write your code here
        n = len(nums)
        total_sum = sum(nums)
        m = total_sum // 2
        
        dp = [[False] * (m + 1) for _ in range(2)]
        
        dp[0][0] = True
        
        for i in range(1, n + 1):
            dp[i % 2][0] = True
            for j in range(1, m + 1):
                if nums[i - 1] > j:
                    dp[i % 2][j] = dp[(i - 1) % 2][j]
                else:
                    dp[i % 2][j] = dp[(i - 1) % 2][j] or dp[(i - 1) % 2][j - nums[i - 1]]
        
        for i in range(m, -1, -1):
            if dp[n % 2][i]:
                return total_sum - 2 * i
        
        return total_sum
 
 

例题 3. 125 Backpack II

带价值的背包问题:

class Solution:
    """
    @param m: An integer m denotes the size of a backpack
    @param A: Given n items with size A[i]
    @param V: Given n items with value V[i]
    @return: The maximum value
    """
    def backPackII(self, m, A, V):
        # write your code here
        n = len(A)
        if n == 0:
            return 0
        
        
        f = [[-1] * (m + 1) for _ in range(n + 1)]
        
        f[0][0] = 0
        
        for i in range(1, n + 1):
            for w in range(m + 1):
                # not picked the last item:
                f[i][w] = f[i - 1][w]
                
                # picked the last item:
                if w >= A[i - 1] and f[i - 1][w - A[i - 1]] != -1:
                    f[i][w] = max(f[i][w],f[i - 1][w - A[i - 1]] + V[i - 1])
                    
        res = 0
        for w in range(m + 1):
            if f[n][w] != -1:
                res = max(res, f[n][w])
                
        return res
 

例题 4. 440. Backpack III

多重背包问题 (可选同一个物品多次): 有了count

class Solution:
    """
    @param A: an integer array
    @param V: an integer array
    @param m: An integer
    @return: an array
    """
    def backPackIII(self, A, V, m):
        # write your code here
        n = len(A)
        if n == 0:
            return 0
        
        
        f = [-1] * (m + 1)
        
        f[0] = 0
        
        for i in range(1, n + 1):
            for w in range(A[i - 1], m + 1):
                # old: f[w]
                # new: f[w - A[i - 1]]
 
                if f[w - A[i - 1]] != -1:
                    f[w] = max(f[w],f[w - A[i - 1]] + V[i - 1])
                    
        res = 0
        for w in range(m + 1):
            if f[w] != -1:
                res = max(res, f[w])
                
        return res
 

18.4 区间型DP

大区间依赖于小区间。

例题 1. []

Comments