基础DP 假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
1 2 3 4 5 6 输入:n = 3 输出:3 解释:有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
递归
dfs(i)
表示从0
爬到第i
阶楼梯一共有多少种方法。最后一步可以爬1阶或者2阶,则dfs(i)
等于dfs(i - 1)
和dfs(i - 2)
之和
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public int climbStairs (int n) { return dfs(n); } public int dfs (int i) { if (i <= 1 ) { return 1 ; } return dfs(i - 1 ) + dfs(i - 2 ); } }
dfs 的搜索过程为一颗二叉树,树高为$n$,节点个数为$2^n$,故搜索时间为$O(2^n)$,空间复杂度为$O(n)$,递归需要$O(n)$个栈空间
记忆化搜索
上面的做法会超时,太慢了,可以引入记忆化数组优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int climbStairs (int n) { int []memo = new int [n + 1 ]; return dfs(n, memo); } public int dfs (int i, int []memo) { if (i <= 1 ) { return 1 ; } if (memo[i] != 0 ) { return memo[i]; } return memo[i] = dfs(i - 1 , memo) + dfs(i - 2 , memo); } }
因为递归是由上至下展开、由下至上收敛的过程。当递归到最小的问题(i <= 1
)时,逐步返回计算结果,并将这些结果存储在 memo
数组中。当递归回到较高层级时,已经计算过的结果保存在 memo
中,可以直接访问,避免重复计算。
递推
1 2 3 4 5 6 7 8 9 10 class Solution { public int climbStairs (int n) { int [] f = new int [n + 1 ]; f[0 ] = f[1 ] = 1 ; for (int i = 2 ; i <= n; i++) { f[i] = f[i - 1 ] + f[i - 2 ]; } return f[n]; } }
我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。具体来说,$f[i]$ 的定义和 $dfs(i)$ 的定义是一样的,都表示从 0 爬到 i 有多少种不同的方法。
相应的递推式(状态转移方程)也和 $dfs$ 一样:$f[i]=f[i−1]+f[i−2]$
递推方法的核心在于通过自底向上的迭代计算,避免了递归中的重复计算和函数调用开销,达到了高效解决问题的目的。
当然,转换为三个变量滚动计算这里就不写了
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
示例 1:
1 2 3 4 输入:[1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。
递归
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public int rob (int [] nums) { int n = nums.length; return dfs(n - 1 , nums); } public int dfs (int i, int []nums) { if (i < 0 ) { return 0 ; } return Math.max(dfs(i - 1 , nums), dfs(i - 2 , nums) + nums[i]); } }
超时,记忆化搜索
记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int rob (int [] nums) { int n = nums.length; int []memo = new int [n]; Arrays.fill(memo, -1 ); return dfs(n - 1 , nums, memo); } public int dfs (int i, int []nums, int [] memo) { if (i < 0 ) { return 0 ; } if (memo[i] != -1 ) { return memo[i]; } return memo[i] = Math.max(dfs(i - 1 , nums, memo), dfs(i - 2 , nums, memo) + nums[i]); } }
memo 数组的初始值 一定不能等于要记忆化的值!上一题的方案数不可能为0,故没有初始化数组,但是这道题要记忆化的值是金额可以为0,故要初始化数组为-1
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public int rob (int [] nums) { int n = nums.length; int []f = new int [n + 2 ]; for (int i = 0 ; i < n; ++ i) { f[i + 2 ] = Math.max(f[i + 1 ], f[i] + nums[i]); } return f[n + 1 ]; } }class Solution { public int rob (int [] nums) { int n = nums.length; int pre = 0 , cur = pre; for (int i = 0 ; i < n; ++ i) { int t = Math.max(cur, pre + nums[i]); pre = cur; cur = t; } return cur; } }
给你一个整数数组 cost
,其中 cost[i]
是从楼梯第 i
个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0
或下标为 1
的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
示例 1:
1 2 3 输入:cost = [1,100,1,1 ,1,100,1,1 ,100 ,1 ] 输出:6 解释:你将从下标为 0 的台阶开始。
递归+记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { int []cost; int []memo; public int minCostClimbingStairs (int [] cost) { this .cost = cost; memo = new int [cost.length + 1 ]; Arrays.fill(memo, -1 ); return dfs(cost.length); } public int dfs (int i) { if (i <= 1 ) return 0 ; if (memo[i] != -1 ) return memo[i]; return memo[i] = Math.min(dfs(i - 1 ) + cost[i - 1 ], dfs(i - 2 ) + cost[i - 2 ]); } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public int minCostClimbingStairs (int [] cost) { int n = cost.length; int []f = new int [n + 2 ]; for (int i = 0 ; i < n - 1 ; ++ i) { f[i + 2 ] = Math.min(f[i + 1 ] + cost[i + 1 ], f[i] + cost[i]); } return f[n]; }public int minCostClimbingStairs (int [] cost) { int n = cost.length; int pre = 0 , cur = pre; for (int i = 0 ; i < n - 1 ; ++ i) { int t = Math.min(cur + cost[i + 1 ], pre + cost[i]); pre = cur; cur = t; } return cur; }
给你一个由 不同 整数组成的数组 nums
,和一个目标整数 target
。请你从 nums
中找出并返回总和为 target
的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
示例 1:
1 2 3 4 5 6 7 8 9 10 11 12 输入:nums = [1 ,2 ,3 ], target = 4 输出:7 解释: 所有可能的组合为: (1 , 1 , 1 , 1 ) (1 , 1 , 2 ) (1 , 2 , 1 ) (1 , 3 ) (2 , 1 , 1 ) (2 , 2 ) (3 , 1 ) 请注意,顺序不同的序列被视作不同的组合。
dfs + 记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int combinationSum4 (int [] nums, int target) { int n = nums.length; int []memo = new int [target + 1 ]; Arrays.fill(memo, -1 ); return dfs(nums, target, memo); } public int dfs (int [] nums, int target, int [] memo) { if (target < 0 ) return 0 ; if (target == 0 ) { return 1 ; } if (memo[target] != -1 ) { return memo[target]; } int res = 0 ; for (int i : nums) { res += dfs(nums, target - i, memo); } return memo[target] = res; } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 public int combinationSum4 (int [] nums, int target) { int n = nums.length; int f[] = new int [target + 1 ]; f[0 ] = 1 ; for (int i = 1 ; i <= target; i ++) { for (int x : nums) { if (x <= i) { f[i] += f[i - x]; } } } return f[target]; }
递推
1 2 3 4 5 6 7 8 9 10 11 public int countGoodStrings (int low, int high, int zero, int one) { int f[] = new int [high + 1 ]; int mod = 1_000_000_007 , ans = 0 ; f[0 ] = 1 ; for (int i = 1 ; i <= high; i ++) { if (i >= zero) f[i] = (f[i] + f[i - zero]) % mod; if (i >= one) f[i] = (f[i] + f[i - one]) % mod; if (i >= low) ans = (ans + f[i]) % mod; } return ans; }
分段 dfs + 记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { final int mod = 1_000_000_007 ; public int countTexts (String pressedKeys) { int []index = {0 , 0 , 3 , 3 , 3 , 3 , 3 , 4 , 3 , 4 }; long res = 1 ; int i = 0 , n = pressedKeys.length(); while (i < n) { int len = 1 ; while (i + 1 < n && pressedKeys.charAt(i) == pressedKeys.charAt(i + 1 )) { i ++; len ++; } int []memo = new int [len + 1 ]; int s = index[pressedKeys.charAt(i) - '0' ]; res = (res * dfs(s, len, memo)) % mod; i ++; } return (int ) res; } public int dfs (int s, int len, int []memo) { if (len == 0 ) { return 1 ; } if (memo[len] != 0 ) return memo[len]; long res = 0 ; for (int i = 1 ; i <= s; i ++) { if (len >= i) { res = (res + dfs(s, len - i, memo)) % mod; } } return memo[len] = (int ) res; } }
给你一个整数数组 nums
,你可以对它进行一些操作。
每次操作中,选择任意一个 nums[i]
,删除它并获得 nums[i]
的点数。之后,你必须删除 所有 等于 nums[i] - 1
和 nums[i] + 1
的元素。
开始你拥有 0
个点数。返回你能通过这些操作获得的最大点数。
示例 2:
1 2 3 4 5 6 输入:nums = [2,2,3,3,3,4] 输出:9 解释: 删除 3 获得 3 个点数,接着要删除两个 2 和 4 。 之后,再次删除 3 获得 3 个点数,再次删除 3 获得 3 个点数。 总共获得 9 个点数。
dfs + 记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int deleteAndEarn (int [] nums) { Map<Integer, Integer> mp = new HashMap <>(); for (int i : nums) { mp.put(i, mp.getOrDefault(i, 0 ) + 1 ); } int n = mp.size(), k = 0 ; int []a = new int [n]; for (int x : mp.keySet()) { a[k ++] = x; } Arrays.sort(a); int []memo = new int [n]; return dfs(a, n - 1 , mp, memo); } public int dfs (int []a, int i, Map<Integer, Integer> mp, int []memo) { if (i < 0 ) return 0 ; if (memo[i] != 0 ) return memo[i]; int j = i; while (j > 0 && a[j - 1 ] >= a[i] - 1 ) -- j; return memo[i] = Math.max(dfs(a, i - 1 , mp, memo), a[i] * mp.get(a[i]) + dfs(a, j - 1 , mp, memo)); } }
一个细节:a[j - 1] >= a[i] - 1
和 dfs(a, j - 1, mp, memo)
这里的 j - 1 不能为 j ,否则可能因为 j 未减小导致无限递归。
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int deleteAndEarn (int [] nums) { Map<Integer, Integer> mp = new HashMap <>(); for (int i : nums) { mp.put(i, mp.getOrDefault(i, 0 ) + 1 ); } int n = mp.size(), k = 0 ; int []a = new int [n]; for (int x : mp.keySet()) { a[k ++] = x; } Arrays.sort(a); int []f = new int [n + 1 ]; int j = 0 ; for (int i = 0 ; i < n; ++ i) { while (a[j] < a[i] - 1 ) ++ j; f[i + 1 ] = Math.max(f[i], f[j] + a[i] * mp.get(a[i])); } return f[n]; } }
一个魔法师有许多不同的咒语。
给你一个数组 power
,其中每个元素表示一个咒语的伤害值,可能会有多个咒语有相同的伤害值。
已知魔法师使用伤害值为 power[i]
的咒语时,他们就 不能 使用伤害为 power[i] - 2
,power[i] - 1
,power[i] + 1
或者 power[i] + 2
的咒语。
每个咒语最多只能被使用 一次 。
请你返回这个魔法师可以达到的伤害值之和的 最大值 。
dfs + 记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public long maximumTotalDamage (int [] nums) { Map<Integer, Integer> mp = new HashMap <>(); for (int i : nums) { mp.put(i, mp.getOrDefault(i, 0 ) + 1 ); } int n = mp.size(), k = 0 ; int []a = new int [n]; for (int x : mp.keySet()) { a[k ++] = x; } Arrays.sort(a); long []memo = new long [n]; return dfs(a, n - 1 , mp, memo); } public long dfs (int []a, int i, Map<Integer, Integer> mp, long []memo) { if (i < 0 ) return 0 ; if (memo[i] != 0 ) return memo[i]; int j = i; while (j > 0 && a[j - 1 ] >= a[i] - 2 ) -- j; return memo[i] = Math.max(dfs(a, i - 1 , mp, memo), (long ) a[i] * mp.get(a[i]) + dfs(a, j - 1 , mp, memo)); } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public long maximumTotalDamage (int [] nums) { Map<Integer, Integer> mp = new HashMap <>(); for (int i : nums) { mp.merge(i, 1 , Integer::sum); } int n = mp.size(), k = 0 ; int []a = new int [n]; for (int x : mp.keySet()) { a[k ++] = x; } Arrays.sort(a); long []f = new long [n + 1 ]; int j = 0 ; for (int i = 0 ; i < n; ++ i) { while (a[j] < a[i] - 2 ) ++ j; f[i + 1 ] = Math.max(f[i], f[j] + (long ) a[i] * mp.get(a[i])); } return f[n]; } }
dfs + 记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { final int mod = 1_000_000_007 ; public int countHousePlacements (int n) { long []memo = new long [n + 1 ]; long res = dfs(n, memo); return (int ) (res % mod * res % mod) % mod; } public long dfs (int i, long []memo) { if (i == 0 ) return 1 ; if (i == 1 ) return 2 ; if (memo[i] != 0 ) return memo[i]; return memo[i] = (dfs(i - 1 , memo) + dfs(i - 2 , memo)) % mod; } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { final int mod = 1_000_000_007 ; public int countHousePlacements (int n) { if (n == 1 ) return 4 ; long []f = new long [n + 1 ]; f[0 ] = 1 ; f[1 ] = 2 ; for (int i = 2 ; i <= n; ++ i) { f[i] = (f[i - 1 ] + f[i - 2 ]) % mod; } return (int ) (f[n] % mod * f[n] % mod) % mod; } }
优化空间
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public int countHousePlacements (int n) { int mod = 1_000_000_007 ; if (n == 1 ) return 4 ; long pre = 1 , cur = 2 ; for (int i = 1 ; i < n; i ++) { long t = (pre + cur) % mod; pre = cur; cur = t; } return (int ) (cur % mod * cur % mod) % mod; } }
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { int []memo; public int rob (int [] nums) { int n = nums.length; memo = new int [n]; Arrays.fill(memo, -1 ); return Math.max(nums[0 ] + dfs(nums, 2 , n - 2 ), dfs(nums, 1 , n - 1 )); } public int dfs (int []nums, int k, int i) { if (i < k) return 0 ; if (memo[i] != -1 ) return memo[i]; return Math.max(dfs(nums, k, i - 1 ), dfs(nums, k, i - 2 ) + nums[i]); } }
递推(按照上面一比一翻译)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { int []memo; public int rob (int [] nums) { return Math.max(nums[0 ] + dfs(nums, 2 ), dfs(nums, 1 )); } public int dfs (int []nums, int start) { int n = nums.length; int []f = new int [n + 2 ]; for (int i = start; i < n; i ++) { f[i + 2 ] = Math.max(f[i + 1 ], f[i] + nums[i]); } return f[n - start + 2 ]; } }
空间优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int rob (int [] nums) { int n = nums.length; return Math.max(nums[0 ] + dfs(nums, 2 , n - 1 ), dfs(nums, 1 , n)); } public int dfs (int []nums, int start, int end) { int n = nums.length; int pre = 0 , cur = pre; for (int i = start; i < end; i ++) { int t = Math.max(nums[i] + pre, cur); pre = cur; cur = t; } return cur; } }
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
1 2 3 输入:nums = [-2 ,1,-3 ,4,-1 ,2,1,-5 ,4] 输出:6 解释:连续子数组 [4,-1 ,2,1] 的和最大,为 6 。
动态规划,空间优化版
1 2 3 4 5 6 7 8 9 public int maxSubArray (int [] nums) { int n = nums.length; int pre = 0 , res = Integer.MIN_VALUE; for (int i = 0 ; i < n; ++ i) { pre = Math.max(pre, 0 ) + nums[i]; res = Math.max(res, pre); } return res; }
前缀和:从前往后遍历过程中维护一个最小的前缀和值,用当前前缀和减去最小前缀和即为可能答案
1 2 3 4 5 6 7 8 9 10 11 public int maxSubArray (int [] nums) { int n = nums.length; int mi = 0 , sum = 0 , res = Integer.MIN_VALUE; for (int i = 0 ; i < n; ++ i) { sum += nums[i]; res = Math.max(sum - mi, res); mi = Math.min(sum, mi); } return res; }
先映射,确定字符串s中每一个字符的cost,接下来就是最大子数组和的做法了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public int maximumCostSubstring (String s, String chars, int [] vals) { int n = s.length(); int []mapping = new int [26 ]; for (int i = 0 ; i < 26 ; ++ i) { mapping[i] = i + 1 ; } for (int i = 0 ; i < vals.length; ++ i) { mapping[chars.charAt(i) - 'a' ] = vals[i]; } int res = 0 , pre = 0 ; for (char c : s.toCharArray()) { pre = Math.max(pre, 0 ) + mapping[c - 'a' ]; res = Math.max(res, pre); } return res; }
请你找出 nums
中 和的绝对值 最大的任意子数组(可能为空 ),并返回该 最大值 。
多维护一个任意子数组的最小值变量mm
1 2 3 4 5 6 7 8 9 public int maxAbsoluteSum (int [] nums) { int mm = 0 , mx = 0 , res = 0 , n = nums.length; for (int i = 0 ; i < n; ++ i) { mx = Math.max(mx, 0 ) + nums[i]; mm = Math.min(mm, 0 ) + nums[i]; res = Math.max(Math.max(mx, - mm), res); } return res; }
给定一个整数数组 arr
和一个整数 k
,通过重复 k
次来修改数组。
例如,如果 arr = [1, 2]
, k = 3
,那么修改后的数组将是 [1, 2, 1, 2, 1, 2]
。
返回修改后的数组中的最大的子数组之和。注意,子数组长度可以是 0
,在这种情况下它的总和也是 0
。
由于 结果可能会很大 ,需要返回的 109 + 7
的 模 。
别被一个小小的变形吓跑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public int kConcatenationMaxSum (int [] arr, int k) { int mod = 1_000_000_007 , sum = 0 ; long res; for (int i : arr) sum += i; res = kadane(arr, 1 ); if (k > 1 ) { res = kadane(arr, 2 ); if (sum > 0 ) { res += (long ) sum * (k - 2 ) % mod; } } return (int ) res % mod; }public int kadane (int []arr, int k) { int n = arr.length; int pre = 0 , res = 0 ; for (int i = 0 ; i < n * k; i ++) { pre = (Math.max(pre, 0 ) + arr[i % n]); res = Math.max(res, pre); } return res; }
给定一个长度为 n
的环形整数数组 nums
,返回 nums
的非空 子数组 的最大可能和 。
示例 1:
1 2 3 输入:nums = 输出:10 解释:从子数组 得到最大和 5 + 5 = 10
思路:同时维护最小子数组和mi,与最大子数组和mx,以及数组总和sum,sum - mi就是跨过边界的子数组最大和
1 2 3 4 5 6 7 8 9 10 11 12 13 public int maxSubarraySumCircular (int [] nums) { int n = nums.length, pmx = 0 , pmi = 0 , mx = Integer.MIN_VALUE, mi = Integer.MAX_VALUE; int sum = 0 ; for (int i = 0 ; i < n; ++ i) { sum += nums[i]; pmx = Math.max(pmx, 0 ) + nums[i]; pmi = Math.min(pmi, 0 ) + nums[i]; mi = Math.min(mi, pmi); mx = Math.max(mx, pmx); } if (mi == sum) return mx; else return Math.max(mx, sum - mi); }
就是求两个数组的差集数组nums2 - nums1
的最大子数组和,再加上nums1
的和;对于nums2
再来一次该操作,最后返回二者最大的即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public int maximumsSplicedArray (int [] nums1, int [] nums2) { int n = nums1.length, sum1 = 0 , sum2 = 0 ; for (int i = 0 ; i < n; ++ i) { int t1 = nums1[i], t2 = nums2[i]; sum1 += t1; sum2 += t2; nums2[i] = t2 - t1; nums1[i] = t1 - t2; } int mx1 = 0 , res1 = 0 , mx2 = 0 , res2 = 0 ; for (int i : nums1) { mx1 = Math.max(mx1 + i, 0 ) ; res1 = Math.max(res1, mx1); } for (int i : nums2) { mx2 = Math.max(mx2 + i, 0 ); res2 = Math.max(res2, mx2); } return Math.max(sum2 + res1, sum1 + res2); }
封装成函数
1 2 3 4 5 6 7 8 9 10 11 12 13 public int maximumsSplicedArray (int [] nums1, int [] nums2) { return Math.max(kadane(nums1, nums2), kadane(nums2, nums1)); }public int kadane (int []nums1, int []nums2) { int mx = 0 , res = 0 , sum = 0 ; for (int i = 0 ; i < nums1.length; ++ i) { sum += nums1[i]; mx = Math.max(mx + nums2[i] - nums1[i], 0 ) ; res = Math.max(res, mx); } return res + sum; }
给你一个整数数组 nums
,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例 1:
1 2 3 输入: nums = [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 有最大乘积 6。
思路就是维护一个乘积的最大值 mx
一个最小值 mi
,每次遇到负数就交换最大最小值,遇到0就把 mx
和 mi
置为1,计算下一段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public int maxProduct (int [] nums) { int n = nums.length, mi = 1 , mx = 1 , res = Integer.MIN_VALUE; for (int i : nums) { if (i == 0 ) { mx = 1 ; mi = 1 ; res = Math.max(res, 0 ); } else if (i < 0 ) { int t = mx; mx = Math.max(i * mi, i); mi = Math.min(i * t, i); res = Math.max(res, mx); } else { mx = Math.max(i * mx, i); mi = Math.min(i * mi, i); res = Math.max(res, mx); } } return res; }
网格图DP 现有一个记作二维矩阵 frame
的珠宝架,其中 frame[i][j]
为该位置珠宝的价值。拿取珠宝的规则为:
只能从架子的左上角开始拿珠宝
每次可以移动到右侧或下侧的相邻位置
到达珠宝架子的右下角时,停止拿取
dfs + 记忆化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { int [][] frame; int [][]memo; public int jewelleryValue (int [][] frame) { this .frame = frame; int n = frame.length, m = frame[0 ].length; memo = new int [n][m]; for (int []a : memo) Arrays.fill(a, -1 ); return dfs(n - 1 , m - 1 ); } public int dfs (int x, int y) { if (x < 0 || y < 0 ) return 0 ; if (memo[x][y] != -1 ) return memo[x][y]; return memo[x][y] = Math.max(dfs(x - 1 , y), dfs(x, y - 1 )) + frame[x][y]; } }
递推
1 2 3 4 5 6 7 8 9 10 public int jewelleryValue (int [][] frame) { int n = frame.length, m = frame[0 ].length; var f = new int [n + 1 ][m + 1 ]; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j < m; ++ j) { f[i + 1 ][j + 1 ] = Math.max(f[i][j + 1 ], f[i + 1 ][j]) + frame[i][j]; } } return f[n][m]; }
空间优化,
1 2 3 4 5 6 7 8 9 10 public int jewelleryValue (int [][] frame) { int n = frame.length, m = frame[0 ].length; var f = new int [2 ][m + 1 ]; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j < m; ++ j) { f[(i + 1 ) % 2 ][j + 1 ] = Math.max(f[i % 2 ][j + 1 ], f[(i + 1 ) % 2 ][j]) + frame[i][j]; } } return f[n % 2 ][m]; }
1 2 3 4 5 6 7 8 9 10 public int jewelleryValue (int [][] frame) { int n = frame.length, m = frame[0 ].length; var f = new int [m + 1 ]; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j < m; ++ j) { f[j + 1 ] = Math.max(f[j + 1 ], f[j]) + frame[i][j]; } } return f[m]; }
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
dfs + memo
1 2 3 4 5 6 7 8 9 10 public int uniquePaths (int m, int n) { int [][]memo = new int [m][n]; return dfs(m - 1 , n - 1 , memo); }public int dfs (int m, int n, int [][]memo) { if (m == 0 || n == 0 ) return 1 ; if (memo[m][n] > 0 ) return memo[m][n]; return memo[m][n] = dfs(m - 1 , n, memo) + dfs(m, n - 1 , memo); }
空间优化
1 2 3 4 5 6 7 8 9 10 public int uniquePaths (int m, int n) { int []f = new int [n]; Arrays.fill(f, 1 ); for (int i = 1 ; i < m; ++ i) { for (int j = 1 ; j < n; ++ j) { f[j] += f[j - 1 ]; } } return f[n - 1 ]; }
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1
和 0
来表示。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { int [][]obstacleGrid; public int uniquePathsWithObstacles (int [][] obstacleGrid) { this .obstacleGrid = obstacleGrid; int m = obstacleGrid.length; int n = obstacleGrid[0 ].length; int [][]memo = new int [m][n]; return dfs(m - 1 , n - 1 , memo); } public int dfs (int x, int y, int [][]memo) { if (x >= 0 && y >= 0 && obstacleGrid[x][y] == 1 ) return 0 ; if (x == 0 && y == 0 ) return 1 ; if (x < 0 || y < 0 ) return 0 ; if (memo[x][y] > 0 ) return memo[x][y]; return memo[x][y] = dfs(x - 1 , y, memo) + dfs(x, y - 1 , memo); } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public int uniquePathsWithObstacles (int [][] obstacleGrid) { int m = obstacleGrid.length; int n = obstacleGrid[0 ].length; int [][]dp = new int [m][n]; for (int i = 0 ; i < m; ++ i) { for (int j = 0 ; j < n; ++ j) { if (obstacleGrid[i][j] != 1 ) { if (i == 0 && j == 0 ) dp[i][j] = 1 ; else if (i == 0 ) dp[i][j] = dp[i][j - 1 ]; else if (j == 0 ) dp[i][j] = dp[i - 1 ][j]; else dp[i][j] = dp[i - 1 ][j] + dp[i][j - 1 ]; } } } return dp[m - 1 ][n - 1 ]; }
空间优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public int uniquePathsWithObstacles (int [][] obstacleGrid) { int m = obstacleGrid.length; int n = obstacleGrid[0 ].length; int []dp = new int [n]; dp[0 ] = obstacleGrid[0 ][0 ] == 0 ? 1 : 0 ; for (int i = 0 ; i < m; ++ i) { for (int j = 0 ; j < n; ++ j) { if (obstacleGrid[i][j] == 1 ) { dp[j] = 0 ; } else if (j > 0 ) { dp[j] += dp[j - 1 ]; } } } return dp[n - 1 ]; }
给定一个包含非负整数的 *m* x *n*
网格 grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明: 每次只能向下或者向右移动一步。
原地修改
1
3
1
1
4
5
1
5
1
修改后–>
2
7
6
4
2
1
6
8
7
1 2 3 4 5 6 7 8 9 10 11 12 13 public int minPathSum (int [][] grid) { int m = grid.length; int n = grid[0 ].length; for (int j = 0 ; j < m; ++ j) { for (int i = 0 ; i < n; ++ i) { if (i == 0 && j == 0 ) continue ; else if (j == 0 ) grid[0 ][i] += grid[0 ][i - 1 ]; else if (i == 0 ) grid[j][0 ] += grid[j - 1 ][0 ]; else grid[j][i] = Math.min(grid[j][i - 1 ], grid[j - 1 ][i]) + grid[j][i]; } } return grid[m - 1 ][n - 1 ]; }
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { List<List<Integer>> triangle; int n; int [][]memo; public int minimumTotal (List<List<Integer>> triangle) { this .triangle = triangle; this .n = triangle.size(); this .memo = new int [n][n]; for (int []a : memo) Arrays.fill(a, Integer.MAX_VALUE); return dfs(0 , 0 ); } public int dfs (int r, int c) { if (r == n) return 0 ; if (memo[r][c] != Integer.MAX_VALUE) return memo[r][c]; return memo[r][c] = Math.min(dfs(r + 1 , c), dfs(r + 1 , c + 1 )) + triangle.get(r).get(c); } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public int minimumTotal (List<List<Integer>> triangle) { int n = triangle.size(); int [][]dp = new int [n + 1 ][n + 1 ]; for (int []a : dp) Arrays.fill(a, Integer.MAX_VALUE); dp[0 ][0 ] = 0 ; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j <= i; ++ j) { dp[i + 1 ][j + 1 ] = Math.min(dp[i][j + 1 ], dp[i][j]) + triangle.get(i).get(j); } } int res = Integer.MAX_VALUE; for (int i = 0 ; i < n; ++ i) { if (dp[n][i + 1 ] < res) res = dp[n][i + 1 ]; } return res; }
空间优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public int minimumTotal (List<List<Integer>> triangle) { int n = triangle.size(); int []dp = new int [n]; Arrays.fill(dp, Integer.MAX_VALUE); dp[0 ] = triangle.get(0 ).get(0 ); for (int i = 1 ; i < n; ++ i) { for (int j = i; j >= 0 ; -- j) { if (j > 0 ) { dp[j] = Math.min(dp[j], dp[j - 1 ]) + triangle.get(i).get(j); } else { dp[j] += triangle.get(i).get(j); } } } int res = Integer.MAX_VALUE; for (int i = 0 ; i < n; ++ i) { if (dp[i] < res) res = dp[i]; } return res; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public int minimumTotal (List<List<Integer>> triangle) { int n = triangle.size(); int [] dp = new int [n]; for (int i = 0 ; i < n; i++) { dp[i] = triangle.get(n - 1 ).get(i); } for (int i = n - 2 ; i >= 0 ; i--) { for (int j = 0 ; j <= i; j++) { dp[j] = Math.min(dp[j], dp[j + 1 ]) + triangle.get(i).get(j); } } return dp[0 ]; }
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minFallingPathSum (int [][] matrix) { int n = matrix.length, m = matrix[0 ].length; int [][]memo = new int [n][m]; for (int []a : memo) Arrays.fill(a, Integer.MAX_VALUE); int res = Integer.MAX_VALUE; for (int i = 0 ; i < m; ++ i) { int t = dfs(n - 1 , i, matrix, memo); if (res > t) res = t; } return res; } public int dfs (int x, int y, int [][]matrix, int [][]memo) { if (x < 0 ) return 0 ; if (y < 0 || y >= matrix[0 ].length) return Integer.MAX_VALUE; if (memo[x][y] != Integer.MAX_VALUE) return memo[x][y]; return memo[x][y] = Math.min(Math.min(dfs(x-1 , y, matrix, memo), dfs(x-1 , y-1 , matrix, memo)), dfs(x-1 , y+1 , matrix, memo)) + matrix[x][y]; } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public int minFallingPathSum (int [][] matrix) { int n = matrix.length; int res = Integer.MAX_VALUE; int [][]dp = new int [n][n + 2 ]; System.arraycopy(matrix[0 ], 0 , dp[0 ], 1 , n); for (int i = 1 ; i < n; ++ i) { dp[i - 1 ][0 ] = dp[i - 1 ][n + 1 ] = Integer.MAX_VALUE; for (int j = 1 ; j <= n; ++ j) { dp[i][j] = Math.min(Math.min(dp[i - 1 ][j - 1 ], dp[i - 1 ][j]), dp[i - 1 ][j + 1 ]) + matrix[i][j - 1 ]; } } for (int i = 1 ; i <= n; ++ i) { if (res > dp[n - 1 ][i]) res = dp[n - 1 ][i]; } return res; }
题目太复杂,建议去原链接里读
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { int [][]grid; int [][]moveCost; int m, n; public int minPathCost (int [][] grid, int [][] moveCost) { this .grid = grid; this .moveCost = moveCost; this .m = grid.length; this .n = grid[0 ].length; int res = Integer.MAX_VALUE; int [][]memo = new int [m][n]; for (int i = 0 ; i < n; ++ i) { int t = dfs(0 , i, memo); if (t < res) res = t; } return res; } public int dfs (int x, int y, int [][]memo) { if (x == m - 1 ) return grid[x][y]; if (memo[x][y] != 0 ) return memo[x][y]; int res = Integer.MAX_VALUE; for (int i = 0 ; i < n; ++ i) { int t = dfs(x + 1 , i, memo) + grid[x][y] + moveCost[grid[x][y]][i]; res = Math.min(res, t); } return memo[x][y] = res; } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public int minPathCost (int [][] grid, int [][] moveCost) { int m = grid.length; int n = grid[0 ].length; int ans = Integer.MAX_VALUE; int [][]dp = new int [m][n]; for (int i = 0 ; i < n; ++ i) dp[0 ][i] = grid[0 ][i]; for (int i = 1 ; i < m; ++ i) { for (int j = 0 ; j < n; ++ j) { int res = Integer.MAX_VALUE; for (int k = 0 ; k < n; ++ k) { int t = dp[i - 1 ][k] + moveCost[grid[i - 1 ][k]][j]; if (res > t) res = t; } dp[i][j] = grid[i][j] + res; } } for (int i = 0 ; i < n; ++ i) { if (ans > dp[m - 1 ][i]) ans = dp[m - 1 ][i]; } return ans; }
空间优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public int minPathCost (int [][] grid, int [][] moveCost) { int m = grid.length; int n = grid[0 ].length; int ans = Integer.MAX_VALUE; for (int i = m - 2 ; i >= 0 ; -- i) { for (int j = 0 ; j < n; ++ j) { int res = Integer.MAX_VALUE; for (int k = 0 ; k < n; ++ k) { int t = grid[i + 1 ][k] + moveCost[grid[i][j]][k]; if (res > t) res = t; } grid[i][j] += res; } } for (int i = 0 ; i < n; ++ i) { if (ans > grid[0 ][i]) ans = grid[0 ][i]; } return ans; }
给你一个 n x n
整数矩阵 grid
,请你返回 非零偏移下降路径 数字和的最小值。
非零偏移下降路径 定义为:从 grid
数组中的每一行选择一个数字,且按顺序选出来的数字中,相邻数字不在原数组的同一列。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { int [][]grid; public int minFallingPathSum (int [][] grid) { this .grid = grid; int res = Integer.MAX_VALUE; int [][]memo = new int [grid.length][grid[0 ].length]; for (int []a : memo) Arrays.fill(a, Integer.MAX_VALUE); for (int i = 0 ; i < grid[0 ].length; ++ i) { res = Math.min(res, dfs(0 , i, memo)); } return res; } public int dfs (int x, int y, int [][]memo) { if (x == grid.length - 1 ) return grid[x][y]; if (memo[x][y] != Integer.MAX_VALUE) return memo[x][y]; int res = Integer.MAX_VALUE; for (int i = 0 ; i < grid[0 ].length; ++ i) { if (i != y) { res = Math.min(dfs(x + 1 , i, memo) + grid[x][y], res); } } return memo[x][y] = res; } }
递推 + 原地修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public int minFallingPathSum (int [][] grid) { int n = grid.length; int ans = Integer.MAX_VALUE; int [][]dp = new int [n][n]; for (int i = n - 2 ; i >= 0 ; -- i) { for (int j = 0 ; j < n; ++ j) { int res = Integer.MAX_VALUE; for (int k = 0 ; k < n; ++ k) { if (k != j) { res = Math.min(res, grid[i+1 ][k]); } } grid[i][j] += res; } } for (int a : grid[0 ]) { ans = Math.min(ans, a); } return ans; }
给你一个大小为 m x n
的矩阵 grid
。最初,你位于左上角 (0, 0)
,每一步,你可以在矩阵中 向右 或 向下 移动。
在从左上角 (0, 0)
开始到右下角 (m - 1, n - 1)
结束的所有路径中,找出具有 最大非负积 的路径。路径的积是沿路径访问的单元格中所有整数的乘积。
返回 最大非负积 对 109 + 7
取余 的结果。如果最大积为 负数 ,则返回 -1
。
注意, 取余是在得到最大积之后执行的。
直接上来就动态规划递推解决,但是在两个地方被卡了一个小时
(int) (mx[m - 1][n - 1] % mod)
取模操作优先级低于类型转换,必须加括号
不认真读题,在每一步更新 mx[i][j]
的同时就对其取模,导致后续下一层比较大小时出问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public int maxProductPath (int [][] grid) { int mod = 1_000_000_007 ; int m = grid.length; int n = grid[0 ].length; long [][]mx = new long [m + 1 ][n + 1 ]; long [][]mi = new long [m + 1 ][n + 1 ]; mx[0 ][0 ] = mi[0 ][0 ] = grid[0 ][0 ]; for (int i = 1 ; i < m; ++ i) { mx[i][0 ] = (mx[i - 1 ][0 ] * grid[i][0 ]); mi[i][0 ] = mx[i][0 ]; } for (int i = 1 ; i < n; ++ i) { mx[0 ][i] = (mx[0 ][i - 1 ] * grid[0 ][i]); mi[0 ][i] = mx[0 ][i]; } for (int i = 1 ; i < m; ++ i) { for (int j = 1 ; j < n; ++ j) { mx[i][j] = (grid[i][j] * Math.max(mx[i][j - 1 ], mx[i - 1 ][j])); mi[i][j] = (grid[i][j] * Math.min(mi[i][j- 1 ], mi[i - 1 ][j])); if (grid[i][j] < 0 ) { mx[i][j] = (grid[i][j] * Math.min(mi[i][j - 1 ], mi[i - 1 ][j])); mi[i][j] = (grid[i][j] * Math.max(mx[i][j - 1 ], mx[i - 1 ][j])); } } } return mx[m - 1 ][n - 1 ] < 0 ? -1 : (int ) (mx[m - 1 ][n - 1 ] % mod); }
给你一个下标从 0 开始的 m x n
整数矩阵 grid
和一个整数 k
。你从起点 (0, 0)
出发,每一步只能往 下 或者往 右 ,你想要到达终点 (m - 1, n - 1)
。
请你返回路径和能被 k
整除的路径数目,由于答案可能很大,返回答案对 109 + 7
取余 的结果。
dfs + memo,区别在于多考虑一个维度,memo的第三维存 x、y
点当前路径和 s
的状态信息
具体来说, memo[x][y][s]
表示从 (x, y)
开始,路径和对 k
取模为 s
的路径数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { final int mod = 1_000_000_007 ; public int numberOfPaths (int [][] grid, int k) { int m = grid.length, n = grid[0 ].length; long [][][]memo = new long [m][n][k]; for (long [][]a : memo) { for (long []b : a) { Arrays.fill(b, -1 ); } } long res = dfs(m - 1 , n - 1 , grid, k, 0 , memo); return (int ) (res % mod); } public long dfs (int x, int y, int [][]grid, int k, int s, long [][][]memo) { int m = grid.length, n = grid[0 ].length; if (x < 0 || y < 0 ) return 0 ; s = (s + grid[x][y]) % k; if (x == 0 && y == 0 ) { return s % k == 0 ? 1 : 0 ; } if (memo[x][y][s] != -1 ) return memo[x][y][s]; return memo[x][y][s] = (dfs(x - 1 , y, grid, k, s, memo) + dfs(x, y - 1 , grid, k, s, memo)) % mod; } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int numberOfPaths (int [][] grid, int k) { int mod = 1_000_000_007 ; int m = grid.length; int n = grid[0 ].length; long [][][] dp = new long [m+1 ][n+1 ][k]; dp[0 ][1 ][0 ] = 1 ; for (int i = 0 ; i < m; ++ i) { for (int j = 0 ; j < n; ++ j) { for (int r = 0 ; r < k; ++ r) { dp[i+1 ][j+1 ][(r + grid[i][j]) % k] = (dp[i][j + 1 ][r] + dp[i + 1 ][j][r]) % mod; } } } return (int ) (dp[m][n][0 ] % mod); } }
给定一个 m x n
整数矩阵 matrix
,找出其中 最长递增路径 的长度。
对于每个单元格,你可以往上,下,左,右四个方向移动。 你 不能 在 对角线 方向上移动或移动到 边界外 (即不允许环绕)。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { int []dx = new int []{1 , 0 , -1 , 0 }; int []dy = new int []{0 , 1 , 0 , -1 }; int [][]matrix; int [][]memo; int m, n; public int longestIncreasingPath (int [][] matrix) { this .m = matrix.length; this .n = matrix[0 ].length; this .matrix = matrix; memo = new int [m][n]; int res = 0 ; for (int i = 0 ; i < m; ++ i) { for (int j = 0 ; j < n; ++ j) { int t = dfs(i, j); if (t > res) res = t; } } return res; } public int dfs (int x, int y) { if (memo[x][y] != 0 ) return memo[x][y]; int t = 1 ; for (int i = 0 ; i < 4 ; ++ i) { int x1 = x + dx[i], y1 = y + dy[i]; if (x1 < 0 || x1 == m || y1 < 0 || y1 == n) continue ; if (matrix[x1][y1] > matrix[x][y]) { t = Math.max(t, 1 + dfs(x1, y1)); } } return memo[x][y] = t; } }
给你一个 m x n
的整数网格图 grid
,你可以从一个格子移动到 4
个方向相邻的任意一个格子。
请你返回在网格图中从 任意 格子出发,达到 任意 格子,且路径中的数字是 严格递增 的路径数目。由于答案可能会很大,请将结果对 109 + 7
取余 后返回。
如果两条路径中访问过的格子不是完全相同的,那么它们视为两条不同的路径。
dfs + memo 把上道题的代码简单改造一下就好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { int []dx = new int []{1 , 0 , -1 , 0 }; int []dy = new int []{0 , 1 , 0 , -1 }; int [][]matrix; int [][]memo; int m, n, mod = 1_000_000_007 ; public int countPaths (int [][] matrix) { this .m = matrix.length; this .n = matrix[0 ].length; this .matrix = matrix; memo = new int [m][n]; long res = 0 ; for (int i = 0 ; i < m; ++ i) { for (int j = 0 ; j < n; ++ j) { res += dfs(i, j); res %= mod; } } return (int ) res % mod; } public int dfs (int x, int y) { if (memo[x][y] != 0 ) return memo[x][y]; int t = 1 ; for (int i = 0 ; i < 4 ; ++ i) { int x1 = x + dx[i], y1 = y + dy[i]; if (x1 < 0 || x1 == m || y1 < 0 || y1 == n) continue ; if (matrix[x1][y1] > matrix[x][y]) { t += dfs(x1, y1); t %= mod; } } return memo[x][y] = t; } }
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { int m, n; char [][]grid; boolean [][][]memo; public boolean hasValidPath (char [][] grid) { this .grid = grid; this .m = grid.length; this .n = grid[0 ].length; memo = new boolean [m][n][(m + n + 1 ) / 2 ]; if (grid[0 ][0 ] == ')' || grid[m-1 ][n-1 ] == '(' || (m + n) % 2 == 0 ) return false ; return dfs(0 , 0 , 0 ); } public boolean dfs (int x, int y, int u) { if (u > m - x + n - y - 1 ) return false ; if (x == m - 1 && y == n - 1 ) return u == 1 ; if (memo[x][y][u]) return false ; memo[x][y][u] = true ; if (grid[x][y] == '(' ) { ++ u; } else { -- u; } return u >= 0 && (x < m - 1 && dfs(x + 1 , y, u) || y < n - 1 && dfs(x, y + 1 , u)); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { int m, n; char [][]grid; boolean [][][]memo; public boolean hasValidPath (char [][] grid) { this .grid = grid; this .m = grid.length; this .n = grid[0 ].length; memo = new boolean [m][n][(m + n + 1 ) / 2 ]; if (grid[0 ][0 ] == ')' || grid[m - 1 ][n - 1 ] == '(' || (m + n) % 2 == 0 ) return false ; return dfs(m - 1 , n - 1 , 0 ); } public boolean dfs (int x, int y, int u) { if (u > x + y + 1 ) return false ; if (x == 0 && y == 0 ) { return u == 1 ; } if (memo[x][y][u]) return false ; memo[x][y][u] = true ; if (grid[x][y] == ')' ) { ++ u; } else { -- u; } return u >= 0 && (x > 0 && dfs(x - 1 , y, u) || y > 0 && dfs(x, y - 1 , u)); } }
给你一个 m x n
的整数矩阵 points
(下标从 0 开始)。一开始你的得分为 0
,你想最大化从矩阵中得到的分数。
你的得分方式为:每一行 中选取一个格子,选中坐标为 (r, c)
的格子会给你的总得分 增加 points[r][c]
。
然而,相邻行之间被选中的格子如果隔得太远,你会失去一些得分。对于相邻行 r
和 r + 1
(其中 0 <= r < m - 1
),选中坐标为 (r, c1)
和 (r + 1, c2)
的格子,你的总得分 减少 abs(c1 - c2)
。
请你返回你能得到的 最大 得分。
dfs+memo也会超时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { int m, n; int [][]points; long [][]memo; public long maxPoints (int [][] points) { this .m = points.length; this .n = points[0 ].length; this .points = points; memo = new long [m][n]; long res = 0 ; for (int i = 0 ; i < n; ++ i) { long t = dfs(m - 1 , i); if (res < t) res = t; } return res; } public long dfs (int x, int y) { if (x == 0 ) return points[x][y]; if (memo[x][y] != 0 ) return memo[x][y]; long t = 0 ; for (int i = 0 ; i < n; ++ i) { long d = dfs(x - 1 , i) ; d = d + points[x][y] - (i - y < 0 ? y - i : i - y); if (t < d) t = d; } return memo[x][y] = t; } }
上面的时间复杂度是O(m*n^2
),超时,下面优化成O(m*n
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public long maxPoints (int [][]points) { int m = points.length, n = points[0 ].length; long [][]dp = new long [m][n]; long res = 0 ; for (int j = 0 ; j < n; ++ j) { dp[0 ][j] = points[0 ][j]; } for (int i = 1 ; i < m; ++ i) { long []leftMax = new long [n]; long []rightMax = new long [n]; leftMax[0 ] = dp[i - 1 ][0 ]; for (int j = 1 ; j < n; ++ j) { leftMax[j] = Math.max(dp[i - 1 ][j], leftMax[j - 1 ] - 1 ); } rightMax[n - 1 ] = dp[i - 1 ][n - 1 ]; for (int j = n - 2 ; j >= 0 ; -- j) { rightMax[j] = Math.max(dp[i - 1 ][j], rightMax[j + 1 ] - 1 ); } for (int j = 0 ; j < n; ++ j) { dp[i][j] = points[i][j] + Math.max(leftMax[j], rightMax[j]); } } for (int j = 0 ; j < n; ++ j) { if (res < dp[m - 1 ][j]) res = dp[m - 1 ][j]; } return res; } }
上面的空间复杂度是O(m*n
),下面优化为滚动数组,空间复杂度O(n
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public long maxPoints (int [][]points) { int m = points.length, n = points[0 ].length; long []pre = new long [n]; long []cur = new long [n]; long res = 0 ; for (int i = 0 ; i < m; ++ i) { long max = 0 ; for (int j = 0 ; j < n; ++ j) { max = Math.max(max - 1 , pre[j]); cur[j] = max; } for (int j = n - 1 ; j >= 0 ; -- j) { cur[j] = Math.max(cur[j], max = Math.max(max - 1 , pre[j])) + points[i][j]; } pre = cur; } for (long t : cur) { if (res < t) res = t; } return res; } }
给你一个 rows x cols
的矩阵 grid
来表示一块樱桃地。 grid
中每个格子的数字表示你能获得的樱桃数目。
你有两个机器人帮你收集樱桃,机器人 1 从左上角格子 (0,0)
出发,机器人 2 从右上角格子 (0, cols-1)
出发。
请你按照如下规则,返回两个机器人能收集的最多樱桃数目:
从格子 (i,j)
出发,机器人可以移动到格子 (i+1, j-1)
,(i+1, j)
或者 (i+1, j+1)
。
当一个机器人经过某个格子时,它会把该格子内所有的樱桃都摘走,然后这个位置会变成空格子,即没有樱桃的格子。
当两个机器人同时到达同一个格子时,它们中只有一个可以摘到樱桃。
两个机器人在任意时刻都不能移动到 grid
外面。
两个机器人最后都要到达 grid
最底下一行。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { int m, n; int [][]grid; int []dy = new int []{-1 , 0 , 1 }; int [][][]memo; public int cherryPickup (int [][] grid) { this .grid = grid; m = grid.length; n = grid[0 ].length; memo = new int [m][n][n]; for (int [][]a : memo) { for (int []b : a) { Arrays.fill(b, -1 ); } } return dfs(0 , 0 , n - 1 ); } public int dfs (int x, int y1, int y2) { if (y1 < 0 || y2 < 0 || y1 == n || y2 == n) return 0 ; if (y1 == y2) return grid[x][y1]; if (x == m - 1 ) return grid[x][y1] + grid[x][y2]; if (memo[x][y1][y2] != -1 ) return memo[x][y1][y2]; int t = 0 ; for (int i = 0 ; i < 3 ; ++ i) { for (int j = 0 ; j < 3 ; ++ j) { t = Math.max(t, dfs(x + 1 , y1 + dy[i], y2 + dy[j])); } } return memo[x][y1][y2] = t + grid[x][y1] + grid[x][y2]; } }
灵神这个版本好理解一些
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { int m, n; int [][]grid; int []dy = new int []{-1 , 0 , 1 }; int [][][]memo; public int cherryPickup (int [][] grid) { this .grid = grid; m = grid.length; n = grid[0 ].length; memo = new int [m][n][n]; for (int [][]a : memo) { for (int []b : a) { Arrays.fill(b, -1 ); } } return dfs(0 , 0 , n - 1 ); } public int dfs (int x, int y1, int y2) { if (x == m || y1 < 0 || y2 < 0 || y1 == n || y2 == n) return 0 ; if (memo[x][y1][y2] != -1 ) return memo[x][y1][y2]; int t = 0 ; for (int i = 0 ; i < 3 ; ++ i) { for (int j = 0 ; j < 3 ; ++ j) { t = Math.max(t, dfs(x + 1 , y1 + dy[i], y2 + dy[j])); } } return memo[x][y1][y2] = t + (y1 == y2 ? grid[x][y1] : grid[x][y1] + grid[x][y2]); } }
给你一个 n x n
的网格 grid
,代表一块樱桃地,每个格子由以下三种数字的一种来表示:
0
表示这个格子是空的,所以你可以穿过它。
1
表示这个格子里装着一个樱桃,你可以摘到樱桃然后穿过它。
-1
表示这个格子里有荆棘,挡着你的路。
请你统计并返回:在遵守下列规则的情况下,能摘到的最多樱桃数:
从位置 (0, 0)
出发,最后到达 (n - 1, n - 1)
,只能向下或向右走,并且只能穿越有效的格子(即只可以穿过值为 0
或者 1
的格子);
当到达 (n - 1, n - 1)
后,你要继续走,直到返回到 (0, 0)
,只能向上或向左走,并且只能穿越有效的格子;
当你经过一个格子且这个格子包含一个樱桃时,你将摘到樱桃并且这个格子会变成空的(值变为 0
);
如果在 (0, 0)
和 (n - 1, n - 1)
之间不存在一条可经过的路径,则无法摘到任何一个樱桃。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Solution { int n; int [][] grid; int [][][][] memo; public int cherryPickup (int [][] grid) { this .grid = grid; n = grid.length; memo = new int [n][n][n][n]; for (int [][][] a : memo) { for (int [][] b : a) { for (int [] c : b) Arrays.fill(c, -1 ); } } int t = dfs(0 , 0 , 0 , 0 ); return t < 0 ? 0 : t; } public int dfs (int x1, int y1, int x2, int y2) { if (x1 == n || y1 == n || x2 == n || y2 == n || grid[x1][y1] == -1 || grid[x2][y2] == -1 ) return Integer.MIN_VALUE; if (x1 == n - 1 && y1 == n - 1 ) return grid[x1][y1]; if (memo[x1][y1][x2][y2] != -1 ) return memo[x1][y1][x2][y2]; int res = Math.max( Math.max(dfs(x1 + 1 , y1, x2 + 1 , y2), dfs(x1 + 1 , y1, x2, y2 + 1 )), Math.max(dfs(x1, y1 + 1 , x2 + 1 , y2), dfs(x1, y1 + 1 , x2, y2 + 1 )) ) + grid[x1][y1] + (x1 == x2 && y1 == y2 ? 0 : grid[x2][y2]); return memo[x1][y1][x2][y2] = res; } }
灵神,减少到三个状态表示,t:每个人走的总步数,j:第一个人走到的纵坐标,k:第二个人走到的纵坐标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import java.util.Arrays;public class Solution { int n; int [][] grid; int [][][] memo; public int cherryPickup (int [][] grid) { this .grid = grid; n = grid.length; memo = new int [2 * n - 1 ][n][n]; for (int [][] a : memo) { for (int [] b : a) { Arrays.fill(b, -1 ); } } int t = dfs(0 , 0 , 0 ); return t < 0 ? 0 : t; } public int dfs (int t, int j, int k) { if (j == n || k == n || t - j == n || t - k == n || grid[t - j][j] == -1 || grid[t - k][k] == -1 ) return Integer.MIN_VALUE; if (t == 2 * n - 2 ) return grid[n - 1 ][n - 1 ]; if (memo[t][j][k] != -1 ) return memo[t][j][k]; int res = Math.max( Math.max(dfs(t + 1 , j + 1 , k), dfs(t + 1 , j, k + 1 )), Math.max(dfs(t + 1 , j + 1 , k + 1 ), dfs(t + 1 , j, k))) + grid[t - j][j] + (j == k ? 0 : grid[t - k][k]); return memo[t][j][k] = res; } }
0-1背包 给你一个非负整数数组 nums
和一个整数 target
。
向数组中的每个整数前添加 '+'
或 '-'
,然后串联起所有整数,可以构造一个 表达式 :
例如,nums = [2, 1]
,可以在 2
之前添加 '+'
,在 1
之前添加 '-'
,然后串联起来得到表达式 "+2-1"
。
返回可以通过上述方法构造的、运算结果等于 target
的不同 表达式 的数目。
dfs + memo: 假设要凑成target,需要添加+号的所有数的和为p,则需要添加负号的为sum(nums) - p,target = p + (p - s)
解得p = (s + target) / 2,因此 s + target 小于0以及为奇数均不合法,题目转化为从n个数组元素中选取元素刚好填满容量为p的背包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { int []nums; public int findTargetSumWays (int [] nums, int target) { this .nums = nums; int sum = Arrays.stream(nums).sum(); target += sum; if (target < 0 || target % 2 != 0 ) return 0 ; int n = nums.length; target /= 2 ; int [][]memo = new int [n][target + 1 ]; return dfs(n - 1 , target, memo); } public int dfs (int i, int c, int [][]memo) { if (i < 0 ) { if (c == 0 ) return 1 ; else return 0 ; } if (memo[i][c] > 0 ) return memo[i][c]; if (c < nums[i]) return memo[i][c] = dfs(i - 1 , c, memo); return memo[i][c] = (dfs(i - 1 , c, memo) + dfs(i - 1 , c - nums[i], memo)); } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public int findTargetSumWays (int [] nums, int target) { int sum = Arrays.stream(nums).sum(); target += sum; if (target < 0 || target % 2 != 0 ) return 0 ; int n = nums.length; target /= 2 ; int [][]f = new int [n + 1 ][target + 1 ]; f[0 ][0 ] = 1 ; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j <= target; ++ j) { if (j >= nums[i]) { f[i + 1 ][j] = f[i][j] + f[i][j - nums[i]]; } else { f[i + 1 ][j] = f[i][j]; } } } return f[n][target]; }
空间优化,倒着枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public int findTargetSumWays (int [] nums, int target) { int sum = Arrays.stream(nums).sum(); target += sum; if (target < 0 || target % 2 != 0 ) return 0 ; int n = nums.length; target /= 2 ; int []f = new int [target + 1 ]; f[0 ] = 1 ; for (int x : nums) { for (int j = target; j >= x; -- j) { f[j] = f[j] + f[j - x]; } } return f[target]; }
给你一个 只包含正整数 的 非空 数组 nums
。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
dfs + memo,最初把memo数组设为boolean,只有两种状态,无法表示是还未访问到还是访问到了但是结果为false
故使用Integer数组,初始为null表示还未被访问,1表示true,0表示false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public boolean canPartition (int [] nums) { int n = nums.length; int sum = Arrays.stream(nums).sum(); if (sum % 2 != 0 ) return false ; sum /= 2 ; Integer [][]memo = new Integer [n][sum + 1 ]; return dfs(nums, memo, sum, 0 ); } public boolean dfs (int []nums, Integer [][]memo, int sum, int u) { int n = nums.length; if (sum == 0 ) return true ; if (u == n || sum < 0 ) { return false ; } if (memo[u][sum] != null ) return memo[u][sum] == 1 ; boolean result = dfs(nums, memo, sum, u + 1 ) || dfs(nums, memo, sum - nums[u], u + 1 ); memo[u][sum] = result ? 1 : 0 ; return result; } }
递推,空间优化,逆序遍历。
为什么逆序?正序的话,因为是用到背包容量 j 减去当前物品体积 i 的状态,是在 j 之前就更新过了的,所以会出错,因为用到新值了,我们要用的是上一层的旧值。逆序就不会出现这种问题,你细想。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public boolean canPartition (int [] nums) { int n = nums.length; int sum = Arrays.stream(nums).sum(); if (sum % 2 != 0 ) return false ; sum /= 2 ; boolean []dp = new boolean [sum + 1 ]; dp[0 ] = true ; for (int i : nums) { for (int j = sum; j >= i; -- j) { dp[j] = dp[j] || dp[j - i]; } } return dp[sum]; } }
给你一个下标从 0 开始的整数数组 nums
和一个整数 target
。
返回和为 target
的 nums
子序列中,子序列 长度的最大值 。如果不存在和为 target
的子序列,返回 -1
。
子序列 指的是从原数组中删除一些或者不删除任何元素后,剩余元素保持原来的顺序构成的数组。
示例 1:
1 2 3 输入:nums = , target = 9 输出:3 解释:总共有 3 个子序列的和为 9 : , 和 。最长的子序列是 和 。所以答案为 3 。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { int [][] memo; public int lengthOfLongestSubsequence (List<Integer> nums, int target) { int n = nums.size(); memo = new int [n][target + 1 ]; for (int [] row : memo) { Arrays.fill(row, -1 ); } int t = dfs(nums, n - 1 , target); return t < 0 ? -1 : t; } public int dfs (List<Integer> nums, int i, int target) { if (target == 0 ) return 0 ; if (i < 0 ) return Integer.MIN_VALUE / 2 ; if (memo[i][target] != -1 ) { return memo[i][target]; } int t = nums.get(i); if (target < t) { return memo[i][target] = dfs(nums, i - 1 , target); } return memo[i][target] = Math.max(dfs(nums, i - 1 , target), dfs(nums, i - 1 , target - t) + 1 ); } }
给你两个 正 整数 n
和 x
。
请你返回将 n
表示成一些 互不相同 正整数的 x
次幂之和的方案数。换句话说,你需要返回互不相同整数 [n1, n2, ..., nk]
的集合数目,满足 n = n1x + n2x + ... + nkx
。
由于答案可能非常大,请你将它对 109 + 7
取余后返回。
比方说,n = 160
且 x = 3
,一个表示 n
的方法是 n = 23 + 33 + 53
。
递推:越来越熟练了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int numberOfWays (int n, int x) { int mod = 1_000_000_007 ; int m = (int ) Math.pow(n, 1d / x) + 1 ; long []dp = new long [n + 1 ]; dp[0 ] = 1 ; for (int i = 1 ; i <= m; ++ i) { int t = (int ) Math.pow(i, x); for (int j = n; j >= t; -- j) { dp[j] = dp[j] + dp[j - t]; } } return (int ) (dp[n] % mod); } }
给你一个整数数组 rewardValues
,长度为 n
,代表奖励的值。
最初,你的总奖励 x
为 0,所有下标都是 未标记 的。你可以执行以下操作 任意次 :
从区间 [0, n - 1]
中选择一个 未标记 的下标 i
。
如果 rewardValues[i]
大于 你当前的总奖励 x
,则将 rewardValues[i]
加到 x
上(即 x = x + rewardValues[i]
),并 标记 下标 i
。
以整数形式返回执行最优操作能够获得的 最大 总奖励。
这道题太抽象了,建议多思考思考怎么抽象成01背包的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int maxTotalReward (int [] rewardValues) { int n = rewardValues.length; Arrays.sort(rewardValues); int s = rewardValues[n - 1 ] * 2 ; boolean dp[] = new boolean [s + 1 ]; dp[0 ] = true ; for (int i = 0 ; i < n; ++ i) { int t = rewardValues[i]; for (int j = s; j >= t; -- j) { if (j < 2 * t) { dp[j] = dp[j] || dp[j - t]; } } } int res = 0 ; for (int j = 0 ; j <= s; ++ j) { if (dp[j]) res = j; } return res; } }
给你一个二进制字符串数组 strs
和两个整数 m
和 n
。
请你找出并返回 strs
的最大子集的长度,该子集中 最多 有 m
个 0
和 n
个 1
。
示例 1:
1 2 3 4 输入:strs = ["10" , "0001" , "111001" , "1" , "0" ], m = 5 , n = 3 输出:4 解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10" ,"0001" ,"1" ,"0" } ,因此答案是 4 。 其他满足题意但较小的子集包括 {"0001" ,"1" } 和 {"10" ,"1" ,"0" } 。{"111001" } 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。
二维,背包有两个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int findMaxForm (String[] strs, int m, int n) { int s = strs.length; int [][][]dp = new int [s + 1 ][m + 1 ][n + 1 ]; dp[0 ][0 ][0 ] = 0 ; for (int i = 0 ; i < s; ++ i) { int zero = 0 , one = 0 ; for (char c : strs[i].toCharArray()) { if (c == '1' ) one ++; else zero ++; } for (int j = 0 ; j <= m; ++ j) { for (int k = 0 ; k <= n; ++ k) { dp[i + 1 ][j][k] = dp[i][j][k]; if (zero <= j && one <= k) { dp[i + 1 ][j][k] = Math.max(dp[i][j][k], dp[i][j - zero][k - one] + 1 ); } } } } return dp[s][m][n]; } }
优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int findMaxForm (String[] strs, int m, int n) { int s = strs.length; int [][]dp = new int [m + 1 ][n + 1 ]; dp[0 ][0 ] = 0 ; for (int i = 0 ; i < s; ++ i) { int zero = 0 , one = 0 ; for (char c : strs[i].toCharArray()) { if (c == '1' ) one ++; else zero ++; } for (int j = m; j >= zero; -- j) { for (int k = n; k >= one; -- k) { dp[j][k] = Math.max(dp[j][k], dp[j - zero][k - one] + 1 ); } } } return dp[m][n]; } }
有一堆石头,用整数数组 stones
表示。其中 stones[i]
表示第 i
块石头的重量。
每一回合,从中选出任意两块石头 ,然后将它们一起粉碎。假设石头的重量分别为 x
和 y
,且 x <= y
。那么粉碎的可能结果如下:
如果 x == y
,那么两块石头都会被完全粉碎;
如果 x != y
,那么重量为 x
的石头将会完全粉碎,而重量为 y
的石头新重量为 y-x
。
最后,最多只会剩下一块 石头。返回此石头 最小的可能重量 。如果没有石头剩下,就返回 0
。
怎么转化为01背包?你真是一个一点就通的天才少年
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int lastStoneWeightII (int [] stones) { int sum = Arrays.stream(stones).sum(); int t = sum / 2 ; int n = stones.length; boolean []dp = new boolean [t + 1 ]; dp[0 ] = true ; for (int s : stones) { for (int j = t; j >= s; -- j) { dp[j] = dp[j] || dp[j - s]; } } int i; for (i = t; i >= 0 ; -- i) { if (dp[i]) break ; } return (sum - i) > i ? sum - 2 * i : 2 * i - sum; } }
你正在安装一个广告牌,并希望它高度最大。这块广告牌将有两个钢制支架,两边各一个。每个钢支架的高度必须相等。
你有一堆可以焊接在一起的钢筋 rods
。举个例子,如果钢筋的长度为 1
、2
和 3
,则可以将它们焊接在一起形成长度为 6
的支架。
返回 广告牌的最大可能安装高度 。如果没法安装广告牌,请返回 0
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { int [][]memo; int sum; public int tallestBillboard (int [] rods) { int n = rods.length; sum = Arrays.stream(rods).sum(); memo = new int [n][sum + 1 ]; for (int []a : memo) Arrays.fill(a, -1 ); return dfs(rods, n - 1 , 0 ) / 2 ; } public int dfs (int []rods, int i, int j) { if (j > sum) { return Integer.MIN_VALUE; } if (i == 0 ) { if (rods[i] == j) return j; if (j == 0 ) return 0 ; return Integer.MIN_VALUE; } if (memo[i][j] != -1 ) return memo[i][j]; int d = 0 ; int t1 = dfs(rods, i - 1 , j); int t2 = dfs(rods, i - 1 , j + rods[i]) + rods[i]; int t3 = dfs(rods, i - 1 , Math.abs(j - rods[i])) + rods[i]; return memo[i][j] = Math.max(Math.max(t1, t2), t3); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int tallestBillboard (int [] rods) { int n = rods.length; int sum = Arrays.stream(rods).sum(); int [][]dp = new int [n + 1 ][sum + 1 ]; for (int i = 0 ; i <= n; ++i) { Arrays.fill(dp[i], Integer.MIN_VALUE); } dp[0 ][0 ] = 0 ; int d = 0 ; for (int i = 0 ; i < n; ++ i) { d += rods[i]; for (int j = 0 ; j <= d; ++ j) { if (rods[i] + j <= sum) { dp[i + 1 ][j] = Math.max(dp[i][j], Math.max(dp[i][Math.abs(rods[i] - j)], dp[i][rods[i] + j]) + rods[i]); } } } return dp[n][0 ] / 2 ; } }
集团里有 n
名员工,他们可以完成各种各样的工作创造利润。
第 i
种工作会产生 profit[i]
的利润,它要求 group[i]
名成员共同参与。如果成员参与了其中一项工作,就不能参与另一项工作。
工作的任何至少产生 minProfit
利润的子集称为 盈利计划 。并且工作的成员总数最多为 n
。
有多少种计划可以选择?因为答案很大,所以 返回结果模 10^9 + 7
的值 。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { int [][][] memo; int []group; int []profit; int mod = 1_000_000_007 ; public int profitableSchemes (int n, int minProfit, int [] group, int [] profit) { int m = group.length; this .group = group; this .profit = profit; memo = new int [m][n + 1 ][minProfit + 1 ]; for (int [][] a : memo) { for (int [] b : a) { Arrays.fill(b, -1 ); } } return dfs(m - 1 , n, minProfit) % mod; } public int dfs (int i, int j, int k) { if (j < 0 ) return 0 ; if (i < 0 ) { if (k == 0 ) return 1 ; else return 0 ; } if (memo[i][j][k] != -1 ) return memo[i][j][k]; int gro = group[i], pro = profit[i]; return memo[i][j][k] = (dfs(i - 1 , j, k) + dfs(i - 1 , j - gro, Math.max(0 , k - pro))) % mod; } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int profitableSchemes (int n, int minProfit, int [] group, int [] profit) { int m = group.length, p = profit.length, mod = 1_000_000_007 ; int [][][]dp = new int [m + 1 ][n + 1 ][minProfit + 1 ]; for (int j = 0 ; j <= n; ++ j) dp[0 ][j][0 ] = 1 ; for (int i = 0 ; i < m; ++ i) { int rs = group[i], lr = profit[i]; for (int j = 0 ; j <= n; ++ j) { for (int k = 0 ; k <= minProfit; ++ k) { if (j < rs) { dp[i + 1 ][j][k] = dp[i][j][k]; } else { dp[i + 1 ][j][k] = (dp[i][j][k] + dp[i][j - rs][Math.max(0 , k - lr)]) % mod; } } } } return dp[m][n][minProfit]; } }
空间优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int profitableSchemes (int n, int minProfit, int [] group, int [] profit) { int m = group.length, p = profit.length, mod = 1_000_000_007 ; int [][]dp = new int [n + 1 ][minProfit + 1 ]; for (int j = 0 ; j <= n; ++ j) dp[j][0 ] = 1 ; for (int i = 0 ; i < m; ++ i) { int rs = group[i], lr = profit[i]; for (int j = n; j >= rs; -- j) { for (int k = 0 ; k <= minProfit; ++ k) { dp[j][k] = (dp[j][k] + dp[j - rs][Math.max(0 , k - lr)]) % mod; } } } return dp[n][minProfit]; } }
给你一个长度为 n
的整数数组 nums
和一个 正 整数 k
。
一个整数数组的 能量 定义为和 等于 k
的子序列的数目。
请你返回 nums
中所有子序列的 能量和 。
由于答案可能很大,请你将它对 109 + 7
取余 后返回。
dp(i,j,k)表示前i个数中长度为j且和为k的子序列的数目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public int sumOfPower (int [] nums, int s) { int n = nums.length, mod = 1_000_000_007 ; int [][][]dp = new int [n + 1 ][n + 1 ][s + 1 ]; dp[0 ][0 ][0 ] = 1 ; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j <= i + 1 ; ++ j) { for (int k = 0 ; k <= s; ++ k) { if (nums[i] > k) { dp[i + 1 ][j][k] = dp[i][j][k]; } else if (j > 0 ) { dp[i + 1 ][j][k] = (dp[i][j][k] + dp[i][j - 1 ][k - nums[i]]) % mod; } } } } long res = 0 ; for (int j = 1 ; j <= n; ++ j) { res += Math.pow(2 , n - j) * dp[n][j][s] % mod; } return (int ) (res % mod); } }
空间优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int sumOfPower (int [] nums, int s) { int n = nums.length, mod = 1_000_000_007 ; int [][]dp = new int [n + 1 ][s + 1 ]; dp[0 ][0 ] = 1 ; for (int i = 0 ; i < n; ++ i) { for (int j = i + 1 ; j > 0 ; -- j) { for (int k = s; k >= nums[i]; -- k) { dp[j][k] = (dp[j][k] + dp[j - 1 ][k - nums[i]]) % mod; } } } long res = 0 ; for (int j = 0 ; j <= n; ++ j) { res += Math.pow(2 , n - j) * dp[j][s] % mod; } return (int ) (res % mod); } }
dp(i, j)表示前i个元素,从中选出的子序列和为j时的能量和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int sumOfPower (int [] nums, int k) { int n = nums.length, mod = 1_000_000_007 ; int [][]dp = new int [n + 1 ][k + 1 ]; dp[0 ][0 ] = 1 ; for (int i = 0 ; i < n; ++ i) { for (int j = k; j >= 0 ; -- j) { dp[i + 1 ][j] = dp[i][j] * 2 % mod; if (j >= nums[i]) { dp[i + 1 ][j] = (dp[i + 1 ][j] + dp[i][j - nums[i]]) % mod; } } } return dp[n][k]; } }
给你一个正整数数组 nums
和一个整数 k
。
分区 的定义是:将数组划分成两个有序的 组 ,并满足每个元素 恰好 存在于 某一个 组中。如果分区中每个组的元素和都大于等于 k
,则认为分区是一个好分区。
返回 不同 的好分区的数目。由于答案可能很大,请返回对 109 + 7
取余 后的结果。
如果在两个分区中,存在某个元素 nums[i]
被分在不同的组中,则认为这两个分区不同。
dfs + memo; 和大于等于k,不太好用背包,转化为求和小于k的分区数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { private static final int mod = 1_000_000_007 ; public int countPartitions (int [] nums, int s) { long sum = 0 ; for (int num : nums) { sum += num; } int n = nums.length; if (sum / 2 < s) return 0 ; long [][]memo = new long [n + 1 ][s + 1 ]; for (long []a : memo) Arrays.fill(a, -1 ); long res = (dfs(nums, memo, n - 1 , s)) % mod; long total = 1L ; for (int i = 0 ; i < n; ++ i) { total = (total * 2 ) % mod; } return (int ) ((total - 2 * res + mod) % mod); } public long dfs (int []nums, long [][]memo, int i, int k) { if (i < 0 ) return k > 0 ? 1 : 0 ; if (memo[i][k] != -1 ) return memo[i][k]; long res = dfs(nums, memo, i - 1 , k); if (k > nums[i]) { res = (res + dfs(nums, memo, i - 1 , k - nums[i])) % mod; } return memo[i][k] = res; } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { private static final int mod = 1_000_000_007 ; public int countPartitions (int [] nums, int s) { long sum = Arrays.stream(nums).mapToLong(o -> (long ) o).sum(); int n = nums.length; if (sum / 2 < s) return 0 ; long [][]dp = new long [n + 1 ][s + 1 ]; dp[0 ][0 ] = 1 ; long ans = 1L ; for (int i = 0 ; i < n; ++ i) { ans = ans * 2 % mod; for (int j = 0 ; j < s; ++ j) { dp[i + 1 ][j] = dp[i][j]; if (j >= nums[i]) { dp[i + 1 ][j] = (dp[i + 1 ][j] + dp[i][j - nums[i]]) % mod; } } } long res = 0L ; for (int i = 0 ; i < s; ++ i) { res = (res + dp[n][i]) % mod; } return (int ) (ans - res * 2 % mod + mod) % mod; } }
空间优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { private static final int mod = 1_000_000_007 ; public int countPartitions (int [] nums, int s) { long sum = Arrays.stream(nums).mapToLong(o -> (long ) o).sum(); int n = nums.length; if (sum / 2 < s) return 0 ; long []dp = new long [s + 1 ]; dp[0 ] = 1L ; long ans = 1L ; for (int i = 0 ; i < n; ++ i) { ans = ans * 2 % mod; for (int j = s - 1 ; j >= nums[i]; -- j) { dp[j] = (dp[j] + dp[j - nums[i]]) % mod; } } long res = 0L ; for (int i = 0 ; i < s; ++ i) { res = (res + dp[i]) % mod; } return (int ) (ans - res * 2 % mod + mod) % mod; } }
完全背包 给你一个整数数组 coins
,表示不同面额的硬币;以及一个整数 amount
,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1
。
你可以认为每种硬币的数量是无限的。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { int [][]memo; public int coinChange (int [] coins, int amount) { int n = coins.length; memo = new int [n][amount + 1 ]; for (int []a : memo) Arrays.fill(a, -1 ); int t = dfs(coins, amount, n - 1 ); return t >= Integer.MAX_VALUE / 2 ? -1 : t; } public int dfs (int []coins, int amount, int u) { if (u < 0 ) { if (amount == 0 ) return 0 ; else return Integer.MAX_VALUE / 2 ; } if (memo[u][amount] != -1 ) return memo[u][amount]; int res = dfs(coins, amount, u - 1 ); if (amount >= coins[u]) { res = Math.min(res, dfs(coins, amount - coins[u], u) + 1 ); } return memo[u][amount] = res; } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { int [][]dp; public int coinChange (int [] coins, int amount) { int n = coins.length; dp = new int [n + 1 ][amount + 1 ]; Arrays.fill(dp[0 ], Integer.MAX_VALUE / 2 ); dp[0 ][0 ] = 0 ; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j <= amount; ++ j) { dp[i + 1 ][j] = dp[i][j]; if (j >= coins[i]) { dp[i + 1 ][j] = Math.min(dp[i][j], dp[i + 1 ][j - coins[i]] + 1 ); } } } return dp[n][amount] == Integer.MAX_VALUE / 2 ? -1 : dp[n][amount]; } }
空间优化,正序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { int []dp; public int coinChange (int [] coins, int amount) { int n = coins.length; dp = new int [amount + 1 ]; Arrays.fill(dp, Integer.MAX_VALUE / 2 ); dp[0 ] = 0 ; for (int i : coins) { for (int j = i; j <= amount; ++ j) { dp[j] = Math.min(dp[j], dp[j - i] + 1 ); } } return dp[amount] == Integer.MAX_VALUE / 2 ? -1 : dp[amount]; } }
给你一个整数数组 coins
表示不同面额的硬币,另给一个整数 amount
表示总金额。
请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0
。
假设每一种面额的硬币有无限个。
题目数据保证结果符合 32 位带符号整数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { int [][]memo; public int change (int amount, int [] coins) { int n = coins.length; memo = new int [n][amount + 1 ]; for (int []a : memo) Arrays.fill(a, -1 ); return dfs(coins, amount, n - 1 ); } public int dfs (int []coins, int amount, int u) { if (u < 0 ) { if (amount == 0 ) return 1 ; else return 0 ; } if (memo[u][amount] != -1 ) return memo[u][amount]; int res = dfs(coins, amount, u - 1 ); if (amount >= coins[u]) { res += dfs(coins, amount - coins[u], u); } return memo[u][amount] = res; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { int []dp; public int change (int amount, int [] coins) { int n = coins.length; dp = new int [amount + 1 ]; dp[0 ] = 1 ; for (int i : coins) { for (int j = i; j <= amount; ++ j) { dp[j] += dp[j - i]; } } return dp[amount]; } }
给你一个整数 n
,返回 和为 n
的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而 3
和 11
不是。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { int [][]memo; public int numSquares (int n) { int t = (int ) Math.sqrt(n); memo = new int [t + 1 ][n + 1 ]; for (int []a : memo) Arrays.fill(a, -1 ); return dfs(t, n); } public int dfs (int t, int n) { if (t < 1 ) { if (n == 0 ) return 0 ; else return Integer.MAX_VALUE / 2 ; } if (memo[t][n] != -1 ) return memo[t][n]; int res = dfs(t - 1 , n); if (n >= t * t) { res = Math.min(res, dfs(t, n - t * t) + 1 ); } return memo[t][n] = res; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { int []dp; public int numSquares (int n) { dp = new int [n + 1 ]; int t = (int ) Math.sqrt(n); Arrays.fill(dp, Integer.MAX_VALUE / 2 ); dp[0 ] = 0 ; for (int i = 1 ; i <= t; ++ i) { for (int j = i * i; j <= n; j ++) { dp[j] = Math.min(dp[j], dp[j - i * i] + 1 ); } } return dp[n]; } }
给你一个整数数组 cost
和一个整数 target
。请你返回满足如下规则可以得到的 最大 整数:
给当前结果添加一个数位(i + 1
)的成本为 cost[i]
(cost
数组下标从 0 开始)。
总成本必须恰好等于 target
。
添加的数位中没有数字 0 。
由于答案可能会很大,请你以字符串形式返回。
如果按照上述要求无法得到任何整数,请你返回 “0” 。
dfs + memo,先通过dfs枚举最长答案(完全背包)最长的字符串一定是最大的整数,接着使用dfs记录下的转移信息,倒着枚举数字,找出最长的字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { int [][]memo; public String largestNumber (int [] cost, int target) { int n = cost.length; memo = new int [n + 1 ][target + 1 ]; for (int []a : memo) Arrays.fill(a, -1 ); int result = dfs(n - 1 , cost, target); String res = "" ; if (result < 0 ) { return "0" ; } for (int i = 9 , j = target; i >= 1 ; -- i) { int u = cost[i - 1 ]; while (j >= u && memo[i - 1 ][j] == memo[i - 1 ][j - u] + 1 ) { res += String.valueOf(i); j -= u; } } return res; } public int dfs (int i, int [] cost, int target) { if (i < 0 ) { if (target == 0 ) return 0 ; else return Integer.MIN_VALUE; } if (memo[i][target] != -1 ) return memo[i][target]; int res = dfs(i - 1 , cost, target); if (target >= cost[i]) { res = Math.max(res, dfs(i, cost, target - cost[i]) + 1 ); } return memo[i][target] = res; } }
分组背包 这里有 n
个一样的骰子,每个骰子上都有 k
个面,分别标号为 1
到 k
。
给定三个整数 n
、k
和 target
,请返回投掷骰子的所有可能得到的结果(共有 kn
种方式),使得骰子面朝上的数字总和等于 target
。
由于答案可能很大,你需要对 109 + 7
取模 。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { private int mod = 1_000_000_007 ; int [][][]memo; public int numRollsToTarget (int n, int k, int target) { memo = new int [n + 1 ][k + 1 ][target + 1 ]; for (int [][]a : memo) { for (int []b : a) Arrays.fill(b, -1 ); } return dfs(n, k, target) % mod; } public int dfs (int n, int k, int target) { if (n == 0 ) return target == 0 ? 1 : 0 ; if (memo[n][k][target] != -1 ) return memo[n][k][target]; int res = 0 ; for (int i = 1 ; i <= k; ++ i) { if (target >= i) { res = (res + dfs(n - 1 , k, target - i)) % mod; } } return memo[n][k][target] = res; } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { private int mod = 1_000_000_007 ; int [][]memo; public int numRollsToTarget (int n, int k, int target) { if (target < n || target > n * k) { return 0 ; } memo = new int [n + 1 ][target + 1 ]; memo[0 ][0 ] = 1 ; for (int i = 0 ; i < n; ++ i) { for (int l = 0 ; l <= target; ++ l) { int res = 0 ; for (int j = 1 ; j <= k; ++ j) { if (l >= j) { res = (res + memo[i][l - j]) % mod; } } memo[i + 1 ][l] = res; } } return memo[n][target] % mod; } }
空间优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { private int mod = 1_000_000_007 ; int []memo; public int numRollsToTarget (int n, int k, int target) { if (target < n || target > n * k) { return 0 ; } memo = new int [target + 1 ]; memo[0 ] = 1 ; for (int i = 0 ; i < n; ++ i) { for (int l = target; l >= 0 ; -- l) { int res = 0 ; for (int j = 1 ; j <= k; ++ j) { if (l >= j) { res = (res + memo[l - j]) % mod; } } memo[l] = res; } } return memo[target] % mod; } }
给你一个大小为 m x n
的整数矩阵 mat
和一个整数 target
。
从矩阵的 每一行 中选择一个整数,你的目标是 最小化 所有选中元素之 和 与目标值 target
的 绝对差 。
返回 最小的绝对差 。
a
和 b
两数字的 绝对差 是 a - b
的绝对值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { boolean [][] memo; int n; int [][] mat; int res = Integer.MAX_VALUE; public int minimizeTheDifference (int [][] mat, int target) { int m = mat.length; this .n = mat[0 ].length; this .mat = mat; int MAX = Math.max(70 * m, target); memo = new boolean [m][MAX + 1 ]; dfs(m - 1 , 0 , target); return res; } public void dfs (int x, int sum, int t) { if (x < 0 ) { res = Math.min(res, Math.abs(sum - t)); return ; } if (memo[x][sum]) return ; memo[x][sum] = true ; for (int j = 0 ; j < n; ++ j) { dfs(x - 1 , sum + mat[x][j], t); } } }
一张桌子上总共有 n
个硬币 栈 。每个栈有 正整数 个带面值的硬币。
每一次操作中,你可以从任意一个栈的 顶部 取出 1 个硬币,从栈中移除它,并放入你的钱包里。
给你一个列表 piles
,其中 piles[i]
是一个整数数组,分别表示第 i
个栈里 从顶到底 的硬币面值。同时给你一个正整数 k
,请你返回在 恰好 进行 k
次操作的前提下,你钱包里硬币面值之和 最大为多少 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { List<List<Integer>> piles; int [][]memo; public int maxValueOfCoins (List<List<Integer>> piles, int k) { int n = piles.size(); this .piles = piles; memo = new int [n][k + 1 ]; for (int []a : memo) Arrays.fill(a, -1 ); return dfs(n - 1 , k); } public int dfs (int i, int k) { if (i < 0 ) { return 0 ; } if (memo[i][k] != -1 ) return memo[i][k]; List<Integer> l = piles.get(i); int t = l.size(), sum = 0 ; int res = dfs(i - 1 , k); for (int j = 0 ; j < t; ++ j) { sum += l.get(j); if (j + 1 <= k) { res = Math.max(res, dfs(i - 1 , k - j - 1 ) + sum); } } return memo[i][k] = res; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int maxValueOfCoins (List<List<Integer>> piles, int k) { int n = piles.size(); int [][] dp = new int [n + 1 ][k + 1 ]; for (int i = 0 ; i < n; ++ i) { List<Integer> l = piles.get(i); int m = l.size(); for (int j = 0 ; j <= k; ++ j) { dp[i + 1 ][j] = dp[i][j]; int sum = 0 ; for (int o = 0 ; o < m; ++ o) { sum += l.get(o); if (o + 1 <= j) { dp[i + 1 ][j] = Math.max(dp[i + 1 ][j], dp[i][j - o - 1 ] + sum); } } } } return dp[n][k]; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int maxValueOfCoins (List<List<Integer>> piles, int k) { int n = piles.size(); int [] dp = new int [k + 1 ]; for (int i = 0 ; i < n; ++ i) { List<Integer> l = piles.get(i); int m = l.size(); for (int j = k; j >= 0 ; -- j) { int sum = 0 ; for (int t = 0 ; t < m; ++ t) { sum += l.get(t); if (t + 1 <= j) { dp[j] = Math.max(dp[j], dp[j - t - 1 ] + sum); } } } } return dp[k]; } }
经典线性 DP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { int [][] memo; public int longestCommonSubsequence (String text1, String text2) { int n = text1.length(); int m = text2.length(); memo = new int [n][m]; for (int []a : memo) Arrays.fill(a, -1 ); return dfs(n - 1 , m - 1 , text1, text2); } public int dfs (int i, int j, String s1, String s2) { if (i < 0 || j < 0 ) return 0 ; if (memo[i][j] != -1 ) return memo[i][j]; int res = Math.max(dfs(i - 1 , j, s1, s2), dfs(i, j - 1 , s1, s2)); if (s1.charAt(i) == s2.charAt(j)) { res = Math.max(res, dfs(i - 1 , j - 1 , s1, s2) + 1 ); } return memo[i][j] = res; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { int [][] memo; public int longestCommonSubsequence (String s1, String s2) { int n = s1.length(); int m = s2.length(); memo = new int [n + 1 ][m + 1 ]; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j < m; ++ j) { memo[i + 1 ][j + 1 ] = Math.max(memo[i][j + 1 ], memo[i + 1 ][j]); if (s1.charAt(i) == s2.charAt(j)) { memo[i + 1 ][j + 1 ] = memo[i][j] + 1 ; } } } return memo[n][m]; } }
一维数组,我们可以发现,当前的状态由他的左边,上边和左上的状态转移过来,一维数组的话左上的状态就会在计算时被覆盖掉,导致我们计算当前状态时拿到的是错误的(被更新过的)左上状态来比较,所以需要用一个变量保存左上状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { int [] memo; public int longestCommonSubsequence (String s1, String s2) { int n = s1.length(); int m = s2.length(); memo = new int [m + 1 ]; for (int i = 0 ; i < n; ++ i) { for (int j = 0 , pre = 0 ; j < m; ++ j) { int tmp = memo[j + 1 ]; memo[j + 1 ] = Math.max(memo[j + 1 ], memo[j]); if (s1.charAt(i) == s2.charAt(j)) { memo[j + 1 ] = pre + 1 ; } pre = tmp; } } return memo[m]; } }
dp[i][j]
表示words中的word的前 i 个字符构造出 target 中的前 j 个字符的方案数,dp[i][j] = dp[i-1][j] + dp[i-1][j-1] * cnt
其中cnt是word的第i位等于target的第j位的word个数,可以先预处理出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { private int mod = 1_000_000_007 ; int [][] idx; public int numWays (String[] words, String target) { int n = words[0 ].length(); int m = target.length(); int [][] memo = new int [n][m]; idx = new int [n][26 ]; for (int [] a : memo) Arrays.fill(a, -1 ); for (int i = 0 ; i < n; ++ i) { for (String s : words) { idx[i][s.charAt(i) - 'a' ] ++; } } return dfs(words, target, memo, n - 1 , m - 1 ) % mod; } public int dfs (String[] words, String target, int [][] memo, int i, int j) { if (j < 0 ) return 1 ; if (i < 0 ) return 0 ; if (memo[i][j] != -1 ) return memo[i][j]; int cnt = idx[i][target.charAt(j) - 'a' ]; long res = dfs(words, target, memo, i - 1 , j); long t = dfs(words, target, memo, i - 1 , j - 1 ); res = (res + t * cnt % mod) % mod; return memo[i][j] = (int ) res; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { private int mod = 1_000_000_007 ; int [][] idx; public int numWays (String[] words, String target) { int n = words[0 ].length(); int m = target.length(); long [][] memo = new long [n + 1 ][m + 1 ]; idx = new int [n][26 ]; for (int i = 0 ; i < n; ++ i) { for (String s : words) { idx[i][s.charAt(i) - 'a' ] ++; } } for (int i = 0 ; i < n; ++ i) memo[i][0 ] = 1 ; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j < m; ++ j) { int cnt = idx[i][target.charAt(j) - 'a' ]; memo[i + 1 ][j + 1 ] = (memo[i][j + 1 ] + cnt * memo[i][j] % mod) % mod; } } return (int ) memo[n][m]; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { private int mod = 1_000_000_007 ; int [][] idx; public int numWays (String[] words, String target) { int n = words[0 ].length(); int m = target.length(); long [] memo = new long [m + 1 ]; idx = new int [n][26 ]; for (int i = 0 ; i < n; ++ i) { for (String s : words) { idx[i][s.charAt(i) - 'a' ] ++; } } memo[0 ] = 1 ; for (int i = 0 ; i < n; ++ i) { for (int j = m - 1 ; j >= 0 ; -- j) { int cnt = idx[i][target.charAt(j) - 'a' ]; memo[j + 1 ] = (memo[j + 1 ] + cnt * memo[j] % mod) % mod; } } return (int ) memo[m]; } }
给你两个字符串 str1
和 str2
,返回同时以 str1
和 str2
作为 子序列 的最短字符串。如果答案不止一个,则可以返回满足条件的 任意一个 答案。
示例 1:
1 2 输入:str1 = "abac" , str2 = "cab" 输出:"cabac"
官方提示的思路,先求出两个字符串的最长公共子序列,再根据dp数组保存的值倒序拼接答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class Solution { public String shortestCommonSupersequence (String str1, String str2) { int n = str1.length(), m = str2.length(); int [][] dp = new int [n + 1 ][m + 1 ]; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j < m; ++ j) { if (str1.charAt(i) == str2.charAt(j)) { dp[i + 1 ][j + 1 ] = dp[i][j] + 1 ; } else { dp[i + 1 ][j + 1 ] = Math.max(dp[i][j + 1 ], dp[i + 1 ][j]); } } } if (dp[n][m] == m && dp[n][m] == n) return str1; String res = "" ; int i = n - 1 , j = m - 1 ; while (i >= 0 && j >= 0 ) { if (str1.charAt(i) == str2.charAt(j)) { res = str1.charAt(i) + res; i --; j--; } else if (dp[i + 1 ][j] > dp[i][j + 1 ]) { res = str2.charAt(j) + res; j --; } else { res = str1.charAt(i) + res; i --; } } if (i >= 0 ) { res = str1.substring(0 , i + 1 ) + res; } if (j >= 0 ) { res = str2.substring(0 , j + 1 ) + res; } return res; } }
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { int [] memo; public int lengthOfLIS (int [] nums) { memo = new int [nums.length]; Arrays.fill(memo, -1 ); int ans = 0 ; for (int i = 0 ; i < nums.length; ++ i) { ans = Math.max(ans, dfs(nums, i)); } return ans; } public int dfs (int [] nums, int i) { if (i < 0 ) return 0 ; if (memo[i] != -1 ) return memo[i]; int res = 0 ; for (int j = i; j >= 0 ; -- j) { if (nums[j] < nums[i]) { res = Math.max(res, dfs(nums, j)); } } return memo[i] = res + 1 ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { int [] dp; public int lengthOfLIS (int [] nums) { int n = nums.length; dp = new int [n]; int ans = 0 ; for (int i = 0 ; i < n; ++ i) { int res = 0 ; for (int j = i; j >= 0 ; -- j) { if (nums[j] < nums[i]) { res = Math.max(res, dp[j]); } } dp[i] = res + 1 ; } for (int i = 0 ; i < n; ++ i) { ans = Math.max(ans, dp[i]); } return ans; } }
贪心+二分
维护的dp数组,dp[i]
表示长度为 i + 1 时的递增子序列末尾元素的最小值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Solution { int [] dp; public int lengthOfLIS (int [] nums) { int n = nums.length; dp = new int [n]; int idx = 0 ; for (int i = 0 ; i < n; ++ i) { int t = bis(dp, 0 , idx, nums[i]); if (t == idx) { dp[idx ++] = nums[i]; } else { dp[t] = nums[i]; } } return idx; } public int bis (int [] nums, int l, int r, int x) { while (l < r) { int mid = l + r >> 1 ; if (nums[mid] < x) { l = mid + 1 ; } else { r = mid; } } return l; } }
划分型DP 给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。
注意: 不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { int []memo; public boolean wordBreak (String s, List<String> wordDict) { int n = s.length(); memo = new int [n + 1 ]; Arrays.fill(memo, -1 ); return dfs(s, n, wordDict); } public boolean dfs (String s, int i, List<String> wordDict) { if (i == 0 ) return true ; if (memo[i] != -1 ) return memo[i] == 1 ; boolean res = false ; for (int j = i - 1 ; j >= 0 ; -- j) { if (wordDict.contains(s.substring(j, i))) { res = res || dfs(s, j, wordDict); } } memo[i] = (res ? 1 : 0 ); return res; } }
递推
1 2 3 4 5 6 7 8 9 10 11 12 13 public boolean wordBreak (String s, List<String> wordDict) { int n = s.length(); boolean []dp = new boolean [n + 1 ]; dp[0 ] = true ; for (int i = 0 ; i < n; ++ i) { for (int j = 0 ; j <= i; ++ j) { if (wordDict.contains(s.substring(j, i + 1 ))) { dp[i + 1 ] = dp[j] || dp[i + 1 ]; } } } return dp[n]; }
给你两个单词 word1
和 word2
, 请返回将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { int [][] memo; public int minDistance (String word1, String word2) { int n = word1.length(); int m = word2.length(); memo = new int [n][m]; for (int []a : memo) Arrays.fill(a, -1 ); return dfs(n - 1 , m - 1 , word1, word2); } public int dfs (int i, int j, String word1, String word2) { if (i < 0 ) return j + 1 ; if (j < 0 ) return i + 1 ; if (memo[i][j] != -1 ) return memo[i][j]; int res = Math.min(dfs(i - 1 , j - 1 , word1, word2), Math.min(dfs(i - 1 , j, word1, word2), dfs(i, j - 1 , word1, word2))) + 1 ; if (word1.charAt(i) == word2.charAt(j)) res = dfs(i - 1 , j - 1 , word1, word2); return memo[i][j] = res; } }
状态压缩DP 假设有从 1 到 n 的 n 个整数。用这些整数构造一个数组 perm
(下标从 1 开始 ),只要满足下述条件 之一 ,该数组就是一个 优美的排列 :
perm[i]
能够被 i
整除
i
能够被 perm[i]
整除
给你一个整数 n
,返回可以构造的 优美排列 的 数量 。
集合S是已选择的数字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int countArrangement (int n) { int [] memo = new int [1 << n]; Arrays.fill(memo, -1 ); return dfs(0 , n, memo); } public int dfs (int s, int n, int [] memo) { if (s == (1 << n) - 1 ) return 1 ; if (memo[s] != -1 ) return memo[s]; int i = Integer.bitCount(s) + 1 ; int res = 0 ; for (int j = 1 ; j <= n; ++ j) { if ((s >> (j - 1 ) & 1 ) == 0 && (i % j == 0 || j % i == 0 )) { res += dfs(s | (1 << (j - 1 )), n, memo); } } return memo[s] = res; } }
集合S为还未选择的数字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int countArrangement (int n) { int [] memo = new int [1 << n]; Arrays.fill(memo, -1 ); return dfs((1 << n) - 1 , n, memo); } public int dfs (int s, int n, int [] memo) { if (s == 0 ) return 1 ; if (memo[s] != -1 ) return memo[s]; int i = n - Integer.bitCount(s) + 1 ; int res = 0 ; for (int j = 1 ; j <= n; j ++) { if ((s >> (j - 1 ) & 1 ) == 1 && (i % j == 0 || j % i == 0 )) { res += dfs(s ^ (1 << (j - 1 )), n, memo); } } return memo[s] = res; } }
给你一个由正整数构成的二维矩阵 grid
。
你需要从矩阵中选择 一个或多个 单元格,选中的单元格应满足以下条件:
所选单元格中的任意两个单元格都不会处于矩阵的 同一行 。
所选单元格的值 互不相同 。
你的得分为所选单元格值的总和 。
返回你能获得的 最大 得分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Solution { public int maxScore (List<List<Integer>> grid) { int m = grid.size(); Map<Integer, Integer> mp = new HashMap <>(); for (int i = 0 ; i < m; ++ i) { for (int j : grid.get(i)) { mp.merge(j, 1 << i, (a, b) -> a | b); } } int n = mp.size(); int [][] memo = new int [101 ][1 << m]; for (int [] a : memo) Arrays.fill(a, -1 ); return dfs(100 , 0 , mp, memo); } public int dfs (int i, int j, Map<Integer, Integer> mp, int [][] memo) { if (i == 0 ) return 0 ; if (memo[i][j] != -1 ) return memo[i][j]; int res = dfs(i - 1 , j, mp, memo); if (mp.containsKey(i)) { for (int t = mp.get(i), lb; t > 0 ; t ^= lb) { lb = t & -t; if ((j & lb) == 0 ) { res = Math.max(res, dfs(i - 1 , j | lb, mp, memo) + i); } } } return memo[i][j] = res; } }public int dfs (int i, int j, Map<Integer, Integer> mp, int [][] memo) { if (i == 0 ) return 0 ; if (memo[i][j] != -1 ) return memo[i][j]; int res = dfs(i - 1 , j, mp, memo); if (mp.containsKey(i)) { for (int t = mp.get(i); t > 0 ; t &= (t - 1 )) { int k = Integer.numberOfTrailingZeros(t); if ((j & (1 << k)) == 0 ) { res = Math.max(res, dfs(i - 1 , j | (1 << k), mp, memo) + i); } } } return memo[i][j] = res; }
不知道什么DP 给你一个长度为 n
的 正 整数数组 nums
。
如果两个 非负 整数数组 (arr1, arr2)
满足以下条件,我们称它们是 单调 数组对:
两个数组的长度都是 n
。
arr1
是单调 非递减 的,换句话说 arr1[0] <= arr1[1] <= ... <= arr1[n - 1]
。
arr2
是单调 非递增 的,换句话说 arr2[0] >= arr2[1] >= ... >= arr2[n - 1]
。
对于所有的 0 <= i <= n - 1
都有 arr1[i] + arr2[i] == nums[i]
。
请你返回所有 单调 数组对的数目。
由于答案可能很大,请你将它对 109 + 7
取余 后返回。
dfs + memo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { int [][]memo; int []nums; int mod = 1_000_000_007 ; public int countOfPairs (int [] nums) { this .nums = nums; int max = Arrays.stream(nums).max().getAsInt(); int n = nums.length; memo = new int [n + 1 ][max + 1 ]; for (int []a : memo) Arrays.fill(a, -1 ); int res = 0 ; for (int i = 0 ; i <= nums[n - 1 ]; ++ i) { res = (res + dfs(n - 1 , i)) % mod; } return res; } public int dfs (int i, int j) { if (i == 0 ) { return 1 ; } if (memo[i][j] != -1 ) return memo[i][j]; int res = 0 ; int mi = Math.min(Math.min(j, nums[i - 1 ]), nums[i - 1 ] - nums[i] + j); for (int k = 0 ; k <= mi; ++ k) { res = (res + dfs(i - 1 , k)) % mod; } return memo[i][j] = res; } }
1:1翻译成递推,真的是完全1:1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { int [][]dp; int []nums; int mod = 1_000_000_007 ; public int countOfPairs (int [] nums) { this .nums = nums; int max = Arrays.stream(nums).max().getAsInt(); int n = nums.length; dp = new int [n + 1 ][max + 1 ]; for (int j = 0 ; j <= nums[0 ]; ++ j) { dp[0 ][j] = 1 ; } for (int i = 1 ; i < n; ++ i) { for (int j = 0 ; j <= nums[i]; ++ j) { int res = 0 ; int mi = Math.min(Math.min(j, nums[i - 1 ]), nums[i - 1 ] - nums[i] + j); for (int k = 0 ; k <= mi; ++ k) { res = (res + dp[i - 1 ][k]) % mod; } dp[i][j] = res; } } int ans = 0 ; for (int i = 0 ; i <= nums[n - 1 ]; ++ i) { ans = (ans + dp[n - 1 ][i]) % mod; } return ans; } }
前缀和优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Solution { int [][]dp; int []nums; int mod = 1_000_000_007 ; public int countOfPairs (int [] nums) { this .nums = nums; int max = Arrays.stream(nums).max().getAsInt(); int n = nums.length; dp = new int [n + 1 ][max + 1 ]; for (int j = 0 ; j <= nums[0 ]; ++ j) { dp[0 ][j] = 1 ; } for (int i = 1 ; i < n; ++ i) { int []preS = new int [nums[i - 1 ] + 1 ]; preS[0 ] = dp[i - 1 ][0 ]; for (int x = 1 ; x <= nums[i - 1 ]; ++ x) { preS[x] = (preS[x - 1 ] + dp[i - 1 ][x]) % mod; } for (int j = 0 ; j <= nums[i]; ++ j) { int mi = Math.min(Math.min(j, nums[i - 1 ]), nums[i - 1 ] - nums[i] + j); dp[i][j] = mi >= 0 ? preS[mi] : 0 ; } } int ans = 0 ; for (int i = 0 ; i <= nums[n - 1 ]; ++ i) { ans = (ans + dp[n - 1 ][i]) % mod; } return ans; } }