概念
数组是最常见的数据结构之一,现在对数组的下标进行特殊处理,使每一次操作仅保存有用信息,新的元素不断循环更新,看上去数组的空间被滚动的利用,此模型成为滚动数组。其主要目的是压缩存储,一般在动态规划问题中使用。
例题
一、0-1背包问题
n和物品x1,x2,...,xn的价值和重量分别为:
v1,v2,...,vn
w1,w2,...,wn
现有一个背包,其最大载重量为m,求如何装包是所装物品的价值和最大?
构造二维动态数组
- 构造:
构造dp[n][m]数组,从n个物品中选择,装到m容量的包中的最大价值和。 - base case:
dp[0][j] = 0
dp[i][0] = 0 - 状态转移:
for i in range(1,n): for j in range(1,m): if j-w[i]>=0: dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) else: dp[i][j] = dp[i-1][j]
滚动数组
- 从推到过程中可以看出,第i阶段的状态值只与i-1阶段有关。所以可以将[0...n][0...m]改为[0...m]
- 构造:
构造dp[m]数组 - 状态转移:
for i in range(n): for j in range(m-1,-1,-1): if j-w[i]>0: dp[j] = max(dp[j],dp[j-w[i]]+v[i])
注意:此处需要逆序处理,这样可以保证在计算dp[j]时,公式dp[j]=max(dp[j],dp[j-w[i]]+v[i])中等号右边的dp[j]和dp[j-w[i]]+v[i]保存的时dp[i-1][j],dp[i-1][j-w[i]]+v[i]的值。
二、不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
# 方法一:回溯,超时 def uniquePathsWithObstacles(obstacleGrid) : if obstacleGrid[0][0] == 1 or obstacleGrid[-1][-1] == 1: return 0 if not obstacleGrid: return 0 m = len(obstacleGrid) n = len(obstacleGrid[0]) res = 0 def backtrack(i,j): nonlocal res if i == m-1 and j == n-1: print(res) res += 1 return if i<0 or i>=m or j<0 or j>=n or obstacleGrid[i][j] == 1: return backtrack(i+1,j) backtrack(i,j+1) backtrack(0,0) return res ''' 方法二:动态规划 dp[i][j]:表示从起点到(i,j)的路径条数 状态转移: 若obstacleGrid[i][j] == 1: dp[i][j] = 0 若obstacleGrid[i][j] == 0: dp[i][j] = dp[i-1][j]+dp[i][j-1] 执行用时:44 ms, 在所有 Python3 提交中击败了60.14%的用户 内存消耗:13.8 MB, 在所有 Python3 提交中击败了14.29%的用户 ''' def uniquePathsWithObstacles(obstacleGrid): if obstacleGrid[0][0] == 1 or obstacleGrid[-1][-1] == 1: return 0 m = len(obstacleGrid) n = len(obstacleGrid[0]) dp = [[0]*n for _ in range(m)] #base case: # 第一列,若该位置可以到达,则到达该位置路径条数为1,否则该位置及其后面的位置无法到达 for i in range(m): if obstacleGrid[i][0] == 1: break dp[i][0] = 1 # 第一行,同上 for j in range(n): if obstacleGrid[0][j] == 1: break dp[0][j] = 1 # 状态转移 for i in range(1,m): for j in range(1,n): if obstacleGrid[i][j] == 1: continue else: dp[i][j] = dp[i-1][j]+dp[i][j-1] return dp[-1][-1] ''' 方法三:采用滚动数组优化代码,提高空间利用率 ''' def uniquePathsWithObstacles(obstacleGrid): if obstacleGrid[0][0] == 1 or obstacleGrid[-1][-1] == 1: return 0 m = len(obstacleGrid) n = len(obstacleGrid[0]) dp = [0 for _ in range(n)] # n列 dp[0] = 0 if obstacleGrid[0][0] == 1 else 1 for i in range(m): for j in range(n): if obstacleGrid[i][j] == 1: dp[j] = 0 # 重要 continue elif j >= 1: dp[j] += dp[j-1] return dp[-1]
三、最大子序列和
状态压缩,应该不属于滚动数组范围,但是还是记录一下。。。
给定一个数组nums[n1,n2,...nn],求最大子序列和。
# 方法一:枚举子序列的左端点,扫描右端点 def maxSubArray(nums): n = len(nums) res = -float('inf') for i in range(n): sum = 0 for j in range(i,n): sum += nums[j] res = max(res,sum) return res # 方法二:一维数组 def maxSubArray(nums): n = len(nums) dp = [nums[i] for i in range(n)] for i in range(1,n): dp[i] = max(nums[i],dp[i-1]+nums[i]) return max(dp) 方法三:压缩数组 def maxSubArray(nums): n = len(nums) res = -float('inf') dp = nums[0] for i in range(1,n): dp = max(nums[i],dp+numd[i]) res = max(res,dp) return res
leetcode 221 -> 四、最大正方形 ***
在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。
剑指offer 1162 -> 五、地图分析
leetcode 46 -> 六、把数字翻译成字符串
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法.
输入: 12258 输出: 5
解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
# 法一:递归 def trans(num): if num < 10 : return 1 if num%100<26 and num%100>9: # 后两位在10-25之间 return trans(num//100)+trans(num//10) else: return trans(num//10) # 法二:动态规格 ''' 动态规划 dp[i] 表示以第i位结尾的数字可以翻译的方案数量 此时考虑第i位和第i-1位组成的数是否在10-25之间,若在:dp[i] = dp[i-2]+dp[i-1] 若不在:dp[i] = dp[i-1] -> (单独翻译) ''' def translate(num): nums = str(num) dp = [0 for _ in range(len(nums))] dp[0] = 1 dp[1] = 2 if 10 <= int(nums[:2]) <=25 else 1 for i in range(2,len(nums)): if 10 <= int(nums[i-1:i+1]) <= 25: dp[i] = dp[i-2] + dp[i-1] else: dp[i] = dp[i-1] return dp # 法三:滚动数组 ''' 因为dp[i]只与dp[i-1]和dp[i-2]有关 则可以利用滚动数组进行空间压缩 dp0,dp1 充当dp[i-2],dp[i-1] ''' def translate(num): if num<10: return 1 nums = str(num) dp0 = dp1 = 1 res = 0 for i in range(1,len(nums)): if 10<=int(nums[i-1:i+1])<=25: res = dp0+dp1 else: res = dp1 dp0 = dp1 dp1 = res return res