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)