Facebook Pixel

3484. Design Spreadsheet

MediumDesignArrayHash TableStringMatrix
Leetcode Link

Problem Description

You need to implement a simple spreadsheet system with 26 columns (labeled 'A' to 'Z') and a specified number of rows. Each cell can store an integer value between 0 and 100,000.

The Spreadsheet class should support these operations:

  1. Spreadsheet(int rows): Creates a new spreadsheet with 26 columns and the given number of rows. All cells start with a value of 0.

  2. setCell(String cell, int value): Sets a specific cell to a given value. The cell is identified by a string like "A1" or "B10", where:

    • The letter indicates the column ('A' through 'Z')
    • The number indicates the row (starting from 1, not 0)
  3. resetCell(String cell): Resets a specific cell back to 0.

  4. getValue(String formula): Evaluates a simple addition formula and returns the result. The formula:

    • Always starts with "="
    • Contains exactly two operands separated by "+"
    • Each operand can be either:
      • A cell reference (like "A1")
      • A non-negative integer (like "5")
    • Example formulas: "=A1+B2", "=5+A1", "=10+20"

An important detail: If getValue references a cell that was never explicitly set using setCell, that cell's value is treated as 0.

The solution uses a hash table to store only the cells that have been explicitly set, which saves memory since unset cells default to 0. When evaluating formulas, it parses the string after the "=" sign, splits by "+", and for each part either converts it to an integer (if it starts with a digit) or looks it up in the hash table (defaulting to 0 if not found).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that most cells in a spreadsheet will remain at their default value of 0. If we were to create a 2D array to represent all possible cells, we'd waste a lot of memory storing zeros. For instance, a spreadsheet with 1000 rows would require 26,000 cells (26 columns × 1000 rows), even if we only use a handful of them.

Instead, we can use a hash table (dictionary) to store only the cells that have been explicitly set to non-zero values. This sparse representation is much more memory-efficient. When we need to retrieve a cell's value, we simply check if it exists in our hash table - if it does, we return its stored value; if not, we know it's still at the default value of 0.

For the formula evaluation in getValue, we need to parse a simple addition expression. Since the formula always follows the pattern "=X+Y", we can:

  1. Remove the leading "=" sign
  2. Split the remaining string by the "+" operator to get our two operands
  3. For each operand, determine if it's a number or a cell reference

To distinguish between a number and a cell reference, we can check the first character - if it's a digit, the entire operand is a number that we can directly convert to an integer. If it starts with a letter ('A' to 'Z'), it's a cell reference that we need to look up in our hash table.

The resetCell operation becomes trivial with this approach - we simply remove the cell from our hash table, effectively returning it to its default value of 0 without explicitly storing that zero.

This design elegantly handles the sparse nature of spreadsheet data while keeping all operations simple and efficient.

Solution Approach

The implementation uses a hash table to efficiently store and retrieve cell values. Let's walk through each method:

Data Structure Choice: We use a dictionary self.d = {} to store cell values. The keys are cell references (strings like "A1", "B2") and the values are integers. This sparse representation only stores cells that have been explicitly set.

__init__(self, rows: int): The constructor initializes an empty dictionary. Interestingly, we don't actually use the rows parameter in our implementation since we're using a sparse representation - we only store cells when they're explicitly set, regardless of the spreadsheet's dimensions.

setCell(self, cell: str, value: int): This method directly stores the value in our dictionary:

self.d[cell] = value

The cell reference string (e.g., "A1") serves as the key, and the integer value is stored directly.

resetCell(self, cell: str): To reset a cell to 0, we remove it from the dictionary:

self.d.pop(cell, None)

Using pop with None as the default ensures no error is thrown if the cell wasn't previously set. After removal, any future reference to this cell will return 0 (the default value).

