划分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)

++g[e[0]].push_back(e[1]);++

// 标记所有可疑方法

vector<int> is_suspicious(n);

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

is_suspicious[x] = true;

++for (int y : g[x]) {++

if (!is_suspicious[y]) { // 避免无限递归

dfs(dfs, y);

}

}

};

dfs(dfs, k);

++// 检查是否有【非可疑方法】->【可疑方法】的边
for (auto& e : invocations) {
if (!is_suspicious[e[0]] && is_suspicious[e[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_suspicious[i]) {

ans.push_back(i);

}

}

return ans;

}

};

lc3578

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

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

class Solution {

public:

const static int N=5e4+10;

int f[N];

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

int n=nums.size();

int mod=1e9+7;

f[0]=1;

deque<int> qx,qn;

int sum=0,l=0;

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

while(!qx.empty()&&nums[i]>=nums[qx.back()]) qx.pop_back();

qx.push_back(i);

while(!qn.empty()&&nums[i]<=nums[qn.back()]) qn.pop_back();

qn.push_back(i);

sum=(sum+f[i])%mod;

while(!qx.empty()&&nums[qx.front()]-nums[qn.front()]>k){

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

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

sum=(sum+mod-f[l])%mod;

l++;

}

f[i+1]=sum;

}

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

return f[n];

}

};

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);

f[0] = 1;

long long sum_f = 0; // 窗口中的 f[i] 之和

int left = 0;

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

int x = nums[i];

++// 1. 入
sum_f += f[i];
++

while (!min_q.empty() && x <= nums[min_q.back()])

min_q.pop_back();

min_q.push_back(i);

while (!max_q.empty() && x >= nums[max_q.back()]) {

max_q.pop_back();

}

max_q.push_back(i);

// 2. 出

++while (nums[max_q.front()] - nums[min_q.front()] > k) {++

++sum_f -= f[left];++

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

min_q.pop_front();

if (max_q.front() < left)

max_q.pop_front();

}

// 3. 更新答案

++f[i + 1] = sum_f % MOD;
}
++

++return f[n];++

}

};

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

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

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

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

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

class Solution {

public:

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

int n = nums.size();

const int MOD = 1e9 + 7;

// f[i]:前 i 个元素的分割方案数

// g[i]:f 的前缀和

long long f[n + 1], g[n + 1];

f[0] = g[0] = 1;

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

multiset<int> ms;

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

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

ms.insert(nums[i - 1]);

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

ms.erase(ms.find(nums[j - 1]));

j++;

}

// j 是最小的左端点

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

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

f[i] = (g[i - 1] - (j - 2 >= 0 ? g[j - 2] : 0) + MOD) % MOD;

g[i] = (g[i - 1] + f[i]) % MOD;

}

return f[n];

}

};

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++) {

cnt[nums[right]]++;

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

int y = nums[left];

if (--cnt[y] == 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(nums[i]);

more.offer(nums[i]);

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

more.remove(nums[left]);

less.remove(nums[left]);

left++;

}

res+=i-left+1;

}

return res;

}

}

相关推荐
Zach_yuan3 小时前
算法1111
算法
不穿格子的程序员3 小时前
从零开始刷算法——二分-搜索旋转排序数组
数据结构·算法
做怪小疯子3 小时前
LeetCode 热题 100——哈希——最长连续序列
算法·leetcode·哈希算法
做怪小疯子4 小时前
LeetCode 热题 100——双指针——三数之和
算法·leetcode·职场和发展
高山上有一只小老虎4 小时前
等差数列前n项的和
java·算法
sin_hielo4 小时前
leetcode 2536
数据结构·算法·leetcode
flashlight_hi4 小时前
LeetCode 分类刷题:203. 移除链表元素
算法·leetcode·链表
py有趣4 小时前
LeetCode算法学习之数组中的第K个最大元素
学习·算法·leetcode
吗~喽4 小时前
【LeetCode】将 x 减到 0 的最小操作数
算法·leetcode