Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

631. Design Excel Sum Formula - Leetcode Solution

Problem Description

The "Design Excel Sum Formula" problem asks you to implement a simplified version of an Excel spreadsheet. Specifically, you need to support the following operations for a table of size H x W (height and width):

  • set(r, c, val): Set the value of the cell at row r and column c to val.
  • get(r, c): Return the current value of the cell at row r and column c.
  • sum(r, c, strs): Set the cell at (r, c) to be the sum of the cells/ranges described by strs. Each string in strs can be a single cell (like "A1") or a rectangular range (like "A1:B2"). If a cell is set to a sum, its value must update automatically when any of the referenced cells change.

You must maintain dependencies so that sum formulas update dynamically, just like in Excel. The key constraints are:

  • Cell references are in Excel format (e.g., "A1", "C2").
  • Formulas can reference overlapping cells and ranges.
  • Setting a value overwrites any existing formula for that cell.
  • The number of rows and columns is fixed at initialization.

Thought Process

The challenge is to support both direct value assignments and dynamic sum formulas, while ensuring changes propagate through all dependent cells. At first glance, a brute-force approach might seem possible: whenever a sum is set, compute the sum and store it. However, if a referenced cell changes, all formulas depending on it must also update, potentially leading to inefficient recalculations.

To optimize, we need to track dependencies between cells—essentially building a graph where each cell knows which other cells depend on it. This way, when a cell's value changes, we can efficiently propagate updates only to affected cells. This is similar to how spreadsheets like Excel manage formulas and dependencies.

The main conceptual shift is from recalculating everything on each change to smartly propagating changes using dependency tracking, minimizing redundant work and ensuring correctness.

Solution Approach

  • Cell Representation: Each cell can either have a direct value or be defined by a sum formula. We need to store both the value and, if applicable, the formula (which cells/ranges it sums).
  • Dependency Tracking: For each cell, we keep track of which other cells depend on it (reverse dependencies). This allows us to propagate changes efficiently.
  • Parsing References: We need to convert Excel-style references (like "A1") into row and column indices. For ranges (like "A1:B2"), we expand them into all constituent cells.
  • Set Operation: When setting a value, we clear any existing formula and update the value. Then, we propagate the change to all dependent cells recursively.
  • Sum Operation: When setting a sum formula, we record the formula (which cells/ranges are referenced) and compute the initial sum. We also update dependencies so that if any referenced cell changes, the sum cell is notified and updated.
  • Propagation: Whenever a cell's value changes (either by set or because a referenced cell changed), we recursively update all cells that depend on it.
  • Edge Cases: Overwriting a formula with a direct value removes all dependencies for that cell.

This approach ensures that updates are efficient and only affect relevant cells, similar to how real spreadsheet software handles cell formulas and dependencies.

Example Walkthrough

Consider a 3x3 spreadsheet (rows 1-3, columns A-C). Let's walk through these operations:

  1. set(1, "A", 5) sets cell A1 to 5.
  2. set(1, "B", 3) sets cell B1 to 3.
  3. sum(1, "C", ["A1", "B1"]) sets C1 to the sum of A1 and B1 (5 + 3 = 8).
  4. get(1, "C") returns 8.
  5. set(1, "A", 10) updates A1 to 10. Since C1 depends on A1, C1 is updated to 10 + 3 = 13.
  6. get(1, "C") now returns 13.

Note how updating A1 automatically causes C1 to update, because C1's formula tracks dependencies on A1 and B1.

Time and Space Complexity

  • Brute-force Approach:
    • Every time a cell changes, recompute all sum formulas in the entire spreadsheet.
    • Time Complexity: O(R x C x F), where F is the number of formulas—very inefficient.
    • Space Complexity: O(R x C), for storing cell values and formulas.
  • Optimized Approach (with Dependency Tracking):
    • When a cell changes, only update its dependents recursively.
    • Time Complexity: O(K), where K is the number of affected dependent cells (often much less than R x C).
    • Space Complexity: O(R x C + E), where E is the total number of dependency edges (i.e., sum of all dependencies for all formulas).

The optimized approach is much more scalable, especially for large spreadsheets with many formulas.

Summary

