Understanding Kadane's Algorithm for 2-D Array - java

I'm trying to write a program which solves the maximum subarray problem. I can understand the intuition behind Kadane's Algorithm on a 1-D array as well as the O(N^4) implementation on a 2-D array. However, I am having some trouble understanding the O(N^3) implementation on a 2-D array.
1) Why do we add up the elements with those from the previous rows within the same column?
for (int i = 1; i <= N; i++) {
for (int j = 1; j <= M; j++)
array[i][j] += array[i-1][j];
}
2) I have no understanding of the second part of the algorithm
Tried looking for an explanation on the web but to no avail. Hope to get some help here!
Thanks in advance!

You know how to compute maximum sum sub-array on a 1D array using Kadane's algorithm. Now we want to extend this algorithm for the 2D array. For an O(N^3) algorithm, we have an intuition. If we somehow create N^2 sub problems and then try to run our O(N) Kadane's algorithm, we can solve the maximum sub array problem.
So basically how we create the N^2 sub problems is by iterating over all the top and bottom rows of the matrix. Then we try to find the optimal columns between which the sub array exists by applying kadane's 1D algorithm. We thus sum the numbers between these two rows column wise and then apply kadane's 1D algorithm on this newly formed 1D array.
But we have a problem here. Computing the sums for all the O(n^2) ranges of the top and bottom rows will itself be O(n^4). This bottle neck can be overcome by modifying our matrix by replacing each element with the sum of all the numbers that are above it in that element's column. Thus, now we can find out the sum of numbers between any two rows in O(n) time by subtracting the appropriate arrays in the matrix.
The java pseudo code -
int kadane2D(int array[N][M]){
// Modify the array's elements to now hold the sum
// of all the numbers that are above that element in its column
for (int i = 1; i < N; i++) {
for (int j = 0; j < M; j++){
array[i][j] += array[i-1][j];
}
}
int ans = 0; // Holds the maximum sum matrix found till now
for(int bottom = 0; bottom < N; bottom++){
for(int top = bottom; top < N; top++){
// loop over all the N^2 sub problems
int[] sums = new int[N];
// store the sum of numbers between the two rows
// in the sums array
for(int i = 0; i < M; i++){
if (bottom > 0) {
sums[i] = array[top][i] - array[bottom-1][i];
} else {
sums[i] = array[top][i];
}
}
// O(n) time to run 1D kadane's on this sums array
ans = Math.max(ans, kadane1d(sums));
}
}
return ans;
}

For people who understand the Kadane's 1D algorithm, below should be easy to understand. Basically we try to convert the 2D matrix into 1D by using the prefix sum for each rows. And for each prefix sum row, we just apply the Kadane's 1D algorithm.
Just posting the working Python code:
class Kadane2D:
def maxSumRetangle(self, grid):
def kadane1D(arr):
curmax, maxsofar = 0, float('-inf')
for a in arr:
curmax = max(a, curmax + a)
maxsofar = max(curmax, maxsofar)
return maxsofar
m, n, ans = len(grid), len(grid[0]), float('-inf')
colCum = [[0] * n]
for row in grid:
colCum.append([pre + now for pre, now in zip(colCum[-1], row)])
for top in range(1, m + 1):
for bottom in range(top, m + 1):
sums = [b - t for b, t in zip(colCum[bottom], colCum[top - 1])]
ans = max(ans, kadane1D(sums))
return ans
grid = [[1, 2, - 3], [3, 4, -6]]
assert Kadane2D().maxSumRetangle(grid) == 10
grid = [[1, 2, -1, -4, -20],
[-8, -3, 4, 2, 1],
[3, 8, 10, 1, 3],
[-4, -1, 1, 7, -6]]
assert Kadane2D().maxSumRetangle(grid) == 29

I know it's an old question. But Google doesn't have the right answers, or they're overworked.
No, this is no correct way. Working example, on O(N^2):
/**
* Kadane 1d
* #return max sum
*/
public static int maxSum(int[] a) {
int result = a[0]; //get first value for correct comparison
int sum = a[0];
for (int i = 1; i < a.length; i++) {
sum = Math.max(sum + a[i], a[i]); //first step getting max sum, temporary value
result = Math.max(result, sum);
}
return result;
}
/**
* Kadane 2d
* #param array
* #return max sum
*/
public static int maxSum2D(int array[][]){
int result = Integer.MIN_VALUE; //result max sum
for (int i = 0; i < array.length; i++) {
int sum = maxSum(array[i]);
result = Math.max(result, sum);
}
return result;
}
Fully examples:
Easy: https://pastebin.com/Qu1x0TL8
Supplemented: https://pastebin.com/Tjv602Ad
With indexes: https://pastebin.com/QsgPBfY6

