I'm struggling with dynamic programming and desperately need help! I would very appreciate it. For hours I've been trying to transform a recursive method into a non-recursive one, but was unable to do that. My initial task was to write two algorithms for a recurrent equation. The first method being a recursive method, the other using a loop and storing the data.
There are two integers, n and w, and two integer arrays s[n] and p[n]. Need to find the return value of a recursive method G1(n, w) then create method G2(n, w) which would complete the same task, but it has to use loops instead of recursion.
private static int G1(int k, int r)
{
if (k == 0 || r == 0)
{
return 0;
}
if (s[k - 1] > r)
{
return G1(k - 1, r);
}
return Max(G1(k - 1, r), p[k - 1] + G1(k - 1, r - s[k - 1]));
}
I found a possible solution for C#, but I couldn't apply it for my equation:
A similar task (RECURSION)
A similar task (LOOP)
This is my code and initial data, but I can't get it to work:
n = 3;
w = 3;
s = new List<int>{ 2, 3, 8 };
p = new List<int> { 1, 3, 5 };
private static int G2(int k, int r)
{
List<Tuple<int, int, int>> data = new List<Tuple<int, int, int>>();
data.Add(new Tuple<int, int, int>(0, 0, 0));
do
{
if (data[0].Item1 == 0 || data[0].Item2 == 0)
{
data[0] = new Tuple<int, int, int>(data[0].Item1, data[0].Item2, 0);
}
else
{
if (s[data[0].Item1 - 1] > data[0].Item2)
{
data.Add(new Tuple<int, int, int>(data[0].Item1 - 1, data[0].Item2, data[0].Item3));
}
if (data[0].Item1 + 1 >= k)
{
data.Add(new Tuple<int, int, int>(data[0].Item1 - 1, data[0].Item2, data[0].Item3));
}
if (data[0].Item2 + 1 >= r)
{
data.Add(new Tuple<int, int, int>(data[0].Item1 - 1, data[0].Item2 - s[data[0].Item1 - 1], data[0].Item3 + p[data[0].Item1 - 1]));
}
}
Console.WriteLine($"DEBUG: current k: {data[0].Item1} current r: {data[0].Item2} current result: {data[0].Item3}");
data.RemoveAt(0);
} while (data.Count > 0 && data.Count(entry => entry.Item1 == k && entry.Item2 == r) <= 0);
return data.First(entry => entry.Item1 == k && entry.Item2 == r).Item3;
}
There is a common solution. You should create a 2D arry by the size of k x r. Then, loop on this array in diagonal zigzag order to fill the value (in bottom-up order, like the following image).
At the end of the filling the value of the 2d array, you will have the value of G2(k,r). You can find the implementation of G2(k,r) in the below.
int G2(int k, int r)
{
int[,] values = new int[k + 1,r + 1];
var maxDim = Max(k + 1,r + 1);
for( int h = 1 ; h < maxDim * 2 ; h++ ) {
for( int j = 0 ; j <= h ; j++ ) {
int i = h - j;
if( i <= k && j <= r && i > 0 && j > 0 ) {
if (s[i - 1] > j)
{
values[i,j] = values[i - 1, j];
}
else
{
values[i,j] = Max(values[i - 1, j], p[i - 1] + values[i - 1, j - s[i - 1]]);
}
}
}
}
return values[k , r];
}
Related
I currently have an issue with zero padding my 2d Array. I want to transfer my current data in my array to a new array, which is the exact same array but with a border of 0's around it.
Example:
|1 2 3|
|4 5 6|
|7 8 9|
Should become
|0 0 0 0 0|
|0 1 2 3 0|
|0 4 5 6 0|
|0 7 8 9 0|
|0 0 0 0 0|
int[,] Array = new int[,] { { 1, 2, 3 }, { 3, 4, 5 }, { 6, 7, 8 } };
int[,] ArrayZeroPad = new int[Array.GetLength(0) + 2, Array.GetLength(1) + 2];
for (int y = 0; y < Array.GetLength(1); y++)
{
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{
if (y == 0)
{ ArrayZeroPad[y, x] = 0; }
else if (y == ArrayZeroPad.GetLength(1))
{ ArrayZeroPad[y, x] = 0; }
else if (x == 0)
{
ArrayZeroPad[y, x] = 0;
}
else if (x == ArrayZeroPad.GetLength(0))
{ ArrayZeroPad[y, x] = 0; }
else ArrayZeroPad[y, x] = Array[y, x];
}
}
for (int y = 0; y < ArrayZeroPad.GetLength(1); y++)
{
Console.WriteLine();
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{ Console.Write(ArrayZeroPad[y, x]); }
Console.ReadLine();
}
}
This is what I have come to thus far, but I keep getting stuck on out of bounds errors, is there anyone who could work this out for me with some explanation?
Kind regards,
D.
This is not quite what you are asking (I thought a completely different alternative would be interesting).
Here is a No-Copy version that works for any type of array, of any size. It's appropriate if the original array is quite large (since it doesn't require a copy).
It uses a 2-dimensional indexer that either returns the default value of T (zero or null) for items on the edge, and uses the original array (with the indexes offset) for non-edge values:
public class ZeroPadArray <T>
{
private readonly T[,] _initArray;
public ZeroPadArray(T[,] arrayToPad)
{
_initArray = arrayToPad;
}
public T this[int i, int j]
{
get
{
if (i < 0 || i > _initArray.GetLength(0) + 1)
{
throw new ArgumentOutOfRangeException(nameof(i),
$#"Index {nameof(i)} must be between 0 and the width of the padded array");
}
if (j < 0 || j > _initArray.GetLength(1) + 1)
{
throw new ArgumentOutOfRangeException(nameof(j),
$#"Index {nameof(j)} must be between 0 and the width of the padded array");
}
if (i == 0 || j == 0)
{
return default(T);
}
if (i == _initArray.GetLength(0) + 1)
{
return default(T);
}
if (j == _initArray.GetLength(1) + 1)
{
return default(T);
}
//otherwise, just offset into the original array
return _initArray[i - 1, j - 1];
}
}
}
I just tested it with some Debug.Assert calls. The test coverage is weak, but it was good enough to say "this probably works":
int[,] array = new int[,] { { 1, 2, 3 }, { 11, 12, 13 }, { 21, 22, 23 } };
var paddedArray = new ZeroPadArray<int>(array);
Debug.Assert(paddedArray[0, 0] == 0);
Debug.Assert(paddedArray[4,4] == 0);
Debug.Assert(paddedArray[2,3] == 13);
And, finally, for fun, I added a nice little hack to make creating these things require less typing. When you call a method, the compiler is often able to deduce the generic type of the object from the method parameters. This doesn't work for constructors. That's why you need to specify new ZeroPadArray<int>(array) even though array is obviously an array of int.
The way to get around this is to create a second, non-generic class that you use as a static factory for creating things. Something like:
public static class ZeroPadArray
{
public static ZeroPadArray<T> Create<T>(T[,] arrayToPad)
{
return new ZeroPadArray<T>(arrayToPad);
}
}
Now, instead of typing:
var paddedArray = new ZeroPadArray<int>(array);
you can type:
var paddedArray = ZeroPadArray.Create(array);
Saving you two characters of typing (but, you need to admit that typing the <int> is frustrating).
int[,] Array = new int[,] { { 1, 2, 3 }, { 3, 4, 5 }, { 6, 7, 8 } };
int[,] ArrayZeroPad = new int[Array.GetLength(0) + 2, Array.GetLength(1) + 2];
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{
for (int y = 0; y < ArrayZeroPad.GetLength(0); y++)
{
//First row and last row
if (x == 0 || x == ArrayZeroPad.GetLength(0) - 1)
ArrayZeroPad[x, y] = 0;
else
{
//Fist column and last column
if (y == 0 || y == ArrayZeroPad.GetLength(0) - 1)
ArrayZeroPad[x, y] = 0;
else
{
//Content
ArrayZeroPad[x, y] = Array[x-1, y-1];
}
}
}
}
It seems that you are confusing dimensions - Array.GetLength(0) is for the first one in access Array[i, j] and Array.GetLength(1) is for the second. Also you can simplify copy by just scanning through Array elements and adjusting destination indexes by one, you don't need to explicitly set others to 0 cause it would be done for you (unless you are using stackalloc and skipping local init but I highly doubt that this is the case):
var length0 = Array.GetLength(0);
var length1 = Array.GetLength(1);
for (int i = 0; i < length0; i++)
{
for (int j = 0; j < length1; j++)
{
ArrayZeroPad[i + 1, j + 1] = Array[i, j];
}
}
And in the "print" method too - y should be the first dimension and x - second:
var length = ArrayZeroPad.GetLength(0);
for (int y = 0; y < length; y++)
{
Console.WriteLine();
var i = ArrayZeroPad.GetLength(1);
for (int x = 0; x < i; x++)
{
Console.Write(ArrayZeroPad[y, x]);
}
Console.ReadLine();
}
You can also solve this using Array.Copy(). If you require highest performance and the arrays are big enough, then this might be faster than explicitly copying each element:
public static int[,] Pad(int[,] input)
{
int h = input.GetLength(0);
int w = input.GetLength(1);
var output = new int[h+2, w+2];
for (int r = 0; r < h; ++r)
{
Array.Copy(input, r*w, output, (r+1)*(w+2)+1, w);
}
return output;
}
The (r+1)*(w+2)+1 requires some explanation. Array.Copy() treats a 2D array as a linear 1D array, and you must specify the destination offset for the copy as an offset from the start of the 1D array (in row-major order).
Since w is the width of the input array and r is the current row of the input array, the destination for the copy of the current input row will be the output row number, (r+1) times the output row width (w+2), plus 1 for to account for the left-hand column of 0 in the output array.
It's possible that using Buffer.BlockCopy() (which operates on bytes) could be even faster:
public static int[,] Pad(int[,] input)
{
int h = input.GetLength(0);
int w = input.GetLength(1);
var output = new int[h+2, w+2];
for (int r = 0; r < h; ++r)
{
Buffer.BlockCopy(input, r*w*sizeof(int), output, ((r+1)*(w+2)+1)*sizeof(int), w*sizeof(int));
}
return output;
}
As always, this is only worth worrying about if performance is critical, and even then only after you've benchmarked the code to verify that it actually is faster.
For example, if the array is like
1 1 0 0
0 1 1 0
0 0 1 0
1 0 0 0
then the answer is 5.
I have a helper function
// Returns the size of the region of 1s containing the point (x0, y0).
// For example, if mat = 0 0 1
// 1 0 0
// 1 1 1
// then max_connected_region(0,0,mat) = 0,
// max_connected_region(2,0,mat) = 1,
// and max_connected_region(0,1,mat) = 4
static int max_connected_region(int x0, int y0, int[,] mat)
{
if(mat[x0,y0] == 0)
return 0;
var surroundings = (new int[][] {
new int[] { x0 - 1, y0 }, new int[] {x0 + 1, y0 },
new int[] { x0 - 1, y0 + 1}, new int[] { x0, y0 + 1 }, new int[] {x0 + 1, y0 + 1},
new int[] { x0 - 1, y0 - 1}, new int[] { x0, y0 - 1 }, new int[] {x0 + 1, y0 - 1} }
).Where(pair => pair[0] >= 0 && pair[0] < mat.GetLength(0) && pair[1] >= 0 && pair[1] < mat.GetLength(1));
int count = 1;
foreach(var pair in surroundings)
count += max_connected_region(pair[0], pair[1], mat);
mat[x0,y0] = 0;
return count;
}
and how I find the maximum connection in an n x m array (n rows, m columns) is using it like
int max_connections = 0;
for(int j = 0; j < n; ++j)
{
for(int i = 0; i < m; ++i)
{
if(matrix[i,j] == 0)
continue;
int connections = max_connected_region(i,j,matrix);
if(connections > max_connections)
max_connections = connections;
}
}
This procedure is giving me either a timeout or an out-of-bounds in the test cases and I can't figure out why.
As noted in the comments, your algorithm is revisiting array elements it's already checked, putting it in an endless loop.
You actually have a program statement that appears to be trying to avoid this, but you execute it after your recursive call. So it has no useful effect. If you simply move it before the loop that performs the recursive calls, your algorithm will work:
static int max_connected_region(int x0, int y0, int[,] mat)
{
if (mat[x0, y0] == 0)
return 0;
var surroundings = (new int[][] {
new int[] { x0 - 1, y0 }, new int[] {x0 + 1, y0 },
new int[] { x0 - 1, y0 + 1}, new int[] { x0, y0 + 1 }, new int[] {x0 + 1, y0 + 1},
new int[] { x0 - 1, y0 - 1}, new int[] { x0, y0 - 1 }, new int[] {x0 + 1, y0 - 1} }
).Where(pair => pair[0] >= 0 && pair[0] < mat.GetLength(0) && pair[1] >= 0 && pair[1] < mat.GetLength(1));
int count = 1;
mat[x0, y0] = 0;
foreach (var pair in surroundings)
count += max_connected_region(pair[0], pair[1], mat);
return count;
}
I note that your algorithm is destructive. That is, it modifies the array that's passed to it. This may be acceptable for your scenario — at worst, it means the caller needs to make sure that it passes a copy of its data. But if this were to be some sort of library method, you might consider making the copy yourself, or using an appropriately-sized bool[,] to track where the algorithm has already visited.
I also feel that allocating a whole new array for the surroundings with each iteration of the method is maybe not the best approach. If you intend to run this algorithm on much larger data sets, it might make more sense to have a static array containing the valid offsets, and then just have an explicit for loop iterating through that array for the recursive calls. This will minimize the extra memory allocation and garbage collection overhead as you visit each array element.
Making those changes, the method, and its supporting class members, would look more like this:
static int max_connected_region2(int x0, int y0, int[,] mat)
{
return max_connected_region2_impl(x0, y0, (int[,])mat.Clone());
}
static int max_connected_region2_impl(int x0, int y0, int[,] mat)
{
if (mat[x0, y0] == 0)
return 0;
int count = 1;
mat[x0, y0] = 0;
for (int i = 0; i < adjacentCells.Length; i++)
{
int[] pair = adjacentCells[i];
int x1 = pair[0] + x0, y1 = pair[1] + y0;
if (x1 >= 0 && x1 < mat.GetLength(0) && y1 >= 0 && y1 < mat.GetLength(1))
{
count += max_connected_region2_impl(x1, y1, mat);
}
}
return count;
}
private static readonly int[][] adjacentCells =
{
new [] { -1, 0 }, new [] { 1, 0 }, new [] { -1, 1 }, new [] {0, 1 },
new [] { 1, 1 }, new [] { -1, -1}, new [] { 0, -1 }, new [] { 1, -1 }
};
Given a large list of integers (more than 1 000 000 values) find how many ways there are of selecting two of them that add up to 0.... Is the question
What I have done is create a positive random integer list:
Random pos = new Random();
int POSNO = pos.Next(1, 1000000);
lstPOS.Items.Add(POSNO);
lblPLus.Text = lstPOS.Items.Count.ToString();
POSCount++;
And created a negative list:
Random neg = new Random();
int NEGNO = neg.Next(100000, 1000000);
lstNEG.Items.Add("-" + NEGNO);
lblNegative.Text = lstNEG.Items.Count.ToString();
NegCount++;
To do the sum checking I am using:
foreach (var item in lstPOS.Items)
{
int POSItem = Convert.ToInt32(item.ToString());
foreach (var negItem in lstNEG.Items)
{
int NEGItem = Convert.ToInt32(negItem.ToString());
int Total = POSItem - NEGItem;
if (Total == 0)
{
lstADD.Items.Add(POSItem + "-" + NEGItem + "=" + Total);
lblAddition.Text = lstADD.Items.Count.ToString();
}
}
}
I know this is not the fastest route. I have considered using an array. Do you have any suggestions?
Let's see; your array is something like this:
int[] data = new int[] {
6, -2, 3, 2, 0, 0, 5, 7, 0, -2
};
you can add up to zero in two different ways:
a + (-a) // positive + negative
0 + 0 // any two zeros
in the sample above there're five pairs:
-2 + 2 (two pairs): [1] + [3] and [3] + [9]
0 + 0 (three pairs): [4] + [5], [4] + [8] and [5] + [8]
So you have to track positive/negative pairs and zeros. The implementation
Dictionary<int, int> positives = new Dictionary<int, int>();
Dictionary<int, int> negatives = new Dictionary<int, int>();
int zeros = 0;
foreach(var item in data) {
int v;
if (item < 0)
if (negatives.TryGetValue(item, out v))
negatives[item] = negatives[item] + 1;
else
negatives[item] = 1;
else if (item > 0)
if (positives.TryGetValue(item, out v))
positives[item] = positives[item] + 1;
else
positives[item] = 1;
else
zeros += 1;
}
// zeros: binomal coefficent: (2, zeros)
int result = zeros * (zeros - 1) / 2;
// positive/negative pairs
foreach (var p in positives) {
int n;
if (negatives.TryGetValue(-p.Key, out n))
result += n * p.Value;
}
// Test (5)
Console.Write(result);
Note, that there's no sorting, and dictionaries (i.e. hash tables) are used for positives and negatives so the execution time will be linear, O(n); the dark side of the implementation is that two additional structures (i.e. additional memory) required. In your case (millions integers only - Megabytes) you have that memory.
Edit: terser, but less readable Linq solution:
var dict = data
.GroupBy(item => item)
.ToDictionary(chunk => chunk.Key, chunk => chunk.Count());
int result = dict.ContainsKey(0) ? dict[0] * (dict[0] - 1) / 2 : 0;
result += dict
.Sum(pair => pair.Key > 0 && dict.ContainsKey(-pair.Key) ? pair.Value * dict[-pair.Key] : 0);
Fastest way without sorting!.
First of all you know that the sum of two integers are only 0 when they have equal absolute value but one is negative and the other is positive. So you dont need to sort. what you need is to Intersect positive list with negative list (by comparing absolute value). the result is numbers that ended up 0 sum.
Intersect has time complexity of O(n+m) where n is size of first list and m is size of second one.
private static void Main(string[] args)
{
Random random = new Random();
int[] positive = Enumerable.Range(0, 1000000).Select(n => random.Next(1, 1000000)).ToArray();
int[] negative = Enumerable.Range(0, 1000000).Select(n => random.Next(-1000000, -1)).ToArray();
var zeroSum = positive.Intersect(negative, new AbsoluteEqual());
foreach (var i in zeroSum)
{
Console.WriteLine("{0} - {1} = 0", i, i);
}
}
You also need to use this IEqualityComparer.
public class AbsoluteEqual : IEqualityComparer<int>
{
public bool Equals(int x, int y)
{
return (x < 0 ? -x : x) == (y < 0 ? -y : y);
}
public int GetHashCode(int obj)
{
return obj < 0 ? (-obj).GetHashCode() : obj.GetHashCode();
}
}
You tried to avoid check two numbers that are close (1, 2 are close, 3, 4 are close), but you didn't avoid check like (-100000, 1), (-1, 100000). Time complexity is O(n^2).
To avoid that you need to sort them first, then search from two direction.
var random = new Random();
var input = Enumerable.Range(1, 100).Select(_ => random.Next(200) - 100).ToArray();
Array.Sort(input); // This causes most computation. Time Complexity is O(n*log(n));
var expectedSum = 0;
var i = 0;
var j = input.Length - 1;
while (i < j) // This has liner time complexity O(n);
{
var result = input[i] + input[j];
if(expectedSum == result)
{
var anchori = i;
while (i < input.Length && input[i] == input[anchori] )
{
i++;
}
var anchorj = j;
while (j >= 0 && input[j] == input[anchorj])
{
j--;
}
// Exclude (self, self) combination
Func<int, int, int> combination = (n, k) =>
{
var mink = k * 2 < n ? k : n - k;
return mink == 0 ? 1
: Enumerable.Range(0, mink).Aggregate(1, (x, y) => x * (n - y))
/ Enumerable.Range(1, mink).Aggregate((x, y) => x * y);
};
var c = i < j ? (i - anchori) * (anchorj - j) : combination(i - anchori, 2);
for (int _ = 0; _ < c; _++)
{
// C# 6.0 String.Format
Console.WriteLine($"{input[anchori]}, {input[anchorj]}");
}
}
else if(result < expectedSum) {
i++;
}
else if(result > expectedSum) {
j--;
}
}
Here is another solution using (huh) LINQ. Hope the code is self explanatory
First some data
var random = new Random();
var data = new int[1000000];
for (int i = 0; i < data.Length; i++) data[i] = random.Next(-100000, 100000);
And now the solution
var result = data
.Where(value => value != int.MinValue)
.GroupBy(value => Math.Abs(value), (key, values) =>
{
if (key == 0)
{
var zeroCount = values.Count();
return zeroCount * (zeroCount - 1) / 2;
}
else
{
int positiveCount = 0, negativeCount = 0;
foreach (var value in values)
if (value > 0) positiveCount++; else negativeCount++;
return positiveCount * negativeCount;
}
})
.Sum();
Theoretically the above should have O(N) time and O(M) space complexity, where M is the count of the unique absolute values in the list.
I guess I'm not setting the array correctly or something, but this throws a "nullreferenceexception" when it gets to the line where it actually sets the new array value to the color_table array (should be the 7th and 12th lines of what you see below). How should I write this so that it works?
public int[] colors = new int[] { 0, 255, 0, 255, 0, 255 };
private int[][] color_table;
public void setcolors()
{
this.color_table[0] = new int[] { 0, 0, 0 };
for (int i = 1; i <= this.precision; i++) {
int r = (((this.colors[1] - this.colors[0]) * ((i - 1) / (this.precision - 1))) + this.colors[0]);
int g = (((this.colors[3] - this.colors[2]) * ((i - 1) / (this.precision - 1))) + this.colors[2]);
int b = (((this.colors[5] - this.colors[4]) * ((i - 1) / (this.precision - 1))) + this.colors[4]);
this.color_table[i] = new int[] { r, g, b };
}
}
I've heard something about that you MUST initialize an array with its length before using it, but a) I don't know how to do that and b) I'm not sure if it's problem. The issue there is that I don't know what the array length is going to be. I tried this to no avail:
private int[this.precision][3] color_table;
Thanks!
this.color_table has not been initialized. Hence you can't assign values to it.
Did you mean something like this:
public void setcolors()
{
color_table = new int[precision + 1][];
for (int i = 1; i <= this.precision; i++)
{
int r = (((this.colors[1] - this.colors[0]) * ((i - 1) / (this.precision - 1))) + this.colors[0]);
int g = (((this.colors[3] - this.colors[2]) * ((i - 1) / (this.precision - 1))) + this.colors[2]);
int b = (((this.colors[5] - this.colors[4]) * ((i - 1) / (this.precision - 1))) + this.colors[4]);
this.color_table[i] = new int[] { r, g, b };
}
}
try to use list if you don't know the length of your array
List<int[]> color_table = new List<int[]>();
...
color_table.Add(new int[] { r, g, b });
It is a leet code contest question which I am trying to attempt after contest is over but my code always exceeds time limit.
Question is
Given four lists A, B, C, D of integer values, compute how many tuples
(i, j, k, l) there are such that A[i] + B[j] + C[k] + D[l] is zero.
To make problem a bit easier, all A, B, C, D
have same length of N where 0 ≤ N ≤ 500.
All integers are in the range of -228 to 228 - 1
and the result is guaranteed to be at most 231 - 1.
Example:
Input:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
Output:
2
Explanation:
The two tuples are:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
My code is
public static int FourSumCount(int[] A, int[] B, int[] C, int[] D)
{
int count = 0;
List<int> map1 = new List<int>();
List<int> map2 = new List<int>();
for (int i = 0; i < A.Length; i++)
for (int y = 0; y < B.Length; y++)
{
map1.Add(A[i] + B[y]);
map2.Add(C[i] + D[y]);
}
for (int i = 0; i < map2.Count(); i++)
{
for (int j = 0; j < map2.Count(); j++)
//if (map1.Contains(map2[i]*-1))
//{
// var newList = map1.FindAll(s => s.Equals(map2[i] * -1));
// count = count + newList.Count();
//}
if (map1[i] + map2[j] == 0)
{
count++;
}
}
return count;
}
is there any better way? Thanks in anticipation.
I suggest kind of meet in the middle algorithm:
A[i] + B[j] + C[k] + D[l] = 0
actually means to find out A[i] + B[j] and C[k] + D[l] such that
(A[i] + B[j]) == (-C[k] - D[l])
We can put all possible A[i] + B[j] sums into a dictionary and then, in the loop over -C[k] - D[l], try to look up in this dictionary. You can implement it like this:
private static int FourSumCount(int[] A, int[] B, int[] C, int[] D) {
// left part: all A[i] + B[j] combinations
Dictionary<int, int> left = new Dictionary<int, int>();
// loop over A[i] + B[j] combinations
foreach (var a in A)
foreach (var b in B) {
int k = a + b;
int v;
if (left.TryGetValue(k, out v))
left[k] = v + 1; // we have such a combination (v of them)
else
left.Add(k, 1); // we don't have such a combination
}
int result = 0;
// loop over -C[k] - D[l] combinations
foreach (var c in C)
foreach (var d in D) {
int v;
if (left.TryGetValue(-c - d, out v))
result += v;
}
return result;
}
As you can see, we have O(|A| * |B| + |C| * |D|) complexity; in case A, B, C and D arrays have the approximately equal sizes N the complexity is O(N**2).
Your first step is okay. But use Dictionary instead of List which will ensure constant time lookup and reduce the complexity of second part.
This was my C++ O(n^2) solution:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
int n = A.size();
int result = 0;
unordered_map<int,int> sumMap1;
unordered_map<int,int> sumMap2;
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j) {
int sum1 = A[i] + B[j];
int sum2 = C[i] + D[j];
sumMap1[sum1]++;
sumMap2[sum2]++;
}
}
for(auto num1 : sumMap1) {
int number = num1.first;
if(sumMap2.find(-1 * number) != sumMap2.end()) {
result += num1.second * sumMap2[-1 * number];
}
}
return result;
}
The core observation is - if W + X + Y + Z = 0 then W + X = -(Y + Z).
Here I used two hash-tables for each of possible sums in both (A, B) and (C, D) find number of occurrences of this sum.
Then, for each sum(A, B) we can find if sum(C, D) contains complimentary sum which will ensure sum(A, B) + sum(C, D) = 0. Add (the number of occurrences of sum(a, b)) * (number of occurrences of complimentary sum(c,d)) to the result.
Creating sum(A, B) and sum(C, D) will take O(n^2) time. And counting the number of tuples is O(n^2) as there are n^2 sum for each pairs(A-B, C-D). Other operation like insertion and search on hashtable is amortized O(1). So, the overall time complexity is O(n^2).