The "Design Excel Sum Formula" problem is a classic example of dependency management, similar to how spreadsheets handle formulas. The key insight is to track dependencies between cells so that updates propagate efficiently and automatically. By representing formulas, values, and dependencies explicitly, we ensure correctness and performance. This approach mirrors real-world spreadsheet engines and teaches valuable lessons about graphs, propagation, and efficient data structures.

Code Implementation

class Excel:
    def __init__(self, H: int, W: str):
        self.H = H
        self.W = ord(W) - ord('A') + 1
        self.cells = [[{'val': 0, 'formula': None} for _ in range(self.W)] for _ in range(self.H)]
        self.dependents = [[set() for _ in range(self.W)] for _ in range(self.H)]
        self.depends_on = [[set() for _ in range(self.W)] for _ in range(self.H)]

    def _parse_ref(self, ref):
        col = ord(ref[0]) - ord('A')
        row = int(ref[1:]) - 1
        return row, col

    def _expand_refs(self, strs):
        refs = []
        for s in strs:
            if ':' in s:
                start, end = s.split(':')
                r1, c1 = self._parse_ref(start)
                r2, c2 = self._parse_ref(end)
                for i in range(min(r1, r2), max(r1, r2)+1):
                    for j in range(min(c1, c2), max(c1, c2)+1):
                        refs.append((i, j))
            else:
                refs.append(self._parse_ref(s))
        return refs

    def set(self, r: int, c: str, v: int) -> None:
        row, col = r-1, ord(c)-ord('A')
        # Remove old dependencies if any
        if self.cells[row][col]['formula'] is not None:
            for dep in self.depends_on[row][col]:
                self.dependents[dep[0]][dep[1]].discard((row, col))
            self.depends_on[row][col] = set()
            self.cells[row][col]['formula'] = None
        self.cells[row][col]['val'] = v
        self._propagate(row, col)

    def get(self, r: int, c: str) -> int:
        row, col = r-1, ord(c)-ord('A')
        return self.cells[row][col]['val']

    def sum(self, r: int, c: str, strs: [str]) -> int:
        row, col = r-1, ord(c)-ord('A')
        refs = self._expand_refs(strs)
        # Remove old dependencies if any
        if self.cells[row][col]['formula'] is not None:
            for dep in self.depends_on[row][col]:
                self.dependents[dep[0]][dep[1]].discard((row, col))
        self.depends_on[row][col] = set(refs)
        for dep in refs:
            self.dependents[dep[0]][dep[1]].add((row, col))
        self.cells[row][col]['formula'] = list(refs)
        self.cells[row][col]['val'] = sum(self.cells[i][j]['val'] for (i, j) in refs)
        self._propagate(row, col)
        return self.cells[row][col]['val']

    def _propagate(self, row, col):
        # Recompute all dependents recursively
        for dep in self.dependents[row][col]:
            refs = self.cells[dep[0]][dep[1]]['formula']
            if refs is not None:
                self.cells[dep[0]][dep[1]]['val'] = sum(self.cells[i][j]['val'] for (i, j) in refs)
                self._propagate(dep[0], dep[1])
    
class Excel {
public:
    int H, W;
    struct Cell {
        int val = 0;
        vector<pair<int, int>> formula;
    };
    vector<vector<Cell>> cells;
    vector<vector<set<pair<int, int>>>> dependents;
    vector<vector<set<pair<int, int>>>> depends_on;

    Excel(int height, char width) {
        H = height;
        W = width - 'A' + 1;
        cells = vector<vector<Cell>>(H, vector<Cell>(W));
        dependents = vector<vector<set<pair<int, int>>>>(H, vector<set<pair<int, int>>>(W));
        depends_on = vector<vector<set<pair<int, int>>>>(H, vector<set<pair<int, int>>>(W));
    }

    pair<int, int> parse_ref(const string& ref) {
        int col = ref[0] - 'A';
        int row = stoi(ref.substr(1)) - 1;
        return {row, col};
    }

    vector<pair<int, int>> expand_refs(const vector<string>& strs) {
        vector<pair<int, int>> refs;
        for (const auto& s : strs) {
            auto pos = s.find(':');
            if (pos != string::npos) {
                auto start = parse_ref(s.substr(0, pos));
                auto end = parse_ref(s.substr(pos+1));
                for (int i = min(start.first, end.first); i <= max(start.first, end.first); ++i) {
                    for (int j = min(start.second, end.second); j <= max(start.second, end.second); ++j) {
                        refs.emplace_back(i, j);
                    }
                }
            } else {
                refs.push_back(parse_ref(s));
            }
        }
        return refs;
    }