Related

Efficient way to solve subset sum variation

Given an integer array, find the maximum number of sums of adjacent elements that are divisible by n.
Example 1:
input: long[] array = [1, 2, 3], n = 7
output: 0
Example 2:
input: long[] array = [1, 2, 4], n = 7
output: 1
Example 3:
input: long[] array = [2, 1, 2, 1, 1, 2, 1, 2], n = 4
output: 6
Constraints:
array.length = 50000
array[index] <= 2^31 - 1
n <= 2^31 - 1
Currently, this is my code:
public static int maxSums(long[] array, long n) {
int count = 0;
if (array.length == 1 && array[0] == n) {
return 1;
} else {
for (int i = 0; i < array.length; i++) {
long sum = 0;
for (int j = i; j < array.length; j++) {
sum += array[j];
if (sum % n == 0) {
count++;
}
}
}
}
return count;
}
which is essentially the window sliding technique. However, this code runs with time complexity O(n^2) which is pretty slow, and results in Apex CPU Time Limit Exceeded towards the higher end of the constraints. Is there a faster way to solve this?
An approach I just thought of is O(n*m), where n is the actual n parameter and m is the array length.
The algorithm remembers for every subsequence up to the current index what reminder the sequence sum has. This information is stored inside the array called currentMod.
When iterating over the input array this currentMod is updated. We simply add to each possible modulo value of iteration i-1 the value of the input array at index i. The updated array includes the number of subsequence sums ending at index i for each possible reminder: 0, 1, 2 up to n-1.
The element first element of tmpMod at index i includes the number of subsequences that end at index i and have a sum divisible by n. Therefore, we add them to our final result.
Here is the algorithm:
public static int maxSums(int[] array, int n) {
int[] currentMod = new int[n];
int count = 0;
for (int i = 0; i < array.length; i++) {
// Add +1 to 0 remainder as a new sequence can start at every index which has sum 0
currentMod[0] += 1;
int[] tmpMod = new int[n];
for (int j = 0; j < currentMod.length; j++) {
// For every subsequence reminder of i-1 calculate the reminders of adding element i to every subsequence
tmpMod[(j + array[i]) % n] += currentMod[j];
}
// Add number of subsequence sums that divide by n with remainder 0 to result
count += tmpMod[0];
currentMod = tmpMod;
}
return count;
}
P.S.: This algorithm is not strictly better/worse than yours. It depends on another input value. This means it depends on your inputs what is more efficient. My algorithm is only better for a case with large arrays and low n values.
EDIT: After a lot of thinking and testing I think I found a good solution. It is O(n) in time complexity. It is also O(n) in space complexity as there can be at most n different remainders with n values in the array.
The algorithm keeps track of the current remainder, which is dividable by the input n from the start. For each new subsequence, we add the 1 at the current remainder. In this way, we already define which total sum (mod n) we need that the subsequence is dividable by n.
public static int maxSums(int[] array, int n) {
Map<Integer, Integer> currentMod = new HashMap<Integer, Integer>();
int count = 0;
int currentZero = 0;
for (int val : array) {
currentMod.put(currentZero, currentMod.getOrDefault(currentZero, 0) + 1);
currentZero = (currentZero + val) % n;
count += currentMod.getOrDefault(currentZero, 0);
}
return count;
}
Also, some comparisons to show that it should work out:
len(array)=50000 and n=1000:
Your method: 11704 ms
My old one: 188 ms
My new one: 13 ms
len(array)=50000 and n=1000000:
Your method: 555 ms
My old one: stopped after 2 minutes
My new one: 6 ms

Max absolute difference of two max values at the different parts of the array?

