lc1671
前后缀分解+最长递增子序列(子序列dp)
左右开弓 典中典
class Solution {
public:
int minimumMountainRemovals(vector<int>& nums) {
int n=nums.size();
vector<int> pre(n,INT_MAX),suf(n,INT_MAX);
for(int i=0;i<n;i++){
pre[i]=i;
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){
pre[i]=min(pre[i],pre[j]+i-j-1);
}
}
}
for(int i=n-1;i>=0;i--){
suf[i]=n-i-1;
for(int j=n-1;j>i;j--){
if(nums[i]>nums[j]){
suf[i]=min(suf[i],suf[j]+j-i-1);
}
}
}
int ans=INT_MAX;
for(int i=1;i<n-1;i++){
if(pre[i]!=i&&suf[i]!=n-i-1) {
ans = min(ans, pre[i] + suf[i]);
}
}
return ans;
}
};
lis复用
class Solution {
public:
//误区:原数组的最大值不一定是答案的峰值
//定义dp[i]为以i结尾最长递增子序列长度
//维护数组pre[i],表示前i个元素的最长递增子序列长度,每次更新完dp[i]插入pre
//将数组反转后求递增子序列长度,即原数组的后缀递减子序列长度
//所以可以包装一个函数,调用两次求出pre和suf数组
//原数组的最长山型序列长度即为pre[i] + suf[n-1-i] - 1(因为第i个元素算了两次)
//枚举i统计最大值maxv,答案即为n - maxv
void LIS(vector<int>& nums,vector<int>& lis){
int n = nums.size();
vector<int> dp(n,1);
for(int i = 0;i < n;i++){
for(int j = i-1;j >= 0;j--){
if(nums[j] < nums[i]) dp[i] = max(dp[i],dp[j] + 1);
}
lis.push_back(dp[i]);
}
}
int minimumMountainRemovals(vector<int>& nums) {
int n = nums.size();
vector<int> pre,suf;
LIS(nums,pre);
reverse(nums.begin(),nums.end());
LIS(nums,suf);
int maxv = 0;
for(int i = 0;i < n;i++){ //注意这里pre、suf都要>1,否则峰值会出现在一端,不符合题意
if(pre[i] > 1 && suf[n - 1 - i] > 1){
maxv = max(maxv,pre[i] + suf[n - 1 - i] - 1);
}
}
return n - maxv;
}
};
优化
两次贪心+二分 分别计算每个位置的最长严格递增子序列(前缀)和最长严格递减子序列(后缀),找到最长山脉子序列的长度后,用数组总长度减去该长度,得到最少需要移除的元素数量

class Solution {
public:
int minimumMountainRemovals(vector<int> &nums) {
int n = nums.size();
vector<int> suf(n), g;
for (int i = n - 1; i; i--)
{
int x = nums[i];
auto it = lower_bound(g.begin(), g.end(), x);
suf[i] = it - g.begin() + 1; // 从 nums[i] 开始的最长严格递减子序列的长度
if (it == g.end())
g.push_back(x);
else
*it = x;
}
int mx = 0;
g.clear();
for (int i = 0; i < n - 1; i++) {
int x = nums[i];
auto it = lower_bound(g.begin(), g.end(), x);
int pre = it - g.begin() + 1; // 在 nums[i] 结束的最长严格递增子序列的长度
if (it == g.end()) {
g.push_back(x);
} else {
*it = x;
}
if (pre >= 2 && suf[i] >= 2) {
mx = max(mx, pre + suf[i] - 1); // 减去重复的 nums[i]
}
}
return n - mx;
}
};
lc1589
差分+前缀和+贪心
++cnt[start]++;++
cnt[end + 1]--;
for (int i = 1; i < n; i++)
cnt[i] += cnt[i - 1];
#include <vector>
#include <algorithm>
using namespace std;
const int MOD = 1e9 + 7;
class Solution {
public:
int maxSumRangeQuery(vector<int>& nums, vector<vector<int>>& requests)
{
int n = nums.size();
// 1. 差分 统计每个索引的查询次数
vector<int> cnt(n, 0);
for (auto& req : requests)
{
int start = req[0], end = req[1];
++cnt[start]++;++
if (end + 1 < n)
++cnt[end + 1]--;++
}
// 2. 前缀和,每个索引的实际查询次数
for (int i = 1; i < n; i++)
++cnt[i] += cnt[i - 1];++
// 3. 对nums和查询次数都降序排序
sort(nums.rbegin(), nums.rend());
sort(cnt.rbegin(), cnt.rend());
// 4. 计算总结果并取余
long long ret = 0;
for (int i = 0; i < n; i++)
ret = (ret + (long long)nums[i] * cnt[i]) % MOD;
return ret % MOD;
}
};
lc1339
两次递归遍历二叉树
先计算整棵树的节点值总和,再++遍历每个子树计算其节点值和++
找出子树和与剩余部分和的最大乘积
class Solution {
long long sum = 0, ret = 0;
const int MOD = 1e9 + 7;
public:
int maxProduct(TreeNode* root)
{
cal_sum(root);
dfs(root);
return ret % MOD;
}
void cal_sum(TreeNode* node)
{
if (!node) return;
sum += node->val;
cal_sum(node->left);
cal_sum(node->right);
}
long long dfs(TreeNode* node)
{
if (!node) return 0;
long long sub_sum = dfs(node->left) + dfs(node->right) + node->val;
long long p = sub_sum * (sum - sub_sum);
ret=max(ret,p);
return sub_sum;
}
};