有N个物品,M容量背包,每个物品选一次,占用空间是v, 价值是w,问尽可能填满背包的情况下最大价值是多少?
#include
using namespace std;
const int N = 1005;
int f[N][N],w[N],v[N];
int n,m;
int temp;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)// i 是遍历的物品
for(int j=0;j<=m;j++)// j 是当前容积
{
f[i][j] = f[i-1][j];
// 本质上是当j>=v[i]时,f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])
if(j>=v[i])
{
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
temp = max(temp,f[i][j]);
}
cout<<temp<<endl;
return 0;
}
///
//一维解法
#include
using namespace std;
int n,m;
const int N = 1005;
int f[N],v[N],w[N],temp;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)//枚举物品
for(int j=m;j>=v[i];j--)//枚举物品的容积 使用j做下标 因为容积有限 要倒着...?
{ // j=v[i]~m ->f[i][j-v[i]]是不对的!
// j=m~v[i] ->f[i-1][j-v[i]]
f[j] = max(f[j],f[j-v[i]]+w[i]);
temp = max(temp, f[j]);
}
cout<<temp<<endl;
return 0;
}
每个物品不限制次数选
#include
using namespace std;
const int N = 1005;
int f[N][N],w[N],v[N];
int n,m;
int temp;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k*v[i]<=j;k++)//选几次
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+w[i]);
cout<<f[n][m]<<endl;
return 0;
}
///
//优化两个循环//通过递推得到公式
// f[i][j] = max( f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-v[i]*2]+2*w[i],...,)
// f[i][j-v[i]] = max( f[i-1][j-v[i]] ,f[i-1][j-v[i]*2]+w[i],...,)
//=> f[i][j] = max( f[i-1][j], f[i][j-v[i]]+w[i] )
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
f[i][j] = f[i-1][j];
if(j>=v[i])
f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
}
cout<<f[n][m]<<endl;
return 0;
}
///
//优化成为一维
#include
using namespace std;
int n,m;
const int N = 1005;
int f[N],v[N],w[N],temp;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
for(int i=1;i<=n;i++)//枚举物品
for(int j=v[i];j<=m;j++)//枚举物品的容积
{
f[j] = max(f[j],f[j-v[i]]+w[i]);
temp = max(temp, f[j]);
}
cout<<temp<<endl;
return 0;
}
每个物品有固定次数可选
#include
using namespace std;
int n,m;
const int N = 105;
int f[N][N],v[N],w[N],s[N];
int temp;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k*v[i]<=j && k<=s[i];k++)
{
// 注意这里的出现的一个问题
// 不选的时候怎么表示???
// 进入k循环之后k=0代表不选 所以k=0~s[i]它把选和不选都包含了...
// 所以不用特地写出来了
if(j>=v[i]*k)
{
f[i][j] = max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
}
}
cout<<f[n][m]<<endl;
return 0;
}
///
/// 优化
// f[i][j] = max( f[i-1][j],f[i-1][j-v[i]]+w[i],f[i-1][j-v[i]*2]+2*w[i],...,f[i-1][j-v[i]*s[i]]+s[i]*w[i])
// f[i][j-v[i]] = max( f[i-1][j-v[i]] ,f[i-1][j-v[i]*2]+w[i],...,f[i-1][j-v[i]*s[i]]+(s[i]-1)*w[i],f[i-1][j-v[i]*(s[i]+1)]+s[i]*w[i]) )
//=> f[i][j] = max( f[i-1][j], f[i][j-v[i]]+w[i] )
多出来一项 不能被削掉 需要用二进制 1 2 4 8 ,..,512 用二进制数表示原来的数
/// 把多个物品进行分组打包... 每组只能选一个...
#include
using namespace std;
int n,m;
const int N = 20005;
int f[N],v[N],w[N];
int temp;
// 1 2 4 8 ..., 2^k , C
// C < 2^(k+1)
// 可以表示: 0~S
// 分好组之后对这些组做一次0-1背包
int main()
{
cin>>n>>m;
int cnt = 0;
for(int i=1;i<=n;i++)
{
int a,b,c;
cin>>a>>b>>c;
//从1开始 1,2,4,8,...
int k = 1;
while(k<=c)
{
cnt++;
v[cnt] = a*k;
w[cnt] = b*k;
// 个数-K
c = c - k;
k = k*2;
}
//剩下就是C
if(c>0)
{
cnt++;
v[cnt] = a*c;
w[cnt] = b*c;
}
}
// cnt 分成几组?
n = cnt;
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)
f[j] = max(f[j],f[j-v[i]]+w[i]);
cout<<f[m]<<endl;
return 0;
}
分组里面的物品最多只能选一个
// 枚举第i组物品选几个?->完全背包
// 枚举第i组物品选哪个?->分组背包 第k个 几重循环???
// f[i,j] = max(f[i,j],f[i,j-v[i,k]]+w[i,k])
#include
using namespace std;
int n,m;
const int N =105;
int f[N],s[N],w[N][N],v[N][N];
int main()
{
cin>>n>>m;
// 一维的先...
for(int i=1;i<=n;i++)
{
cin>>s[i];//组里面的物品数
for(int j=0;j<s[i];j++)
cin>>v[i][j]>>w[i][j];
}
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k = 0;k<s[i];k++)
if(j>=v[i][k])
f[j] = max(f[j],f[j-v[i][k]]+w[i][k]);
cout<<f[m]<<endl;
return 0;
}
爬楼梯问题其实是斐波那契数列的一个变种?
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
vector<int> dp(cost.size());
dp[0] = cost[0];
dp[1] = cost[1];
for(int i=2;i<cost.size();i++)
{
dp[i] = min(dp[i-1],dp[i-2])+cost[i];
}
// 为什么不直接输出dp[n-1]? 因为是前两步的最小值 这样直接输出会漏掉一种情况
return min(dp[dp.size()-1],dp[dp.size()-2]);
// return dp[dp.size()-1];
}
};
杨辉三角又是你啊啊啊!
其实就是组合数的排列三角。
class Solution {
public:
int uniquePaths(int m, int n) {
// dp[i][j] => (0,0)到(i,j)的路径种数
// 转移状态: dp[i][j-1] + dp[i-1][j] => dp[i][j] // 不同路径
// end = (m-1,n-1)
// (0,0)->(i,0)路径种数 all 1 (0,0)->(0,i) all 1
// (0,0)->(1,0) = 1
// (0,0)->(2,0) = 1
// 初始化dp vector> a(N,v) 类似于reshape v是一个vector数组
// vector a(n,0) 初始化为全0的大小为n的T类型数组
vector<vector<int>> dp(m, vector<int>(n,0));
for(int i=0;i<m;i++) dp[i][0] = 1;
for(int i=0;i<n;i++) dp[0][i] = 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];
}
};
一维数组 每次走到一个格子都是1种
///保存每次走到某列的结果 dp[i] 走到第i列时的总数
class Solution {
public:
int uniquePaths(int m, int n) {
vector<int> dp(n,1);
for(int j=1;j<m;j++)
for(int i=1;i<n;i++)
dp[i] += dp[i-1];
return dp[n-1];
}
};
变形版 路径里有了障碍 所以要先排除掉障碍才能进行计算
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
//获取行列
int row = obstacleGrid.size();
int column = obstacleGrid[0].size();
vector<vector<int>> dp(row, vector<int> (column, 0));
// 标记第一列和第一行的初始值
for(int i=0;i<row && obstacleGrid[i][0] == 0;i++) dp[i][0] = 1;
for(int i=0;i<column && obstacleGrid[0][i] == 0;i++) dp[0][i] = 1;
for(int i=1;i<row;i++)
for(int j=1;j<column;j++)
//这里要限制当前的格子不是障碍物才行走... 漏掉这个会错
if(obstacleGrid[i][j]==0)
dp[i][j] = dp[i-1][j] + dp[i][j-1];
return dp[row-1][column-1];
}
};
还有个不同路径三,但是用的是DFS,这里先不刷这题。
租车骑绿岛
一车最多坐两人 最大载重M 人数是N 输入N个人的体重信息
问最小几辆车?0<=N<=1e6
/
//第一种 用for来做...
#include
#include
#include
using namespace std;
int main()
{
int m, n;
cin >> m >> n;
vector<int> w(n);
for(int i=0; i<n; i++)
{
cin >> w[i];
}
sort(w.begin(), w.end());
int count = 0;
int i,j;
for(i=0,j=n-1; i<j; )
{
int cur = w[i] + w[j];
if(cur > m)
{
j--;
count++;
}
else
{
i++;
j--;
count++;
}
}
if(i == j)//只剩下一个人
{
count++;
}
cout << count << endl;
return 0;
}
///
//第二种 用while
#include
#include
#include
//平时可以用万能头,考试的时候应该不能用
using namespace std;
int m,n;
// 逻辑模拟...0-0真的菜啊
//一车最多坐两人 最大载重M 人数是N
//问最小几辆车
int main()
{
vector<int> w;
cin>>m>>n;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
w.push_back(x);
}
// 对权重排序
// 其实已经想到这里了
sort(w.begin(),w.end());
//设置两个遍历对象
int l = 0, r = w.size()-1;
int res = 0;
int cur = w[l]+w[r];
// 测试的时候没有弹出 已经使用的权重...
while(l<r)
{
if(cur>m)
{
//当前重量>m 移动右边的,因为左边的越小越可能满足条件
r-=1;
res+=1;//说明这个w[r]自己坐一辆车
cur = w[l]+w[r];//更新当前的重量
}
else
{
//当前重量<=m 移动左边和右边
r-=1;
l+=1;
res+=1;//两个人坐一辆车
cur = w[l]+w[r];
}
}
if(l==r) res+=1;//?为啥...两者相遇=>总会剩下一个人
cout<<res<<endl;
return 0;
}
1042. 不邻接植花
每个花园最多三个进入离开的路,说明出度、入度 是3
只能染色4种,使得有连接的花园之间的颜色不一样
输出一种可行方案,(输出所有可行方案总数)
// 这里直接给出邻接的坐标... 不是传入g[i][j] = 1 表示双向有边
// path:[[1,2] [2,3] [3,1]]====> 这里需要 x->y y->x ===>
// 实际上是这么存储的 (1->2,2->1) (2->3,3->2) (1->3,3->1)
// g[0] = {2}//代表第一个花园的邻接花园集合
// g[1] = {3}
// g[2] = {1}
// 所以x的下标需要映射y的坐标 所以y的下标需要映射x的坐标 它们都是从下标0开始所以第一下标要-1
class Solution {
public:
vector<int> gardenNoAdj(int N, vector<vector<int>>& paths) {
vector<vector<int>> g(N);
vector<int> ans(N,0);
for(int i=0;i<paths.size();i++)
{
// 这里取的是下标起始是0
g[paths[i][0]-1].push_back(paths[i][1]);
g[paths[i][1]-1].push_back(paths[i][0]);
}
for(int i=0;i<N;i++)
{
//遍历x->y 表
int c[4] = {0};//染色数组
for(int j=0;j<g[i].size();j++)//遍历当前的花园的邻接花园
if(ans[g[i][j]-1]!=0)// ans[g[i][j]-1]的值: 第i个花园的邻接花园j的颜色编号
c[ ans[g[i][j]-1]-1 ] += 1;// c是被选择的颜色的次数,c[0] = N 第一种颜色被选N次
for(int k = 0;k<4;k++)
{// 这里k从0开始 颜色编号从1开始 所以颜色编号要+1
if(c[k] == 0)//当颜色没被使用的话...
{
ans[i] = k+1;
break;
}
}
}
return ans;
}
};
如果输出所有方案总数:
class Solution {
public:
vector<vector<int>> gardenNoAdj(int N, vector<vector<int>>& paths) {
vector<vector<int>> g(N);
vector<int> ans(N,0);
vector<vector<int>> res;
for(int i=0;i<paths.size();i++)
{
g[paths[i][0]-1].push_back(paths[i][1]);
g[paths[i][1]-1].push_back(paths[i][0]);
}
dfs(g, ans, res, 0);
return res;
}
private:
void dfs(vector<vector<int>>& g, vector<int>& ans, vector<vector<int>>& res, int index) {
if (index == ans.size()) {
res.push_back(ans);
return;
}
vector<bool> used(5, false);
for (int j = 0; j < g[index].size(); j++) {
if (ans[g[index][j] - 1] != 0) {
used[ans[g[index][j] - 1]] = true;
}
}
for (int k = 1; k <= 4; k++) {
if (!used[k]) {
ans[index] = k;
dfs(g, ans, res, index + 1);
ans[index] = 0;
}
}
}
};
一车最多坐两人 最大载重M 人数是N 输入N个人的体重信息
问最小几辆车?0<=N<=1e6
/
//第一种 用for来做...
#include
#include
#include
using namespace std;
int main()
{
int m, n;
cin >> m >> n;
vector<int> w(n);
for(int i=0; i<n; i++)
{
cin >> w[i];
}
sort(w.begin(), w.end());
int count = 0;
int i,j;
for(i=0,j=n-1; i<j; )
{
int cur = w[i] + w[j];
if(cur > m)
{
j--;
count++;
}
else
{
i++;
j--;
count++;
}
}
if(i == j)//只剩下一个人
{
count++;
}
cout << count << endl;
return 0;
}
///
//第二种 用while
#include
#include
#include
//平时可以用万能头,考试的时候应该不能用
using namespace std;
int m,n;
// 逻辑模拟...0-0真的菜啊
//一车最多坐两人 最大载重M 人数是N
//问最小几辆车
int main()
{
vector<int> w;
cin>>m>>n;
for(int i=0;i<n;i++)
{
int x;
cin>>x;
w.push_back(x);
}
// 对权重排序
// 其实已经想到这里了
sort(w.begin(),w.end());
//设置两个遍历对象
int l = 0, r = w.size()-1;
int res = 0;
int cur = w[l]+w[r];
// 测试的时候没有弹出 已经使用的权重...
while(l<r)
{
if(cur>m)
{
//当前重量>m 移动右边的,因为左边的越小越可能满足条件
r-=1;
res+=1;//说明这个w[r]自己坐一辆车
cur = w[l]+w[r];//更新当前的重量
}
else
{
//当前重量<=m 移动左边和右边
r-=1;
l+=1;
res+=1;//两个人坐一辆车
cur = w[l]+w[r];
}
}
if(l==r) res+=1;//?为啥...两者相遇=>总会剩下一个人
cout<<res<<endl;
return 0;
}
完美走位 ----> LeetCode变形
我一开始想得很简单,以为是按照固定次序的字符串排列,但是后来发现不太对,看了LeetCode上面的题解,终于明白了怎么写!
#include
#include
#include
using namespace std;
map <char,int> mp;
int main()
{
string s;
cin>>s;
int left = 0, right = 0;
for(int i=0;i<s.size();i++)
mp[s[i]]+=1;
int ans = 0x3f3f3f3f;
int len = int(s.size()/4);
//这里要特判一下 不然输出的就是很大的ans
if(mp['W']==len && mp['A']==len && mp['D']==len && mp['S']==len)
{
cout<<0<<endl;
return 0;
}
for(int right=0;right<s.size();right++)
{
mp[s[right]] -=1;// 有元素进入窗口 然后在窗外面的要减一
while(mp['W']<= len && mp['S']<=len && mp['A']<=len && mp['D']<=len)
{
ans = min(ans,right-left+1);
mp[s[left++]]+=1;
//求最小子串长 所以符合条件之后移动左端点 元素减少 窗外面的增加
}
}
cout<<ans<<endl;
return 0;
}
先看这个会明白很多!感谢灵神的馈赠!
同步双指针
有一个只含有 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符,且长度为 n 的字符串。
假如在该字符串中,这四个字符都恰好出现 n/4 次,那么它就是一个「平衡字符串」。
给你一个这样的字符串 s
,请通过「替换一个子串」的方式,使原字符串 s
变成一个「平衡字符串」。
你可以用和「待替换子串」长度相同的 任何 其他字符串来完成替换。
请返回待替换子串的最小可能长度。
如果原字符串自身就是一个平衡字符串,则返回 0。
这里需要注意的是因为输入字符串的长度是4的倍数
所以先不用判断长度 但是这里是字串里面所有字符出现次数==n/4 顺序没有要求
所以不能按照一个固定顺序来判断! QWER是合理的 REWQ、EWQR也合理…
真狡猾啊!
官方说:划定一个滑动小窗口作为待替换区域,然后判断窗口外的每一种字符的数量,
如果当前某个字符数目小于等于n/4,则满足条件成为平衡字符串,否则不能成为。
我直接问号满头?Why?!
噢,仔细看了之后发现是满足次数为n/4,那么大于之后肯定不能变成n/4,因为不能修改外面的字符…,这是只能修改窗口内的字符,此时这个窗口大小会随着遍历的次数增加而变化
使用左端点为left,右端点为right来维护一个窗口,当right++的时候,窗口多进入一个元素,left++的时候,窗口减少一个元素,但是外面需要计算的是窗口外的元素,所以数组统计的时候要反过来…
right++的时候,窗口外的当前元素个数–,left++的时候,窗口外的当前元素个数++
同向双指针
class Solution {
public:
int balancedString(string s) {
map <char,int> mp;
int len = int(s.size()/4);
for(int i=0;i<s.size();i++)
mp[s[i]]+=1;
if(mp['Q']==len && mp['E'] == len && mp['R'] == len && mp['W'] == len)
return 0;
int ans = 0x3f3f3f3f, left = 0;
for(int right = 0;right<s.size();right++)
{
mp[s[right]]-=1;
//只要满足条件就持续缩小窗口长度 即左指针右移动
while(mp['Q']<=len && mp['E'] <= len && mp['R'] <= len && mp['W'] <= len)
{
ans = min(ans,right-left+1);
mp[s[left++]]+=1;//窗口外的元素增加 所以次数也增加
}
}
return ans;
}
};
一道同理题…也可以用滑动窗口
哈希表初阶
抓住这俩trick用上述方法尝试解决!
区别是这个窗口大小不变,而之前的窗口大小会变化,这里关注窗口大小里元素数量判断
假设已知:初始字符串s1
: abcdabc
和匹配字符串s2
: da
直接规定滑动窗口大小是s2.size()
初始化两个哈希表h
,hash
只统计在[0-s2.size())
里面的所有元素的出现次数
[d,a] -> len = 2
, hash={'a':1,'b':0,...,'d':1,...,'z':0}
[a,b,c,d,a,b,c] -> h = {'a':1,'b':1,'c':0,'d':0,...}
在s1上
滑动… 统计元素数量…
右移窗口会导致窗口中的元素数量个数++,移出去的元素数量–
[[a,b],c,d,a,b,c]->
注意这里没有进行操作是因为之前已经进行了…
[a,[b,c],d,a,b,c] -> h['a']--,h['c']++
[a,b,[c,d],a,b,c] -> h['b']--,h['d']++
[a,b,c,[d,a],b,c] -> h['c']--,h['a']++
h={'a':1,'b':0,'c':0,'d':1} ->hash
匹配 输出true
否则输出false
但是这时候我们发现最后一个窗口并没有被匹配上…所以最后还需要判断h==hash
如果两个都是一样的说明匹配…
映射关系是当前h[s[i-len]]--,h[s[i]]++
class Solution {
public:
bool checkInclusion(string s1, string s2) {
//特判s1>s2的情况 之前没有特判...
if(s1.size()>s2.size()) return false;
int win_size = s1.size();
// 初始化hash1,hash2两个哈希表 并保存所有字母的映射关系
vector<int> hash1(26,0);
vector<int> hash2(26,0);
for(int i=0;i<win_size;i++)
{
hash1[s1[i]-'a']+=1;
hash2[s2[i]-'a']+=1;
}
for(int i=win_size;i<s2.size();i++)
{
//主要操作的是s2的匹配情况
if(hash2 == hash1) return true;
hash2[s2[i-win_size]-'a']-=1;
hash2[s2[i]-'a']+=1;
}
// vector可以直接比较两个数组的大小情况
return hash1==hash2;
}
};
短信数量
买短信条数,给出预算M,以及一个大小为N的数组,下标从1开始,代表价格为 i 的短信可以发多少条,求出预算内最多可发短信数量?
输入:
6 5
10 20 30 40 60
输出:
70
分析:预算为6块,花1块可以发10条短信,2块发20条,3块30条,4块40条,5块60条。总共花费(5+1块)发(60+10=70)条短信
EMMM,完全背包?
// 物品可以选无限次,但是总重量必须在容器范围内,求最大价值?
// 物品就是短信,价值是几条短信,容器大小是预算数目
// 速通!
#include
using namespace std;
const int N =105;
int n,m;
int v[N],w[N],f[N];
int main()
{
cin>>m>>n;
for(int i=1;i<=n;i++)
{
cin>>w[i];
v[i]=i;
}
for(int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++)
f[j] = max(f[j],f[j-v[i]]+w[i]);
cout<<f[m]<<endl;
return 0;
}