getValue(self, formula: str): This is the most complex method. It evaluates formulas of the form "=X+Y":

  1. First, we skip the "=" sign by slicing from index 1: formula[1:]
  2. Split the remaining string by "+" to get the two operands
  3. For each operand, we check if it's a number or a cell reference:
    • If cell[0].isdigit() is true, the operand is a number, so we convert it directly: int(cell)
    • Otherwise, it's a cell reference, so we look it up in our dictionary: self.d.get(cell, 0)
    • The get method with default value 0 handles cells that haven't been set
  4. Accumulate the sum and return the result

The elegance of this approach lies in its simplicity - by using get(cell, 0), we automatically handle unset cells without any special case logic, and the sparse representation keeps memory usage minimal.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to see how the solution works:

Step 1: Create a spreadsheet with 3 rows

spreadsheet = Spreadsheet(3)
# Internal state: d = {}  (empty dictionary)

Step 2: Set some cells

spreadsheet.setCell("A1", 10)
# Internal state: d = {"A1": 10}

spreadsheet.setCell("B2", 25)
# Internal state: d = {"A1": 10, "B2": 25}

spreadsheet.setCell("C1", 5)
# Internal state: d = {"A1": 10, "B2": 25, "C1": 5}

Step 3: Evaluate formula with two cell references

result = spreadsheet.getValue("=A1+B2")
# Process:
# 1. Remove "=" → "A1+B2"
# 2. Split by "+" → ["A1", "B2"]
# 3. Evaluate each operand:
#    - "A1": starts with 'A' (letter) → lookup d.get("A1", 0) = 10
#    - "B2": starts with 'B' (letter) → lookup d.get("B2", 0) = 25
# 4. Sum: 10 + 25 = 35
# Returns: 35

Step 4: Evaluate formula with cell reference and number

result = spreadsheet.getValue("=A1+15")
# Process:
# 1. Remove "=" → "A1+15"
# 2. Split by "+" → ["A1", "15"]
# 3. Evaluate each operand:
#    - "A1": starts with 'A' (letter) → lookup d.get("A1", 0) = 10
#    - "15": starts with '1' (digit) → convert to int(15) = 15
# 4. Sum: 10 + 15 = 25
# Returns: 25

Step 5: Reference an unset cell

result = spreadsheet.getValue("=D1+A1")
# Process:
# 1. Remove "=" → "D1+A1"
# 2. Split by "+" → ["D1", "A1"]
# 3. Evaluate each operand:
#    - "D1": starts with 'D' (letter) → lookup d.get("D1", 0) = 0 (not in dictionary)
#    - "A1": starts with 'A' (letter) → lookup d.get("A1", 0) = 10
# 4. Sum: 0 + 10 = 10
# Returns: 10

Step 6: Reset a cell and reference it

spreadsheet.resetCell("A1")
# Internal state: d = {"B2": 25, "C1": 5}  (A1 removed)

result = spreadsheet.getValue("=A1+B2")
# Process:
# 1. Remove "=" → "A1+B2"
# 2. Split by "+" → ["A1", "B2"]
# 3. Evaluate each operand:
#    - "A1": starts with 'A' (letter) → lookup d.get("A1", 0) = 0 (was reset)
#    - "B2": starts with 'B' (letter) → lookup d.get("B2", 0) = 25
# 4. Sum: 0 + 25 = 25
# Returns: 25

This walkthrough demonstrates how the sparse dictionary representation efficiently handles both set and unset cells, while the formula parser correctly distinguishes between numeric literals and cell references by checking the first character.

Solution Implementation

