LeetCode 1715 - Count Apples and Oranges
This problem asks us to compute the total number of apples and oranges across all boxes, taking into account that some boxes may contain a chest. The Boxes table gives us the count of apples and oranges in each box, and optionally the chestid if a chest is present in that box.
Difficulty: 🟡 Medium
Topics: Database
Solution
Problem Understanding
This problem asks us to compute the total number of apples and oranges across all boxes, taking into account that some boxes may contain a chest. The Boxes table gives us the count of apples and oranges in each box, and optionally the chest_id if a chest is present in that box. The Chests table gives the count of apples and oranges contained in each chest.
The output is a single row with two columns: apple_count and orange_count, representing the sum of all apples and oranges in every box, including the contents of chests when present.
Key points to note are that not every box contains a chest (chest_id can be null) and not every chest may be used (chest_id in the Chests table may not appear in Boxes). We also need to sum correctly even when multiple boxes refer to the same chest.
Edge cases include boxes with no chest, boxes with chest_id pointing to a chest, and boxes whose chest_id might not exist (though problem constraints likely ensure referential integrity). The problem guarantees that box_id and chest_id are unique identifiers for their respective tables.
Approaches
The brute-force approach would be to iterate through all boxes, and for each box that contains a chest, perform a nested search on the Chests table to retrieve the corresponding counts. This approach is correct but inefficient because it could result in an O(n*m) time complexity where n is the number of boxes and m is the number of chests.
The optimal approach leverages a SQL LEFT JOIN between Boxes and Chests on chest_id. By doing this join, we can directly add the apples and oranges from the chest to the corresponding box in one pass. Using SQL aggregate functions (SUM) ensures the total is computed efficiently in O(n) time relative to the number of rows in the Boxes table.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n*m) | O(1) | Nested lookup of chest for each box, slow for large datasets |
| Optimal | O(n) | O(1) | Use SQL LEFT JOIN to sum box and chest contents directly |
Algorithm Walkthrough
- Perform a
LEFT JOINofBoxestoChestsonchest_id. This ensures every box appears in the result even if it does not have a chest. - In the joined result, for each row, compute the total apples as
box.apple_count + IFNULL(chest.apple_count, 0)and total oranges asbox.orange_count + IFNULL(chest.orange_count, 0). This handles boxes without chests correctly by treating null as zero. - Use SQL
SUM()on these computed totals to get the final total apples and oranges. - Return the result as a single row with columns
apple_countandorange_count.
Why it works: By joining boxes to their respective chests and summing, each box contributes its own apples and oranges, and if a chest is present, its contents are included. The LEFT JOIN guarantees that boxes without a chest are still counted.
Python Solution
import sqlite3
from typing import Tuple
def countApplesAndOranges(conn: sqlite3.Connection) -> Tuple[int, int]:
cursor = conn.cursor()
query = """
SELECT
SUM(b.apple_count + IFNULL(c.apple_count, 0)) AS apple_count,
SUM(b.orange_count + IFNULL(c.orange_count, 0)) AS orange_count
FROM Boxes b
LEFT JOIN Chests c
ON b.chest_id = c.chest_id
"""
cursor.execute(query)
result = cursor.fetchone()
return result[0], result[1]
The Python solution executes the SQL query on the provided database connection. The LEFT JOIN ensures every box is included, and IFNULL handles the absence of a chest by using zero. The SUM aggregates the total counts for all boxes including the chest contents.
Go Solution
package main
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
func countApplesAndOranges(db *sql.DB) (int, int, error) {
query := `
SELECT
SUM(b.apple_count + IFNULL(c.apple_count, 0)) AS apple_count,
SUM(b.orange_count + IFNULL(c.orange_count, 0)) AS orange_count
FROM Boxes b
LEFT JOIN Chests c
ON b.chest_id = c.chest_id
`
row := db.QueryRow(query)
var appleCount, orangeCount int
err := row.Scan(&appleCount, &orangeCount)
if err != nil {
return 0, 0, err
}
return appleCount, orangeCount, nil
}
In Go, the query logic is the same as in Python. We use QueryRow and Scan to extract the single row of results. Error handling is important in Go to catch any issues executing the query.
Worked Examples
Using the problem's example:
| box_id | chest_id | box_apples | box_oranges | chest_apples | chest_oranges | total_apples | total_oranges |
|---|---|---|---|---|---|---|---|
| 2 | null | 6 | 15 | 0 | 0 | 6 | 15 |
| 18 | 14 | 4 | 15 | 20 | 10 | 24 | 25 |
| 19 | 3 | 8 | 4 | 19 | 4 | 27 | 8 |
| 12 | 2 | 19 | 20 | 8 | 8 | 27 | 28 |
| 20 | 6 | 12 | 9 | 5 | 6 | 17 | 15 |
| 8 | 6 | 9 | 9 | 5 | 6 | 14 | 15 |
| 3 | 14 | 16 | 7 | 20 | 10 | 36 | 17 |
Final totals: 151 apples, 123 oranges.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each box is processed once, join and sum are O(n) in SQL |
| Space | O(1) | Aggregation does not require storing individual rows in memory |
The SQL engine efficiently handles joins and aggregation internally, so our algorithm scales linearly with the number of boxes.
Test Cases
# test cases
assert countApplesAndOranges(conn) == (151, 123) # Provided example
# Edge case: no boxes
assert countApplesAndOranges(empty_conn) == (0, 0)
# Edge case: boxes without chests
assert countApplesAndOranges(boxes_no_chests_conn) == (sum_apples, sum_oranges)
# Edge case: boxes all reference the same chest
assert countApplesAndOranges(boxes_same_chest_conn) == (sum_apples, sum_oranges)
| Test | Why |
|---|---|
| Provided example | Verifies correctness on typical input with mixed boxes and chests |
| No boxes | Verifies that empty tables return zero |
| Boxes without chests | Ensures boxes without chests are counted correctly |
| Boxes referencing same chest | Checks repeated chest aggregation is handled correctly |
Edge Cases
One important edge case is when Boxes has no rows. The algorithm correctly handles this because SQL SUM on an empty table returns NULL, which is interpreted as zero when fetching results.
Another edge case is boxes that do not have a chest (chest_id is null). The LEFT JOIN ensures these rows are included, and IFNULL guarantees that null chest values contribute zero to the sum.
A third edge case is multiple boxes referencing the same chest. Our implementation correctly sums the chest contents for each box that references it, as the SQL join duplicates the chest row for each referencing box, ensuring all counts are included.