01背包
剑指 Offer II 101. 分割等和子集
给定一个非空的正整数数组 nums ,请判断能否将这些数字分成元素和相等的两部分。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:nums 可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:nums 不可以分为和相等的两部分
提示:
- 1 <= nums.length <= 200
- 1 <= nums[i] <= 100
- class Solution {
- public:
- bool canPartition(vector<int>& nums) {
- //nums = [1,5,11,5] -> [1, 11],[5,5] -> true
- int nlen = nums.size();
- if (nlen < 2) {
- return false;
- }
- int total_sum = accumulate(nums.begin(), nums.end(), 0);
- // 和为奇数,肯定不可以拆分
- if (total_sum % 2) {
- return false;
- }
- //找最大值
- int max_sum = *max_element(nums.begin(), nums.end());
- int part_sum = total_sum / 2;
- if (max_sum > part_sum) {
- return false;
- }
- //1. dp代表部分和为i
- // vector<vector<int> > dp(nlen, vector<int>(part_sum + 1, 0));
- // for (int i = 0; i < nlen; ++i) {
- // dp[i][0] = true;
- // }
- // //第0个数可以构成num[0]的部分和
- // dp[0][nums[0]] = true;
- // for (int i = 1; i < nlen; ++i)
- // {
- // int num = nums[i];
- // for (int j = 1; j <= part_sum; ++j)
- // {
- // //当前数小于部分和, 不加 | 加当前数
- // if (nums[i] <= j) {
- // dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
- // } else {
- // dp[i][j] = dp[i - 1][j];
- // }
- // }
- // }
- // return dp[nlen - 1][part_sum];
-
- //2. 优化
- vector<int> dp(part_sum + 1, 0);
- dp[0] = true;
- for (int i = 0; i < nlen; ++i)
- {
- int num = nums[i];
- for (int j = part_sum; j >= num; --j)
- {
- //不加 | 加
- dp[j] = dp[j] | dp[j - num];
- }
- }
- return dp[part_sum];
- }
- };
完全背包
322. 零钱兑换
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
提示:
- 1 <= coins.length <= 12
- 1 <= coins[i] <= 231 - 1
- 0 <= amount <= 104
法一:动态规划
- class Solution:
- def coinChange(self, coins: List[int], amount: int) -> int:
- if amount == 0:
- return 0
- clen = len(coins)
- Max = amount + 1
- dp = [Max for _ in range(amount + 1)]
- dp[0] = 0
- for i in range(1, amount + 1):
- for j in range(0, clen):
- if coins[j] <= i:
- dp[i] = min(dp[i], dp[i - coins[j]] + 1)
-
- return dp[amount] if dp[amount] <= amount else -1
法二:记忆化搜索
- class Solution {
- vector<int> count;
- int dp(vector<int>& coins, int rest_amount)
- {
- if (rest_amount < 0) return -1;
- if (rest_amount == 0) return 0;
- //如果下次还要计算这个问题的值直接从数组中取出返回即可,这样能保证每个子问题最多只被计算一次
- if (count[rest_amount - 1] != 0) return count[rest_amount - 1];
- int Min = INT_MAX;
- for (int coin : coins)
- {
- //重复选
- int res = dp(coins, rest_amount - coin);
- if (res >= 0 && res < Min) {
- Min = res + 1;
- }
- }
- count[rest_amount - 1] = (Min == INT_MAX ? -1 : Min);
- return count[rest_amount - 1];
- }
- public:
- int coinChange(vector<int>& coins, int amount) {
- if (amount < 1) return 0;
- //count为0~amount所需要的最小硬币数
- count.resize(amount);
- return dp(coins, amount);
- }
- };
python写法:使用 @functools.lru_cache(max_len)
- class Solution:
- def coinChange(self, coins: List[int], amount: int) -> int:
- # 2. 记忆化搜索
- @functools.lru_cache(amount)
- def dp(rest_amount):
- if rest_amount < 0: return -1
- if rest_amount == 0: return 0
- mini = int(1e9)
- for coin in coins:
- res = dp(rest_amount - coin)
- if res >= 0 and res < mini:
- mini = res + 1
- return mini if mini < int(1e9) else -1
-
- if amount < 1: return 0
- return dp(amount)
一、简单DP
62. 不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
- 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右
- 向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3
输出:28
示例 4:
输入:m = 3, n = 3
输出:6
提示:
- 1 <= m, n <= 100
- 题目数据保证答案小于等于 2 * 109
- class Solution {
- int ans;
- public:
- // 1. dfs:超时
- // void dfs(int i, int j, int m, int n)
- // {
- // if (i < 0 || i == m || j < 0 || j == n) {
- // return;
- // }
- // if (i == m - 1 && j == n - 1) {
- // ans += 1;
- // return;
- // }
-
- // int dirs[][2] = {
- {0, 1}, {1, 0}};
- // for (int k = 0; k < 2; k++)
- // {
- // int x = i + dirs[k][0], y = j + dirs[k][1];
- // if (x < 0 || x == m || y < 0 || y == n) {
- // continue;
- // }
- // dfs(x, y, m, n);
- // }
- // }
- // int uniquePaths(int m, int n) {
- // dfs(0, 0, m, n);
- // return ans;
- // }
- //2. 动态规划: O(n^2)
- // int uniquePaths(int m, int n)
- // {
- // vector<vector<int> > dp(m + 1, vector<int>(n + 1));
- // for (int i = 0; i < m; ++i) {
- // dp[i][0] = 1;
- // }
- // for (int j = 0; j < n; ++j) {
- // dp[0][j] = 1;
- // }
- // for (int i = 1; i < m; ++i)
- // {
- // for (int j = 1; j < n; ++j)
- // {
- // dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
- // }
- // }
- // return dp[m - 1][n - 1];
- // }
-
- // O(m)
- int uniquePaths(int m, int n)
- {
- // C(m + n - 2, m - 1): m + n - 2次移动,有m-1次向下,n-1次向右。因此路径总数
- long long ans = 1;
- for (int x = n, y = 1; y < m; ++x, ++y)
- {
- ans = ans * x / y;
- }
- return ans;
- }
- };
动态规划:时间复杂度O(n2),空间复杂度O(n2)
组合数:时间复杂度O(m), 空间O(1)