LeetCode 1321 - Restaurant Growth
This problem asks us to compute a rolling seven day summary of restaurant revenue. The input table stores individual cus
Difficulty: 🟡 Medium
Topics: Database
Solution
Problem Understanding
This problem asks us to compute a rolling seven day summary of restaurant revenue. The input table stores individual customer transactions. Each row represents how much a single customer spent on a specific date.
The important detail is that multiple customers may visit on the same day, which means we cannot directly apply a moving average on the raw rows. Instead, we first need to aggregate all customer spending per day.
For every day starting from the seventh available day, we must compute:
- The total amount spent during the current day and the previous six days
- The average daily revenue across those seven days
- The average rounded to two decimal places
The result should contain:
visited_on, the ending date of the seven day windowamount, the total revenue across the entire seven day windowaverage_amount, the seven day average revenue
The output must be ordered chronologically by visited_on.
The problem guarantees that there is at least one customer every day. This guarantee is extremely important because it means there are no missing dates in the timeline. Without this guarantee, we would need additional logic to generate missing calendar dates before computing rolling windows.
Consider the example carefully. On 2019-01-10, there are two customer transactions:
- 130 from customer 1
- 150 from customer 3
The daily revenue for 2019-01-10 is therefore:
130 + 150 = 280
The seven day window from 2019-01-04 through 2019-01-10 becomes:
130 + 110 + 140 + 150 + 80 + 110 + 280 = 1000
Then the average is:
1000 / 7 = 142.857...
Rounded to two decimal places:
142.86
A naive implementation can easily make mistakes in several places:
- Forgetting to aggregate multiple transactions occurring on the same day
- Using seven rows instead of seven distinct dates
- Starting output before a full seven day window exists
- Forgetting to round the average properly
- Mishandling ordering of dates
The input guarantees continuous dates, which simplifies the sliding window computation significantly.
Approaches
Brute Force Approach
The brute force solution begins by aggregating daily revenue for every date. After that, for each eligible day, we recompute the total revenue for the previous seven days from scratch.
Suppose we have n distinct days. For every day starting at index 6, we iterate backward across the previous seven days and sum their revenues manually.
This approach is correct because every seven day window is independently computed using the exact definition from the problem statement. However, it performs redundant work. Consecutive windows overlap heavily. When moving from one window to the next, six out of seven days remain the same, yet the brute force solution recalculates everything again.
Although seven is technically constant, the general pattern is inefficient and does not scale well for larger window sizes.
Optimal Approach
The key observation is that adjacent seven day windows differ by only two values:
- One day leaves the window
- One new day enters the window
Instead of recomputing the entire sum every time, we maintain a running window total.
This is a classic sliding window technique.
We first aggregate daily revenue by date. Then we iterate through dates in chronological order while maintaining:
- The current seven day sum
- The current window boundaries
When the window grows beyond seven days, we subtract the oldest day's revenue. This allows every new result to be computed in constant time.
This avoids repeated summation work and produces an efficient linear solution.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n × 7) | O(n) | Recomputes each seven day window independently |
| Optimal | O(n) | O(n) | Uses a sliding window with running totals |
Algorithm Walkthrough
Step 1: Aggregate revenue by date
The raw table contains customer level transactions, but the problem requires day level revenue.
We first group rows by visited_on and compute:
daily_total[date] = sum(amounts for that date)
For example:
| Date | Daily Revenue |
|---|---|
| 2019-01-01 | 100 |
| 2019-01-02 | 110 |
| 2019-01-03 | 120 |
| ... | ... |
This transformation is necessary because the rolling window operates on days, not individual customers.
Step 2: Sort dates chronologically
The sliding window only works correctly when dates are processed in ascending order.
We sort all distinct dates.
Step 3: Initialize the sliding window
We maintain:
window_sum, the total revenue inside the current seven day window- A left pointer representing the oldest day in the window
Initially, both pointers start at the beginning.
Step 4: Expand the window one day at a time
As we iterate through dates:
- Add the current day's revenue to
window_sum - Include the current date in the active window
Step 5: Shrink the window if it exceeds seven days
If the window becomes larger than seven days:
- Remove the oldest day's revenue
- Move the left pointer forward
This guarantees the window always contains at most seven days.
Step 6: Record results once the window size reaches seven
Whenever the window contains exactly seven days:
amount = window_sumaverage_amount = round(window_sum / 7, 2)
Store:
(current_date, amount, average_amount)
Step 7: Return results ordered by date
Because we processed dates chronologically, the results are already sorted correctly.
Why it works
The sliding window always maintains the exact revenue total for the current seven day interval.
At every step:
- New days are added exactly once
- Old days are removed exactly once
- The window never contains fewer or more than seven days when producing output
Therefore, every computed sum and average corresponds precisely to the required seven day period.
Python Solution
from collections import defaultdict
from typing import List
class Solution:
def restaurantGrowth(self, customer: List[List]) -> List[List]:
daily_revenue = defaultdict(int)
# Aggregate revenue by date
for _, _, visited_on, amount in customer:
daily_revenue[visited_on] += amount
# Sort dates
sorted_dates = sorted(daily_revenue.keys())
result = []
left = 0
window_sum = 0
for right in range(len(sorted_dates)):
current_date = sorted_dates[right]
window_sum += daily_revenue[current_date]
# Maintain exactly 7 days in the window
if right - left + 1 > 7:
oldest_date = sorted_dates[left]
window_sum -= daily_revenue[oldest_date]
left += 1
# Record result when window size becomes 7
if right - left + 1 == 7:
average_amount = round(window_sum / 7, 2)
result.append([
current_date,
window_sum,
average_amount
])
return result
The implementation begins with a defaultdict(int) to aggregate daily revenue. This is necessary because multiple customers can appear on the same date.
After aggregation, the dates are sorted so the sliding window processes days in chronological order.
The variables left and right define the active seven day window. As the right pointer advances, the current day's revenue is added to window_sum.
Whenever the window grows larger than seven days, the oldest day's revenue is removed and the left pointer advances. This ensures the window always represents the correct rolling interval.
Once the window reaches exactly seven days, the algorithm computes the average and appends the result.
The solution performs only constant work per day, making it highly efficient.
Go Solution
package main
import (
"math"
"sort"
)
func restaurantGrowth(customer [][]interface{}) [][]interface{} {
dailyRevenue := make(map[string]int)
// Aggregate revenue by date
for _, row := range customer {
visitedOn := row[2].(string)
amount := row[3].(int)
dailyRevenue[visitedOn] += amount
}
// Sort dates
var dates []string
for date := range dailyRevenue {
dates = append(dates, date)
}
sort.Strings(dates)
var result [][]interface{}
left := 0
windowSum := 0
for right := 0; right < len(dates); right++ {
currentDate := dates[right]
windowSum += dailyRevenue[currentDate]
// Keep window size at most 7
if right-left+1 > 7 {
oldestDate := dates[left]
windowSum -= dailyRevenue[oldestDate]
left++
}
// Record result for valid 7-day windows
if right-left+1 == 7 {
average := math.Round((float64(windowSum)/7.0)*100) / 100
result = append(result, []interface{}{
currentDate,
windowSum,
average,
})
}
}
return result
}
The Go implementation follows the same logic as the Python version, but there are several language specific details.
Go does not have Python style dynamic dictionaries with automatic defaults, so a standard map is used for aggregation.
Dates are stored as strings because ISO formatted dates sort correctly lexicographically.
Floating point rounding requires explicit handling using math.Round.
The result type uses [][]interface{} because the rows contain mixed types, strings and numeric values.
Worked Examples
Example 1
Input daily revenue after aggregation:
| Date | Revenue |
|---|---|
| 2019-01-01 | 100 |
| 2019-01-02 | 110 |
| 2019-01-03 | 120 |
| 2019-01-04 | 130 |
| 2019-01-05 | 110 |
| 2019-01-06 | 140 |
| 2019-01-07 | 150 |
| 2019-01-08 | 80 |
| 2019-01-09 | 110 |
| 2019-01-10 | 280 |
Now trace the sliding window:
| Right Date | Window Dates | Window Sum | Output? |
|---|---|---|---|
| 2019-01-01 | 01-01 | 100 | No |
| 2019-01-02 | 01-01 to 01-02 | 210 | No |
| 2019-01-03 | 01-01 to 01-03 | 330 | No |
| 2019-01-04 | 01-01 to 01-04 | 460 | No |
| 2019-01-05 | 01-01 to 01-05 | 570 | No |
| 2019-01-06 | 01-01 to 01-06 | 710 | No |
| 2019-01-07 | 01-01 to 01-07 | 860 | Yes |
| 2019-01-08 | 01-02 to 01-08 | 840 | Yes |
| 2019-01-09 | 01-03 to 01-09 | 840 | Yes |
| 2019-01-10 | 01-04 to 01-10 | 1000 | Yes |
For 2019-01-07:
average = 860 / 7 = 122.857...
Rounded:
122.86
For 2019-01-10:
average = 1000 / 7 = 142.857...
Rounded:
142.86
Final output:
| visited_on | amount | average_amount |
|---|---|---|
| 2019-01-07 | 860 | 122.86 |
| 2019-01-08 | 840 | 120.00 |
| 2019-01-09 | 840 | 120.00 |
| 2019-01-10 | 1000 | 142.86 |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each date enters and leaves the sliding window once |
| Space | O(n) | Stores aggregated daily revenue |
The aggregation phase processes each row once. The sliding window also processes each distinct date once. Since every date is added and removed from the window at most one time, the overall runtime is linear.
The auxiliary space comes primarily from storing daily aggregated revenue.
Test Cases
from collections import defaultdict
class Solution:
def restaurantGrowth(self, customer):
daily_revenue = defaultdict(int)
for _, _, visited_on, amount in customer:
daily_revenue[visited_on] += amount
sorted_dates = sorted(daily_revenue.keys())
result = []
left = 0
window_sum = 0
for right in range(len(sorted_dates)):
current_date = sorted_dates[right]
window_sum += daily_revenue[current_date]
if right - left + 1 > 7:
oldest_date = sorted_dates[left]
window_sum -= daily_revenue[oldest_date]
left += 1
if right - left + 1 == 7:
result.append([
current_date,
window_sum,
round(window_sum / 7, 2)
])
return result
solution = Solution()
# Basic example from problem statement
assert solution.restaurantGrowth([
[1, "Jhon", "2019-01-01", 100],
[2, "Daniel", "2019-01-02", 110],
[3, "Jade", "2019-01-03", 120],
[4, "Khaled", "2019-01-04", 130],
[5, "Winston", "2019-01-05", 110],
[6, "Elvis", "2019-01-06", 140],
[7, "Anna", "2019-01-07", 150],
[8, "Maria", "2019-01-08", 80],
[9, "Jaze", "2019-01-09", 110],
[1, "Jhon", "2019-01-10", 130],
[3, "Jade", "2019-01-10", 150],
]) == [
["2019-01-07", 860, 122.86],
["2019-01-08", 840, 120.0],
["2019-01-09", 840, 120.0],
["2019-01-10", 1000, 142.86],
] # standard rolling window test
# Exactly seven days
assert solution.restaurantGrowth([
[1, "A", "2020-01-01", 10],
[2, "B", "2020-01-02", 20],
[3, "C", "2020-01-03", 30],
[4, "D", "2020-01-04", 40],
[5, "E", "2020-01-05", 50],
[6, "F", "2020-01-06", 60],
[7, "G", "2020-01-07", 70],
]) == [
["2020-01-07", 280, 40.0]
] # smallest valid output
# Multiple customers on same day
assert solution.restaurantGrowth([
[1, "A", "2020-01-01", 100],
[2, "B", "2020-01-01", 200],
[3, "C", "2020-01-02", 100],
[4, "D", "2020-01-03", 100],
[5, "E", "2020-01-04", 100],
[6, "F", "2020-01-05", 100],
[7, "G", "2020-01-06", 100],
[8, "H", "2020-01-07", 100],
]) == [
["2020-01-07", 900, 128.57]
] # verifies aggregation by date
# Uniform revenue every day
assert solution.restaurantGrowth([
[i, str(i), f"2020-01-0{i}", 50]
for i in range(1, 8)
]) == [
["2020-01-07", 350, 50.0]
] # constant rolling average
# Large revenue values
assert solution.restaurantGrowth([
[1, "A", "2020-01-01", 1000000],
[2, "B", "2020-01-02", 1000000],
[3, "C", "2020-01-03", 1000000],
[4, "D", "2020-01-04", 1000000],
[5, "E", "2020-01-05", 1000000],
[6, "F", "2020-01-06", 1000000],
[7, "G", "2020-01-07", 1000000],
]) == [
["2020-01-07", 7000000, 1000000.0]
] # tests large sums
| Test | Why |
|---|---|
| Problem statement example | Validates standard rolling window behavior |
| Exactly seven days | Ensures first valid window works correctly |
| Multiple customers same day | Verifies daily aggregation logic |
| Uniform revenue | Checks stable averages |
| Large revenue values | Ensures large sums are handled correctly |
Edge Cases
Multiple transactions on the same date
One of the easiest mistakes is treating each row as a separate day. The input table stores customer transactions, not daily summaries.
If multiple customers visit on the same date, their amounts must be combined before sliding window computation begins.
The implementation handles this correctly using:
daily_revenue[visited_on] += amount
This guarantees every date contributes exactly one aggregated value to the rolling window.
Exactly seven distinct days
If the input contains exactly seven days, there is only one valid output row.
Some implementations incorrectly require more than seven days before generating results. Others accidentally produce no output.
The sliding window logic explicitly checks:
if right - left + 1 == 7
This ensures the first valid window is produced immediately once seven days are available.
Large daily revenue values
Revenue totals may become very large when many customers spend large amounts over seven days.
An implementation using small integer types could overflow in some languages.
Python integers automatically expand to arbitrary precision, so the Python solution is naturally safe. In Go, standard int values are sufficient for typical LeetCode constraints, but using integer arithmetic carefully still matters.
The algorithm stores running sums incrementally, avoiding unnecessary recomputation and minimizing arithmetic risk.