3484. Design Spreadsheet
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:
-
Spreadsheet(int rows)
: Creates a new spreadsheet with 26 columns and the given number of rows. All cells start with a value of 0. -
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)
-
resetCell(String cell)
: Resets a specific cell back to 0. -
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"
)
- A cell reference (like
- Example formulas:
"=A1+B2"
,"=5+A1"
,"=10+20"
- Always starts with
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).
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:
- Remove the leading
"="
sign - Split the remaining string by the
"+"
operator to get our two operands - 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"
:
- First, we skip the
"="
sign by slicing from index 1:formula[1:]
- Split the remaining string by
"+"
to get the two operands - 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
- If
- 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 EvaluatorExample 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 dictionarysetCell
:O(1)
- Dictionary insertion operationresetCell
:O(1)
- Dictionary deletion operationgetValue
:O(L)
whereL
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)
- The method splits the formula string by "+" which takes
Space Complexity:
__init__
:O(1)
- Creates an empty dictionarysetCell
:O(1)
- Stores a single key-value pairresetCell
:O(1)
- Removes an entry, no additional space neededgetValue
:O(L)
whereL
is the length of the formula string- The
split("+")
operation creates a list of substrings whose total length is proportional toL
- This temporary list requires
O(L)
space - Overall:
O(L)
- The
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
A heap is a ...?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!