Problem
Given an array with non negative numbers, divide the array into two parts such that the average of both the parts is equal. Return both parts (If exist). If there is no solution. return an empty list.
Examples
Example 1:
Input: [1 7 15 29 11 9]
Output:
[9 15] [1 7 11 29]
The average of part is (15 + 9) / 2 = 12,
average of second part elements is (1 + 7 + 11 + 29) / 4 = 12
NOTE 1: If a solution exists, you should return a list of exactly 2 lists of integers A and B which follow the following condition:
- numElements in A <= numElements in B
- If numElements in A = numElements in B, then A is lexicographically smaller than B
NOTE 2: If multiple solutions exist, return the solution where length(A) is minimum. If there is still a tie, return the one where A is lexicographically smallest.
NOTE 3: Array will contain only non negative numbers.
Solution
Code:
public static void main(String[] args) {
ArrayList<Integer> A = new ArrayList<Integer>(Arrays.asList(1, 7, 15, 29, 11, 9));
ArrayList<ArrayList<Integer>> result = avgSet(A);
System.out.println(result);
}
public static ArrayList<ArrayList<Integer>> avgSet(ArrayList<Integer> A) {
Collections.sort( A );
final ArrayList<ArrayList<Integer>> res = new ArrayList<>();
final ArrayList<Integer> path = new ArrayList<>();
int N = A.size();
int totalSum = 0;
for( int a : A ) {
totalSum += a;
}
final boolean T[][][] = new boolean[N][totalSum+1][N];
for( int i =0; i < N; i++) {
for( int j =0; j <= totalSum; j++) {
for( int k =0; k < N; k++) {
T[i][j][k] = true;
}
}
}
for( int i = 1; i < N; i++ ) {
if((totalSum*i)%N != 0) {
continue;
}
final int sumOfSet_i = (totalSum*i)/N;
if( isPossible( 0, sumOfSet_i, i, A, T, path )) {
int i1=0;
int i2=0;
final ArrayList<Integer> res2 = new ArrayList<>();
while( i2 < path.size() || i1 < N ) {
if( i2 < path.size() && path.get( i2 ) == A.get( i1 )) {
i2++;
i1++;
continue;
}
res2.add( A.get( i1++ ) );
}
if( path.size() > res2.size()) {
res.add( res2 );
res.add( path );
}
else {
res.add( path );
res.add( res2 );
}
return res;
}
}
return res;
}
private static boolean isPossible(int index, int currentSum, int len, ArrayList<Integer> A, final boolean [][][] T, final ArrayList<Integer> path) {
if( len == 0) {
return currentSum == 0;
}
if( index >= A.size()) {
return false;
}
if( !T[index][currentSum][len]) {
return false;
}
if( currentSum >= A.get( index )) {
path.add( A.get( index ) );
if( isPossible(index+1, currentSum-A.get( index ), len-1, A, T, path)) {
return true;
}
path.remove( path.size()-1 );
}
if( isPossible( index+1, currentSum, len, A, T, path )) {
return true;
}
T[index][currentSum][len] = false;
return false;
}
// Editorial
private ArrayList<Integer> array;
private boolean[][][] dp;
private ArrayList<Integer> indexSetA;
public ArrayList<ArrayList<Integer>> avgSet_Editorial(ArrayList<Integer> array) {
if (array == null || array.isEmpty()) {
return new ArrayList<>();
}
this.array = array;
Collections.sort(this.array);
int sum = 0;
for (int element : array) {
sum += element;
}
int n = array.size();
// memoization table by three states : (index, sum of setA, size of setA)
this.dp = new boolean[n][1 + sum][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < 1 + sum; j++) {
for (int k = 0; k < n; k++) {
this.dp[i][j][k] = true;
}
}
}
this.indexSetA = new ArrayList<>();
// iterate for third state : size of setA which varies from 1 to n-1
for (int sizeA = 1; sizeA < n; sizeA++) {
if ((sum * sizeA) % n != 0) {
continue;
}
int sumA = (sum * sizeA) / n;
if (isPartitionPossible(0, sumA, sizeA) == true) {
break;
}
}
return generatePartitions();
}
private boolean isPartitionPossible(final int index, final int sumA, final int sizeA) {
if (sizeA == 0) {
return sumA == 0;
}
int n = this.array.size();
if (index >= n) {
return false;
}
if (this.dp[index][sumA][sizeA] == false) {
return false;
}
if (sumA >= this.array.get(index)) {
// include the current index i.e. include the current element in setA
this.indexSetA.add(index);
if (isPartitionPossible(index + 1, sumA - this.array.get(index), sizeA - 1) == true) {
return true;
}
this.indexSetA.remove(this.indexSetA.size() - 1);
}
if (isPartitionPossible(index + 1, sumA, sizeA)) {
// skip the current index i.e. don't include the current element in setA
return true;
}
this.dp[index][sumA][sizeA] = false;
return this.dp[index][sumA][sizeA];
}
private ArrayList<ArrayList<Integer>> generatePartitions() {
int i = 0, j = 0;
int sizeA = this.indexSetA.size();
int n = this.array.size();
if (sizeA == n || sizeA == 0) { // no solution exists
return new ArrayList<>();
}
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
result.add(new ArrayList<>());
result.add(new ArrayList<>());
// index i is used to iterate over all elements and index j is used to iterate over indexSetA
while (i < n && j < sizeA) {
if (i == this.indexSetA.get(j)) {
result.get(0).add(this.array.get(i));
j++;
} else {
result.get(1).add(this.array.get(i));
}
i++;
}
while (i < n) {
result.get(1).add(this.array.get(i));
i++;
}
return result;
}
Hint and Solution Approach
Lets try to simplify the problem.
Lets assume the two sets are set1
and set2
.
Assume sum of set1 = Sum_of_Set1
, with size = size_of_set1
.
Assume sum of set2 = Sum_of_Set2
, with size = size_of_set2
SUM_of_Set1 / size_of_set1 = SUM_of_Set2 / size_of_set2
SUM_of_Set1 = SUM_of_Set2 * (size_of_set1 / size_of_set2)
total_sum = Sum_of_Set1 + Sum_of_Set2
AND size_of_set2 = total_size - size_of_set1
Sum_of_Set1 = (total_sum - Sum_of_Set1) * (size_of_set1 / (total_size - size_of_set1))
OR on simplifying,
total_sum / Sum_of_Set1 - 1 = (total_size - size_of_set1) / size_of_set1
total_sum / Sum_of_Set1 = total_size / size_of_set1
Sum_of_Set1 / size_of_set1 = total_sum / total_size
Note that you need the solution with minimum size_of_set1
if multiple solutions exist. So, just iterate on size_of_set1
. Based on size_of_set1
, you can determine the value of Sum_of_Set1
. Now, the problem reduces to
Can I select size_of_set1 values from the array whose sum is Sum_of_Set1 (i.e., current_sum)?
Lets define our function as isPossible(ind, current_sum, current_size)
which returns true if it is possible to use elements with index >= ind
to construct a set of size current_size
whose sum is current_sum
.
isPossible(ind, current_sum, current_size : |
|
| isPossible(ind + 1, current_sum, current_size) [ Do not include current element ]
Or(|) Logical operator |
|
|
|
| isPossible(ind + 1, current_sum - value_at(ind), current_size - 1)
|
Can you memoize values to reduce the time complexity of the above recursive function ?