Display Table of Food Orders in a Restaurant
MedPrompt
Given the array orders, which represents the orders that customers have done in a restaurant. More specifically orders[i]=[customerNamei,tableNumberi,foodItemi] where customerNamei is the name of the customer, tableNumberi is the table customer sit at, and foodItemi is the item customer orders.
Return the restaurant's “display table”. The “display table” is a table whose row entries denote how many of each food item each table ordered. The first column is the table number and the remaining columns correspond to each food item in alphabetical order. The first row should be a header whose first column is “Table”, followed by the names of the food items. Note that the customer names are not part of the table. Additionally, the rows should be sorted in numerically increasing order.
Example 1:
Input: orders = [["David","3","Ceviche"],["Corina","10","Beef Burrito"],["David","3","Fried Chicken"],["Carla","5","Water"],["Carla","5","Ceviche"],["Rous","3","Ceviche"]]
Output: [["Table","Beef Burrito","Ceviche","Fried Chicken","Water"],["3","0","2","1","0"],["5","0","1","0","1"],["10","1","0","0","0"]]
Explanation:
The displaying table looks like:
Table,Beef Burrito,Ceviche,Fried Chicken,Water
3 ,0 ,2 ,1 ,0
5 ,0 ,1 ,0 ,1
10 ,1 ,0 ,0 ,0
For the table 3: David orders "Ceviche" and "Fried Chicken", and Rous orders "Ceviche".
For the table 5: Carla orders "Water" and "Ceviche".
For the table 10: Corina orders "Beef Burrito". Example 2:
Input: orders = [["James","12","Fried Chicken"],["Ratesh","12","Fried Chicken"],["Amadeus","12","Fried Chicken"],["Adam","1","Canadian Waffles"],["Brianna","1","Canadian Waffles"]]
Output: [["Table","Canadian Waffles","Fried Chicken"],["1","2","0"],["12","0","3"]]
Explanation:
For the table 1: Adam and Brianna order "Canadian Waffles".
For the table 12: James, Ratesh and Amadeus order "Fried Chicken".Example 3:
Input: orders = [["Laura","2","Bean Burrito"],["Jhon","2","Beef Burrito"],["Melissa","2","Soda"]]
Output: [["Table","Bean Burrito","Beef Burrito","Soda"],["2","1","1","1"]]
Constraints:
1 <= orders.length <= 5 * 10^4orders[i].length == 31 <= customerNamei.length, foodItemi.length <= 20customerNameiandfoodItemiconsist of lowercase and uppercase English letters and the space character.tableNumberiis a valid integer between1and500.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach involves multiple iterations over the input data. First, it collects all unique food items and table numbers. Then, it sorts them to establish the structure of the output table. Finally, it iterates through the orders again to populate the counts, using inefficient linear searches to find the correct row and column for each order.
Algorithm
- Step 1: Collect Unique Items:
- Create a
HashSetfor food items and anotherHashSetfor table numbers. - Iterate through the
orderslist once to populate both sets with all unique values.
- Create a
- Step 2: Sort Headers and Rows:
- Convert the food item set to a
Listand sort it alphabetically. This list will form the column headers. - Convert the table number set to a
List, parse the string numbers to integers, sort them numerically, and store the sorted list.
- Convert the food item set to a
- Step 3: Initialize Result Table:
- Create the final result
List<List<String>>. - Add the header row, which consists of "Table" followed by the sorted food items.
- For each sorted table number, add a new row to the result. This row will start with the table number, followed by "0" for each food item count.
- Create the final result
- Step 4: Populate Counts:
- Iterate through the original
orderslist a second time. - For each
order, find its corresponding row and column in the result table by performing a linear search on the sorted table list and sorted food list. - Once the correct cell is located, parse its string value to an integer, increment it, and update the cell with the new count as a string.
- Iterate through the original
- Step 5: Return Result:
- Return the fully populated table.
Walkthrough
The brute-force method tackles the problem in a straightforward but inefficient manner. It separates the problem into distinct phases: data collection, sorting, table initialization, and data population. The main drawback is the final population step. For every single order in the input, it searches for the correct table row and food column by scanning through the previously created lists of sorted tables and foods. This repeated searching process makes the algorithm very slow as the number of orders, tables, or food items increases.
import java.util.*;import java.util.stream.Collectors; class Solution { public List<List<String>> displayTable(List<List<String>> orders) { // Step 1: Collect unique food items and table numbers Set<String> foodSet = new HashSet<>(); Set<String> tableSet = new HashSet<>(); for (List<String> order : orders) { tableSet.add(order.get(1)); foodSet.add(order.get(2)); } // Step 2: Sort headers and rows List<String> foodHeader = new ArrayList<>(foodSet); Collections.sort(foodHeader); List<String> sortedTables = new ArrayList<>(tableSet); Collections.sort(sortedTables, Comparator.comparingInt(Integer::parseInt)); // Step 3: Initialize result table List<List<String>> displayTable = new ArrayList<>(); List<String> headerRow = new ArrayList<>(); headerRow.add("Table"); headerRow.addAll(foodHeader); displayTable.add(headerRow); for (String table : sortedTables) { List<String> row = new ArrayList<>(Collections.nCopies(foodHeader.size() + 1, "0")); row.set(0, table); displayTable.add(row); } // Step 4: Populate counts with linear scans (inefficient) for (List<String> order : orders) { String table = order.get(1); String food = order.get(2); int rowIndex = sortedTables.indexOf(table) + 1; // Linear scan int colIndex = foodHeader.indexOf(food) + 1; // Linear scan List<String> targetRow = displayTable.get(rowIndex); int currentCount = Integer.parseInt(targetRow.get(colIndex)); targetRow.set(colIndex, String.valueOf(currentCount + 1)); } return displayTable; }}Complexity
Time
O(N * (T + U)), where N is the number of orders, T is the number of unique tables, and U is the number of unique food items. The final pass to populate counts dominates, as each of the N orders requires searching through T tables and U food items.
Space
O(T * U + U*L_f + T*L_t), where T is the number of unique tables, U is the number of unique food items, and L_f/L_t are average string lengths. The dominant term is `O(T * U)` for storing the result table.
Trade-offs
Pros
Conceptually simple and breaks the problem down into easy-to-understand steps.
Cons
Highly inefficient due to multiple passes over the data.
The final step of populating counts involves linear searches within a loop, leading to a poor time complexity of
O(N * (T + U)), which will likely result in a 'Time Limit Exceeded' error for large inputs.
This approach improves upon the brute-force method by using data structures that automatically maintain sorted order, namely TreeMap and TreeSet. It processes all orders in a single pass to aggregate the data, and then directly constructs the final table from these sorted structures. This avoids both multiple passes and expensive manual sorting steps after data collection.
Algorithm
- Step 1: Initialize Sorted Data Structures:
- Initialize a
TreeSet<String>to store unique food items. TheTreeSetwill automatically keep them sorted alphabetically. - Initialize a
TreeMap<Integer, Map<String, Integer>>to store the counts. TheTreeMapwill keep table numbers (parsed toInteger) sorted numerically.
- Initialize a
- Step 2: Aggregate Data:
- Iterate through the
orderslist once. For each order[name, table, food]:- Add the
foodto theTreeSet. - Parse the
tablestring to an integer. - Use
computeIfAbsenton theTreeMapto get or create the innerMapfor the table. - Increment the count for the
foodin the innerMap.
- Add the
- Iterate through the
- Step 3: Construct the Output Table:
- Create the header row: start with "Table", then add all items from the
TreeSet(which are already sorted). - Add the header to the result list.
- Iterate through the entries of the
TreeMap. Since it's aTreeMap, the tables are already sorted by number.- For each
(tableNumber, foodCounts)entry, create a new row. - Start the row with the
tableNumber(converted back to a string). - Iterate through the sorted food items from the
TreeSet. For each food item, get its count from thefoodCountsmap (or 0 if not present) and add it to the row. - Add the completed row to the result list.
- For each
- Create the header row: start with "Table", then add all items from the
- Step 4: Return Result:
- Return the final list.
Walkthrough
By leveraging TreeSet and TreeMap, this method integrates the sorting process into the data aggregation phase. A TreeSet is used to collect all unique food items, and because it's a balanced binary search tree, it keeps the items in alphabetical order. Similarly, a TreeMap is used to store the food counts for each table. By using the integer representation of the table number as the key, the TreeMap automatically keeps the table entries sorted numerically. After a single pass through all the orders, the food items and table numbers are already collected and sorted. The final step is to simply iterate through these sorted structures to build the display table.
import java.util.*; class Solution { public List<List<String>> displayTable(List<List<String>> orders) { // Use TreeSet to keep food items sorted alphabetically Set<String> foodSet = new TreeSet<>(); // Use TreeMap to keep tables sorted numerically // Key: Table number (Integer), Value: Map of food items to their counts Map<Integer, Map<String, Integer>> tableOrders = new TreeMap<>(); for (List<String> order : orders) { int tableNumber = Integer.parseInt(order.get(1)); String foodItem = order.get(2); foodSet.add(foodItem); tableOrders.computeIfAbsent(tableNumber, k -> new HashMap<>()).merge(foodItem, 1, Integer::sum); } // Prepare the header row List<String> header = new ArrayList<>(); header.add("Table"); header.addAll(foodSet); // Prepare the result list List<List<String>> displayTable = new ArrayList<>(); displayTable.add(header); // Populate the data rows from the sorted TreeMap for (Map.Entry<Integer, Map<String, Integer>> entry : tableOrders.entrySet()) { Integer tableNumber = entry.getKey(); Map<String, Integer> foodCounts = entry.getValue(); List<String> row = new ArrayList<>(); row.add(String.valueOf(tableNumber)); for (String food : foodSet) { row.add(String.valueOf(foodCounts.getOrDefault(food, 0))); } displayTable.add(row); } return displayTable; }}Complexity
Time
O(N * (log T + log U) + T * U). The first term comes from iterating through N orders and performing insertions into a `TreeMap` (O(log T)) and a `TreeSet` (O(log U)). The second term comes from building the final `T x U` table.
Space
O(N + T * U + U*L_f), where N is the number of orders, T is unique tables, U is unique foods, and L_f is food name length. `O(N)` for the map storing counts (at most N unique table-food pairs), `O(U*L_f)` for the food set, and `O(T*U)` for the final result table.
Trade-offs
Pros
Aggregates data in a single pass.
Simplifies the code by using data structures that handle sorting implicitly.
Avoids expensive re-sorting or searching after the initial data pass.
Cons
The logarithmic factor on every insertion during the main loop (
O(N * (log T + log U))) can be slower than a simpleO(N)pass followed by explicit sorting, especially when N is large.
This is the most efficient approach. It uses HashMap and HashSet for fast O(1) average time data aggregation in a single pass. After collecting all the necessary data, it performs sorting on the much smaller sets of unique table numbers and food items just once. This separation of concerns—fast aggregation followed by efficient sorting—yields the best overall performance.
Algorithm
- Step 1: Initialize Hash-Based Data Structures:
- Initialize a
HashSet<String>to store unique food items. - Initialize a
HashMap<String, Map<String, Integer>>to store the counts per table.
- Initialize a
- Step 2: Aggregate Data:
- Iterate through the
orderslist once. This pass is very fast, with an average time complexity ofO(N). - For each order
[name, table, food]:- Add the
foodto theHashSet. - Use
computeIfAbsenton the outerHashMapto get or create an innerHashMapfor thetable. - Increment the count for the
foodin the inner map.
- Add the
- Iterate through the
- Step 3: Sort Keys:
- After the loop, create a
Listof food items from theHashSetand sort it alphabetically. - Create a
Listof table numbers from theHashMap's key set. Convert the strings to integers, sort them numerically, and store the result.
- After the loop, create a
- Step 4: Construct Final Table:
- Create the header row using "Table" and the sorted food item list.
- Add the header to the result list.
- Iterate through the sorted list of table numbers. For each table number:
- Create a new row, starting with the table number (as a string).
- Iterate through the sorted list of food items. For each food, retrieve its count from the map for the current table (defaulting to 0 if absent).
- Add the count as a string to the row.
- Add the completed row to the result.
- Step 5: Return Result:
- Return the final list.
Walkthrough
This optimized approach prioritizes speed during the most intensive part of the algorithm: processing the initial list of N orders. By using HashMap and HashSet, each order can be processed in average constant time, O(1). The entire aggregation phase completes in O(N) time. Only after this fast pass are the sorting operations performed. Since the number of unique tables (T) and unique food items (U) is typically much smaller than the total number of orders (N), sorting these smaller collections (O(T log T) and O(U log U)) is significantly more efficient than incurring a logarithmic cost for every one of the N orders, as seen in the TreeMap approach. This makes it the best-performing solution in practice.
import java.util.*;import java.util.stream.Collectors; class Solution { public List<List<String>> displayTable(List<List<String>> orders) { // Step 1 & 2: Use HashMap/HashSet for fast O(1) aggregation Set<String> foodSet = new HashSet<>(); Map<String, Map<String, Integer>> tableOrders = new HashMap<>(); for (List<String> order : orders) { String tableNumber = order.get(1); String foodItem = order.get(2); foodSet.add(foodItem); tableOrders.computeIfAbsent(tableNumber, k -> new HashMap<>()).merge(foodItem, 1, Integer::sum); } // Step 3: Sort the collected unique items List<String> sortedFood = new ArrayList<>(foodSet); Collections.sort(sortedFood); List<Integer> sortedTables = tableOrders.keySet().stream() .map(Integer::parseInt) .sorted() .collect(Collectors.toList()); // Step 4: Construct the final table List<String> header = new ArrayList<>(); header.add("Table"); header.addAll(sortedFood); List<List<String>> displayTable = new ArrayList<>(); displayTable.add(header); for (Integer tableNum : sortedTables) { String tableNumStr = String.valueOf(tableNum); List<String> row = new ArrayList<>(); row.add(tableNumStr); Map<String, Integer> foodCounts = tableOrders.get(tableNumStr); for (String food : sortedFood) { row.add(String.valueOf(foodCounts.getOrDefault(food, 0))); } displayTable.add(row); } return displayTable; }}Complexity
Time
O(N + U log U + T log T + T * U). `O(N)` for the initial pass. `O(U log U)` to sort food items. `O(T log T)` to sort table numbers. `O(T * U)` to build the final table. This is generally faster than other approaches because the expensive `log` operations are not performed inside the main `N`-sized loop.
Space
O(N + T * U + U*L_f), where N is the number of orders, T is unique tables, U is unique foods, and L_f is food name length. `O(N)` for the map storing counts, `O(U*L_f)` for the food set, and `O(T*U)` for the final result table.
Trade-offs
Pros
Fastest approach due to
O(1)average time for data aggregation.Efficiently separates the
O(N)data processing from the sorting of smaller, unique sets of items (TandU).Scales best with a large number of orders (
N).
Cons
Requires explicit sorting steps after the aggregation phase, which can add a small amount of code complexity compared to the
TreeMap/TreeSetapproach.
Solutions
Solution
class Solution {public List<List<String>> displayTable(List<List<String>> orders) { Set<Integer> tables = new HashSet<>(); Set<String> foods = new HashSet<>(); Map<String, Integer> mp = new HashMap<>(); for (List<String> order : orders) { int table = Integer.parseInt(order.get(1)); String food = order.get(2); tables.add(table); foods.add(food); String key = table + "." + food; mp.put(key, mp.getOrDefault(key, 0) + 1); } List<Integer> t = new ArrayList<>(tables); List<String> f = new ArrayList<>(foods); Collections.sort(t); Collections.sort(f); List<List<String>> res = new ArrayList<>(); List<String> title = new ArrayList<>(); title.add("Table"); title.addAll(f); res.add(title); for (int table : t) { List<String> tmp = new ArrayList<>(); tmp.add(String.valueOf(table)); for (String food : f) { tmp.add(String.valueOf(mp.getOrDefault(table + "." + food, 0))); } res.add(tmp); } return res; }}class Solution: def displayTable(self, orders: List[List[str]]) -> List[List[str]]: tables = set() foods = set() mp = Counter() for _, table, food in orders: tables . add(int(table)) foods . add(food) mp[f ' { table } . { food } '] += 1 foods = sorted(list(foods)) tables = sorted(list(tables)) res = [['Table'] + foods] for table in tables: t = [str(table)] for food in foods: t . append(str(mp[f ' { table } . { food } '])) res . append(t) return resclass Solution {public: vector<vector<string>> displayTable(vector<vector<string>> &orders) { unordered_set<int> tables; unordered_set<string> foods; unordered_map<string, int> mp; for (auto &order : orders) { int table = stoi(order[1]); string food = order[2]; tables.insert(table); foods.insert(food); ++mp[order[1] + "." + food]; } vector<int> t; t.assign(tables.begin(), tables.end()); sort(t.begin(), t.end()); vector<string> f; f.assign(foods.begin(), foods.end()); sort(f.begin(), f.end()); vector<vector<string>> res; vector<string> title; title.push_back("Table"); for (auto e : f) title.push_back(e); res.push_back(title); for (int table : t) { vector<string> tmp; tmp.push_back(to_string(table)); for (string food : f) { tmp.push_back(to_string(mp[to_string(table) + "." + food])); } res.push_back(tmp); } return res; }};Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.