[M双指针] lc5643. 将数组分成三个子数组的方案数(双指针+前缀和+周赛222_3)

文章目录

    • 1. 题目来源
    • 2. 题目解析

1. 题目来源

链接:5643. 将数组分成三个子数组的方案数

2. 题目解析

十分经典的双指针应用,算是比较难的双指针应用了,边界情况较多。其实完全也可以二分来做,但是双指针可以直接达到 O ( n ) O(n) O(n),是最优的做法,且具有很强的可推广性。
[M双指针] lc5643. 将数组分成三个子数组的方案数(双指针+前缀和+周赛222_3)_第1张图片

思路:

  • 预处理前缀和,由于元素均非负,则前缀和数组具有单调性。
  • 枚举 i,在 1~i-1 中枚举合法的 x 的个数即可找到方案数。
  • 这样会将整个数组分为三段,即:1~x-1x~i-1i~n。在此由于说的是前缀和数组,则下标从 1 开始的。
  • 可发现由于 i 的向右移动,x 的左边界 j 和右边界 k 只会向右移动。具体证明可参考上面的笔记。
  • 故双指针的单调性质就找到了。

细节:

  • i 要从 3 开始,这三段都必须有元素,不可为空。

  • j 指针的时候是看当前不行了,再去看下一个行不行,尽量少的向右走。

  • k 指针的时候是看下一个行不行,如果下一个 k+1 满足了,则 k 就往右边走一个到 k+1 的位置,再看下一个位置行不行,尽量多的向右走。

  • 关于前缀和求区间和的方式注意下标的使用。注意这三段区间的划分配合前缀和的使用。

  • 时间复杂度 O ( n ) O(n) O(n)

  • 空间复杂度 O ( n ) O(n) O(n)

代码:

class Solution {
     
public:
    int waysToSplit(vector<int>& nums) {
     
        int n = nums.size(), mod = 1e9+7;
        vector<int> s(n + 1);
        int res =0;
        for (int i = 1; i <= n; i ++ ) s[i] = s[i - 1] + nums[i - 1];
        for (int i = 3, j = 2, k = 2; i <= n; i ++ ) {
     
            while (s[n] - s[i - 1] < s[i - 1] - s[j - 1]) j ++ ;    // 不满足往后走
            while (k + 1 < i && s[i - 1] - s[k] >= s[k]) k ++ ;     // 试探下一个k是否满足,k-1和+1抵消了
            if (j <= k && s[n] - s[i - 1] >= s[i - 1] - s[j - 1] && s[i - 1] - s[k - 1] >= s[k - 1])
                res = (res + k - j + 1) % mod;
        }
        return res;
    }
};

你可能感兴趣的:(LeetCode,LeetCode)