划分dp+滑窗+前缀和|deque优化

lc3310

dfs标记可疑方法

入度+连通块

class Solution {

public:

vector<int> remainingMethods(int n, int k, vector<vector<int>>& invocations)

{

vector<vector<int>> g(n);

for (auto& e : invocations)

++ge\[0].push_back(e1);++

// 标记所有可疑方法

vector<int> is_suspicious(n);

auto dfs = \&(auto&& dfs, int x) -> void {

is_suspiciousx = true;

++for (int y : gx) {++

if (!is_suspiciousy) { // 避免无限递归

dfs(dfs, y);

}

}

};

dfs(dfs, k);

++// 检查是否有【非可疑方法】->【可疑方法】的边
for (auto& e : invocations) {
if (!is_suspiciouse\[0] && is_suspiciouse\[1]) {
// 无法移除可疑方法
vector<int> ans(n);
iota(ans.begin(), ans.end(), 0);
return ans;
}
}
++

// 移除所有可疑方法

vector<int> ans;

for (int i = 0; i < n; i++) {

if (!is_suspiciousi) {

ans.push_back(i);

}

}

return ans;

}

};

lc3578

其实是很典型 单调dq优化线性dp

半月前应该也刷到了一道类似的,第一时间还是没优化出来orz哭哭哭

class Solution {

public:

const static int N=5e4+10;

int fN;

int countPartitions(vector<int>& nums, int k) {

int n=nums.size();

int mod=1e9+7;

f0=1;

deque<int> qx,qn;

int sum=0,l=0;

for(int i=0;i<n;i++){

while(!qx.empty()&&numsi>=numsqx.back()) qx.pop_back();

qx.push_back(i);

while(!qn.empty()&&numsi<=numsqn.back()) qn.pop_back();

qn.push_back(i);

sum=(sum+fi)%mod;

while(!qx.empty()&&numsqx.front()-numsqn.front()>k){

if(l==qx.front()) qx.pop_front();

if(l==qn.front()) qn.pop_front();

sum=(sum+mod-fl)%mod;

l++;

}

fi+1=sum;

}

// for(int i=0;i<=n;i++) cout<<fi<<' ';puts("");

return fn;

}

};

deque优化划分dp

++idx 单调dq 维护窗口内的最大、最小值++,结合前缀和数组

++滑动窗口中统计元素差不超过k的合法分区数并取模++

class Solution {

public:

int countPartitions(vector<int>& nums, int k) {

const int MOD = 1'000'000'007;

int n = nums.size();

deque<int> min_q, max_q;

vector<int> f(n + 1);

f0 = 1;

long long sum_f = 0; // 窗口中的 fi 之和

int left = 0;

for (int i = 0; i < n; i++) {

int x = numsi;

++// 1. 入
sum_f += fi;
++

while (!min_q.empty() && x <= numsmin_q.back())

min_q.pop_back();

min_q.push_back(i);

while (!max_q.empty() && x >= numsmax_q.back()) {

max_q.pop_back();

}

max_q.push_back(i);

// 2. 出

++while (numsmax_q.front() - numsmin_q.front() > k) {++

++sum_f -= fleft;++

++left++;
if (min_q.front() < left)
++

min_q.pop_front();

if (max_q.front() < left)

max_q.pop_front();

}

// 3. 更新答案

++fi + 1 = sum_f % MOD;
}
++

++return fn;++

}

};

  1. 问题转化:要统计"所有元素差不超过K的子段分割方案数",可以用动态规划,设 ++fi 为前 i -1个元素的✓condition分割方案数++

  2. 滑动窗口找合法左边界:

用 multiset 维护窗口内的最大/最小值,通过双指针 j 找到每个右端点 i 对应的最小合法左边界(保证窗口内元素差≤K)

  1. 前缀和优化转移: fi 的方案数等于"前 i-1 个元素的所有合法前缀方案数之和",用前缀和++数组 g 快速计算这个区间和++ ,从而将转移时间复杂度优化到O(1)。

结合动态规划+滑动窗口+前缀和,就能高效解决子段分割计数问题啦~

class Solution {

public:

int countPartitions(vector<int>& nums, int K) {

int n = nums.size();

const int MOD = 1e9 + 7;

// fi:前 i 个元素的分割方案数

// gi:f 的前缀和

long long fn + 1, gn + 1;

f0 = g0 = 1;

// 用 multiset 记录滑动窗口里的数,方便求出最小值和最大值

multiset<int> ms;

// 枚举双指针的右端点 i,计算合法子段左端点的最小值 j

for (int i = 1, j = 1; i <= n; i++) {

ms.insert(numsi - 1);

while (j < i && *prev(ms.end()) - *ms.begin() > K) {

ms.erase(ms.find(numsj - 1));

j++;

}

// j 是最小的左端点

// 那么上一个子段最小的右端点就是 j - 1

// 前缀和就得减去 j - 2 的值

fi = (gi - 1 - (j - 2 >= 0 ? gj - 2 : 0) + MOD) % MOD;

gi = (gi - 1 + fi) % MOD;

}

return fn;

}

};

lc2762 不间断子数组

滑窗

class Solution {

public:

long long continuousSubarrays(vector<int>& nums) {

long long ans = 0;

map<int, int> cnt;

int left = 0;

for (int right = 0; right < nums.size(); right++) {

cntnums\[right]++;

while (cnt.rbegin()->first - cnt.begin()->first > 2) {

int y = numsleft;

if (--cnty == 0) {

cnt.erase(y);

}

left++;

}

ans += right - left + 1;

}

return ans;

}

};

双堆法

class Solution {

public long continuousSubarrays(int\[\] nums) {

long res=0;

int left=0;

PriorityQueue<Integer> less=new PriorityQueue<>();//最小堆

PriorityQueue<Integer> more=new PriorityQueue<>((a,b)->(b-a));//最大堆

for(int i=0;i<nums.length;i++){

less.offer(numsi);

more.offer(numsi);

while(more.peek()-less.peek()>2){

more.remove(numsleft);

less.remove(numsleft);

left++;

}

res+=i-left+1;

}

return res;

}

}

相关推荐
折哥的程序人生 · 物流技术专研7 小时前
Java面试85题图解版 · 特别篇:2026后端高频面试题复盘(算法底层逻辑+高并发架构设计全解析,附Java实战代码)
java·网络·数据库·算法·面试
想吃火锅10058 小时前
【leetcode】14.最长公共前缀js
算法·leetcode·职场和发展
云絮.9 小时前
数据库操作
数据库·mysql·算法·oracle
小林ixn10 小时前
LeetCode 206. 反转链表(迭代 + 递归详解)
算法·leetcode·链表
凡人叶枫10 小时前
Effective C++ 条款17:以独立语句将 newed 对象置入智能指针
java·linux·开发语言·c++·算法
菜鸟‍11 小时前
LeetCode 1 27 和 704 || 两数之和 移除元素 二分查找
算法·leetcode·职场和发展
退休倒计时12 小时前
【每日一题】LeetCode 142. 环形链表 II TypeScript
算法·leetcode·链表·typescript
popcorn_min13 小时前
Digits 手写数字识别:随机森林多分类 + 像素级特征热力图
算法·随机森林·分类
liulilittle13 小时前
拥塞控制:排水终止的两种决策:OR 与 AND
网络·tcp/ip·计算机网络·算法·信息与通信·tcp·通信
weixin_3077791314 小时前
从脚本执行到智能体协作:AI辅助测试能力的范式重构
运维·开发语言·人工智能·算法·测试用例