|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[1418. 点菜展示表](https://leetcode-cn.com/problems/display-table-of-food-orders-in-a-restaurant/solution/gong-shui-san-xie-ha-xi-biao-yu-hong-hei-jmli/)** ,难度为 **中等**。 |
| 4 | + |
| 5 | +Tag : 「数据结构」、「哈希表」、「红黑树」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +给你一个数组 orders,表示客户在餐厅中完成的订单,确切地说, `orders[i]=[customerNamei,tableNumberi,foodItemi] `,其中 `customerNamei `是客户的姓名,`tableNumberi `是客户所在餐桌的桌号,而 `foodItemi `是客户点的餐品名称。 |
| 10 | + |
| 11 | +请你返回该餐厅的 点菜展示表 。在这张表中,表中第一行为标题,其第一列为餐桌桌号 "Table" ,后面每一列都是按字母顺序排列的餐品名称。接下来每一行中的项则表示每张餐桌订购的相应餐品数量,第一列应当填对应的桌号,后面依次填写下单的餐品数量。 |
| 12 | + |
| 13 | +注意:客户姓名不是点菜展示表的一部分。此外,表中的数据行应该按餐桌桌号升序排列。 |
| 14 | + |
| 15 | + |
| 16 | +示例 1: |
| 17 | +``` |
| 18 | +输入:orders = [["David","3","Ceviche"],["Corina","10","Beef Burrito"],["David","3","Fried Chicken"],["Carla","5","Water"],["Carla","5","Ceviche"],["Rous","3","Ceviche"]] |
| 19 | + |
| 20 | +输出:[["Table","Beef Burrito","Ceviche","Fried Chicken","Water"],["3","0","2","1","0"],["5","0","1","0","1"],["10","1","0","0","0"]] |
| 21 | + |
| 22 | +解释: |
| 23 | +点菜展示表如下所示: |
| 24 | +Table,Beef Burrito,Ceviche,Fried Chicken,Water |
| 25 | +3 ,0 ,2 ,1 ,0 |
| 26 | +5 ,0 ,1 ,0 ,1 |
| 27 | +10 ,1 ,0 ,0 ,0 |
| 28 | +对于餐桌 3:David 点了 "Ceviche" 和 "Fried Chicken",而 Rous 点了 "Ceviche" |
| 29 | +而餐桌 5:Carla 点了 "Water" 和 "Ceviche" |
| 30 | +餐桌 10:Corina 点了 "Beef Burrito" |
| 31 | +``` |
| 32 | +示例 2: |
| 33 | +``` |
| 34 | +输入:orders = [["James","12","Fried Chicken"],["Ratesh","12","Fried Chicken"],["Amadeus","12","Fried Chicken"],["Adam","1","Canadian Waffles"],["Brianna","1","Canadian Waffles"]] |
| 35 | + |
| 36 | +输出:[["Table","Canadian Waffles","Fried Chicken"],["1","2","0"],["12","0","3"]] |
| 37 | + |
| 38 | +解释: |
| 39 | +对于餐桌 1:Adam 和 Brianna 都点了 "Canadian Waffles" |
| 40 | +而餐桌 12:James, Ratesh 和 Amadeus 都点了 "Fried Chicken" |
| 41 | +``` |
| 42 | +示例 3: |
| 43 | +``` |
| 44 | +输入:orders = [["Laura","2","Bean Burrito"],["Jhon","2","Beef Burrito"],["Melissa","2","Soda"]] |
| 45 | + |
| 46 | +输出:[["Table","Bean Burrito","Beef Burrito","Soda"],["2","1","1","1"]] |
| 47 | +``` |
| 48 | + |
| 49 | +提示: |
| 50 | +* 1 <= orders.length <= 5 * 10ドル^4$ |
| 51 | +* orders[i].length == 3 |
| 52 | +* 1 <= customerNamei.length, foodItemi.length <= 20 |
| 53 | +* customerNamei 和 foodItemi 由大小写英文字母及空格字符 ' ' 组成。 |
| 54 | +* tableNumberi 是 1 到 500 范围内的整数。 |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +### 基本分析 |
| 59 | + |
| 60 | +这是一道考虑「数据结构运用」与「简单设计」的模拟题。 |
| 61 | + |
| 62 | +我们可以根据最终的 "结果" 反推数据结构存储格式。 |
| 63 | + |
| 64 | +最终 "结果" 包含两部分: |
| 65 | + |
| 66 | +1. `title` : 由 **"Table" + 排序去重的餐品** 组成 |
| 67 | +2. 内容 : 由 **桌号 + 每件餐品对应的数量** 组成 |
| 68 | + |
| 69 | +基于此,不难设计出使用 `Set` 存储 `title` 相关内容,使用 `Map` 存储内容相关部分。 |
| 70 | + |
| 71 | +去重 `Map` 的部分 `Key` 为桌号,同时为了快速索引当前桌号「某个餐品的数量」,需要再套一层 `Map`。即最终存储格式为 `桌号 : {餐品 : 个数}`。 |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +### HashSet & HashMap |
| 76 | + |
| 77 | +有了基本分析,我们可以使用最常规的 `HashSet` 和 `HashMap` 进行实现。 |
| 78 | + |
| 79 | +由于 `HashSet` 是基于 `HashMap`,而 `HashMap` 的底层数据结构实现是 **哈希表**,因此我们需要在构造答案时手动排个序。 |
| 80 | + |
| 81 | + |
| 82 | + |
| 83 | +代码: |
| 84 | +```Java [] |
| 85 | +class Solution { |
| 86 | + public List<List<String>> displayTable(List<List<String>> os) { |
| 87 | + List<List<String>> ans = new ArrayList<>(); |
| 88 | + // 桌号 : {餐品 : 个数}(用于构造内容) |
| 89 | + Map<Integer, Map<String, Integer>> tm = new HashMap<>(); |
| 90 | + // 餐品(用于构造 title) |
| 91 | + Set<String> ts = new HashSet<>(); |
| 92 | + for (List<String> o : os) { |
| 93 | + String c = o.get(0), t = o.get(1), f = o.get(2); |
| 94 | + Integer tidx = Integer.parseInt(t); |
| 95 | + ts.add(f); |
| 96 | + Map<String, Integer> map = tm.getOrDefault(tidx, new HashMap<>()); |
| 97 | + map.put(f, map.getOrDefault(f, 0) + 1); |
| 98 | + tm.put(tidx, map); |
| 99 | + } |
| 100 | + int n = tm.size() + 1, m = ts.size() + 1; |
| 101 | + // 构造 title & 手动排序 |
| 102 | + List<String> foods = new ArrayList<>(ts); |
| 103 | + Collections.sort(foods); |
| 104 | + List<String> title = new ArrayList<>(); |
| 105 | + title.add("Table"); |
| 106 | + title.addAll(foods); |
| 107 | + ans.add(title); |
| 108 | + // 构造内容 & 手动排序 |
| 109 | + List<Integer> tables = new ArrayList<>(tm.keySet()); |
| 110 | + Collections.sort(tables); |
| 111 | + for (int tidx : tables) { |
| 112 | + Map<String, Integer> map = tm.get(tidx); |
| 113 | + List<String> cur = new ArrayList<>(); |
| 114 | + cur.add(tidx + ""); |
| 115 | + for (String food : foods) { |
| 116 | + cur.add(map.getOrDefault(food, 0) + ""); |
| 117 | + } |
| 118 | + ans.add(cur); |
| 119 | + } |
| 120 | + return ans; |
| 121 | + } |
| 122 | +} |
| 123 | +``` |
| 124 | +* 时间复杂度:`HashSet` 和 `HashMap` 的基本操作都是 $O(1)$。预处理所有的订单复杂度为 $O(n)$;去重后的桌数为 $r,ドル餐品数量为 $c,ドル对两者排序的复杂度分别为 $O(r\log{r})$ 和 $O(c\log{c})$;构造答案复杂度为 $O(r * c)$;最终复杂度为 $O(\max(n, r\log{r}, c\log{c}, r * c))$ |
| 125 | +* 空间复杂度:$O(r + c + r * c)$ |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +### TreeSet & TreeMap |
| 130 | + |
| 131 | +与 `HashSet` 和 `HashMap` 的关系类似,`TreeSet` 是基于 `TreeMap` 实现的,而 `TreeMap` 底层数据结构实现是 **红黑树**。 |
| 132 | + |
| 133 | +**得益于 Java 的「面向接口编程(IOP)」设计,我们可以毫不费力的将解法一中的 `HashSet` 替换成 `TreeSet`、将 `HashMap` 替换成 `TreeMap`,并删除手动排序相关代码,得到我们的解法二。** |
| 134 | + |
| 135 | +利用 `TreeMap` 的默认排序规则(数值升序、非数值字典序升序)来简化我们的实现。 |
| 136 | + |
| 137 | +但需要注意的是,利用 `TreeMap` 的内部有序特性,调整操作可能会发生在每一次插入操作中,而解法一则是利用 `Collections.sort` 进行一次性的排序,对于非自定义类 `Collections.sort` 是基于 `Arrays.sort` 实现的,会根据「数组大小」、「数组本身是否大致有序」等因素综合决定最终的排序方案,在数据完全随机的情况下,执行效率很大程度要优于 `TreeMap` 的多次调整,但两者复杂度都是 $O(n\log{n})$。 |
| 138 | + |
| 139 | +因此在所有数据都提前给定的「离线」情况下,其实更推荐使用解法一。 |
| 140 | + |
| 141 | + |
| 142 | + |
| 143 | +代码: |
| 144 | +```Java [] |
| 145 | +class Solution { |
| 146 | + public List<List<String>> displayTable(List<List<String>> os) { |
| 147 | + List<List<String>> ans = new ArrayList<>(); |
| 148 | + // 桌号 : {餐品 : 个数}(用于构造内容) |
| 149 | + Map<Integer, Map<String, Integer>> tm = new TreeMap<>(); |
| 150 | + // 餐品(用于构造 title) |
| 151 | + Set<String> ts = new TreeSet<>(); |
| 152 | + for (List<String> o : os) { |
| 153 | + String c = o.get(0), t = o.get(1), f = o.get(2); |
| 154 | + Integer tidx = Integer.parseInt(t); |
| 155 | + ts.add(f); |
| 156 | + Map<String, Integer> map = tm.getOrDefault(tidx, new HashMap<>()); |
| 157 | + map.put(f, map.getOrDefault(f, 0) + 1); |
| 158 | + tm.put(tidx, map); |
| 159 | + } |
| 160 | + int n = tm.size() + 1, m = ts.size() + 1; |
| 161 | + // 构造 title |
| 162 | + List<String> title = new ArrayList<>(); |
| 163 | + title.add("Table"); |
| 164 | + title.addAll(ts); |
| 165 | + ans.add(title); |
| 166 | + // 构造内容 |
| 167 | + for (int tidx : tm.keySet()) { |
| 168 | + Map<String, Integer> map = tm.get(tidx); |
| 169 | + List<String> cur = new ArrayList<>(); |
| 170 | + cur.add(tidx + ""); |
| 171 | + for (String food : ts) { |
| 172 | + cur.add(map.getOrDefault(food, 0) + ""); |
| 173 | + } |
| 174 | + ans.add(cur); |
| 175 | + } |
| 176 | + return ans; |
| 177 | + } |
| 178 | +} |
| 179 | +``` |
| 180 | +* 时间复杂度:`TreeSet` 和 `TreeMap` 的基本操作都是 $O(log{k})$。预处理所有的订单复杂度为 $O(n\log{n})$;去重后的桌数为 $r,ドル餐品数量为 $c,ドル构造答案复杂度为 $O(r\log{r} * c\log{c})$;最终复杂度为 $O(\max(n\log{n}, r\log{r} * c\log{c}))$ |
| 181 | +* 空间复杂度:$O(r + c + r * c)$ |
| 182 | + |
| 183 | + |
| 184 | +--- |
| 185 | + |
| 186 | +### 最后 |
| 187 | + |
| 188 | +这是我们「刷穿 LeetCode」系列文章的第 `No.1418` 篇,系列开始于 2021年01月01日,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 189 | + |
| 190 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 191 | + |
| 192 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 193 | + |
| 194 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 195 | + |
0 commit comments