Problem overview
A dot product multiplies two vectors position by position and adds the products: A[0] * B[0] + A[1] * B[1] + .... A sparse vector is one that is mostly zeros, so instead of storing every position it stores only the non-zero entries, each as a pair [index, value]. The vector [6, 0, 0, 9, 7] is stored as [[0, 6], [3, 9], [4, 7]].
The method Dot receives two such lists as int[][], named first and second, and returns the dot product as an int. For first = [[0, 6], [3, 9], [4, 7]] and second = [[1, 3], [3, 4]] the answer is 36. The only position where both vectors are non-zero is index 3, and 9 * 4 = 36.
Approach: walk both lists in index order
A zero multiplied by anything is zero, so only positions that appear in both lists contribute. Expanding both vectors to full length and multiplying would work, but it throws away the point of the sparse format and could use far more memory than the entries need.
Each list is sorted by index, so a two-pointer scan can find the common indices. Keep a pointer i into first and j into second, and compare the index parts of the current entries:
- If
first's index is smaller,secondhas no entry at that position, so its value there is 0 and the product is 0. Skip it by advancingi. - If
second's index is smaller, advancejfor the same reason. - If the indices are equal, both vectors are non-zero here. Multiply the values, add the product to the total, and advance both pointers.
Every comparison advances at least one pointer, so the work is proportional to the number of entries in the two lists, not to the length of the vectors.
On the example: indices 0 and 1 differ, and 0 is smaller, so i moves. Now 3 meets 1, and 1 is smaller, so j moves. Then 3 meets 3, the values 9 and 4 give 36, and both pointers move. The second list is used up, so the loop ends with 36.
Step by step
- Declare the pointers and the total.
iandjstart at 0, andresultstarts at 0. - Loop while both lists have entries left. Compare each pointer with its own list's
Length. Once either list is exhausted, no further common index is possible. - Skip the smaller index in
first. The entry's index is element 0 of the pair, so the test isfirst[i][0] < second[j][0]. - Skip the smaller index in
secondwith the mirror case, in anelse if. - Handle a match in the
elsebranch. If neither index is smaller, they are equal. The values are element 1 of each pair, so multiplyfirst[i][1]bysecond[j][1]and add the product toresult. - Advance both pointers in that branch, so the matched entries are not used again.
- Return
result. Entries left in one list have no partner in the other, so they add nothing.
The solution in C#
class Solution {
public int Dot(int[][] first, int[][] second) {
int i = 0, j = 0;
int result = 0;
while (i < first.Length && j < second.Length) {
if (first[i][0] < second[j][0]) {
i++;
} else if (first[i][0] > second[j][0]) {
j++;
} else {
result += first[i][1] * second[j][1];
i++;
j++;
}
}
return result;
}
}
C# notes:
int[][]is a jagged array: an array whose elements are themselves arrays.first[i]is thei-th pair, anint[]of length 2, andfirst[i][0]is that pair's index. It is different from the rectangularint[,], which is indexed asa[i, j].- Jagged arrays are built by hand, for example
new int[][] { new int[] {0, 6}, new int[] {3, 9} }, or with the shorternew[] { new[] {0, 6}, new[] {3, 9} }when the element types can be inferred. +=adds the right side to the variable, soresult += xmeansresult = result + x.- Overflow wraps silently. In normal default (unchecked) code, an
intproduct or sum that is too large does not throw. For example,100000 * 100000stored in anintgives 1410065408. The lesson's values are small enough that this does not arise.
Complexity
Time: O(n + m), where n and m are the numbers of non-zero entries in the two vectors. Every iteration advances at least one pointer, and neither pointer moves backward.
Space: O(1). The method uses three integers and builds no new arrays.
Edge cases and common mistakes
- No common index. For
[[2, 5]]and[[3, 5]]the pointers never meet on an equal index, so the result is 0. - An empty entry list. A vector of all zeros is an empty list. The loop condition fails immediately and the method returns 0.
- Mixing up index and value. Element 0 of a pair is the position and element 1 is the stored value. Multiplying the indices instead of the values is an easy slip.
- Advancing only one pointer on a match. The matched entry would be compared again on the next pass. Indices are unique within a vector, so the next comparison could not match it, but the extra step is wasted work and obscures the intent. Advance both.
- Comparing values instead of indices in the
ifconditions. Only the index part decides which pointer moves.
Testing
first = [[0, 6], [3, 9], [4, 7]],second = [[1, 3], [3, 4]]gives 36.first = [],second = [[1, 3]]gives 0.first = [[2, 5]],second = [[3, 5]]gives 0.