1class Spreadsheet:
2    def __init__(self, rows: int) -> None:
3        """
4        Initialize the spreadsheet with a given number of rows.
5      
6        Args:
7            rows: Number of rows in the spreadsheet (currently unused in implementation)
8        """
9        # Dictionary to store cell values, where key is cell name (e.g., "A1") and value is integer
10        self.cells_data = {}
11
12    def setCell(self, cell: str, value: int) -> None:
13        """
14        Set the value of a specific cell.
15      
16        Args:
17            cell: Cell identifier (e.g., "A1", "B2")
18            value: Integer value to store in the cell
19        """
20        self.cells_data[cell] = value
21
22    def resetCell(self, cell: str) -> None:
23        """
24        Reset (delete) a cell's value from the spreadsheet.
25      
26        Args:
27            cell: Cell identifier to reset
28        """
29        # Remove the cell from dictionary if it exists, otherwise do nothing
30        self.cells_data.pop(cell, None)
31
32    def getValue(self, formula: str) -> int:
33        """
34        Calculate and return the value of a formula.
35      
36        The formula format is "=<expression>" where expression can be:
37        - A single number (e.g., "=5")
38        - A single cell reference (e.g., "=A1")
39        - Sum of numbers and/or cell references separated by "+" (e.g., "=A1+B2+5")
40      
41        Args:
42            formula: Formula string starting with "="
43          
44        Returns:
45            Calculated integer value of the formula
46        """
47        # Initialize result accumulator
48        result = 0
49      
50        # Remove the leading "=" and split by "+" to get individual terms
51        terms = formula[1:].split("+")
52      
53        # Process each term in the formula
54        for term in terms:
55            # Check if the term is a number or a cell reference
56            if term[0].isdigit():
57                # If first character is a digit, treat entire term as a number
58                result += int(term)
59            else:
60                # Otherwise, treat it as a cell reference and get its value (default to 0 if not found)
61                result += self.cells_data.get(term, 0)
62      
63        return result
64
65
66# Your Spreadsheet object will be instantiated and called as such:
67# obj = Spreadsheet(rows)
68# obj.setCell(cell, value)
69# obj.resetCell(cell)
70# param_3 = obj.getValue(formula)
71
1class Spreadsheet {
2    // Map to store cell values, where key is cell name (e.g., "A1") and value is the integer value
3    private Map<String, Integer> cellValues;
4
5    /**
6     * Constructor to initialize the spreadsheet with a given number of rows
7     * @param rows The number of rows in the spreadsheet (currently unused in this implementation)
8     */
9    public Spreadsheet(int rows) {
10        this.cellValues = new HashMap<>();
11    }
12
13    /**
14     * Sets the value of a specific cell
15     * @param cell The cell identifier (e.g., "A1", "B2")
16     * @param value The integer value to store in the cell
17     */
18    public void setCell(String cell, int value) {
19        cellValues.put(cell, value);
20    }
21
22    /**
23     * Resets (removes) a cell from the spreadsheet
24     * @param cell The cell identifier to reset
25     */
26    public void resetCell(String cell) {
27        cellValues.remove(cell);
28    }
29
30    /**
31     * Evaluates a formula and returns the computed value
32     * Formula format: "=A1+B2+5" (starts with '=' and contains cell references and/or numbers separated by '+')
33     * @param formula The formula string to evaluate
34     * @return The computed sum of all values in the formula
35     */
36    public int getValue(String formula) {
37        int sum = 0;
38      
39        // Remove the leading '=' and split by '+' to get individual terms
40        String[] terms = formula.substring(1).split("\\+");
41      
42        // Process each term in the formula
43        for (String term : terms) {
44            // Check if the term is a number or a cell reference
45            if (Character.isDigit(term.charAt(0))) {
46                // If it starts with a digit, parse it as a number
47                sum += Integer.parseInt(term);
48            } else {
49                // Otherwise, treat it as a cell reference and get its value (default to 0 if not found)
50                sum += cellValues.getOrDefault(term, 0);
51            }
52        }
53      
54        return sum;
55    }
56}
57
58/**
59 * Your Spreadsheet object will be instantiated and called as such:
60 * Spreadsheet obj = new Spreadsheet(rows);
61 * obj.setCell(cell,value);
62 * obj.resetCell(cell);
63 * int param_3 = obj.getValue(formula);
64 */
65
1class Spreadsheet {
2private:
3    // Hash map to store cell values with cell name as key
4    unordered_map<string, int> cellValues;
5
6public:
7    // Constructor: Initialize spreadsheet with given number of rows
8    // Note: rows parameter is not used in this implementation
9    Spreadsheet(int rows) {
10        // No initialization needed for the hash map
11    }
12
13    // Set the value of a specific cell
14    void setCell(string cell, int value) {
15        cellValues[cell] = value;
16    }
17
18    // Reset a cell by removing it from the spreadsheet
19    void resetCell(string cell) {
20        cellValues.erase(cell);
21    }
22
23    // Calculate and return the value based on the given formula
24    // Formula format: "=cell1+cell2+...+celln" or "=value1+value2+...+valuen"
25    // Can also be a mix of cells and numeric values
26    int getValue(string formula) {
27        int result = 0;
28      
29        // Remove the '=' prefix and create a string stream for parsing
30        stringstream formulaStream(formula.substr(1));
31        string token;
32      
33        // Parse the formula by splitting on '+' delimiter
34        while (getline(formulaStream, token, '+')) {
35            // Check if token is a numeric value
36            if (isdigit(token[0])) {
37                // Convert string to integer and add to result
38                result += stoi(token);
39            } else {
40                // Token is a cell reference
41                // Add cell value if it exists, otherwise add 0
42                result += cellValues.count(token) ? cellValues[token] : 0;
43            }
44        }
45      
46        return result;
47    }
48};
49
50/**
51 * Your Spreadsheet object will be instantiated and called as such:
52 * Spreadsheet* obj = new Spreadsheet(rows);
53 * obj->setCell(cell,value);
54 * obj->resetCell(cell);
55 * int param_3 = obj->getValue(formula);
56 */
57
1// Map to store cell values where key is cell name (e.g., "A1") and value is the cell's numeric value
2let spreadsheetData: Map<string, number>;
3
4/**
5 * Initializes the spreadsheet with the specified number of rows
6 * @param rows - The number of rows in the spreadsheet
7 */
8function initSpreadsheet(rows: number): void {
9    spreadsheetData = new Map<string, number>();
10}
11
12/**
13 * Sets the value of a specific cell in the spreadsheet
14 * @param cell - The cell identifier (e.g., "A1", "B2")
15 * @param value - The numeric value to set in the cell
16 */
17function setCell(cell: string, value: number): void {
18    spreadsheetData.set(cell, value);
19}
20
21/**
22 * Resets a cell by removing its value from the spreadsheet
23 * @param cell - The cell identifier to reset
24 */
25function resetCell(cell: string): void {
26    spreadsheetData.delete(cell);
27}
28
29/**
30 * Evaluates a formula and returns the calculated value
31 * Formula format: "=A1+B2+5" where cells and numbers are separated by '+'
32 * @param formula - The formula string starting with '=' followed by cell references and/or numbers
33 * @returns The sum of all referenced cell values and numeric literals
34 */
35function getValue(formula: string): number {
36    let sum: number = 0;
37  
38    // Remove the leading '=' and split by '+' to get individual terms
39    const terms: string[] = formula.slice(1).split('+');
40  
41    // Process each term in the formula
42    for (const term of terms) {
43        // Check if the term is a number or a cell reference
44        const numericValue: number = Number(term);
45      
46        if (isNaN(numericValue)) {
47            // Term is a cell reference - get its value from the map (default to 0 if not found)
48            sum += spreadsheetData.get(term) || 0;
49        } else {
50            // Term is a numeric literal - add it directly
51            sum += numericValue;
52        }
53    }
54  
55    return sum;
56}
57

