子数组和问题

我们刷leetcod时总是遇到各种各样的子数组和的问题,这类问题一般都是可以通过构建一个前缀和数组,以O(N^2)的时间复杂度求解,但这并不是最优解。

目录

问题一:和为K的数组

问题二:和等于k的最长/短子数组

问题三:和为k的整数倍的数组


问题一:和为K的数组

问题描述:

给定一整型数组nums和一整数k,找到该数组中和为k的子数组的个数。

input : nums = [1,2,3,-3,-2,-1,0] k = 0

output: 5  分别为[3,-3],  [2,3,-3,-2],   [1,2,3,-3,-2,-1],  [1,2,3,-3,-2,-1,0] , [0]

解法一:

依次以每个元素作为头结点,以其之后元素作为尾结点,判断此时的子数组和是否为k。由于判断该子数组的和的复杂度可以使用前缀和数组由O(N)降到O(1),实现代码如下:

    public int subarraySum(int[] nums, int k) {
        int[] sums = new int[nums.length + 1];
        for(int i = 0; i < nums.length; i++){
            sums[i + 1] = sums[i] + nums[i];
        }
        int result = 0;
        for(int start = 1; start < sums.length; start++){
            for(int end = start; end < sums.length; end++){
                if(sums[end] - sums[start - 1] == k){
                    result++;
                }
            }
        }
        return result;
    }

tip:计算前缀和数组时,可以多给一位,sums[i] 为从开头到i-1位置之和,这样做的好处是后面在处理以第一个节点开头的子数组时不用特殊处理。

解法二:

对于前缀和数组还可以进行如下优化,使用一HashMap结构,key为遍历过程中的前缀和,value为当前前缀和出现的次数,因此遍历过程中便可以在map中寻找(sum - k)的值是否出现过,若存在则证明以当前结点结尾有map.get(sum - k)个子数组。时间复杂度降为O(N)。

    public int subarraySum(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();// k = sum ,value = 次数
        int sum = 0;
        int result = 0;
        map.put(0, 1);
        for(int num : nums){
            sum += num;
            if(map.containsKey(sum - k)){
                result += map.get(sum - k);
            }
            map.put(sum, map.containsKey(sum) ? map.get(sum) + 1 : 1);
        }
        return result;
    }

问题二:和等于k的最长/短子数组

问题描述:

给定一整型数组nums及一整数k,找到nums中最长/短的和等于k的子数组,不存在返回-1。本题求解的是最长的子数组,最短的同理。

input : nums = [1,2,3,-3,-2,-1,0] k = 0

output:7 而不能是这些[3,-3],  [2,3,-3,-2],   [1,2,3,-3,-2,-1],  [0]

解决方案:

如之前介绍,该问题依然可以使用前缀和数组,将所有满足条件的子数组都求解出来,返回其中最大的。如此还是O(N^2)的时间复杂度,该解法就不加赘述了。

遍历过程找到当前结点为尾结点的满足条件的最长子数组。

该问题依然可以使用一HashMap来优化,key存储的是遍历过程中的前缀和,不同的是value存储的是该前缀和最早出现的位置。由于找的是最长的,由于后面再次出现该sum时,得到满足条件的子数组总是比前面的更短。实现代码如下:

    public static int maxSubArraySum(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, -1);
        int sum = 0;
        int result = -1;
        for(int i = 0; i < nums.length; i++) {
            sum += nums[i];
            if(map.containsKey(sum - k)) {
                result = Math.max(result, 
                    i - map.get(sum - k)); // 从map.get(sum - k) + 1 到 i为子数组
            }else {
                map.put(sum, i);
            }
        }
        return result;
    }

问题三:和为k的整数倍的数组

题目描述:

给定一非负的整型数组,以及一整数k。判断该数组中是否存在一子数组使得组数组的和等于k的整数倍。要求子数组长度大于2。

input : nums = [23,2,4,6,7], k = 6

output : true 

解决方案:

该问题的一般解法还是可以通过构建前缀和数组遍历出所有的子数组,以O(N^2)的时间复杂度求解,不过需要注意的是,该问题中要求k的整数倍,因此不可避免的需要对k取余,因此对于k=0的情况需要特殊处理.。

想要使用HashMap优化,需要确定key的含义。

sum1 % k == sum2 % k   =>   (sum2 - sum1) % k == 0

不妨设 sum1 = k * m + p, sum2 = k * n + p,则(sum2 - sum1) % k =(n - m) * k % k ==0

因此可以使用当前前缀和对k取余的余数作为key,遍历过程,若出现当前前缀和的余数之前出现过,则说明中间这一段对k取余等于0。由于题目还要求子数组的长度至少为2,因此将当前的下标作为value。

此外上述所提的下标应该为该余数第一次出现的下标。例如案例[ 1 7 7] k = 7就可以说明问题。实现代码如下:

class Solution {
    public boolean checkSubarraySum(int[] nums, int k) {
        if(k == 0){
            return checkZeroOption(nums);
        }
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, -1);
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
            if(map.containsKey(sum % k)){
                if(i - map.get(sum % k) > 1){
                    return true;
                }
            }else{
                map.put(sum % k, i);
            }
        }
        return false;
    }
    // 处理 k=0情况
    public boolean checkZeroOption(int[] nums){
        for(int i = 1; i < nums.length; i++){
            if(nums[i - 1] == 0 && nums[i] == 0){
                return true;
            }
        }
        return false;
    }
}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最大数组问题是一个经典的算法问题,可以使用C语言来实现。 首先,我们可以定义一个函数来计算最大数组的和,传入一个整型数组数组的大小。如下所示: ```c int maxSubarraySum(int arr[], int size); ``` 然后,在函数内部,我们可以使用动态规划的思想来解决这个问题。我们可以创建一个变量`maxSum`和`currentSum`,分别表示当前最大数组的和和当前累加的和。初始化`maxSum`为数组第一个元素,`currentSum`为0。 接下来,我们使用一个循环遍历整个数组。在循环内部,我们首先将`currentSum`加上当前元素的值,然后判断`currentSum`是否大于`maxSum`,如果是,则将`maxSum`更新为`currentSum`。如果`currentSum`小于0,则将`currentSum`重置为0,相当于从当前位置重新开始累加。 最后,循环结束后,我们可以得到最大的数组和`maxSum`。返回`maxSum`即可。 下面是完整的代码实现: ```c #include <stdio.h> int maxSubarraySum(int arr[], int size) { int maxSum = arr[0]; int currentSum = 0; for (int i = 0; i < size; i++) { currentSum += arr[i]; if (currentSum > maxSum) { maxSum = currentSum; } if (currentSum < 0) { currentSum = 0; } } return maxSum; } int main() { int arr[] = { -2, 1, -3, 4, -1, 2, 1, -5, 4 }; int size = sizeof(arr) / sizeof(arr[0]); printf("最大数组的和为:%d\n", maxSubarraySum(arr, size)); return 0; } ``` 运行这段代码,输出结果为:最大数组的和为:6。这个结果对应的最大数组为:[4, -1, 2, 1]。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值