动态规划||单调队列:LeetCode1425.带限制的子序列和
1425.带限制的子序列和
意思就是寻找最大子区间和,子区间的每一个相邻元素可以下表最多相差k.
一、
想到求最大连续子区间和O(n)的方法,但是这题显然状态转移方程并不是dp[i]=max(dp[i-1],0)+a[i],他可以跳过最多k-1个元素 所以里面应该嵌套一层循环 for(int j=1->k)dp[i]=max(dp[i-j],dp[i]);循环后dp[i]=max(dp[i],0)+a[i];时间复杂度为O(n*k).但我们看数据范围:
n和k都可以达到1e5,则时间复杂度最多达到1e10,显然超时,需要改进算法。
二、Binary tree
我们就想到用binary tree ,用multiset这个可以自动排序来存储每i个的前k个,有点想slide window,长度为k的窗口一直在滑动,然后每次dp[i]取multiset中的最大值,如果最大值都小于0就取0,加上a[i]即可。外层循环dp[i],时间复杂度为n,内层循环利用multiset的equal_range,时间复杂度为logk,所以总的时间复杂度为O(n*logk),不会超时。
上代码:
1 //Binary tree nlogk 2 class Solution { 3 public: 4 int constrainedSubsetSum(vector<int>& nums, int k) 5 { 6 const int n = nums.size(); 7 multiset<int>m{-0x7fffffff};//必须得给这个初始化一个最小值 !!这样就不用检查multiset是不是空的 8 vector<int>dp(n); 9 int ans = -0x7fffffff; 10 for (int i = 0; i < n; ++i) 11 { 12 if (i > k) 13 m.erase(m.equal_range(dp[i - (1 + k)]).first); 14 dp[i] = max(*rbegin(m), 0) + nums[i];//在这地方如果multiset是空的就会出错 加一个最小值在m中防止报错 15 m.insert(dp[i]); 16 ans = max(dp[i], ans); 17 } 18 return ans; 19 } 20 };
三、Monotonic queue
也可以利用单调队列,时间复杂度为O(n)
1 class Solution { 2 public: 3 int constrainedSubsetSum(vector<int>& nums, int k) 4 { 5 const int n = nums.size(); 6 vector<int>dp(n); 7 deque<int>q;//存的是index 下标 8 int ans = -0x7fffffff; 9 for (int i = 0; i < n; ++i) 10 { 11 if (i > k && q.front() == i - (k + 1))//可以选择坐标相差k 但相差k+1就不行了 得从窗口中出队 12 q.pop_front(); 13 dp[i] = (q.empty() ? 0 : max(dp[q.front()], 0)) + nums[i]; 14 while (!q.empty() && dp[i] >= dp[q.back()]) 15 q.pop_back(); 16 q.push_back(i); 17 ans = max(ans, dp[i]); 18 19 } 20 return ans; 21 } 22 };