📜  绝对和最接近 K 的子数组(1)

📅  最后修改于: 2023-12-03 15:27:36.418000             🧑  作者: Mango

题目描述

给定一个整数数组和一个目标值 K,找到该数组中和最接近 K 的连续子数组。如果存在多个答案,返回任意一个即可。

示例 1:

输入: nums = [-1,2,1,-4], k = 1 输出: [0,2] 解释: 与目标值最接近的连续子数组是 [-1,2,1],其和为 2 (与 K 的差为 1),下标起始位置为 0,结束位置为 2。

解法

这道题最容易想到的暴力解法是枚举所有子数组,然后计算它们的和是否接近 K,时间复杂度为 O(n^3)。这种暴力枚举的复杂度很高,不可取。

我们可以使用前缀和进行优化,时间复杂度可以降到 O(n^2)。具体做法是遍历数组,对于每个位置 i,记录它前面所有元素的和 preSum,然后枚举以 i 结尾的连续子数组,计算它们的和 sum,并更新离 K 最近的值即可。

代码如下:

class Solution {
public:
    vector<int> subarraySumClosest(vector<int>& nums, int k) {
        vector<pair<int, int>> preSum;
        preSum.push_back({0, -1});
        int sum = 0, n = nums.size(), diff = INT_MAX;
        for (int i = 0; i < n; i++) {
            sum += nums[i];
            auto it = lower_bound(preSum.begin(), preSum.end(), make_pair(sum - k, 0));
            for (int j = it - preSum.begin(); j < preSum.size(); j++) {
                int s = sum - preSum[j].first;
                if (abs(s - k) < diff) {
                    diff = abs(s - k);
                    res = {preSum[j].second + 1, i};
                }
            }
            preSum.insert(it, {sum, i});
        }
        return res;
    }
private:
    vector<int> res{0, 0};
};
思路

在做这道题目之前,需要了解一下前缀和的概念。前缀和就是数组前缀的和,也就是说 sum[i] 表示从 0 到 i 的元素和,它的值可以通过以下公式计算:

sum[i] = nums[0] + nums[1] + ... + nums[i]

那么,如果要计算一个子数组的和,可以用以下公式计算:

sum[i, j] = sum[j] - sum[i - 1]

其中,i 和 j 分别表示子数组的左右端点。

使用前缀和的好处是可以把子数组求和的时间复杂度降为 O(1)。

对于这道题目,我们可以先计算出数组的前缀和,然后枚举每个以 i 为右端点的连续子数组,计算它们的和,最后找到和最接近 K 的子数组即可。

具体做法如下:

  1. 使用一个 vector<pair<int, int>> preSum 记录数组的前缀和,其中 pair<int, int> 表示前缀和和前缀和对应的下标;
  2. 初始化 preSum 为 {0, -1},表示前缀和为 0 的下标为 -1,便于计算整个数组的和;
  3. 使用指针 it 指向 preSum 中第一个大于等于 sum - k 的元素,然后枚举此元素到数组结尾的所有 preSum,计算它们的差值与 K 的差值,找到最小的差值即可;
  4. 从 preSum.begin() 到 it 的元素的前缀和都小于等于 sum - k,因此不需要枚举这部分;
  5. 记录最小的差值和所对应的子数组起始和终止下标即可。
复杂度分析
  • 时间复杂度:O(n^2)。遍历数组需要 O(n) 的时间,每次计算以 i 为结尾的连续子数组的和需要 O(n) 的时间,因此总时间复杂度为 O(n^2);
  • 空间复杂度:O(n)。需要一个 vector<pair<int, int>> 来记录前缀和。
参考文献