    void set(int r, char c, int v) {
        int row = r - 1, col = c - 'A';
        // Remove old dependencies
        if (!cells[row][col].formula.empty()) {
            for (auto& dep : depends_on[row][col]) {
                dependents[dep.first][dep.second].erase({row, col});
            }
            depends_on[row][col].clear();
            cells[row][col].formula.clear();
        }
        cells[row][col].val = v;
        propagate(row, col);
    }

    int get(int r, char c) {
        int row = r - 1, col = c - 'A';
        return cells[row][col].val;
    }

    int sum(int r, char c, vector<string> strs) {
        int row = r - 1, col = c - 'A';
        auto refs = expand_refs(strs);
        // Remove old dependencies
        if (!cells[row][col].formula.empty()) {
            for (auto& dep : depends_on[row][col]) {
                dependents[dep.first][dep.second].erase({row, col});
            }
        }
        depends_on[row][col] = set<pair<int, int>>(refs.begin(), refs.end());
        for (auto& dep : refs) {
            dependents[dep.first][dep.second].insert({row, col});
        }
        cells[row][col].formula = refs;
        int s = 0;
        for (auto& p : refs) s += cells[p.first][p.second].val;
        cells[row][col].val = s;
        propagate(row, col);
        return s;
    }

    void propagate(int row, int col) {
        for (auto& dep : dependents[row][col]) {
            auto& refs = cells[dep.first][dep.second].formula;
            if (!refs.empty()) {
                int s = 0;
                for (auto& p : refs) s += cells[p.first][p.second].val;
                cells[dep.first][dep.second].val = s;
                propagate(dep.first, dep.second);
            }
        }
    }
};
    
import java.util.*;

class Excel {
    int H, W;
    Cell[][] cells;
    Set<int[]>[][] dependents;
    Set<int[]>[][] dependsOn;

    static class Cell {
        int val = 0;
        List<int[]> formula = null;
    }

    public Excel(int height, char width) {
        H = height;
        W = width - 'A' + 1;
        cells = new Cell[H][W];
        dependents = new HashSet[H][W];
        dependsOn = new HashSet[H][W];
        for (int i = 0; i < H; ++i) {
            for (int j = 0; j < W; ++j) {
                cells[i][j] = new Cell();
                dependents[i][j] = new HashSet<>();
                dependsOn[i][j] = new HashSet<>();
            }
        }
    }

    private int[] parseRef(String ref) {
        int col = ref.charAt(0) - 'A';
        int row = Integer.parseInt(ref.substring(1)) - 1;
        return new int[]{row, col};
    }

    private List<int[]> expandRefs(List<String> strs) {
        List<int[]> refs = new ArrayList<>();
        for (String s : strs) {
            if (s.contains(":")) {
                String[] parts = s.split(":");
                int[] start = parseRef(parts[0]);
                int[] end = parseRef(parts[1]);
                for (int i = Math.min(start[0], end[0]); i <= Math.max(start[0], end[0]); ++i) {
                    for (int j = Math.min(start[1], end[1]); j <= Math.max(start[1], end[1]); ++j) {
                        refs.add(new int[]{i, j});
                    }
                }
            } else {
                refs.add(parseRef(s));
            }
        }
        return refs;
    }

    public void set(int r, char c, int v) {
        int row = r - 1, col = c - 'A';
        // Remove old dependencies
        if (cells[row][col].formula != null) {
            for (int[] dep : dependsOn[row][col]) {
                dependents[dep[0]][dep[1]].removeIf(x -> x[0] == row && x[1] == col);
            }
            dependsOn[row][col].clear();
            cells[row][col].formula = null;
        }
        cells[row][col].val = v;
        propagate(row, col);
    }

    public int get(int r, char c) {
        int row = r - 1, col = c - 'A';
        return cells[row][col].val;
    }

