左神算法:未排序正数数组中累加和为给定值的最长子数组长度(Java版)

本题来自左神《程序员代码面试指南》“未排序正数数组中累加和为给定值的最长子数组长度”题目。

题目

牛客OJ:未排序数组中累加和为给定值的最长子数组长度

在这里插入图片描述

题解

本文提供的方法可以做到时间复杂度为O(N)、额外空间复杂度为O(N)。

为了说明解法,先定义s 的概念,s(i)代表子数组arr[0…i]所有元素的累加和。那么子数组arr[j…i(] 0≤j≤i<arr.length)的累加和为s(i)-s(j-1),因为根据定义,s(i)=arr[0…i]的累加和等于arr[0…j-1]的累加和与arr[j…i]的累加和相加,又有arr[0…j-1]的累加和为s(j-1)。所以,arr[j…i]的累加和为s(i)-s(j-1),这个结论是求解这道题的核心。

整个过程只遍历一次arr,具体过程为:

  1. 设置变量sum=0,表示从0 位置开始一直加到i 位置所有元素的和。设置变量len=0,表示累加和为k 的最长子数组长度。设置哈希表map,其中,key 表示从arr 最左边开始累加的过程中出现过的sum 值,对应的value 值则表示sum 值最早出现的位置。
  2. 从左到右开始遍历,遍历的当前元素为arr[i]。
    1. 令sum=sum+arr[i],即之前所有元素的累加和s(i),在map 中查看是否存在sum-k。
      • 如果 sum-k 存在,从map 中取出sum-k 对应的value 值,记为j,j 代表从左到右不断累加的过程中第一次加出sum-k 这个累加和的位置。根据之前得出的结论,arr[j+1…i]的累加和为s(i)-s(j),此时s(i)=sum,又有s(j)=sum-k,所以arr[j+1…i]的累加和为k。同时因为map 中只记录每一个累加和最早出现的位置,所以此时的arr[j+1…i]是在必须以arr[i]结尾的所有子数组中,最长的累加和为k 的子数组,如果该子数组的长度大于len,就更新len。
      • 如果 sum-k 不存在,说明在必须以arr[i]结尾的情况下没有累加和为k 的子数组。
    2. 检查当前的sum(即s(i))是否在map 中。如果不存在,说明此时的sum 值是第一次出现的,就把记录(sum,i)加入到map 中。如果sum 存在,说明之前已经出现过sum,map 只记录一个累加和最早出现的位置,所以此时什么记录也不加。
  3. 继续遍历下一个元素,直到所有的元素遍历完。

大体过程如上,但还有一个很重要的问题需要处理。根据arr[j+1…i]的累加和为s(i)-(j),所以,如果从0 位置开始累加,会导致j+1≥1。也就是说,所有从0 位置开始的子数组都没有考虑过。所以,应该从-1 位置开始累加,也就是在遍历之前先把(0,-1)这个记录放进map,这个记录的意义是如果任何一个数都不加时,累加和为0。这样,从0 位置开始的子数组就被我们考虑到了。

具体过程请参看如下代码中的 maxLength 方法。

package chapter_8_arrayandmatrix;

import java.util.HashMap;

public class Problem_11_LongestSumSubArrayLength {

	public static int maxLength(int[] arr, int k) {
		if (arr == null || arr.length == 0) {
			return 0;
		}
		HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(); // 含义:<从0位置元素的累加和, 满足此累加和的最小下标>
		map.put(0, -1); // 注意不要写成 (0, 0)
		int len = 0;
		int sum = 0;
		for (int i = 0; i < arr.length; i++) {
			sum += arr[i];
			if (map.containsKey(sum - k)) {
				len = Math.max(i - map.get(sum - k), len);
			}
			if (!map.containsKey(sum)) {
				map.put(sum, i);
			}
		}
		return len;
	}

	public static int[] generateArray(int size) {
		int[] result = new int[size];
		for (int i = 0; i != size; i++) {
			result[i] = (int) (Math.random() * 11) - 5;
		}
		return result;
	}

	public static void printArray(int[] arr) {
		for (int i = 0; i != arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		int[] arr = generateArray(20);
		printArray(arr);
		System.out.println(maxLength(arr, 10));
	}
}

附:ACM赛制牛客题解(需手动输入输出),即我的解法,已通过全部测试用例

package test;

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int k = scanner.nextInt();
        int[] arr = new int[n];
        for (int i = 0; i < n; i++) {
            arr[i] = scanner.nextInt();
        }
        // solution
        Map<Integer, Integer> map = new HashMap<>(); // <从0位置元素的累加和, 满足此累加和的最小下标>
        map.put(0, -1); // 注意不要写成 (0, 0)
        int maxLen = 0, curSum = 0;
        for (int i = 0; i < n; i++) {
            curSum += arr[i];
            if (!map.containsKey(curSum)) {
                map.put(curSum, i);
            }
            int gap = curSum - k;
            if (map.containsKey(gap)) {
                maxLen = Math.max(i - map.get(gap), maxLen);
            }
        }
        System.out.println(maxLen);
    }
}
/*
【示例1】
11 0
1 -2 1 1 1 -1 1 -1 1 -1 2
答案:9
解析:1 [-2 1 1 1 -1 1 -1 1 -1] 2

【示例2】
20 0
-1 1 0 4 5 -2 2 -2 2 -2 2 -4 4 5 -2 -2 -1 1 1 1
答案:12
解析:-1 1 0 4 5 [-2 2 -2 2 -2 2 -4 4 5 -2 -2 -1] 1 1 1
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值