Time and Space Complexity

Time Complexity:

  • __init__: O(1) - Simply initializes an empty dictionary
  • setCell: O(1) - Dictionary insertion operation
  • resetCell: O(1) - Dictionary deletion operation
  • getValue: O(L) where L is the length of the formula string
    • The method splits the formula string by "+" which takes O(L) time
    • It then iterates through each cell/value in the split result
    • For each element, it either converts to integer or looks up in dictionary (both O(1) operations)
    • The number of elements after splitting is proportional to L
    • Overall: O(L)

Space Complexity:

  • __init__: O(1) - Creates an empty dictionary
  • setCell: O(1) - Stores a single key-value pair
  • resetCell: O(1) - Removes an entry, no additional space needed
  • getValue: O(L) where L is the length of the formula string
    • The split("+") operation creates a list of substrings whose total length is proportional to L
    • This temporary list requires O(L) space
    • Overall: O(L)

The dominant operations are in getValue, giving the overall time complexity of O(L) and space complexity of O(L), where L is the length of the formula string.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Not Handling Edge Cases in Formula Parsing

The current implementation assumes the formula is always well-formed. It doesn't validate:

  • Whether the formula actually starts with "="
  • Whether there are exactly two operands
  • Whether cell references are valid (e.g., "AA1" or "A0" would be processed incorrectly)