That's the interview question that I failed back in the days. Nobody of my friends knows where the mistake is and why I've been told that I failed. That's why I decided to ask you to correct my solution
Given an array of N integers. An integer K divides array into two subarrays.
Left part: A[0], A[1]...A[K];
Right part: A[K+1], A[K+2]... A[N-1];
Need to find the max possible absolute difference of max values in every subarray.
MaxDiff = Math.Abs(Max(A[0], A[1]...A[K]) - Max(A[K+1], A[K+2]... A[N-1]))
Example 1: [1, 3, -3]. If K=1, max difference is |3-(-3)| = 6.
Example 2: [4, 3, 2, 5, 1, 1]. If K=3, max difference is |5 - 1| = 4.
Time and space complexity should be O(n).
As I see space complexity of my solution is not O(n) already..
int getMaxDifference(int[]A){
int [] leftMax = new int [A.length];
int [] rightMax = new int [A.length];
int max1 = Integer.MIN_VALUE;
int max2 = Integer.MIN_VALUE;
int dif = 0;
int maxDif = 0;
for (int i = 0; i< A.length; i++){
if (A[i]>max1) {max1 = A[i];}
leftMax[i] = max1;
}
for (int j = A.length-1; j>0; j--){
if (A[j]>max2) {max2 = A[j];}
rightMax[j] = max2;
}
for (int k = 0; k<A.length; k++){
dif = Math.abs(leftMax[k] - rightMax[k]);
if (dif>maxDif) {maxDif = dif;}}
return maxDif;
}
In your program:
leftMax[k] holds the greatest value in A[0],...,A[k].
rightMax[k] holds the greatest value in A[k],...,A[n-1].
However, the right part should start at index k+1, not at k.
Therefore I suggest you change this part:
for (int k = 0; k<A.length; k++){
dif = Math.abs(leftMax[k] - rightMax[k]);
if (dif>maxDif) {
maxDif = dif;
}
}
to
for (int k = 0; k<A.length - 1; k++){
dif = Math.abs(leftMax[k] - rightMax[k + 1]);
if (dif>maxDif) {
maxDif = dif;
}
}
In other words, the requirement is to compute:
Math.Abs(Max(A[0], A[1]...A[K]) - Max(A[K+1], A[K+2]... A[N-1]))
but I believe your current program computes:
Math.Abs(Max(A[0], A[1]...A[K]) - Max(A[k], A[K+1], A[K+2]... A[N-1]))
The problem is in the Difference Calculation:
If the Input Array is {4,3,2,5,1,1}
Then the Left Array becomes : {4,4,4,5,5,5}
And the Left Array becomes : {5,5,5,5,1,1}
To Calculate the Difference you should compute the difference at kth index of array leftMAX and (k+1)th index of array rightMax .
i.e. for SubArray {4,3,2,5} consider leftMax's subArray {4,4,4,5} and for SubArray {1,1} consider rightMax's subArray {1,1}
i.e. for SubArray {4,3,2,5} and {1,1} the calculation should be between 3rd Index of leftMax and 4th index of rightMax.
Hence Code becomes
for (int k = 0; k<A.length-1; k++){
dif = Math.abs(leftMax[k] - rightMax[k+1]);
if (dif>maxDif) {maxDif = dif;}}
Please note that the rightmost element of leftMax and leftmost element of rightMax doesn't gets included in the calculation.
I'm pretty sure you misinterpreted the question, which was actually "find the maximum absolute difference between any two elements of the 2 arrays".
The answer would require you to find both the max and min elements of each array, then chose the greatest of the absolute of either mina - maxb or maxa - minb.
There is a trivial one-pass O(n) solution that finds both the max and min of each array.
The introduction of K is mostly irrelevant, and possibly a red herring. There are 2 unrelated subarrays specified by an array reference and start and end indices.

Java how to find the middle of a 2d array