    public int sum(int r, char c, List<String> strs) {
        int row = r - 1, col = c - 'A';
        List<int[]> refs = expandRefs(strs);
        // Remove old dependencies
        if (cells[row][col].formula != null) {
            for (int[] dep : dependsOn[row][col]) {
                dependents[dep[0]][dep[1]].removeIf(x -> x[0] == row && x[1] == col);
            }
        }
        dependsOn[row][col] = new HashSet<>();
        for (int[] dep : refs) {
            dependsOn[row][col].add(dep);
            dependents[dep[0]][dep[1]].add(new int[]{row, col});
        }
        cells[row][col].formula = refs;
        int s = 0;
        for (int[] p : refs) s += cells[p[0]][p[1]].val;
        cells[row][col].val = s;
        propagate(row, col);
        return s;
    }

    private void propagate(int row, int col) {
        for (int[] dep : dependents[row][col]) {
            List<int[]> refs = cells[dep[0]][dep[1]].formula;
            if (refs != null) {
                int s = 0;
                for (int[] p : refs) s += cells[p[0]][p[1]].val;
                cells[dep[0]][dep[1]].val = s;
                propagate(dep[0], dep[1]);
            }
        }
    }
}
    
class Excel {
    constructor(H, W) {
        this.H = H;
        this.W = W.charCodeAt(0) - 'A'.charCodeAt(0) + 1;
        this.cells = Array.from({length: H}, () => Array.from({length: this.W}, () => ({val: 0, formula: null})));
        this.dependents = Array.from({length: H}, () => Array.from({length: this.W}, () => new Set()));
        this.dependsOn = Array.from({length: H}, () => Array.from({length: this.W}, () => new Set()));
    }

    _parseRef(ref) {
        let col = ref.charCodeAt(0) - 'A'.charCodeAt(0);
        let row = parseInt(ref.slice(1)) - 1;
        return [row, col];
    }

    _expandRefs(strs) {
        let refs = [];
        for (let s of strs) {
            if (s.includes(":")) {
                let [start, end] = s.split(":");
                let [r1, c1] = this._parseRef(start);
                let [r2, c2] = this._parseRef(end);
                for (let i = Math.min(r1, r2); i <= Math.max(r1, r2); ++i) {
                    for (let j = Math.min(c1, c2); j <= Math.max(c1, c2); ++j) {
                        refs.push([i, j]);
                    }
                }
            } else {
                refs.push(this._parseRef(s));
            }
        }
        return refs;
    }

    set(r, c, v) {
        let row = r - 1, col = c.charCodeAt(0) - 'A'.charCodeAt(0);
        // Remove old dependencies
        if (this.cells[row][col].formula !== null) {
            for (let dep of this.dependsOn[row][col]) {
                this.dependents[dep[0]][dep[1]].delete([row, col]);
            }
            this.dependsOn[row][col] = new Set();
            this.cells[row][col].formula = null;
        }
        this.cells[row][col].val = v;
        this._propagate(row, col);
    }

    get(r, c) {
        let row = r - 1, col = c.charCodeAt(0) - 'A'.charCodeAt(0);
        return this.cells[row][col].val;
    }

    sum(r, c, strs) {
        let row = r - 1, col = c.charCodeAt(0) - 'A'.charCodeAt(0);
        let refs = this._expandRefs(strs);
        // Remove old dependencies
        if (this.cells[row][col].formula !== null) {
            for (let dep of this.dependsOn[row][col]) {
                this.dependents[dep[0]][dep[1]].delete([row, col]);
            }
        }
        this.dependsOn[row][col] = new Set(refs.map(x => x));
        for (let dep of refs) {
            this.dependents[dep[0]][dep[1]].add([row, col]);
        }
        this.cells[row][col].formula = refs;
        this.cells[row][col].val = refs.reduce((acc, [i, j]) => acc + this.cells[i][j].val, 0);
        this._propagate(row, col);
        return this.cells[row][col].val;
    }

    _propagate(row, col) {
        for (let dep of this.dependents[row][col]) {
            let refs = this.cells[dep[0]][dep[1]].formula;
            if (refs !== null) {
                this.cells[dep[0]][dep[1]].val = refs.reduce((acc, [i, j]) => acc + this.cells[i][j].val, 0);
                this._propagate(dep[0], dep[1]);
            }
        }
    }
}