跳转至

4063. 至多一次取反能被 K 整除的最长子数组 I

难度中等

题目描述

给你一个整数数组 nums 和一个整数 k。

如果一个子数组的和能够被 k 整除,或者在 将该子数组中的一个元素取反 后能使和被 k 整除,则称该子数组是 有效的 。

将一个元素取反意味着将其值 x 替换为 -x。

返回 最长有效子数组的长度 。如果不存在有效的子数组,则返回 0。

子数组 是数组中一个连续且非空的元素序列。

 

示例 1:

输入: nums = [4,1,2], k = 3

输出: 3

解释:

  • 整个数组的和为 7,且 7 % 3 = 1,因此它不能被 k = 3 整除。
  • 将 nums[2] = 2 取反,其和变为 4 + 1 − 2 = 3,能够被 k 整除。
  • 因此,整个数组是一个有效子数组,其长度为 3。

示例 2:

输入: nums = [5,3,4], k = 7

输出: 2

解释:

  • 整个数组的和为 12,且将其中任何一个元素取反都无法使其和被 7 整除。
  • 然而,子数组 [3, 4] 的和为 7,无需任何取反操作即可被 k = 7 整除。
  • 因此,最长有效子数组的长度为 2。

示例 3:

输入: nums = [2,2,5], k = 6

输出: 2

解释:

  • 整个数组的和为 9,且将其中任何一个元素取反都无法使其和被 6 整除。
  • 子数组 [2, 2] 的和为 4。将其中任意一个元素取反会使其变为 [-2, 2] 或 [2, -2],这两者的和皆为 0。
  • 因此,最长有效子数组的长度为 2。

 

提示:

  • 1 <= nums.length <= 1000
  • -105 <= nums[i] <= 105
  • 1 <= k <= 105

解法

方法一:枚举取反位置

思考

子数组和能被 \(k\) 整除,等价于两端前缀和模 \(k\) 相等。直接枚举每个子数组,再尝试取反其中的每个元素,大约是 \(O(n^3)\)。 \(n\le 1000\),还需要再降一阶。

把元素 \(x\) 取反,所有包含它的子数组和都减少 \(2x\),不包含它的子数组和不变。取反位置只有 \(n\) 种,再加上完全不取反,每种情形都只要找最长的、和能被 \(k\) 整除的子数组。

前缀和模 \(k\) 的每个余数只保留第一次出现的下标。右端点再次遇到同一余数时,左端点越早,子数组越长。不含被取反元素的子数组在原数组上已经统计过。

把 \(\textit{nums}[i]\) 取反后,包含下标 \(i\) 的子数组和减少 \(2\times\textit{nums}[i]\),不含 \(i\) 的子数组和不变。对原数组,以及依次把每一个位置取反后的数组,分别求「和能被 \(k\) 整除的最长子数组」,答案是这些长度的最大值。

扫描时维护前缀和模 \(k\) 的余数,余数统一落到 \([0, k)\)。每个余数只记录第一次出现的下标,余数 \(0\) 初始对应下标 \(-1\)。扫到下标 \(i\) 时,若当前余数曾经出现在下标 \(j\),则 \(\textit{nums}[j+1..i]\) 的和能被 \(k\) 整除,长度为 \(i-j\)。Python、Java、Go 和 TypeScript 用哈希表保存这些下标。C++ 用长度为 \(k\) 的数组,下标就是余数,扫描时用参数标出被取反的位置。

时间复杂度 \(O(n^2)\),空间复杂度 \(O(n)\)。C++ 每次重置这个数组,时间复杂度 \(O(n(n+k))\),空间复杂度 \(O(k)\)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Solution:
    def longestSubarray(self, nums: list[int], k: int) -> int:
        def f(nums: list[int], k: int) -> int:
            d = {0: -1}
            s = res = 0
            for i, x in enumerate(nums):
                s = (s + x) % k
                if s in d:
                    res = max(res, i - d[s])
                else:
                    d[s] = i
            return res

        ans = f(nums, k)
        for i, x in enumerate(nums):
            nums[i] = -x
            ans = max(ans, f(nums, k))
            nums[i] = x
        return ans
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
    public int longestSubarray(int[] nums, int k) {
        int ans = f(nums, k);
        for (int i = 0; i < nums.length; ++i) {
            nums[i] = -nums[i];
            ans = Math.max(ans, f(nums, k));
            nums[i] = -nums[i];
        }
        return ans;
    }

    private int f(int[] nums, int k) {
        Map<Integer, Integer> d = new HashMap<>();
        d.put(0, -1);
        int s = 0, res = 0;
        for (int i = 0; i < nums.length; ++i) {
            s = ((s + nums[i]) % k + k) % k;
            if (d.containsKey(s)) {
                res = Math.max(res, i - d.get(s));
            } else {
                d.put(s, i);
            }
        }
        return res;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public:
    int longestSubarray(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int> d(k, -2);

        auto f = [&](int skip) {
            fill(d.begin(), d.end(), -2);
            d[0] = -1;
            int s = 0, res = 0;
            for (int i = 0; i < n; ++i) {
                int x = i == skip ? -nums[i] : nums[i];
                s = (s + x) % k;
                if (s < 0) {
                    s += k;
                }
                if (d[s] != -2) {
                    res = max(res, i - d[s]);
                } else {
                    d[s] = i;
                }
            }
            return res;
        };

        int ans = f(-1);
        for (int i = 0; i < n; ++i) {
            ans = max(ans, f(i));
        }
        return ans;
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func longestSubarray(nums []int, k int) int {
    f := func(nums []int) int {
        d := map[int]int{0: -1}
        s, res := 0, 0
        for i, x := range nums {
            s = (s + x) % k
            if s < 0 {
                s += k
            }
            if j, ok := d[s]; ok {
                res = max(res, i-j)
            } else {
                d[s] = i
            }
        }
        return res
    }

    ans := f(nums)
    for i, x := range nums {
        nums[i] = -x
        ans = max(ans, f(nums))
        nums[i] = x
    }
    return ans
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function longestSubarray(nums: number[], k: number): number {
    const f = (nums: number[]): number => {
        const d = new Map<number, number>([[0, -1]]);
        let s = 0,
            res = 0;
        for (let i = 0; i < nums.length; ++i) {
            s = (s + nums[i]) % k;
            if (s < 0) {
                s += k;
            }
            if (d.has(s)) {
                res = Math.max(res, i - d.get(s)!);
            } else {
                d.set(s, i);
            }
        }
        return res;
    };

    let ans = f(nums);
    for (let i = 0; i < nums.length; ++i) {
        nums[i] = -nums[i];
        ans = Math.max(ans, f(nums));
        nums[i] = -nums[i];
    }
    return ans;
}

评论