JavaScript算法03-和相同的二元子数组

一、问题描述

给定一个二元数据nums,和一个整数goal,请你统计并返回有多少和为goal的非空子数组。其中子数组是数组中的一段连续部分。
示例1:
输入:nums = [1,0,1,0,1], goal = 2
输出:4
解释:有 4 个满足题目要求的子数组:[1,0,1]、[1,0,1,0]、[0,1,0,1]、[1,0,1]
提示:

  • 1 <= nums.length <= 3 * 104
  • nums[i] 不是 0 就是 1
  • 0 <= goal <= nums.length

二、解题方法

2.1 暴力双循环

看到这个题最先想到的也是最直接最简单的就是双for暴力破解了。解题思路如下:

  • 下标i从零开始遍历数组,用一个新的变量num记录当前下标的数组值,若num==goal,则计数器加一;
  • 否则,创建新的下标j,令num+=nums[j],若num==goal,计数器加一,大于goal则跳出循环。这是因为题目给出了限定条件,数组中元素都为0,1。且子数组需要是连续的数组,所以遇到不相等的情况可以直接跳出循环。
  • i++,从下一个元素开始重新执行下一个内部循环。
    代码如下:
/**
 * @param {number[]} nums
 * @param {number} goal
 * @return {number}
 */
var numSubarraysWithSum = function(nums, goal) {
	let num=0,count=0;
	for(let i =0;i<nums.length;i++){
		num=nums[i];
		if(num==goal){
			count++;
		}
		for(let j=i+1;j<nums.length;j++){
			num+=nums[j];
			if(num==goal){count++;}
			else if(num>goal) {break;}
		}
	}
	return count;
}

2.1的双for循环在时间复杂度为O(n^2)上有点不尽人意,所以这里给出两个官方给的解法。

2.2 哈希表+前缀和

具体思路如下:
**前缀和:**假设preSum[i+1]为数组前i项的和,则nums[i]到nums[j]的连续序列和sum[i,j]为:preSum[j+1]-preSum[i];
**哈希表:**所求解子序列之和goal可通过前缀和获得:goal=sum[i,j]=preSum[j+1]-preSum[i]。数组中任意两个数之和等于给定目标数,思路可参考两数之和,使用哈希表,时间复杂度为O(n)。
计数:对于每个preSum[j+i],与其关联的解个数等于preSum[i]的个数,计数方式为:count+=count(preSum[i])。其中,i<j,count(preSum[i])为preSum[i]在前缀和数组前j项中出现的个数。

var numSubarraysWithSum = function (nums, goal) {
			const preSum=[0];
			for(const num of nums)
				//将前i项的和压入栈
				preSum.push(preSum[preSum.lenght-1]+num);
				let count=0;
				//这里添加了(0,1)这一项是因为之后要是满足了sum-goal=0,就说明原数组中存在sum=goal的这个子数组,需要计数加一。所以手动写入了。
				const preSumCount=new Map().set(0,1)
 				for(let i=0;i<nums.length;++i){
 					// goal = preSum[i + 1] - preSum[prev] -> preSum[prev] = preSum[i + 1] - goal
					count+=(preSumCount.get(preSum[i+1]-goal)||0);
					preSumCount.set(preSum[i+1],(preSumCount.get(preSum[i+1])||0)+1);
					//key为和的值,value为这样的和的子数组个数
				}
			return count;
    };

简化代码:

var numSubarraysWithSum =function(nums,goal){
	let sum=0;
	const sumcount=new Map();
	let count=0;
	for(const num of nums){
		sumcount.set(sum,(sumcount.get(sum)||0)+1);
		sum+=num;
		count+=sumcount.get(sum-goal)||0;
	}
	return count;
}

2.3滑动窗口

具体思路:这个思路其实是在上面的思路转化过来的,对于方法2.2中的每一个j,满足sum[j]-sum[i]=goal的i总是落在一个连续的区间中,i值取区间中每一个数都满足条件(这里是因为数组中的元素均为0,1)。并且伴随j右移,其对应的区间的左右端点也将右移,可以使用滑动窗口解决本题。
具体地,我们令滑动窗口右边界为 right,使用两个左边界 left1和 left2表示左区间 [left1,left2),此时有 left2−left1​ 个区间满足条件。
在实际代码中,我们需要注意 left1≤left2≤right+1,因此需要在代码中限制 left1和 left2不超出范围。

var numSubarraysWithSum =function(nums,goal){
	const n=nums.length;
	let left1=0,left2=0,right=0;
	let sum1=0,sum2=0;
	let ret=0;
	while(right<n){
		sum1+=nums[right];
		while(left1<=right&&sum1>goal){
		sum1-=nums[left1];
		left1++;
		}
		sum2+=nums[right];
		while(left2<=rigth&&sum2>=goal){ //这一句sum2>=goal就很精妙
			sum2-=nums[left2];
			left2++;
		}
		ret+=left2-left1;
		right++;
	}
	return ret;
};

这个思路我和同门还讨论了一会,实现的时候写了一大串复杂的代码,从思考到转变为代码再到简化代码,真的道阻且长。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值