279.完全平方数
题目链接:. - 力扣(LeetCode)
讲解视频:
题目描述:
给你一个整数 n
,返回 和为 n
的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而 3
和 11
不是。
示例 1:
输入:n = 12
输出:3
解释:12 = 4 + 4 + 4
解题思路:
完全背包问题变形
1. 状态表示:
dp[i] 表示:和为 i 的完全平⽅数的最少数量。
2. 状态转移⽅程:
对于 dp[i] ,可以根据小于等于 i 的所有完全平⽅数 x 进⾏划分:
- x = 1 时,最⼩数量为: 1 + dp[i - 1] ;
- x = 4 时,最⼩数量为: 1 + dp[i - 4] ......⼀直枚举到 x <= i 为⽌。
为了⽅便枚举完全平⽅数,我们采⽤下⾯的策略: for(int j = 1; j * j <= i; j++)综上所述,状态转移⽅程为:dp[i] = min(dp[i], dp[i - j * j] + 1)
3. 初始化:
当 n = 0 的时候,没法拆分,结果为 0 ;当 n = 1 的时候,显然为 1 。
4. 填表顺序:
从左往右。
5. 返回值:
根据题意,返回 dp[n] 的值。
代码:
cpp
class Solution {
public:
const int INF = 0x3f3f3f3f;
int numSquares(int n) {
vector<int> dp(n+1,INF);
dp[0] = 0;
for(int i = 1; i*i <= n; i++)
for(int j = i*i; j <= n; j++)
dp[j] = min(dp[j],dp[j-i*i]+1);
return dp[n];
}
};
139.单词拆分
题目链接:. - 力扣(LeetCode)
讲解视频:
题目描述:
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。
**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
解题思路:
1. 状态表示:
对于线性 dp ,我们可以⽤「经验 + 题⽬要求」来定义状态表⽰:
- 以某个位置为结尾;
- 以某个位置为起点。
这⾥我们选择⽐较常⽤的⽅式,以某个位置为结尾,结合题⽬要求,定义⼀个状态表⽰:dp[i] 表示: [0, i] 区间内的字符串,能否被字典中的单词拼接⽽成。
2. 状态转移⽅程:
对于 dp[i] ,为了确定当前的字符串能否由字典⾥⾯的单词构成,根据最后⼀个单词的起始位置 j ,我们可以将其分解为前后两部分:
- 前⾯⼀部分 [0, j - 1] 区间的字符串;
- 后⾯⼀部分 [j, i] 区间的字符串。其中前⾯部分我们可以在 dp[j - 1] 中找到答案,后⾯部分的⼦串可以在字典⾥⾯找到。
因此,我们得出⼀个结论:当我们在从 0 ~ i 枚举 j 的时候,只要 dp[j - 1] = true并且后⾯部分的⼦串 s.substr(j, i - j + 1) 能够在字典中找到,那么 dp[i] =true 。
3. 初始化:
可以在最前⾯加上⼀个「辅助结点」,帮助我们初始化。使⽤这种技巧要注意两个点:
- 辅助结点⾥⾯的值要「保证后续填表是正确的」;
- 「下标的映射关系」。
在本题中,最前⾯加上⼀个格⼦,并且让 dp[0] = true ,可以理解为空串能够拼接⽽成。其中为了⽅便处理下标的映射关系,我们可以将字符串前⾯加上⼀个占位符 s = ' ' + s ,这样就没有下标的映射关系的问题了,同时还能处理「空串」的情况。
4. 填表顺序:
显⽽易⻅,填表顺序「从左往右」。
5. 返回值:
由「状态表⽰」可得:返回 dp[n] 位置的布尔值。
代码:
cpp
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> st;
for(auto x : wordDict) st.insert(x);
int n = s.size();
vector<bool> dp(n+1);
dp[0] = true;
s = " " + s;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= i; j++)
{
if(dp[j-1] && st.count(s.substr(j,i-j+1)))
{
dp[i] = true;
break;
}
}
return dp[n];
}
};
多重背包问题
题目链接:56. 携带矿石资源(第八期模拟笔试)
题目描述:
你是一名宇航员,即将前往一个遥远的行星。在这个行星上,有许多不同类型的矿石资源,每种矿石都有不同的重要性和价值。你需要选择哪些矿石带回地球,但你的宇航舱有一定的容量限制。
给定一个宇航舱,最大容量为 C。现在有 N 种不同类型的矿石,每种矿石有一个重量 w[i],一个价值 v[i],以及最多 k[i] 个可用。不同类型的矿石在地球上的市场价值不同。你需要计算如何在不超过宇航舱容量的情况下,最大化你所能获取的总价值。
解题思路:
在01背包问题基础上加一个for循环遍历一个每种商品的数量。
代码:
cpp
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int space, n;
cin >> space >> n;
vector<int> weight(n+1);
vector<int> value(n+1);
vector<int> nums(n+1);
for(int i = 1; i <= n; i++) cin >> weight[i];
for(int i = 1; i <= n; i++) cin >> value[i];
for(int i = 1; i <= n; i++) cin >> nums[i];
vector<int> dp(space+1);
for(int i = 1; i <= n; i++)
for(int j = space; j >= weight[i]; j--)
for(int k = 1; k <= nums[i] && j >= k * weight[i]; k++)
dp[j] = max(dp[j], dp[j-k*weight[i]]+k*value[i]);
cout << dp[space] <<endl;
return 0;
}