题目
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
示例 1:
输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量0和1的最长连续子数组。
示例 2:
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。
提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1
来源:力扣(LeetCode)
链接:No525 连续数组
分析
略加思索就可以发现这道题和我昨天说的一道题非常相似,都是使用 前缀和+哈希表解决问题
如果对上一道题感兴趣的可以看看这一篇题解
—题目leetcode No.523 连续的子数组和
—题解leetcode No.523 连续的子数组和 java
为什么这两道题相似呢,我们来看:
要求0 和 1两个数字在一段连续子数组中数量相等,意思就是说0的数量等于1的数量,我们转念一想,把0换成-1,那这段子数组之和不就等于0了吗,这样我们就巧妙地将题目变换为前一道题了
下面放代码:
class Solution {
public int findMaxLength(int[] nums) {
int len = nums.length;
int[] sum = new int[len + 1];
for (int i = 1; i <= len; i++) {
if (nums[i - 1] == 0)
sum[i] = sum[i - 1] - 1;
else
sum[i] = sum[i - 1] + 1;
}
Map<Integer, Integer> map = new HashMap<>();
int max = 0;
for (int i = 2; i <= len; i++) {
if (!map.containsKey(sum[i - 2])) map.put(sum[i - 2], i - 2);
if (map.containsKey(sum[i])) {
max = Math.max(max, i - map.get(sum[i]));
}
}
return max;
}
}
因为返回值是子数组的长度,因此我使用了一个map来存储数据,这里有几个要注意的小细节:
- map保存的数据,key是sum元素值,value是sum下标
- 要有重复判断:if (!map.containsKey(sum[i - 2])) …
- 使用max记录子数组最大长度
这是一种朴素的做法,其效率只能说是一般
时间复杂度:O(n)
空间复杂度:O(n)
其实我们可以像官方题解一样,将开辟sum数组的空间省略掉,使用一个count动态的累加nums数组来判断。
代码如下:
class Solution {
public int findMaxLength(int[] nums) {
int maxLength = 0;
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
int counter = 0;
map.put(counter, -1);
int n = nums.length;
for (int i = 0; i < n; i++) {
int num = nums[i];
if (num == 1) {
counter++;
} else {
counter--;
}
if (map.containsKey(counter)) {
int prevIndex = map.get(counter);
maxLength = Math.max(maxLength, i - prevIndex);
} else {
map.put(counter, i);
}
}
return maxLength;
}
}
还有一种比较nb的做法,这是参考了别人的优秀代码来分析的:
class Solution {
public int findMaxLength(int[] nums) {
int[] cnt = new int[2*nums.length + 1];
Arrays.fill(cnt, -1);
int count = 0;
int max = 0;
for(int i = 0; i < nums.length; i++) {
count += (nums[i] == 0? 1 : -1);
if(count == 0) max = i + 1; // 从开头到这是最大的
else if(cnt[count + nums.length] != -1) {
max = Math.max(max, i - cnt[count + nums.length]);
} else {
cnt[count + nums.length] = i;
}
}
return max;
}
}
- 首先,它的总体思路没错,都是使用了前缀和进行解题,但是没有使用哈希表,因此速度快很多。
- 其次,它使用count的思路和官方题解的count用法是一样的,都是由于动态计算nums前i个数总和。
- 最核心的不同,就是它使用的数组代替哈希表,因为count有正有负,因此我们不以0为基准,而是以nums.length为基准,因为count最大能到nums.length - 1,最小也能到 -nums.length + 1,因此在功能上完美代替了哈希表。
if(count == 0) max = i + 1; // 从开头到这是最大的
else if(cnt[count + nums.length] != -1) {
max = Math.max(max, i - cnt[count + nums.length]);
} else {
cnt[count + nums.length] = i;
}
这一段就是最核心的代码,要理解透彻这一部分。
总结
一道题目有很多的优化方法,本题是leetcodeNo523的变种,使用的都是前缀和+哈希表,在优化方案中,可以做如下优化:
- 省去开前缀和数组的空间。
- 可以不使用哈希表。
有任何想补充的欢迎补充,喜欢的看客们点个赞吧~