Problem Example:

spreadsheet.getValue("A1+B2")  # Missing "=" - would crash
spreadsheet.getValue("=A1")     # Single operand - works but may not be intended
spreadsheet.getValue("=A1+B2+C3")  # Three operands - works but exceeds spec

Solution: Add validation to ensure the formula meets specifications:

def getValue(self, formula: str) -> int:
    # Validate formula starts with "="
    if not formula.startswith("="):
        raise ValueError("Formula must start with '='")
  
    # Remove "=" and split by "+"
    expression = formula[1:]
    terms = expression.split("+")
  
    # Validate exactly two operands
    if len(terms) != 2:
        raise ValueError("Formula must have exactly two operands")
  
    result = 0
    for term in terms:
        if term[0].isdigit():
            result += int(term)
        else:
            # Optionally validate cell reference format
            if not self._is_valid_cell(term):
                raise ValueError(f"Invalid cell reference: {term}")
            result += self.cells_data.get(term, 0)
  
    return result

def _is_valid_cell(self, cell: str) -> bool:
    """Validate cell reference format (e.g., A1-Z999)"""
    if len(cell) < 2:
        return False
    if cell[0] not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
        return False
    try:
        row = int(cell[1:])
        return row > 0  # Row numbers start from 1
    except ValueError:
        return False

2. Memory Leak with resetCell

While using pop() is correct, repeatedly setting and resetting cells without bounds checking could theoretically allow unlimited cell references to be created if the input isn't validated.

Problem Example:

# Malicious or buggy code could create arbitrary cell references
for i in range(1000000):
    spreadsheet.setCell(f"A{i}", 100)
    spreadsheet.resetCell(f"A{i}")

Solution: Add bounds checking in setCell:

def __init__(self, rows: int) -> None:
    self.cells_data = {}
    self.max_rows = rows  # Store the row limit

def setCell(self, cell: str, value: int) -> None:
    # Extract and validate row number
    col = cell[0]
    row_num = int(cell[1:])
  
    if row_num < 1 or row_num > self.max_rows:
        raise ValueError(f"Row {row_num} out of bounds (1-{self.max_rows})")
  
    if col not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
        raise ValueError(f"Invalid column: {col}")
  
    self.cells_data[cell] = value

3. Integer Overflow in getValue

The problem states cell values are between 0 and 100,000, but adding two maximum values could exceed typical integer bounds in some languages (though Python handles big integers automatically).

Problem Example:

spreadsheet.setCell("A1", 100000)
spreadsheet.setCell("B1", 100000)
result = spreadsheet.getValue("=A1+B1")  # 200,000

Solution: Add overflow checking if needed:

def getValue(self, formula: str) -> int:
    # ... existing parsing code ...
  
    if result > 100000:  # Or whatever the maximum allowed result is
        raise ValueError(f"Formula result {result} exceeds maximum allowed value")
  
    return result
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

A heap is a ...?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More