状态机dp
lc3748
1. 确定DP状态
- 定义 f[i][0] :以 nums[i] 结尾、不删除元素的最长非递减子数组长度;
- 定义 f[i][1] :以 nums[i] 结尾、删除过一次元素的最长非递减子数组长度。
2. 确定状态转移方程
- 若 nums[i-1] <= nums[i] (连续非递减): f[i][0] = f[i-1][0[i1 , f[i][1] = f[i-1][1[i1 ;
- 若 nums[i-1] > nums[i] (断开): f[i][0] = 1 (重新开始);
- 若 i≥2 且 nums[i-2] <= nums[i] (删中间 nums[i-1] 能衔接): f[i][1] = max(f[i][1], f[i-2][0[i2) ;
- 否则(删后仅含 nums[i-1] 和 nums[i] ): f[i][1] = max(f[i][1], 2) 。
3. 初始化DP数组
- f[0] = {1, 1} :单个元素,不删/删(删后无效仍按1算)长度均为1;
- ans = 1 :初始最大值为第一个元素的长度。
4. 确定遍历顺序
- 从 i=1 到 n-1 正序遍历:需用到前一个( i-1 )和前两个( i-2 )状态。
5. 确定最终结果
- 遍历中持续更新 ans ,取“前一个不删+当前(删前一个)”和“当前删一次”的最大值,最终返回 ans
class Solution {
public:
int longestSubarray(vector<int>& nums) {
int n = nums.size();
vector<array<int, 2>> f(n);
f[0] = {1, 1};
int ans = 1; // 以 nums[0] 结尾的子数组长度
for (int i = 1; i < n; i++) {
if (nums[i - 1] <= nums[i]) {
f[i][0] = f[i - 1][0] + 1;
f[i][1] = f[i - 1][1] + 1;
} else {
f[i][0] = 1;
// 不需要写 f[i][1] = 1,因为下面算出来的值至少是 2
}
if (i >= 2 && nums[i - 2] <= nums[i]) {
f[i][1] = max(f[i][1], f[i - 2][0] + 2);
} else {
f[i][1] = max(f[i][1], 2);
}
// ans = max({ans, f[i - 1][0] + 1, f[i][1]}); 这种写法比下面的慢
ans = max(ans, max(f[i - 1][0] + 1, f[i][1]));
}
return ans;
}
};
lc2431
1. 确定DP状态
- 定义 dp[j][k] :使用 j 张优惠券、花费不超过 k 金额时,能获得的最大美味值。
- 核心逻辑:用“优惠券数量+花费金额”双维度锁定状态,聚焦“选/不选当前物品”的决策。
2. 确定状态转移方程
对每个物品 i ,分两种选择更新状态(用临时数组 ndp 避免覆盖原状态):
- 不使用优惠券买:若 k + price[i] ≤ maxAmount ,则 ndp[j][k+price[i]] = max(原价值, dp[j][k] + tastiness[i]) ;
- 使用优惠券买:若 j < maxCoupons 且 k + price[i]/2 ≤ maxAmount ,则 ndp[j+1][k+price[i]/2] = max(原价值, dp[j][k] + tastiness[i]) ;
- 不买当前物品: ndp 继承 dp 原有值(默认逻辑,无需额外代码)。
3. 初始化DP数组
- dp 为 (maxCoupons+1)×(maxAmount+1) 的二维数组,初始值全为0:未用优惠券、未花费金额时,美味值为0。
4. 确定遍历顺序
- 外层正序遍历每个物品 i (0到n-1):逐个考虑是否纳入选择;
- 内层先遍历优惠券数 j (0到maxCoupons),再遍历金额 k (0到maxAmount):基于历史状态更新新状态,避免重复计算。
5. 确定最终结果
- 遍历整个 dp 数组,取所有 dp[j][k] (0≤j≤maxCoupons,0≤k≤maxAmount)的最大值,即为最大美味值
class Solution {
public:
int maxTastiness(vector<int>& price, vector<int>& tastiness, int maxAmount, int maxCoupons) {
int n = price.size();
vector<vector<int>> dp(maxCoupons+1, vector<int>(maxAmount+1));
for(int i=0; i<n; ++i){
vector<vector<int>> ndp(dp.begin(), dp.end());
for(int j=0; j<maxCoupons+1; ++j){
for(int k=0; k<maxAmount+1; ++k){
if(k + price[i] <= maxAmount){
ndp[j][k+price[i]] = max(ndp[j][k+price[i]], dp[j][k]+tastiness[i]);
}
if(j < maxCoupons and k + price[i]/2 <= maxAmount){
ndp[j+1][k + price[i]/2] = max(ndp[j+1][k + price[i]/2], dp[j][k]+tastiness[i]);
}
}
}
dp = move(ndp);
}
int ans = 0;
for(int j=0; j<maxCoupons+1; ++j){
for(int k=0; k<maxAmount+1; ++k){
ans = max(ans, dp[j][k]);
}
}
return ans;
}
};
lc3610
选不选
if (i > 0) {//不选
dp[i][j] = dp[i - 1][j];
}
// 选
if (j >= p[i])
dp[i][j] = min(dp[i][j], dp[i][j - p[i]] + 1);
class Solution {
vector<int> p;
void getPrimes(int m) {
// 线性筛法获取前m个质数
vector<bool> isPrime(1000, true); // 质数范围足够大
isPrime[0] = isPrime[1] = false;
int cnt = 0;
for (int i = 2; cnt < m; ++i) {
if (isPrime[i]) {
p.push_back(i);
cnt++;
for (int j = 2 * i; j < 1000; j += i) {
isPrime[j] = false;
}
}
}
}
public:
int minNumberOfPrimes(int n, int m) {
getPrimes(m);
if (p.empty()) return -1; // 没有可用质数,直接返回-1
// dp[i][j]:前i+1个质数组成和为j的最小个数
vector<vector<long long>> dp(m, vector<long long>(n + 1, n + 1)); // 初始化为n+1(最大不可能超过n)
for (int i = 0; i < m; ++i) {
dp[i][0] = 0; // 和为0时需要0个质数
}
for (int i = 0; i < m; ++i) {
for (int j = 1; j <= n; ++j) {
// 不选第i个质数
if (i > 0) {
dp[i][j] = dp[i - 1][j];
}
// 选第i个质数(如果j不小于当前质数)
if (j >= p[i]) {
dp[i][j] = min(dp[i][j], dp[i][j - p[i]] + 1);
}
}
}
int result = dp[m - 1][n];
return result > n ? -1 : result;
}
};
一维
int minNumberOfPrimes(int n, int m) {
getPrimes(m);
if (p.size() < m) return -1; // 无法获取m个质数,直接返回-1
// dp[j]:组成和为j时所需的最小质数个数(空间优化,一维DP)
vector<int> dp(n + 1, n + 1);
dp[0] = 0; // 和为0时需要0个质数
for (int prime : p) {
for (int j = prime; j <= n; ++j) {
dp[j] = min(dp[j],dp[j - prime] + 1);
}
}
return dp[n] > n ? -1 : dp[n];
}
};
