Welcome to Subscribe On Youtube
3915. Maximum Sum of Alternating Subsequence With Distance at Least K
Description
You are given an integer array nums of length n and an integer k.
Pick a subsequence with indices 0 <= i1 < i2 < ... < im < n such that:
- For every
1 <= t < m,it+1 - it >= k. - The selected values form a strictly alternating sequence. In other words, either:
nums[i1] < nums[i2] > nums[i3] < ..., ornums[i1] > nums[i2] < nums[i3] > ...
A subsequence of length 1 is also considered strictly alternating. The score of a valid subsequence is the sum of its selected values.
Return an integer denoting the maximum possible score of a valid subsequence.
Example 1:
Input: nums = [5,4,2], k = 2
Output: 7
Explanation:
An optimal choice is indices [0, 2], which gives values [5, 2].
- The distance condition holds because
2 - 0 = 2 >= k. - The values are strictly alternating because
5 > 2.
The score is 5 + 2 = 7.
Example 2:
Input: nums = [3,5,4,2,4], k = 1
Output: 14
Explanation:
An optimal choice is indices [0, 1, 3, 4], which gives values [3, 5, 2, 4].
- The distance condition holds because each pair of consecutive chosen indices differs by at least
k = 1. - The values are strictly alternating since
3 < 5 > 2 < 4.
The score is 3 + 5 + 2 + 4 = 14.
Example 3:
Input: nums = [5], k = 1
Output: 5
Explanation:
The only valid subsequence is [5]. A subsequence with 1 element is always strictly alternating, so the score is 5.
Constraints:
1 <= n == nums.length <= 1051 <= nums[i] <= 1051 <= k <= n
Solutions
Solution 1: Dynamic Programming + Binary Indexed Tree
State Definition
Let $f[i][0]$ denote the maximum sum of a valid subsequence ending at index $i$ where the last element is a valley (the next element must be larger to maintain alternation), and $f[i][1]$ denote the maximum sum where the last element is a peak (the next element must be smaller).
Transitions
When transitioning, we enumerate a predecessor index $j$ satisfying $j \leq i - k$:
- State $f[i][0]$ (valley): transitions from $f[j][1]$, requiring $\text{nums}[j] > \text{nums}[i]$, i.e., query the maximum $f[\cdot][1]$ over the value range $(\text{nums}[i],\ +\infty)$:
- State $f[i][1]$ (peak): transitions from $f[j][0]$, requiring $\text{nums}[j] < \text{nums}[i]$, i.e., query the maximum $f[\cdot][0]$ over the value range $[1,\ \text{nums}[i]-1]$:
The final answer is $\max_{0 \leq i < n}\max(f[i][0],\ f[i][1])$.
Optimization
The transitions involve dynamic prefix/suffix maximum queries over a value domain, which can be maintained efficiently with two Binary Indexed Trees (BITs):
- BIT $\text{bit}_0$: indexed by value, maintains the prefix maximum of $f[\cdot][0]$, used to query cases where $\text{nums}[j] < \text{nums}[i]$.
- BIT $\text{bit}_1$: indexed by $M + 1 - \text{val}$ (reversed, where $M = \max(\text{nums})$ ), maintains the prefix maximum of $f[\cdot][1]$, equivalent to a suffix maximum over the value domain, used to query cases where $\text{nums}[j] > \text{nums}[i]$.
To ensure only indices $j \leq i - k$ participate in transitions, when processing index $i$, we insert the state of index $i - k$ into the BITs using a sliding pointer.
The time complexity is $O(n \log M)$ and the space complexity is $O(M)$, where $n$ is the length of the array and $M = \max(\text{nums})$.
-
class Solution { public long maxAlternatingSum(int[] nums, int k) { long maxSum = 0; int n = nums.length; int m = Arrays.stream(nums).max().getAsInt(); long[][] dp = new long[n][2]; SegmentTree[] sts = new SegmentTree[2]; for (int j = 0; j < 2; j++) { sts[j] = new SegmentTree(m + 1); } for (int i = 0; i < n; i++) { if (i >= k) { sts[0].update(nums[i - k], dp[i - k][0]); sts[1].update(nums[i - k], dp[i - k][1]); } dp[i][0] = sts[1].getMax(0, nums[i] - 1) + nums[i]; dp[i][1] = sts[0].getMax(nums[i] + 1, m) + nums[i]; maxSum = Math.max(maxSum, Math.max(dp[i][0], dp[i][1])); } return maxSum; } } class SegmentTree { private int n; private long[] tree; public SegmentTree(int n) { this.n = n; this.tree = new long[n * 4]; } public long getMax(int start, int end) { return getMax(start, end, 0, 0, n - 1); } public void update(int index, long value) { update(index, value, 0, 0, n - 1); } private long getMax(int rangeStart, int rangeEnd, int treeIndex, int treeStart, int treeEnd) { if (rangeStart > rangeEnd) { return 0; } if (rangeStart == treeStart && rangeEnd == treeEnd) { return tree[treeIndex]; } int mid = treeStart + (treeEnd - treeStart) / 2; if (rangeEnd <= mid) { return getMax(rangeStart, rangeEnd, treeIndex * 2 + 1, treeStart, mid); } else if (rangeStart > mid) { return getMax(rangeStart, rangeEnd, treeIndex * 2 + 2, mid + 1, treeEnd); } else { return Math.max(getMax(rangeStart, mid, treeIndex * 2 + 1, treeStart, mid), getMax(mid + 1, rangeEnd, treeIndex * 2 + 2, mid + 1, treeEnd)); } } private void update(int rangeIndex, long value, int treeIndex, int start, int end) { if (start == end) { tree[treeIndex] = value; return; } int mid = start + (end - start) / 2; if (rangeIndex <= mid) { update(rangeIndex, value, treeIndex * 2 + 1, start, mid); } else { update(rangeIndex, value, treeIndex * 2 + 2, mid + 1, end); } tree[treeIndex] = Math.max(tree[treeIndex * 2 + 1], tree[treeIndex * 2 + 2]); } } -
class Solution { public: long long maxAlternatingSum(vector<int>& nums, int K) { int n = nums.size(); // 离散化 int idx[n]; map<int, int> mp; for (int x : nums) mp[x] = 1; int m = 0; for (auto& p : mp) p.second = ++m; for (int i = 0; i < n; i++) idx[i] = mp[nums[i]]; const long long INF = 1e18; // tree[0]:前缀最大值(用于查询 < nums[i] 的最大 f[j][0]) // tree[1]:后缀最大值(用于查询 > nums[i] 的最大 f[j][1]) long long tree[2][m + 1]; for (int i = 0; i < 2; i++) for (int j = 0; j <= m; j++) tree[i][j] = -INF; // 树状数组模板开始 auto lb = [&](int x) { return x & (-x); }; auto update = [&](long long* tree, int pos, long long val) { for (; pos <= m; pos += lb(pos)) tree[pos] = max(tree[pos], val); }; auto query = [&](long long* tree, int pos) { long long ret = -INF; for (; pos; pos -= lb(pos)) ret = max(ret, tree[pos]); return ret; }; // 树状数组模板结束 long long ans = 0; long long f[n + 1][2]; for (int i = 0; i <= n; i++) for (int j = 0; j < 2; j++) f[i][j] = -INF; // 滑动窗口:只有 j <= i - K 的位置才加入树状数组 for (int i = 1, j = 1; i <= n; i++) { while (i - j >= K) { update(tree[0], idx[j - 1], f[j][0]); update(tree[1], m + 1 - idx[j - 1], f[j][1]); j++; } // 谷:从 tree[1] 查询值 > nums[i] 的最大 f[j][1] f[i][0] = max(0LL, query(tree[1], m - idx[i - 1])) + nums[i - 1]; // 峰:从 tree[0] 查询值 < nums[i] 的最大 f[j][0] f[i][1] = max(0LL, query(tree[0], idx[i - 1] - 1)) + nums[i - 1]; ans = max({ans, f[i][0], f[i][1]}); } return ans; } }; -
class FenwickTree: def __init__(self, n): self.n = n self.tree = [0] * (n + 1) def update(self, index: int, val: int) -> None: while index <= self.n: self.tree[index] = max(self.tree[index], val) index += index & (-index) # 往后更新 def preSum(self, pos): # 按照预期的方式求前缀最大值 ans = 0 while pos >= 1: ans = max(ans, self.tree[pos]) pos -= pos & (-pos) return ans class Solution: def maxAlternatingSum(self, nums: list[int], k: int) -> int: stl = sorted(set(nums)) # 将nums中不同的数字进行排序 rank = { v: i + 1 for i, v in enumerate(stl) } # 将nums中的值快速转换成stl中的索引 fwt0 = FenwickTree(len(stl)) fwt1 = FenwickTree(len(stl)) n = len(nums) dp = [[0, 0] for _ in range(n)] res = nums[0] for i in range(n): dp[i][0] = dp[i][1] = nums[i] if i >= k: indx = rank[nums[i]] # 找到nums[i]在stl中的索引 dp[i][1] = max( dp[i][1], fwt0.preSum(indx - 1) + nums[i] ) # indx-1即表示小于nums[i]的部分 dp[i][0] = max( dp[i][0], fwt1.preSum(len(stl) - indx) + nums[i] ) # len(stl)-indx即表示在倒序列表中大于nums[i]的部分 if i - k + 1 >= 0: indx = rank[nums[i - k + 1]] fwt0.update(indx, dp[i - k + 1][0]) # 在正序列表中更新i-k+1位置的值 fwt1.update( len(stl) - indx + 1, dp[i - k + 1][1] ) # 在倒序列表中更新i-k+1位置的值 res = max(res, dp[i][0], dp[i][1]) # 更新答案 return res -
type fenwick []int64 func (f fenwick) update(i int, val int64) { for ; i < len(f); i += i & -i { f[i] = max(f[i], val) } } // [1, i] 中的最大值 func (f fenwick) preMax(i int) (res int64) { for ; i > 0; i &= i - 1 { res = max(res, f[i]) } return } func maxAlternatingSum(nums []int, k int) (ans int64) { // 离散化 nums sorted := slices.Clone(nums) slices.Sort(sorted) sorted = slices.Compact(sorted) n := len(nums) fInc := make([]int64, n) // fInc[i] 表示以 nums[i] 结尾且最后两项递增的交替子序列的最大和 fDec := make([]int64, n) // fDec[i] 表示以 nums[i] 结尾且最后两项递减的交替子序列的最大和 // 值域树状数组 m := len(sorted) inc := make(fenwick, m+1) // 维护 fInc[i] 的最大值 dec := make(fenwick, m+1) // 维护 fDec[i] 的最大值 for i, x := range nums { if i >= k { // 在这个时候才把 fInc[i-k] 和 fDec[i-k] 添加到值域树状数组中,从而保证转移来源的下标 <= i-k j := nums[i-k] inc.update(m-j, fInc[i-k]) // m-j 可以把后缀变成前缀 dec.update(j+1, fDec[i-k]) } j := sort.SearchInts(sorted, x) nums[i] = j // 注意这里修改了 nums[i],这样上面的 nums[i-k] 无需二分 fInc[i] = dec.preMax(j) + int64(x) // 计算满足 nums[i'] < x 的 fDec[i'] 的最大值 fDec[i] = inc.preMax(m-1-j) + int64(x) // 计算满足 nums[i'] > x 的 fInc[i'] 的最大值 ans = max(ans, fInc[i], fDec[i]) // 枚举子序列以 nums[i] 结尾 } return }