矩阵的最小路径和
给一个矩阵,从左上角走到右下角,只能向右或向下走,求和最小的路径。
#include <bits/stdc++.h>
using namespace std;
// 普通的动态规划,空间复杂度和时间复杂度均为O(N*M)
int minSumPath(vector<vector<int> > a) {
if (a.size() == 0 || a[0].size() == 0)
return 0;
int row = a.size(), col = a[0].size();
vector<vector<int> > dp(row, vector<int>(col));
dp[0][0] = a[0][0];
for (int i = 1; i < row; ++i) {
dp[i][0] = dp[i-1][0] + a[i][0];
}
for (int j = 1; j < col; ++j) {
dp[0][j] = dp[0][j-1] + a[0][j];
}
for (int i = 1; i < row; ++i) {
for (int j = 1; j < col; ++j) {
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + a[i][j];
}
}
return dp[row-1][col-1];
}
// 只维护一个数组,每遍历一层的时候更新数组.时间复杂度为O(N*M),空间复杂度为O(min{N,M})
int minSumPath2(vector<vector<int> > a) {
if (a.size() == 0 || a[0].size() == 0)
return 0;
int row = a.size(), col = a[0].size();
int more = max(row, col), less = min(row, col);
vector<int> dp(less);
dp[0] = a[0][0];
for (int i = 1; i < less; i++)
dp[i] = dp[i-1] + (row > col ? a[0][i] : a[i][0]);
for (int i = 1; i < more; i++) {
dp[0] = dp[0] + (row > col ? a[i][0] : a[0][i]);
for (int j = 1; j < col; j++) {
dp[j] = min(dp[j-1],dp[j]) + (row > col ? a[i][j] : a[j][i]);
}
}
return dp[less-1];
}
int main(int argc, char const *argv[]) {
vector<vector<int> > a {{1,3,5,9}, {8,1,3,4},{5,0,6,1},{8,8,4,0}};
cout << minSumPath(a) << endl;
cout << minSumPath2(a) << endl;
return 0;
}
换钱的最少货币数
问题一:给定数组arr中所有的值都代表一种面值的货币,每种面值的货币可以使用任意张。 再给定一个整数aim代表要找的钱,求组成aim的最少货币数
问题二:给定数组arr中所有的值都代表一种面值的货币,每种面值的货币只可以使用一次。再给定一个整数aim代表要找的钱,求组成aim的最少货币数
#include <bits/stdc++.h>
using namespace std;
/*
给定数组arr中所有的值都代表一种面值的货币,每种面值的货币可以使用任意张。
再给定一个整数aim代表要找的钱,求组成aim的最少货币数
*/
// 时间复杂度和空间复杂度均为O(N*aim),N为数组长度
int minCoins(vector<int> a, int aim) {
if (a.size() == 0 || aim < 0) return 0;
int n = a.size();
// dp[i][j]的含义:在可以任意使用a[0..i]货币的情况下,组成j所需的最小张数
vector<vector<int> > dp(n, vector<int>(aim+1));
for (int j = 1; j <= aim; ++j) {
dp[0][j] = INT_MAX; // 表示只用第一种货币,组成j所需的最小张数,不能组成设置为最大值MAX
if (j - a[0] >= 0 && dp[0][j-a[0]] != INT_MAX) // 避免j-a[0]<0,作为数组下标会溢出
dp[0][j] = dp[0][j-a[0]]+1;
}
int left = INT_MAX;
for (int i = 1; i < n; ++i) {
for (int j = 1; j <= aim; j++) {
left = INT_MAX;
// 递推公式:dp[i][j] = min(dp[i-1][j], dp[i][j-a[i]]+1);
if (j - a[i] >= 0 && dp[i][j-a[i]] != INT_MAX)
left = dp[i][j-a[i]]+1;
dp[i][j] = min(left, dp[i-1][j]);
}
}
return dp[n-1][aim] != INT_MAX ? dp[n-1][aim] : -1;
}
// 只维护一个dp数组,空间压缩。时间复杂度O(N*aim),空间复杂度(aim)
int minCoins2(vector<int> a, int aim) {
if (a.size() == 0 || aim < 0) return 0;
int n = a.size();
// dp[i]的含义:组成钱数为i的最小张数
vector<int> dp(aim+1);
for (int i = 1; i <= aim; i++) { // 之所以不从下标0开始,是因为dp[0]代表第一种货币,不能设置成最大值
dp[i] = INT_MAX;
if (i - a[0] >= 0 && dp[i-a[0]] != INT_MAX) // 如果a[0]=2,那么dp[2]=1,dp[4]=2,dp[6]=3...
dp[i] = dp[i-a[0]]+1;
}
int left = INT_MAX;
for (int i = 1; i < n; ++i) {
for (int j = 1; j <= aim; ++j) {
left = INT_MAX;
// 递推公式:dp[j] = min(dp[j], dp[j-a[i]]+1);
if (j-a[i] >= 0 && dp[j-a[i]] != INT_MAX)
left = dp[j-a[i]]+1;
dp[j] = min(left, dp[j]);
}
}
return dp[aim] != INT_MAX ? dp[aim] : -1;
}
/*
给定数组arr中所有的值都代表一种面值的货币,每种面值的货币只可以使用一次。
再给定一个整数aim代表要找的钱,求组成aim的最少货币数
*/
// 时间、空间复杂度O(N*aim),N为数组长度
int minCoins3(vector<int> a, int aim) {
if (a.size() == 0 || aim < 0) return 0;
int n = a.size();
// dp[i][j]的含义:任意使用a[0..i]货币的情况下(每个只能用一次),组成j的最小张数
vector<vector<int> > dp(n, vector<int>(aim+1));
for (int i = 1; i <= aim; ++i) { // 因为只能使用一次,所以只有dp[0][a[0]]为1,其余均设置成最大值MAX
dp[0][i] = INT_MAX;
}
if (a[0] <= aim) { // 如果a[0]=2,那么能找开的钱仅为2,令dp[0][2]=1
dp[0][a[0]] = 1;
}
int leftup = 0;
for (int i = 1; i < n; ++i) {
for (int j = 1; j <= aim; j++) {
leftup = INT_MAX;
// 递推公式: dp[i][j] = min(dp[i-1][j], dp[i-1][j-a[i]]+1);
if (j - a[i] >= 0 && dp[i-1][j-a[i]] != INT_MAX)
leftup = dp[i-1][j-a[i]]+1;
dp[i][j] = min(leftup, dp[i-1][j]);
}
}
return dp[n-1][aim] != INT_MAX ? dp[n-1][aim] : -1;
}
// 时间复杂度O(N*aim) 空间复杂度O(aim)
int minCoins4(vector<int> a, int aim) {
if (a.size() == 0 || aim < 0) return 0;
int n = a.size();
// dp[i]的含义:组成钱数为i的最小张数
vector<int> dp(aim+1);
for (int i = 1; i <= aim; i++)
dp[i] = INT_MAX;
if (a[0] <= aim)
dp[a[0]] = 1;
int leftup = 0;
for (int i = 1; i < n; ++i) {
for (int j = 1; j <= aim; j++) {
leftup = INT_MAX;
// 递推公式:dp[j] = min(dp[j], dp[j-a[i]]+1);
if (j - a[i] >= 0 && dp[j-a[i]] != INT_MAX)
leftup = dp[j-a[i]]+1;
dp[j] = min(leftup, dp[j]);
}
}
return dp[aim] != INT_MAX ? dp[aim] : -1;
}
int main(int argc, char const *argv[]) {
vector<int> a {5,2,3};
cout << minCoins(a, 20) << endl;
cout << minCoins2(a, 20) << endl;
cout << minCoins3(a, 10) << endl;
cout << minCoins4(a, 10) << endl;
return 0;
}
换钱的方法数
给定数组a,每个值代表一种面值的货币,每种面值的货币可以使用任意张。再给定aim代表要找的钱,求换钱有多少种方法
#include <bits/stdc++.h>
using namespace std;
// 暴力递归方法
// 如果用a[index..n-1]这些面值的钱组成aim,返回的总方法数
int process1(vector<int> &a, int index, int aim) {
int res = 0;
if (index == a.size()) {
res = aim == 0 ? 1 : 0; // 目标钱数为0,返回值为1,表示各种面值的货币都使用0张
} else {
for (int i = 0; a[index]*i <= aim; i++)
res += process1(a, index+1, aim-a[index]*i);
}
return res;
}
int coins1(vector<int> a, int aim) {
if (aim < 0 || a.size() == 0) return 0;
return process1(a, 0, aim);
}
// 记忆搜索:对暴力递归做了进一步的优化。时间复杂度O(N*aim^2)
// 准备好全局遍历map,记录已经计算过的递归过程的结果,防止下次重复计算。
int process2(vector<int> &a, int index, int aim, vector<vector<int> > &map) {
int res = 0;
if (index == a.size()) {
res = aim == 0 ? 1 : 0;
} else {
int mapValue = 0;
for (int i = 0; a[index]*i <= aim; i++) {
mapValue = map[index+1][aim-a[index]*i];
if (mapValue == 0) { // 因为初始化为0,所以0表示没计算过
res += process2(a, index+1, aim-a[index]*i, map);
} else { // 如果mapValue值为-1,那么曾经计算过且返回值为0
res += mapValue == -1 ? 0 : mapValue;
}
}
}
// 如果返回值res为0,则标记mapValue值为-1
map[index][aim] = res == 0 ? -1 : res;
return res;
}
int coins2(vector<int> a, int aim) {
if (aim < 0 || a.size() == 0) return 0;
vector<vector<int> > map(a.size()+1, vector<int>(aim+1));
return process2(a, 0, aim, map);
}
// 动态规划方法:行数为N,列数为aim+1的矩阵dp
// dp[i][j]表示使用a[0..i]货币的情况下,组成钱数j有多少种方法
int coins3(vector<int> a, int aim) {
if (aim < 0 || a.size() == 0) return 0;
vector<vector<int> > dp(a.size(), vector<int>(aim+1));
// 组成钱数为0的方法都是1
for (int i = 0; i < a.size(); i++)
dp[i][0] = 1;
// 只用第一种货币(倍数)的方法为1
for (int i = 1; a[0]*i <= aim; i++)
dp[0][a[0]*i] = 1;
int num = 0;
for (int i = 1; i < a.size(); i++) {
for (int j = 1; j <= aim; j++) {
num = 0;
// 递推公式:dp[i][j] = dp[i-1][j-a[i]*k]
for (int k = 0; j-a[i]*k >= 0; k++)
num += dp[i-1][j-a[i]*k];
dp[i][j] = num;
}
}
return dp[a.size()-1][aim];
}
// 时间复杂度为O(N*aim)
int coins4(vector<int> a, int aim) {
if (aim < 0 || a.size() == 0) return 0;
vector<vector<int> > dp(a.size(), vector<int>(aim+1));
// 组成钱数为0的方法都是1
for (int i = 0; i < a.size(); i++)
dp[i][0] = 1;
// 只用第一种货币(倍数)的方法为1
for (int i = 1; a[0]*i <= aim; i++)
dp[0][a[0]*i] = 1;
for (int i = 1; i < a.size(); i++) {
for (int j = 1; j <= aim; j++) {
// 递推公式:dp[i][j] = dp[i-1][j] + dp[i][j-a[i]]
dp[i][j] = dp[i-1][j];
dp[i][j] += j-a[i] >= 0 ? dp[i][j-a[i]] : 0;
}
}
return dp[a.size()-1][aim];
}
// 空间压缩,时间复杂度为O(N*aim),空间复杂度O(aim)
int coins5(vector<int> a, int aim) {
if (aim < 0 || a.size() == 0) return 0;
vector<int> dp(aim+1);
for (int i = 0; a[0]*i <= aim; i++)
dp[a[0]*i] = 1;
for (int i = 1; i < a.size(); i++) {
for (int j = 1; j <= aim; j++) {
dp[j] += j-a[i] >= 0 ? dp[j-a[i]] : 0;
}
}
return dp[aim];
}
int main(int argc, char const *argv[]) {
ios::sync_with_stdio(false);
vector<int> v {5,10,25,1};
cout << coins1(v, 25) << endl;
cout << coins2(v, 25) << endl;
cout << coins3(v, 25) << endl;
cout << coins4(v, 25) << endl;
cout << coins5(v, 25) << endl;
return 0;
}
创造新世界
输入x,n,m,分别表示x个物品,n个0,m个1。
接下来x行输入1和0的字符串,分别表示第i个物品需要多少个0和1。
求在仅给n个0和m个1的情况下,最多能创建多少个物品。
#include <bits/stdc++.h>
using namespace std;
/*
动态规划 dp[i][j] 表示用i个0和j个1能创建的最多物品数
*/
vector<string> *l;
int solve(int i, int numZeros, int numOnes) {
vector<string> &list = *l;
// 递归的终止条件
if (i == list.size()-1) {
for (int j = 0; j < list[i].size(); ++j) {
if (list[i][j] == '1') --numOnes;
if (list[i][j] == '0') --numZeros;
}
return ((numZeros | numOnes) >= 0) ? 1 : 0;
}
// 不创建当前item
int a = solve(i+1, numZeros, numOnes);
// 创建当前item
for (int j = 0; j < list[i].size(); ++j) {
if (list[i][j] == '1') --numOnes;
if (list[i][j] == '0') --numZeros;
}
// 如果不能创建当前item,则直接返回a,否则返回a和b的最大值
if ((numZeros | numOnes) < 0) return a;
int b = 1 + solve(i+1, numZeros, numOnes);
return max(a, b);
}
int main(int argc, char const *argv[]) {
int x, n, m;
vector<string> v;
cin >> x >> n >> m;
for (int i = 0; i < x; i++) {
string tmp;
cin >> tmp;
v.push_back(tmp);
}
l = &v;
cout << solve(0, n, m) << endl;
return 0;
}
数组的最长递增子序列
#include <bits/stdc++.h>
using namespace std;
/*
给定数组a,返回a的最长递增子序列
如a=[2,1,5,3,6,4,8,9,7],返回的最长递增子序列为[1,3,4,8,9]
*/
// 时间复杂度O(n^2) dp[i]表示在以a[i]这个数结尾的情况下,a[0..i]中的最大递增子序列长度
vector<int> list1(vector<int> a) {
if (a.size() == 0) return vector<int> {};
vector<int> dp(a.size());
// 第一步:求出dp数组 O(n^2)
for (int i = 0; i < a.size(); i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (a[j] < a[i])
dp[i] = max(dp[i], dp[j]+1);
}
}
// 第二步:根据dp数组得到最长递增子序列 O(n)
int len = 0, index = 0;
for (int i = 0; i < dp.size(); ++i) { // 先找出最大值以及下标位置
if (dp[i] > len) {
len = dp[i];
index = i;
}
}
vector<int> res(len);
res[--len] = a[index];
for (int i = index-1; i >= 0; i--) {
// 满足如下公式
if (a[i] < a[index] && dp[i]+1 == dp[index]) { // 不断寻找次大值及下标位置
res[--len] = a[i];
index = i;
}
}
return res;
}
// 时间复杂度O(nlogn)
vector<int> list2(vector<int> a) {
if (a.size() == 0) return vector<int> {};
vector<int> dp(a.size());
vector<int> ends(a.size());
// 第一步:求出dp数组 O(nlogn)
dp[0] = 1;
ends[0] = a[0];
int l, m, r, right;
l = m = r = right = 0;
// ends[0..right]为有效区。如果有ends[b]=c,则表示遍历到目前为止,
// 在所有长度为b+1的递增序列中,最小的结尾数为c
for (int i = 1; i < a.size(); i++) {
l = 0;
r = right;
while (l <= r) { // 二分查找在数组中找>=a[i]的数
m = (l+r)/2;
if (ends[m] < a[i]) {
l = m+1;
} else {
r = m-1;
}
}
// 没有找到>=a[i]的数时,l就比right大,更新最右边界right的值
right = max(right, l);
// l为在ends数组中>=a[i]的位置下标,并替换内容为a[i]
ends[l] = a[i];
// dp[i]表示在以a[i]这个数结尾的情况下,a[0..i]中的最大递增子序列长度
dp[i] = l+1;
}
// 第二步:根据dp数组得到最长递增子序列 O(n)
int len = 0, index = 0;
for (int i = 0; i < dp.size(); ++i) {
if (dp[i] > len) {
len = dp[i];
index = i;
}
}
vector<int> res(len);
res[--len] = a[index];
for (int i = index-1; i >= 0; i--) {
// 满足如下公式
if (a[i] < a[index] && dp[i]+1 == dp[index]) {
res[--len] = a[i];
index = i;
}
}
return res;
}
int main(int argc, char const *argv[]) {
vector<int> a {2,1,5,3,6,4,8,9,7};
vector<int> res = list1(a);
for (auto i : res)
cout << i << " ";
cout << endl;
vector<int> res2 = list2(a);
for (auto i : res2)
cout << i << " ";
cout << endl;
return 0;
}