算法题-C++(5)动规

经典背包问题

  • 0-1背包
  • 完全背包
  • 多重背包
  • 分组背包
0-1背包

有N个物品,M容量背包,每个物品选一次,占用空间是v, 价值是w,问尽可能填满背包的情况下最大价值是多少?

#include<bits/stdc++.h>
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<bits/stdc++.h>
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<bits/stdc++.h>
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<bits/stdc++.h>
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<bits/stdc++.h>
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<bits/stdc++.h>
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<bits/stdc++.h>
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;
}

初阶DP问题

爬楼梯问题其实是斐波那契数列的一个变种?

746. 使用最小花费爬楼梯

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];
    }
};

62. 不同路径

杨辉三角又是你啊啊啊!
其实就是组合数的排列三角。

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<vector<T>> a(N,v) 类似于reshape v是一个vector<T>数组
        // vector<T> 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];
    }
};

63. 不同路径 II

变形版 路径里有了障碍 所以要先排除掉障碍才能进行计算

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,这里先不刷这题。

X道模拟题

租车骑绿岛
一车最多坐两人 最大载重M 人数是N 输入N个人的体重信息
问最小几辆车?0<=N<=1e6

/
//第一种 用for来做...
#include<iostream>
#include<algorithm>
#include<vector>
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<iostream>
#include<algorithm>
#include<vector>
//平时可以用万能头,考试的时候应该不能用 
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;
            }
        }
    }
};

完美走位 ----> LeetCode变形

我一开始想得很简单,以为是按照固定次序的字符串排列,但是后来发现不太对,看了LeetCode上面的题解,终于明白了怎么写!

#include<iostream>
#include<string>
#include<map>
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;
}

先看这个会明白很多!感谢灵神的馈赠!

同步双指针

1234. 替换子串得到平衡字符串

有一个只含有 ‘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;
    }
};

一道同理题…也可以用滑动窗口

567. 字符串的排列

哈希表初阶

  • 首先一个子串s1的任意排列被另一个字符串s2包含的话,所以有匹配子串的长度相等
  • 枚举排列?不需要,只需要当前子串中每个字符数量和匹配子串里相等即可,因为枚举耗费大量时间,只需要知道能组合成排列数就可以了

抓住这俩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<iostream>
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;
}

猜你喜欢

转载自blog.csdn.net/daxuanzi515/article/details/130170168