题解 | 「力扣」第 325 题:和等于 k 的最长子数组长度(中等)

1,144 阅读1分钟

摘要:理解前缀和与哈希表的应用技巧。如果只使用前缀和,枚举区间长度还需要平方级别的时间复杂度。本题介绍的方法可以通过一次遍历,在遍历的过程中记住一些信息。

325. 和等于 k 的最长子数组长度

给定一个数组 nums 和一个目标值 k,找到和等于 k 的最长子数组长度。如果不存在任意一个符合要求的子数组,则返回 0。

注意

nums 数组的总和是一定在 32 位有符号整数范围之内的。

示例 1

输入: nums = [1, -1, 5, -2, 3], k = 3
输出: 4 
解释: 子数组 [1, -1, 5, -2] 和等于 3,且长度最长。

示例 2:

输入: nums = [-2, -1, 2, 1], k = 1
输出: 2 
解释: 子数组 [-1, 2] 和等于 1,且长度最长。

进阶: 你能使时间复杂度在 O(n) 内完成此题吗?

方法一:通过前缀和求区间和(超时)

public class Solution {

    // 通过前缀和得到区间和:超时

    public int maxSubArrayLen(int[] nums, int k) {
        int len = nums.length;
        int[] preSum = new int[len + 1];
        for (int i = 0; i < len; i++) {
            preSum[i + 1] = preSum[i] + nums[i];
        }

        int res = 0;
        for (int i = 0; i < len; i++) {
            for (int j = i; j < len; j++) {
                // 区间和
                int sum = preSum[j + 1] - preSum[i];
                if (sum == k) {
                    res = Math.max(res, j - i + 1);
                }
            }
        }
        return res;
    }
}

方法二:前缀和与哈希表

关键字:连续子数组,连续很重要。

我们发现,通过前缀和求区间和(空间换时间),还需要枚举所有的区间,时间复杂度 O(N2)O(N^2)

是不是可以一次遍历呢?事实上是可以的,继续「空间换时间」,在求出前缀和的时候,记住以前遍历过的前缀和的信息。根据

前缀和_j + 区间和 k = 前缀和_i

把「区间和 k」从左边移到右边,得到:

前缀和_j = 前缀和_i - 区间和 k

因此我们在遍历到一个新的数 nums[i] 的时候,可以计算出已经遍历到的元素的前缀和 preSum,进而判断 preSum - k 是不是在哈希表中。

然后把「键值对」

key:preSum ,value:i

存入哈希表。是不是和「两数之和」的思想差不多?关键的地方写在代码注释里了。

注意(很重要)

由于要找的是「最长的」 连续 子数组,我们只记录前缀和第 1 次出现的下标。

参考代码

import java.util.HashMap;
import java.util.Map;

public class Solution {

    public int maxSubArrayLen(int[] nums, int k) {
        int len = nums.length;
        Map<Integer, Integer> hashMap = new HashMap<>(len);
        // 计算长度的时候,经常要加上这个哨兵
        hashMap.put(0, -1);

        int res = 0;
        int preSum = 0;
        for (int i = 0; i < len; i++) {
            preSum += nums[i];
            if (hashMap.containsKey(preSum - k)) {
                res = Math.max(res, i - hashMap.get(preSum - k));
            }

            // 注意:由于要找最长的,因此只记录第 1 次出现的下标
            if (!hashMap.containsKey(preSum)) {
                hashMap.put(preSum, i);
            }
        }
        return res;
    }
}

理解下标之间相减等于区间的长度:

image.png