|
| 1 | +# [1152. 用户网站访问行为分析](https://leetcode.cn/problems/analyze-user-website-visit-pattern/) |
| 2 | + |
| 3 | +- 标签:数组、哈希表、字符串、排序 |
| 4 | +- 难度:中等 |
| 5 | + |
| 6 | +## 题目链接 |
| 7 | + |
| 8 | +- [1152. 用户网站访问行为分析 - 力扣](https://leetcode.cn/problems/analyze-user-website-visit-pattern/) |
| 9 | + |
| 10 | +## 题目大意 |
| 11 | + |
| 12 | +**描述**:题目给了三个等长的数组 —— 用户名列表 $username$、访问时间列表 $timestamp$、网站列表 $website$。把它们组合起来看,每个位置 $i$ 代表:「用户 $username[i]$ 在时间 $timestamp[i]$ 访问了网站 $website[i]$。」 |
| 13 | + |
| 14 | +**访问模式(Pattern)**:就是按顺序访问的三个网站,比如 `["home", "about", "career"]`。三个网站可以重复,比如 `["leetcode", "love", "leetcode"]` 也算。 |
| 15 | + |
| 16 | +**得分**:一个模式每被一个用户按顺序访问过(不要求连续,按顺序就行),就得 1 分。同一个用户重复走同一个模式只算 1 次。 |
| 17 | + |
| 18 | +**要求**:返回得分最高的那个模式(以列表形式)。如果最高分有多个模式并列,返回字典序(可以理解为按字母排的先后顺序)最小的那个。 |
| 19 | + |
| 20 | +**说明**: |
| 21 | + |
| 22 | +- $3 \le username.length \le 50$(最多 50 条访问记录)。 |
| 23 | +- $1 \le username[i].length \le 10$。 |
| 24 | +- $timestamp.length == username.length$。 |
| 25 | +- $1 \le timestamp[i] \le 10^{9}$。 |
| 26 | +- $website.length == username.length$。 |
| 27 | +- $1 \le website[i].length \le 10$。 |
| 28 | +- $username[i]$ 和 $website[i]$ 都只含小写字符。 |
| 29 | +- 保证至少有一个用户访问了至少三个网站。 |
| 30 | +- 所有记录 $[username[i], timestamp[i], website[i]]$ 均不重复。 |
| 31 | + |
| 32 | +**示例**: |
| 33 | + |
| 34 | +- 示例 1: |
| 35 | + |
| 36 | +```python |
| 37 | +输入:username = ["joe","joe","joe","james","james","james","james","mary","mary","mary"], |
| 38 | + timestamp = [1,2,3,4,5,6,7,8,9,10], |
| 39 | + website = ["home","about","career","home","cart","maps","home","home","about","career"] |
| 40 | +输出:["home","about","career"] |
| 41 | +解释: |
| 42 | +joe 访问了:home → about → career(刚好三次,组成一个模式) |
| 43 | +james 访问了:home → cart → maps → home(四次,从中可以抽出多种三元组) |
| 44 | +mary 访问了:home → about → career(刚好三次,和 joe 模式相同) |
| 45 | +所以模式 ("home", "about", "career") 有 joe 和 mary 两个用户,得分最高。 |
| 46 | +``` |
| 47 | + |
| 48 | +- 示例 2: |
| 49 | + |
| 50 | +```python |
| 51 | +输入: username = ["ua","ua","ua","ub","ub","ub"], |
| 52 | + timestamp = [1,2,3,4,5,6], |
| 53 | + website = ["a","b","a","a","b","c"] |
| 54 | +输出: ["a","b","a"] |
| 55 | +解释: |
| 56 | +ua 访问了 a → b → a,模式 ("a","b","a") 得 1 分。 |
| 57 | +ub 访问了 a → b → c,模式 ("a","b","c") 等也得 1 分。 |
| 58 | +("a","b","a") 字典序最小,所以选它。 |
| 59 | +``` |
| 60 | + |
| 61 | +## 解题思路 |
| 62 | + |
| 63 | +### 思路 1:哈希表 + 组合枚举 |
| 64 | + |
| 65 | +这道题本质上就是一个「统计哪种访问模式最受欢迎」的问题。可以把数据看作一堆「谁在什么时间访问了什么网站」的记录,需要从中找出最受欢迎的「三个网站的访问序列」。 |
| 66 | + |
| 67 | +用人话来说,整个过程可以分成以下几步: |
| 68 | + |
| 69 | +**第 1 步:把三条数据合并成一条记录,并按时间排序。** |
| 70 | + |
| 71 | +三个数组是分开给的,但描述的是同一件事。先把它们捏在一起,形成 `(时间, 用户, 网站)` 这样的三元组(可以想象成一条表格里的三列)。然后按时间排好序,这样每个用户的访问顺序就清楚了。 |
| 72 | + |
| 73 | +**第 2 步:按用户分组,整理出每个人依次访问了哪些网站。** |
| 74 | + |
| 75 | +用哈希表(可以想象成一本通讯录,每个用户名字对应一个列表)来整理。遍历排序后的记录,把每个用户访问的网站按时间先后顺序放进他的列表里。 |
| 76 | + |
| 77 | +比如: |
| 78 | +``` |
| 79 | +joe → ["home", "about", "career"] |
| 80 | +james → ["home", "cart", "maps", "home"] |
| 81 | +mary → ["home", "about", "career"] |
| 82 | +``` |
| 83 | + |
| 84 | +**第 3 步:对每个用户,找出他访问过的所有「三个网站的组合」。** |
| 85 | + |
| 86 | +这里的「组合」是指按顺序选三个网站,但不要求连续。比如 james 访问了 4 个网站,从中按顺序选 3 个,有这些可能: |
| 87 | +- `("home", "cart", "maps")` |
| 88 | +- `("home", "cart", "home")` |
| 89 | +- `("home", "maps", "home")` |
| 90 | +- `("cart", "maps", "home")` |
| 91 | + |
| 92 | +注意,同一用户重复的相同模式只算一次(比如 joe 和 mary 虽然模式相同,但他们是不同用户,各算一次)。 |
| 93 | + |
| 94 | +**第 4 步:统计每个模式被多少个不同的用户访问过。** |
| 95 | + |
| 96 | +再用一个哈希表来统计:键是模式(三个网站组成的元组),值是一个集合(用来去重,记录有哪些用户访问过这个模式)。遍历每个用户的模式列表,把用户加入对应模式的集合中。 |
| 97 | + |
| 98 | +**第 5 步:找出得分最高的模式。** |
| 99 | + |
| 100 | +遍历统计结果,找到集合大小(也就是用户数)最大的模式。如果有多个并列,选字典序(可以理解为字符串比较的顺序)最小的那个。 |
| 101 | + |
| 102 | +**小结:** 这个方法的思路很简单——先把数据理清楚,再把所有可能的模式都列出来,最后统计哪个模式最受欢迎。 |
| 103 | + |
| 104 | +### 思路 1:代码 |
| 105 | + |
| 106 | +```python |
| 107 | +from collections import defaultdict |
| 108 | +from itertools import combinations |
| 109 | + |
| 110 | +class Solution: |
| 111 | + def mostVisitedPattern(self, username: List[str], timestamp: List[int], website: List[str]) -> List[str]: |
| 112 | + # 第 1 步:把用户名、时间、网站三个数组合并,按时间排序 |
| 113 | + # zip 把三个数组中对应位置的元素捏成三元组,sorted 按时间先后排列 |
| 114 | + data = sorted(zip(timestamp, username, website)) |
| 115 | + |
| 116 | + # 第 2 步:按用户分组,记录每个人依次访问了哪些网站 |
| 117 | + # defaultdict(list) 的意思是:用字典来存,值默认是空列表 |
| 118 | + user_visits = defaultdict(list) |
| 119 | + for time, user, site in data: |
| 120 | + user_visits[user].append(site) |
| 121 | + |
| 122 | + # 第 3~4 步:统计每个模式被多少个不同的用户访问过 |
| 123 | + # pattern_count 的键是 (网站1, 网站2, 网站3),值是一个集合(存放用户名) |
| 124 | + pattern_count = defaultdict(set) |
| 125 | + |
| 126 | + for user, sites in user_visits.items(): |
| 127 | + # 如果该用户访问的网站不够 3 个,不可能组成模式,直接跳过 |
| 128 | + if len(sites) < 3: |
| 129 | + continue |
| 130 | + |
| 131 | + # 用 combinations 函数生成所有可能的"按顺序选 3 个"组合 |
| 132 | + # combinations(sites, 3) 会按顺序选出所有三元素子序列 |
| 133 | + # 用 set 去重,同一用户重复的相同模式只算一次 |
| 134 | + patterns = set(combinations(sites, 3)) |
| 135 | + |
| 136 | + # 把这个用户加入每个模式的集合中 |
| 137 | + for pattern in patterns: |
| 138 | + pattern_count[pattern].add(user) |
| 139 | + |
| 140 | + # 第 5 步:从所有模式中找出得分最高的 |
| 141 | + max_count = 0 |
| 142 | + result = None |
| 143 | + |
| 144 | + for pattern, users in pattern_count.items(): |
| 145 | + count = len(users) # 这个模式有多少个不同的用户访问过 |
| 146 | + # 如果得分更高,或者得分相同但字典序更小,就更新结果 |
| 147 | + if count > max_count or (count == max_count and (result is None or pattern < result)): |
| 148 | + max_count = count |
| 149 | + result = pattern |
| 150 | + |
| 151 | + # 把结果从元组转成列表返回 |
| 152 | + return list(result) |
| 153 | +``` |
| 154 | + |
| 155 | +### 思路 1:复杂度分析 |
| 156 | + |
| 157 | +- **时间复杂度**:$O(n \log n + m \times k^3)$。用人话说就是:排序花了一些时间($n$ 越大越明显);每个用户 $m$ 需要从他访问的 $k$ 个网站中枚举所有三元组组合,组合数量最多是 $\binom{k}{3}$(即 $k$ 个里选 3 个的方案数)。 |
| 158 | +- **空间复杂度**:$O(n + m \times k^3)$。需要存储所有用户的访问记录,以及所有可能的三元组模式。 |
0 commit comments