So, I have an array containing only 0\'s and 1\'s. I have to find out the largest subarray containing equal number of 0\'s and 1\'s. One can be a naive approach have complex
public int equalNumber(int arr[]){
int sum[] = new int[arr.length];
sum[0] = arr[0] == 0? -1 : 1;
for(int i=1; i < sum.length; i++){
sum[i] = sum[i-1] + (arr[i] == 0? -1 : 1);
}
Map<Integer,Integer> pos = new HashMap<Integer,Integer>();
int maxLen = 0;
int i = 0;
for(int s : sum){
if(s == 0){
maxLen = Math.max(maxLen, i+1);
}
if(pos.containsKey(s)){
maxLen = Math.max(maxLen, i-pos.get(s));
}else{
pos.put(s, i);
}
i++;
}
return maxLen;
}
First convert your zeros to -1. Then you are looking for a maximum subarray of zero sum. An algorithm for this is described here
Algorithm
We make use of a HashMap map to store the entries in the form of (index,count). We make an entry for a count in the map whenever the count is encountered first, and later on use the corresponding index to find the length of the largest subarray with equal no. of zeros and ones when the same count is encountered again.
public class Solution {
public int findMaxLength(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, -1);
int maxlen = 0, count = 0;
for (int i = 0; i < nums.length; i++) {
count = count + (nums[i] == 1 ? 1 : -1);
if (map.containsKey(count)) {
maxlen = Math.max(maxlen, i - map.get(count));
} else {
map.put(count, i);
}
}
return maxlen;
}
}
Time complexity : O(n). The entire array is traversed only once.
Space complexity : O(n). Maximum size of the HashMap map will be n, if all the elements are either 1 or 0.
This is closely related to @templatetypedef's answer.
However, rather than see it as a the difference between number of ones and zeros. I'm seeing at as a tracker
of sorts that is incremented when a one is seen and decremented when a zero is seen .
Here's a tested solution.
/**
* given an array of 0's and 1's return the length of the maximal
* subarray that has only 0's and 1's
* O(n) solution inspired by https://stackoverflow.com/a/31201586/919858
*
* in 0 0 1 1
* aux 0 -1 -2 -1 0
* @param in
* @return
*/
static int lenMaxSubArray(int[] in) {
int maxLen = -1;
int[] oneCount = new int[in.length + 1];
oneCount[0] = 0;
for (int i = 0; i < in.length; i++) {
if (in[i] == 1) {
oneCount[i + 1] = oneCount[i] + 1;
} else {
oneCount[i + 1] = oneCount[i] - 1;
}
}
Map<Integer, List<Integer>> map = new HashMap<>();
for (int i = 0; i < oneCount.length; i++) {
List<Integer> list = map.getOrDefault(oneCount[i], new ArrayList<>());
list.add(i);
map.put(oneCount[i], list);
}
for (int i = 0; i < oneCount.length; i++) {
List<Integer> list = map.get(oneCount[i]);
if (list != null) {
int start = list.get(0);
int end = list.get(list.size() - 1);
maxLen = Math.max(maxLen, end - start);
}
}
return maxLen;
}
Here's an O(n)-time, O(n)-space algorithm. I'm not sure it's optimal, but it beats quadratic time.
The basic idea is the following. Suppose that you scan from the left of the array to the right recording, at each step, the difference between the number of 1s and the number of 0s. If you write these values out at each step, you'll get something like this:
1, 0, 1, 0, 0, 0, 0
0, 1, 0, 1, 0, -1, -2, -3
If you have a sub array with the same number of 0s and 1s, then the net difference of 0s and 1s at the start of the subarray will equal the net number after the subarray. Therefore, this problem can be reframed as trying to find two equal values in the auxiliary array that are equal and as far apart as possible.
The good news is that every entry in the array is between -n and +n, so you can make a 2n+1 element table and store in it the indices of the first and last time each number appears. From there, it's easy to find the longest range. Overall, this needs O(n) space and everything can be populated and searched in O(n) time.
Hope this helps!
JS implementation of the @templatetypedef algorithm with a modification of using a map to help find the max length
function findMaxLen(a) {
// secondary array, initialized with the first element as 0
let b = [0]
let sum = 0
// populate the secondary array and update the sum as per the algorithm
for (let i = 0; i < a.length; i++) {
b[i + 1] = a[i] == 0 ? sum - 1 : sum + 1
sum = b[i + 1]
}
// map of sum vs the first index of the secondary array with the sum value
let map = new Map()
let maxLen = 0
for (let i = 0; i < b.length; i++) {
if (map.has(b[i]))
maxLen = Math.max(maxLen, i - map.get(b[i]))
else
map.set(b[i], i)
}
return maxLen
}