|
| 1 | +--- |
| 2 | +comments: true |
| 3 | +difficulty: 中等 |
| 4 | +edit_url: https://github.com/doocs/leetcode/edit/main/solution/3700-3799/3705.Find%20Golden%20Hour%20Customers/README.md |
| 5 | +tags: |
| 6 | + - 数据库 |
| 7 | +--- |
| 8 | + |
| 9 | +<!-- problem:start --> |
| 10 | + |
| 11 | +# [3705. Find Golden Hour Customers](https://leetcode.cn/problems/find-golden-hour-customers) |
| 12 | + |
| 13 | +[English Version](/solution/3700-3799/3705.Find%20Golden%20Hour%20Customers/README_EN.md) |
| 14 | + |
| 15 | +## 题目描述 |
| 16 | + |
| 17 | +<!-- description:start --> |
| 18 | + |
| 19 | +<p>Table: <code>restaurant_orders</code></p> |
| 20 | + |
| 21 | +<pre> |
| 22 | ++------------------+----------+ |
| 23 | +| Column Name | Type | |
| 24 | ++------------------+----------+ |
| 25 | +| order_id | int | |
| 26 | +| customer_id | int | |
| 27 | +| order_timestamp | datetime | |
| 28 | +| order_amount | decimal | |
| 29 | +| payment_method | varchar | |
| 30 | +| order_rating | int | |
| 31 | ++------------------+----------+ |
| 32 | +order_id is the unique identifier for this table. |
| 33 | +payment_method can be cash, card, or app. |
| 34 | +order_rating is between 1 and 5, where 5 is the best (NULL if not rated). |
| 35 | +order_timestamp contains both date and time information. |
| 36 | +</pre> |
| 37 | + |
| 38 | +<p>Write a solution to find <strong>golden hour customers</strong> - customers who consistently order during peak hours and provide high satisfaction. A customer is a <strong>golden hour customer</strong> if they meet ALL the following criteria:</p> |
| 39 | + |
| 40 | +<ul> |
| 41 | + <li>Made <strong>at least</strong> <code>3</code> orders.</li> |
| 42 | + <li><strong>At least</strong> <code>60%</code> of their orders are during <strong>peak hours </strong>(<code>11:00</code>-<code>14:00</code> or <code>18:00</code>-<code>21:00</code>).</li> |
| 43 | + <li>Their <strong>average rating</strong> for rated orders is at least <code>4.0,</code> round it to<code> 2 </code>decimal places.</li> |
| 44 | + <li>Have rated <strong>at least</strong> <code>50%</code> of their orders.</li> |
| 45 | +</ul> |
| 46 | + |
| 47 | +<p>Return <em>the result table ordered by</em> <code>average_rating</code> <em>in <strong>descending</strong> order, then by</em> <code>customer_id</code> <em>in <strong>descending</strong> order</em>.</p> |
| 48 | + |
| 49 | +<p>The result format is in the following example.</p> |
| 50 | + |
| 51 | +<p> </p> |
| 52 | +<p><strong class="example">Example:</strong></p> |
| 53 | + |
| 54 | +<div class="example-block"> |
| 55 | +<p><strong>Input:</strong></p> |
| 56 | + |
| 57 | +<p>restaurant_orders table:</p> |
| 58 | + |
| 59 | +<pre class="example-io"> |
| 60 | ++----------+-------------+---------------------+--------------+----------------+--------------+ |
| 61 | +| order_id | customer_id | order_timestamp | order_amount | payment_method | order_rating | |
| 62 | ++----------+-------------+---------------------+--------------+----------------+--------------+ |
| 63 | +| 1 | 101 | 2024-03-01 12:30:00 | 25.50 | card | 5 | |
| 64 | +| 2 | 101 | 2024-03-02 19:15:00 | 32.00 | app | 4 | |
| 65 | +| 3 | 101 | 2024-03-03 13:45:00 | 28.75 | card | 5 | |
| 66 | +| 4 | 101 | 2024-03-04 20:30:00 | 41.00 | app | NULL | |
| 67 | +| 5 | 102 | 2024-03-01 11:30:00 | 18.50 | cash | 4 | |
| 68 | +| 6 | 102 | 2024-03-02 12:00:00 | 22.00 | card | 3 | |
| 69 | +| 7 | 102 | 2024-03-03 15:30:00 | 19.75 | cash | NULL | |
| 70 | +| 8 | 103 | 2024-03-01 19:00:00 | 55.00 | app | 5 | |
| 71 | +| 9 | 103 | 2024-03-02 20:45:00 | 48.50 | app | 4 | |
| 72 | +| 10 | 103 | 2024-03-03 18:30:00 | 62.00 | card | 5 | |
| 73 | +| 11 | 104 | 2024-03-01 10:00:00 | 15.00 | cash | 3 | |
| 74 | +| 12 | 104 | 2024-03-02 09:30:00 | 18.00 | cash | 2 | |
| 75 | +| 13 | 104 | 2024-03-03 16:00:00 | 20.00 | card | 3 | |
| 76 | +| 14 | 105 | 2024-03-01 12:15:00 | 30.00 | app | 4 | |
| 77 | +| 15 | 105 | 2024-03-02 13:00:00 | 35.50 | app | 5 | |
| 78 | +| 16 | 105 | 2024-03-03 11:45:00 | 28.00 | card | 4 | |
| 79 | ++----------+-------------+---------------------+--------------+----------------+--------------+ |
| 80 | +</pre> |
| 81 | + |
| 82 | +<p><strong>Output:</strong></p> |
| 83 | + |
| 84 | +<pre class="example-io"> |
| 85 | ++-------------+--------------+----------------------+----------------+ |
| 86 | +| customer_id | total_orders | peak_hour_percentage | average_rating | |
| 87 | ++-------------+--------------+----------------------+----------------+ |
| 88 | +| 103 | 3 | 100 | 4.67 | |
| 89 | +| 101 | 4 | 75 | 4.67 | |
| 90 | +| 105 | 3 | 100 | 4.33 | |
| 91 | ++-------------+--------------+----------------------+----------------+ |
| 92 | +</pre> |
| 93 | + |
| 94 | +<p><strong>Explanation:</strong></p> |
| 95 | + |
| 96 | +<ul> |
| 97 | + <li><strong>Customer 101</strong>: |
| 98 | + |
| 99 | + <ul> |
| 100 | + <li>Total orders: 4 (at least 3) </li> |
| 101 | + <li>Peak hour orders: 3 out of 4 (12:30, 19:15, 13:45, and 20:30 are in peak hours)</li> |
| 102 | + <li>Peak hour percentage: 3/4 = 75% (at least 60%) </li> |
| 103 | + <li>Rated orders: 3 out of 4 (75% rating completion) </li> |
| 104 | + <li>Average rating: (5+4+5)/3 = 4.67 (at least 4.0) </li> |
| 105 | + <li>Result: <strong>Golden hour customer</strong></li> |
| 106 | + </ul> |
| 107 | + </li> |
| 108 | + <li><strong>Customer 102</strong>: |
| 109 | + <ul> |
| 110 | + <li>Total orders: 3 (at least 3) </li> |
| 111 | + <li>Peak hour orders: 2 out of 3 (11:30, 12:00 are in peak hours; 15:30 is not)</li> |
| 112 | + <li>Peak hour percentage: 2/3 = 66.67% (at least 60%) </li> |
| 113 | + <li>Rated orders: 2 out of 3 (66.67% rating completion) </li> |
| 114 | + <li>Average rating: (4+3)/2 = 3.5 (less than 4.0) </li> |
| 115 | + <li>Result: <strong>Not a golden hour customer</strong> (average rating too low)</li> |
| 116 | + </ul> |
| 117 | + </li> |
| 118 | + <li><strong>Customer 103</strong>: |
| 119 | + <ul> |
| 120 | + <li>Total orders: 3 (at least 3) </li> |
| 121 | + <li>Peak hour orders: 3 out of 3 (19:00, 20:45, 18:30 all in evening peak)</li> |
| 122 | + <li>Peak hour percentage: 3/3 = 100% (at least 60%) </li> |
| 123 | + <li>Rated orders: 3 out of 3 (100% rating completion) </li> |
| 124 | + <li>Average rating: (5+4+5)/3 = 4.67 (at least 4.0) </li> |
| 125 | + <li>Result: <strong>Golden hour customer</strong></li> |
| 126 | + </ul> |
| 127 | + </li> |
| 128 | + <li><strong>Customer 104</strong>: |
| 129 | + <ul> |
| 130 | + <li>Total orders: 3 (at least 3) </li> |
| 131 | + <li>Peak hour orders: 0 out of 3 (10:00, 09:30, 16:00 all outside peak hours)</li> |
| 132 | + <li>Peak hour percentage: 0/3 = 0% (less than 60%) </li> |
| 133 | + <li>Result: <strong>Not a golden hour customer</strong> (insufficient peak hour orders)</li> |
| 134 | + </ul> |
| 135 | + </li> |
| 136 | + <li><strong>Customer 105</strong>: |
| 137 | + <ul> |
| 138 | + <li>Total orders: 3 (at least 3) </li> |
| 139 | + <li>Peak hour orders: 3 out of 3 (12:15, 13:00, 11:45 all in lunch peak)</li> |
| 140 | + <li>Peak hour percentage: 3/3 = 100% (at least 60%) </li> |
| 141 | + <li>Rated orders: 3 out of 3 (100% rating completion) </li> |
| 142 | + <li>Average rating: (4+5+4)/3 = 4.33 (at least 4.0) </li> |
| 143 | + <li>Result: <strong>Golden hour customer</strong></li> |
| 144 | + </ul> |
| 145 | + </li> |
| 146 | + |
| 147 | +</ul> |
| 148 | + |
| 149 | +<p>The results table is ordered by average_rating DESC, then customer_id DESC.</p> |
| 150 | +</div> |
| 151 | + |
| 152 | +<!-- description:end --> |
| 153 | + |
| 154 | +## 解法 |
| 155 | + |
| 156 | +<!-- solution:start --> |
| 157 | + |
| 158 | +### 方法一:分组统计 |
| 159 | + |
| 160 | +我们可以将订单按照 `customer_id` 进行分组,统计每个顾客的总订单数、峰值时段订单数、评分订单数和平均评分,然后根据题目中的条件进行筛选,最后按照平均评分降序、顾客 ID 降序排序。 |
| 161 | + |
| 162 | +<!-- tabs:start --> |
| 163 | + |
| 164 | +#### MySQL |
| 165 | + |
| 166 | +```sql |
| 167 | +# Write your MySQL query statement below |
| 168 | +SELECT |
| 169 | + customer_id, |
| 170 | + COUNT(1) total_orders, |
| 171 | + ROUND( |
| 172 | + SUM( |
| 173 | + TIME(order_timestamp) BETWEEN '11:00:00' AND '14:00:00' |
| 174 | + OR TIME(order_timestamp) BETWEEN '18:00:00' AND '21:00:00' |
| 175 | + ) / COUNT(1) * 100 |
| 176 | + ) peak_hour_percentage, |
| 177 | + ROUND(AVG(order_rating), 2) average_rating |
| 178 | +FROM restaurant_orders |
| 179 | +GROUP BY customer_id |
| 180 | +HAVING |
| 181 | + total_orders >= 3 |
| 182 | + AND peak_hour_percentage >= 60 |
| 183 | + AND average_rating >= 4.0 |
| 184 | + AND SUM(order_rating IS NOT NULL) / total_orders >= 0.5 |
| 185 | +ORDER BY average_rating DESC, customer_id DESC; |
| 186 | +``` |
| 187 | + |
| 188 | +#### Pandas |
| 189 | + |
| 190 | +```python |
| 191 | +import pandas as pd |
| 192 | +import numpy as np |
| 193 | + |
| 194 | + |
| 195 | +def find_golden_hour_customers(restaurant_orders: pd.DataFrame) -> pd.DataFrame: |
| 196 | + df = restaurant_orders.copy() |
| 197 | + df["order_timestamp"] = pd.to_datetime(df["order_timestamp"]) |
| 198 | + df["is_peak_hour"] = df["order_timestamp"].dt.time.between( |
| 199 | + pd.to_datetime("11:00:00").time(), pd.to_datetime("14:00:00").time() |
| 200 | + ) | df["order_timestamp"].dt.time.between( |
| 201 | + pd.to_datetime("18:00:00").time(), pd.to_datetime("21:00:00").time() |
| 202 | + ) |
| 203 | + grouped = ( |
| 204 | + df.groupby("customer_id") |
| 205 | + .agg( |
| 206 | + total_orders=("order_timestamp", "count"), |
| 207 | + peak_hour_count=("is_peak_hour", "sum"), |
| 208 | + average_rating=("order_rating", lambda x: x.dropna().mean()), |
| 209 | + non_null_rating_count=("order_rating", lambda x: x.notna().sum()), |
| 210 | + ) |
| 211 | + .reset_index() |
| 212 | + ) |
| 213 | + grouped["average_rating"] = grouped["average_rating"].round(2) |
| 214 | + grouped["peak_hour_percentage"] = ( |
| 215 | + grouped["peak_hour_count"] / grouped["total_orders"] * 100 |
| 216 | + ).round() |
| 217 | + filtered = grouped[ |
| 218 | + (grouped["total_orders"] >= 3) |
| 219 | + & (grouped["peak_hour_percentage"] >= 60) |
| 220 | + & (grouped["average_rating"] >= 4.0) |
| 221 | + & (grouped["non_null_rating_count"] / grouped["total_orders"] >= 0.5) |
| 222 | + ] |
| 223 | + filtered = filtered.sort_values( |
| 224 | + by=["average_rating", "customer_id"], ascending=[False, False] |
| 225 | + ) |
| 226 | + return filtered[ |
| 227 | + ["customer_id", "total_orders", "peak_hour_percentage", "average_rating"] |
| 228 | + ] |
| 229 | +``` |
| 230 | + |
| 231 | +<!-- tabs:end --> |
| 232 | + |
| 233 | +<!-- solution:end --> |
| 234 | + |
| 235 | +<!-- problem:end --> |
0 commit comments