Dot Product of Two Sparse Vectors
Implement dot product for vectors with mostly zero entries — store only non-zero indices.
4 min read
hash-map design two-pointer
Problem#
Implement SparseVector from a dense vector and compute dot products efficiently when most entries are zero.
Examples#
v1 = [1,0,0,2,3], v2 = [0,3,0,4,0],dot(v1, v2) = 1*0 + 0*3 + 0*0 + 2*4 + 3*0 = 8.v1 = [0,1,0,0,0], v2 = [0,0,0,0,2],dot = 0.
Constraints#
n <= 10^5. Non-zero counts can be small.
Hints#
Hint
Store sorted
(index, value) pairs; on dot, march two pointers along each side. Approach#
Store non-zero entries as sorted vector<pair<int,int>>. On dot, use two pointers; advance whichever index is smaller, and on equality multiply and advance both.
Solution#
class SparseVector { vector<pair<int,int>> data; // (index, value), sorted by indexpublic: SparseVector(vector<int>& nums) { for (int i = 0; i < (int)nums.size(); ++i) if (nums[i] != 0) data.emplace_back(i, nums[i]); }
int dotProduct(SparseVector& vec) { int i = 0, j = 0, ans = 0; auto& A = data; auto& B = vec.data; while (i < (int)A.size() && j < (int)B.size()) { if (A[i].first == B[j].first) { ans += A[i].second * B[j].second; ++i; ++j; } else if (A[i].first < B[j].first) ++i; else ++j; } return ans; }};type SparseVector struct { data [][2]int // (index, value), sorted by index}
func Constructor(nums []int) SparseVector { sv := SparseVector{} for i, v := range nums { if v != 0 { sv.data = append(sv.data, [2]int{i, v}) } } return sv}
func (sv *SparseVector) DotProduct(vec SparseVector) int { i, j, ans := 0, 0, 0 A, B := sv.data, vec.data for i < len(A) && j < len(B) { switch { case A[i][0] == B[j][0]: ans += A[i][1] * B[j][1] i++ j++ case A[i][0] < B[j][0]: i++ default: j++ } } return ans}from typing import List
class SparseVector: def __init__(self, nums: List[int]): self.data = [(i, v) for i, v in enumerate(nums) if v != 0]
def dotProduct(self, vec: 'SparseVector') -> int: i, j, ans = 0, 0, 0 A, B = self.data, vec.data while i < len(A) and j < len(B): if A[i][0] == B[j][0]: ans += A[i][1] * B[j][1] i += 1 j += 1 elif A[i][0] < B[j][0]: i += 1 else: j += 1 return ansvar SparseVector = function(nums) { this.data = []; // [index, value], sorted by index for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) this.data.push([i, nums[i]]); }};
SparseVector.prototype.dotProduct = function(vec) { let i = 0, j = 0, ans = 0; const A = this.data, B = vec.data; while (i < A.length && j < B.length) { if (A[i][0] === B[j][0]) { ans += A[i][1] * B[j][1]; i++; j++; } else if (A[i][0] < B[j][0]) { i++; } else { j++; } } return ans;};class SparseVector { private List<int[]> data; // (index, value), sorted by index
SparseVector(int[] nums) { data = new ArrayList<>(); for (int i = 0; i < nums.length; i++) { if (nums[i] != 0) data.add(new int[]{i, nums[i]}); } }
public int dotProduct(SparseVector vec) { int i = 0, j = 0, ans = 0; List<int[]> A = data, B = vec.data; while (i < A.size() && j < B.size()) { int[] a = A.get(i), b = B.get(j); if (a[0] == b[0]) { ans += a[1] * b[1]; i++; j++; } else if (a[0] < b[0]) i++; else j++; } return ans; }}class SparseVector { private data: Array<[number, number]> = []; // [index, value], sorted by index
constructor(nums: number[]) { for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) this.data.push([i, nums[i]]); } }
dotProduct(vec: SparseVector): number { let i = 0, j = 0, ans = 0; const A = this.data, B = vec.data; while (i < A.length && j < B.length) { if (A[i][0] === B[j][0]) { ans += A[i][1] * B[j][1]; i++; j++; } else if (A[i][0] < B[j][0]) { i++; } else { j++; } } return ans; }}Editorial#
For two sparse vectors with non-zero counts k1, k2, two-pointer dot product runs in O(k1 + k2) — strictly better than scanning the dense form when both are sparse. If one vector is dense and the other sparse, iterating only the sparse one’s entries and indexing into the dense one is even faster.
Complexity#
- Time: O(k1 + k2).
- Space: O(nnz).
Concept revision#
Related#