题目链接:
LeetCode 热题 100 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台
算法思想:
法一(暴力法):
使用两层for循环列举出所有的子数组,使用一个变量sum求得子数组的和并判断是否为k
//暴力解法
public int subarraySum(int[] nums, int k) {
int ret = 0;
for(int i = 0;i < nums.length;i++) {
int sum = 0;
//因为某单个元素也是子数组,所以内层循环j=i
for(int j = i;j < nums.length;j++) {
sum += nums[j];
if(sum == k) {
ret++;
}
}
}
return ret;
}
法二(前缀和+哈希表):
前缀和:前缀和就是从下标为0的元素开始到当前下标之间所有元素的和(假设是一个数组arr的话,下标为i对应的前缀和就等于arr[0]+arr[1]+arr[2]+......+arr[i-1]+arr[i]),举一个列子:
假设给定一个数组为arr:{1,3,5,7,9}
前缀和数组就是sum:{1,4,9,16,25}
所以就可以推导出前缀和的公式:sum[i]=sum[i-1]+arr[i],如果计算给定下标范围(i,j)之间元素的和可以使用arr[i]+arr[i+1]+......+arr[j-1]+arr[j]=sum[j]-sum[i-1]
为了加深大家对前缀和的理解,这样附上一道有关前缀和的算法题: 【模板】前缀和_牛客题霸_牛客网
代码如下:
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int q = scanner.nextInt();
int[] array = new int[n+1];
for(int i = 1;i <= n;i++) {
array[i] = scanner.nextInt();
}
long[] dp = new long[n+1];
for(int i = 1;i <= n;i++) {
dp[i] = dp[i-1] + array[i];
}
while(q > 0) {
int l = scanner.nextInt();
int r = scanner.nextInt();
System.out.println(dp[r] - dp[l-1]);
q--;
}
}
}
有了前缀和的知识来解决整个题目就会变得相对简单一点了,假设某个下标范围内(i,j)之间的和为k,那么根据前缀和可知sum[j]=sum[i-1]+k,经过简单移项之后可以得到sum[i-1]=sum[j]-k,那么整个问题就转换成了以j下标为基准,之前有几个子数组的前缀和为sum[j]-k,k是已知的,sum[j]可以使用相加的方式算出,通过遍历数组,计算每个位置的前缀和,并使用一个哈希表来存储每个前缀和出现的次数,在遍历的过程中,我们检查是否存在sum[j] - k的前缀和,如果存在,说明从某个位置到当前位置的连续子数组的和为k,我们将对应的次数累加到结果中
//法二:前缀和+哈希表
public int subarraySum(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();
map.put(0,1);
int sum = 0;
int ret = 0;
for(int x : nums) {
sum += x;//计算前缀和
ret += map.getOrDefault(sum-k,0);//统计结果
map.put(sum,map.getOrDefault(sum,0)+1);//更新map中的前缀和
}
return ret;
}
注意:这里需要预先在map中存入一次为0的前缀和,目的是为了防止下标从0开始累加到某一位时前缀和刚好时k,这样刚好存在一个子数组和为k,那么就会在map寻找sum-k=0的前缀和出现了几次,如果不提前预存一个的话就会漏掉一种结果。