前言
每日一题做到有关前缀和的题目,在这里总结下下。 525. 连续数组
题意:
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
示例 1:
输入: nums = [0,1] 输出: 2 说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。
示例 2:
输入: nums = [0,1,0] 输出: 2 说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/co… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
前缀和
要解答这道题,首先得明白什么是前缀和。前缀和本质上是求线段差值,比如一个数组为[....Ai....Aj...],0-i的前缀和为sum[i],0-j的前缀和为sum[j],那么sum[j]-sum[i]等于下标i+1到j的数组值之和。
我的思路
既然是求含有相同数量的 0 和 1 的最长连续子数组,那么要求闭区间[i,j]有相同数量的0和1,那么下标为闭区间[i,j]的数组值之和必为(j-i+1)的一半,因为[i,j]之间有一半的数组值为1。代码如下:
public int findMaxLength(int[] nums) {
int[] sum = new int[nums.length+1];
for (int i=1; i<=nums.length; i++){
sum[i] = nums[i-1]+sum[i-1];
}
int maxLength = 0;
for (int i=1; i<=nums.length; i++){
for (int j=1; j<i; j++){
int temp = sum[i] - sum[j]+nums[j-1];//根据前缀和算出区间[i,j]的数值和
int ans = i-j+1;
if (temp*2 == ans){ //数值和为ans的一半
maxLength = Math.max(maxLength, i-j+1);
}
}
}
return maxLength;
}
提交结果如下:
思路应该是正确的,但因为用了双重for循环导致超时严重,只过了32个测试用例。⊙﹏⊙∥ 优化方法应该从双重循环下手,前缀和题目一般都是用哈希表进行优化,现在还没想好怎么用哈希表优化时间复杂度╯︿╰,等做多以后想到再更新~。
官方题解
官方题解很巧妙,巧妙的地方有两处。
第一处是因为数组元素全部由0,1组成,所以可以将0当作-1,这样题目结果就转化为求:元素和为 0的最长连续数组。
第二处是使用了哈希表优化,优化的方式是这样,假设存在元素和为0的连续数组(i,j],区间为左开右闭,那么必然存在前缀和sum[i]=sum[j],设从下标i+1到j的数组元素和为a,那么sum[i]+a=sum[j],又因为a=0,所以sum[i]必然等于sum[j]。所以,将sum[i]放进哈希表里,当存在sum[j]这个key在哈希表中存在时,那么下标区间(i,j]的元素和必然为0。
代码如下:
public int findMaxLength2(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
int counter = 0, maxLength = 0;
for (int i=0; i<nums.length; i++){
if (nums[i] == 0)
counter --;
if (nums[i] == 1)
counter ++;
if (map.containsKey(counter)){
maxLength = Math.max(maxLength, i-map.get(counter));
}else{
map.put(counter, i);
}
}
return maxLength;
}