LC 0311 [M] Sparse Matrix Multiplication - ALawliet/algorithms GitHub Wiki

https://www.youtube.com/watch?v=3cdeehULUBo

https://leetcode.com/problems/sparse-matrix-multiplication/discuss/76151/54ms-Detailed-Summary-of-Easiest-JAVA-solutions-Beating-99.9

https://en.wikipedia.org/wiki/Sparse_matrix#Dictionary_of_keys_(DOK)


matmul:

  • The number of columns of the 1st matrix must equal the number of rows of the 2nd matrix.
  • And the result will have the same number of rows as the 1st matrix, and the same number of columns as the 2nd matrix.

If the matrix is relatively sparse, we can take advantage of "result will have the same number of columns as the 2nd matrix". We can use a DOK to skip a for loop and skip zeroes because they don't affect the sum.

b) The smart solution, the key part of smart solution is that: it does not calculate the final result at once, and it takes each value from A, and calculate and partial sum and accumulate it into the final spot:
For example, for each value A[i][k], if it is not zero, it will be used at most nB times ( n is B[0].length ), which can be illustrated as follow:
Generally for the following equation:
C[ i ][ j ] = A[ i ][0]*B[0][j] + A[i][1]*B[1][j] + A[i][2]*B[2][j] + ... A[i][k]*B[k][j] .... A[i][K]*B[K][j]
j can be from 0 to nB, if we write all of them down, it will like following:
[For i from 0 to nB
C[ i ][ 0 ]=A[ i ][0]*B[0][0] + A[i][1]*B[1][0] + A[i][2]*B[2][0] + ... A[i][k]B[k][0] .... A[i][K]*B[K][0]
C[ i ][ 1 ]=A[ i ][0]*B[0][1] + A[i][1]*B[1][1] + A[i][2]*B[2][1] + ... A[i][k]B[k][0] .... A[i][K]*B[K][1]
...
C[ i ][ nB ]=A[ i ][0]*B[0][nB] + A[i][1]*B[1][nB] + A[i][2]*B[2][nB] + ... A[i][k]B[k][nB] .... A[i][K]*B[K][nB]

As you can see from above: for the same value A[i][k] from the first matrix, it will be used at most nB times if A[i][k] is not zero. And the smart solution is taking advantage of that!!!, the smart solution can be described as:

For each value A[i][k] in matrix A, if it is not zero, we calculate A[i][k] * B[k][j] and accumulate it into C[ i ][ j ] (Key part: the C[ i ][ j ] by now is not the final value in the result matrix !! Remember, in the brute force solution, the final value of C[i][j], takes sum of all multiplication values of K corresponding values from A and B? here C[ i ][ j ] is only sum of some multiplication values, NOT ALL until the program is done )

BY NOW, it is very clear that, if the value A[ i ][ k ] from matrix is zero, we skip a For-loop- calculation, which is a loop iterating nB times, and this is the key part of why the smart solution is smart!!!

class SparseMatrix(object):
    def __init__(self, nrow, ncol, S):
        self.nrow = nrow
        self.ncol = ncol
        self.S = S
    
    @staticmethod
    def to_sparse(M):
        S = dict()
        for r, row in enumerate(M):
            for c, value in enumerate(row):
                if value: S[(r, c)] = value
        return S
    
    @classmethod
    def from_dense(cls, M):
        nrow, ncol = len(M), len(M[0])
        S = cls.to_sparse(M)
        return cls(nrow, ncol, S)
    
    @classmethod
    def from_sparse(cls, nrow, ncol, S):
        return cls(nrow, ncol, S)
    
    def __matmul__(self, B):
        A = self.S
        C = dict()

        for (a_r, a_c), a_val in A.items():
            for b_c in range(B.ncol): 
                # if B.S.get((a_c, b_c), 0) != 0
                if (a_c, b_c) in B.S: 
                    b_val = B.S[(a_c, b_c)]
                    C[(a_r, b_c)] = C.get((a_r, b_c), 0) + a_val * b_val # we
        return self.from_sparse(self.nrow, B.ncol, C)
    
    def to_dense(self):
        M = [[0] * self.ncol for _ in range(self.nrow)]
        for (r, c), value in self.S.items():
            M[r][c] = value
        return M
        
class Solution:
    def multiply(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
        A = SparseMatrix.from_dense(A) # O(n^2)
        B = SparseMatrix.from_dense(B) # O(n^2)
        C = A @ B # O(K) where K is ~ non-zero elements in C
        return C.to_dense()