I'm writing a function that tries to find the middle of a 2d array, and here's what I have so far:
int findMiddle(int[][] grid,int [] m) {
int[] list = new int[grid.length*grid[0].length];
int listPos = 0;
for(int i = 0 ; i < grid.length; i++) {
for(int j = 0; j < grid.length; j++) {
list[listPos++] = grid[i][j];
}
}
int middle = m.length/2;
if (m.length%2 == 1) {
return m[middle];
} else {
return (m[middle-1] + m[middle]) / 2.0;
}
}
Suppose I have an array of
{{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 0, 1}}
It should return 6, as it is integer division.
Also, the definition of middle in this code is the middle integer of the whole original array (needless to say if it is sorted or not).
How would I do this? ( my code also doesn't compile)
This compiles, and will give you the middle number in your sequence, or the average of the middle two if it splits the middle:
static double findMiddle(int[][] grid) {
int[] list = new int[grid.length*grid[0].length];
int listPos = 0;
for(int i = 0 ; i < grid.length; i++) {
for(int j = 0; j < grid[i].length; j++) {
list[listPos++] = grid[i][j];
}
}
int middle = list.length/2;
if ((list.length%2) == 1) {
return list[middle];
}
return (list[middle-1] + list[middle]) / 2.0;
}
However, I suspect you'll want to sort you numbers after you combine them into a single array, before you find the middle numbers. (as piyush121 commented)
Assuming your definition of 'median' is correct (the middle number of an odd length set or the average of the two middle numbers of an even length set), and if grid[][] is sorted, and if grid[][] is a square array (i.e. grid.length = grid[i].length for all i in 0..length-1, then you don't need to copy data to another array. The following should suffice:
static int findMiddle(int[][] grid) {
int l = grid.length;
if (l%2 == 1) {
return grid[l/2][l/2];
} else {
return (grid[l/2-1][l-1]+grid[l/2][0])/2;
};
Looking at your existing code it seems you are defining 'median' as the middle value in the matrix if all values were put into a single row-wise list. In other words you don't need to cope with odd numbers of rows (when two numbers from the same column are in the middle) or odd numbers of rows and columns (when there are four numbers in the middle).
If that definition is correct then you can cope with all the complexity of uneven rows by streaming all values and then selecting the middle ones.
For your interest, here's a Java 8 solution that does that:
int[] flat = Arrays.stream(grid).flatMapToInt(Arrays::stream).toArray();
double middle = Arrays.stream(flat).skip(flat.length / 2).limit(1 + flat.length % 2)
.average().getAsDouble();

Transferring the contents of a one-dimensional array to a two-dimensional array

I'm trying to make an encryption program where the user enters a message and then converts the "letters into numbers".
For example the user enters a ABCD as his message. The converted number would be 1 2 3 4 and the numbers are stored into a one dimensional integer array. What I want to do is be able to put it into a 2x2 matrix with the use of two dimensional arrays.
Here's a snippet of my code:
int data[] = new int[] {10,20,30,40};
*for(i=0;i<2;i++)
{
for(j=0;j<2;j++)
{
for (int ctr=0; ictr<data.length(); ictr++){
a[i][j] = data[ctr];}
}
}
I know there's something wrong with the code but I am really lost.
How do I output it as the following?
10 20
30 40
(instead of just 10,20,30,40)
Here's one way of doing it. It's not the only way. Basically, for each cell in the output, you calculate the corresponding index of the initial array, then do the assignment.
int data[] = new int[] {10, 20, 30, 40, 50, 60};
int width = 3;
int height = 2;
int[][] result = new int[height][width];
for(int i = 0; i < height; i++) {
for(int j = 0; j < width; j++) {
result[i][j] = data[i * width + j];
}
}
Seems like you want to output a 2xn matrix while still having the values stored in a one-dimensional array. If that's the case then you can to this:
Assume the cardinality m of your set of values is known. Then, since you want it to be 2 rows, you calculate n=ceil(m/2), which will be the column count for your 2xn matrix. Note that if m is odd then you will only have n-1 values in your second row.
Then, for your array data (one-dimension array) which stores the values, just do
for(i=0;i<2;i++) // For each row
{
for(j=0;j<n;j++) // For each column,
// where index is baseline+j in the original one-dim array
{
System.out.print(data[i*n+j]);
}
}
But make sure you check the very last value for an odd cardinality set. Also you may want to do Integer.toString() to print the values.
Your code is close but not quite right. Specifically, your innermost loop (the one with ctr) doesn't accomplish much: it really just repeatedly sets the current a[i][j] to every value in the 1-D array, ultimately ending up with the last value in the array in every cell. Your main problem is confusion around how to work ctr into those loops.
There are two general approaches for what you are trying to do here. The general assumption I am making is that you want to pack an array of length L into an M x N 2-D array, where M x N = L exactly.
The first approach is to iterate through the 2D array, pulling the appropriate value from the 1-D array. For example (I'm using M and N for sizes below):
for (int i = 0, ctr = 0; i < M; ++ i) {
for (int j = 0; j < N; ++ j, ++ ctr) {
a[i][j] = data[ctr];
}
} // The final value of ctr would be L, since L = M * N.
Here, we use i and j as the 2-D indices, and start ctr at 0 and just increment it as we go to step through the 1-D array. This approach has another variation, which is to calculate the source index explicitly rather than using an increment, for example:
for (int i = 0; i < M; ++ i) {
for (int j = 0; j < N; ++ j) {
int ctr = i * N + j;
a[i][j] = data[ctr];
}
}
The second approach is to instead iterate through the 1-D array, and calculate the destination position in the 2-D array. Modulo and integer division can help with that:
for (int ctr = 0; ctr < L; ++ ctr) {
int i = ctr / N;
int j = ctr % N;
a[i][j] = data[ctr];
}
All of these approaches work. Some may be more convenient than others depending on your situation. Note that the two explicitly calculated approaches can be more convenient if you have to do other transformations at the same time, e.g. the last approach above would make it very easy to, say, flip your 2-D matrix horizontally.
check this solution, it works for any length of data
public class ArrayTest
{
public static void main(String[] args)
{
int data[] = new int[] {10,20,30,40,50};
int length,limit1,limit2;
length=data.length;
if(length%2==0)
{
limit1=data.length/2;
limit2=2;
}
else
{
limit1=data.length/2+1;
limit2=2;
}
int data2[][] = new int[limit1][limit2];
int ctr=0;
//stores data in 2d array
for(int i=0;i<limit1;i++)
{
for(int j=0;j<limit2;j++)
{
if(ctr<length)
{
data2[i][j] = data[ctr];
ctr++;
}
else
{
break;
}
}
}
ctr=0;
//prints data from 2d array
for(int i=0;i<limit1;i++)
{
for(int j=0;j<limit2;j++)
{
if(ctr<length)
{
System.out.println(data2[i][j]);
ctr++;
}
else
{
break;
}
}
}
}
}

symmetric matrix half vectorization

I am facing an issue concerning how to store the lower triangular factor of a given symmetric matrix (it's a distance matrix) into a vector.
Generally, I would like directly generating the lower triangular entries by only giving the coordinates (Y,Z) of a set of points on a rectangular grid: and actually it's where I got terribly stuck.
So that, I started thinking to attack the problem from a slightly different point of view: generating the full distance matrix (again given the (Y,Z) couples) and then half vectorize the distance matrix.
Nevertheless, I don't really have a sound idea on how to achieve the goal by means of for loops.
Besides I also know that there may be any external Java library which implements the vech function: vech returns the vector obtained by eliminating all supradiagonal elements of the square matrix X and stacking the result one column above the other. This has uses in matrix calculus where the underlying matrix is symmetric and it would be pointless to keep values above the main diagonal.
Substantially, given a matrix A = {{a,c},{b,d}}, by applying vech(A), the result will be
vech(A) = {a,b,d}.
EDIT
I mean something like the following:
a11 a12 a13 a14
a22 a23 a24
A= a33 a34 (aij = aji)
a44
Packed storage of the upper triangle of A:
AP = { a11, a12, a22, a13, a23, a33, a14, a24, a34, a44 }
public static double[] vech(double[][] a) {
int na = Math.min(a.length, a[0].length); // Dimension of the matrix
int nv = na * (na + 1) / 2; // 1 + 2 + 3 + .. + na
double[] v = new double[nv];
int k = 0; // index in v.
for (int i = 0; i < na; ++i) {
for (int j = 0; j <= i; ++j) {
v[k] = a[i][j];
++k;
}
}
return v;
}
Case 2x2 Matrix:
Picks [0][0], [1][0], [1][1] (skipping [0][1])
Row-major order: (C, C#, Java) a[i][j] is element at row i, column j.
The code flattens the bottom left triangle.
Column-major order: (MATLAB, SciLab) a[i][j] is element at column i, row j.
The code flattens the upper right triangle.
Other sequence
The other triangle would be given as:
for (int j = i; j < na; ++j) {
Combined with mirroring in the main diagonal, one receives the orignal triangle again:
a[j][i]
There is another interesting thing about all this packing. You can also define a mapping algorithm to convert (i, j) positon in symmetric matrix into equivalent offset in flatten array (just like you described). You can use ideas of Arithmetic progression to define such mapping. I did this while working on RandomSymmetricMatrixSource.java class in la4j. So, you can use these formulas (it doesn't handle the case when i == j):
int flatten(int i, int j) {
int offset = -1;
if (i < j) {
offset = j - (i + 1) + (int)((((size - 1) + (size - i)) / 2.0) * i);
} else {
offset = i - (j + 1) + (int)((((size - 1) + (size - j)) / 2.0) * j);
}
return offset;
}
, where size is the size of symmetric matrix.
I can't think of a library that would let you do this, although i'm sure there is one somewhere, but you could use a for loop as follows:
ArrayList<ArrayList<int>> matrix = new ArrayList<ArrayList<int>>();
// add other arraylists to your matrix here i.e.:
ArrayList<Integer> first = new ArrayList<Integer>();
first.add(1);
first.add(2);
first.add(3);
ArrayList<Integer> second = new ArrayList<Integer>();
second.add(4);
second.add(5);
second.add(6);
ArrayList<Integer> third = new ArrayList<Integer>();
third.add(7);
third.add(8);
third.add(9);
matrix.add(first);
matrix.add(second);
matrix.add(third);
ArrayList<int> finalArray = new ArrayList<int>();
for(int i=0; i<matrix.size(); i++)
{
ArrayList<Integer> inner = matrix.get(i);
for(int j=0; j<i+1; j++)
{
finalArray.add(inner.get(j));
}
}
This gives: matrix=[[1, 2, 3], [4, 5, 6], [7, 8, 9]] and finalArray=[1, 4, 5, 7, 8, 9]
Of course, this is assuming your matrix is structured using arraylists.

Categories

Resources