Skip to content

Commit 73d4d35

Browse files
committed
补充第 1100 ~ 1199 题的题目解析(增加 61 道题)
1 parent 02499d0 commit 73d4d35

63 files changed

Lines changed: 6189 additions & 1 deletion

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎docs/00_preface/00_05_solutions_list.md‎

Lines changed: 62 additions & 1 deletion
Large diffs are not rendered by default.
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# [1138. 字母板上的路径](https://leetcode.cn/problems/alphabet-board-path/)
2+
3+
- 标签:哈希表、字符串
4+
- 难度:中等
5+
6+
## 题目链接
7+
8+
- [1138. 字母板上的路径 - 力扣](https://leetcode.cn/problems/alphabet-board-path/)
9+
10+
## 题目大意
11+
12+
**描述**:给定一个字符串 $target$,代表我们想要拼出的字母序列。
13+
14+
字母板是一块 6 行 5 列的板子,上面按顺序排列着字母:
15+
16+
```
17+
a b c d e
18+
f g h i j
19+
k l m n o
20+
p q r s t
21+
u v w x y
22+
z
23+
```
24+
25+
注意最后一行只有字母 `z` 一个,其他行都是 5 个字母。
26+
27+
一开始我们的指针在位置 $(0, 0)$,也就是左上角的字母 `a` 上。我们可以通过以下指令移动指针:
28+
29+
- `U`:向上移一行(如果目标位置有字母的话)
30+
- `D`:向下移一行(如果目标位置有字母的话)
31+
- `L`:向左移一列(如果目标位置有字母的话)
32+
- `R`:向右移一列(如果目标位置有字母的话)
33+
- `!`:把当前位置的字母添加到答案中
34+
35+
**要求**:输出一个指令序列,使得我们的指针依次走到 $target$ 中每个字母的位置,并用 `!` 把字母记录下来。要求指令序列尽可能短(返回任意一种最短路径即可)。
36+
37+
**说明**:
38+
39+
- $1 \le target.length \le 10^{3}$。
40+
- $target$ 仅含有小写英文字母。
41+
42+
**示例**:
43+
44+
- 示例 1:
45+
46+
```python
47+
输入:target = "leet"
48+
输出:"DDR!UURRR!!DDD!"
49+
```
50+
51+
- 示例 2:
52+
53+
```python
54+
输入:target = "code"
55+
输出:"RR!DDRR!UUL!R!"
56+
```
57+
58+
## 解题思路
59+
60+
### 思路 1:模拟法(按坐标一步步走)
61+
62+
这道题其实就是一个「按图索骥」的过程。我们可以把字母板想象成一张地图,每个字母都有一个确定的坐标(第几行第几列)。我们要做的,就是从当前位置出发,一步步移动到目标字母的位置,然后按 `!` 记录下来,接着再走向下一个字母。
63+
64+
**这里的难点只有一个:字母 `z` 比较特殊,它孤零零地待在最后一行的第一列,周围没有别的字母。** 如果移动顺序不对,可能经过没有字母的区域(越界)。所以需要特殊处理。
65+
66+
**步骤拆解:**
67+
68+
1. **建立字母到坐标的映射。** 每个字母在字母板上的位置可以用「行号 = (字母序号 ÷ 5) 的整数部分,列号 = (字母序号 ÷ 5) 的余数」来计算。字母序号就是把 `a` 当作 0、`b` 当作 1……`z` 当作 25。
69+
70+
2. **从起始位置出发。** 初始位置是 `a` 的坐标,即第 0 行第 0 列。
71+
72+
3. **逐个处理目标字符串中的每个字母:**
73+
- 先算出目标字母的行号和列号。
74+
- 然后从当前位置移动到目标位置。
75+
- 移动的时候要留意:如果当前位置是 `z`(第 5 行第 0 列),或者目标位置是 `z`,需要调整移动顺序,避免走到板子外面去。
76+
77+
4. **关于 `z` 的特殊处理规则**(只需记住两个口诀):
78+
- **从别处走向 `z` 时**:先左移(如果当前在右边),再上移(如果当前在下面),再下移,最后右移。因为 `z` 在最下面一行却只有第一列,从右边或下面过来时,必须先走到安全的路径再往下。
79+
- **从 `z` 走向别处时**:先上移(离开最后一行!),再处理其他方向。如果不先上移,向左或向右都可能走到不存在的格子。
80+
81+
**为什么这个顺序能保证不越界?**
82+
因为字母板除了最后一行只有 `z` 一个字母外,其他格子都是完整的。所以只要保证:到达 `z` 之前先调整好列,离开 `z` 之后先离开最后一行,就不会走到空白区域。
83+
84+
### 思路 1:代码
85+
86+
```python
87+
class Solution:
88+
def alphabetBoardPath(self, target: str) -> str:
89+
# 辅助函数:给定字母,返回它在字母板上的行列坐标
90+
def get_pos(ch):
91+
idx = ord(ch) - ord('a') # 把字母转成数字,a=0, b=1, ..., z=25
92+
return idx // 5, idx % 5 # 行号 = 除以5取整,列号 = 除以5取余数
93+
94+
res = [] # 收集指令
95+
cur_row, cur_col = 0, 0 # 指针当前位置,一开始在 (0,0) 也就是字母 a
96+
97+
# 逐个处理目标字符串中的每个字母
98+
for ch in target:
99+
tar_row, tar_col = get_pos(ch) # 目标字母的行列
100+
101+
# 情况1:目标字母是 z(需要特殊处理移动顺序)
102+
if ch == 'z':
103+
# 如果当前在 z 的右边,先向左移
104+
if cur_col > tar_col:
105+
res.append('L' * (cur_col - tar_col))
106+
cur_col = tar_col
107+
# 如果当前在 z 的下边(其实不会发生,但为了统一逻辑),先向上移
108+
if cur_row > tar_row:
109+
res.append('U' * (cur_row - tar_row))
110+
cur_row = tar_row
111+
# 如果当前在 z 的上边,向下移
112+
if cur_row < tar_row:
113+
res.append('D' * (tar_row - cur_row))
114+
cur_row = tar_row
115+
# 最后如果当前在 z 的左边,向右移
116+
if cur_col < tar_col:
117+
res.append('R' * (tar_col - cur_col))
118+
cur_col = tar_col
119+
else:
120+
# 情况2:目标字母不是 z
121+
# 如果当前位置是 z(在最后一行),先向上移,避免越界
122+
if cur_row > tar_row:
123+
res.append('U' * (cur_row - tar_row))
124+
cur_row = tar_row
125+
# 向下移
126+
if cur_row < tar_row:
127+
res.append('D' * (tar_row - cur_row))
128+
cur_row = tar_row
129+
# 向左移
130+
if cur_col > tar_col:
131+
res.append('L' * (cur_col - tar_col))
132+
cur_col = tar_col
133+
# 向右移
134+
if cur_col < tar_col:
135+
res.append('R' * (tar_col - cur_col))
136+
cur_col = tar_col
137+
138+
# 到达目标字母后,用 ! 记录下来
139+
res.append('!')
140+
141+
# 把指令列表拼接成字符串返回
142+
return ''.join(res)
143+
```
144+
145+
### 思路 1:复杂度分析
146+
147+
- **时间复杂度**:$O(n)$。其中 $n$ 是字符串 $target$ 的长度。对于每个字母,我们最多移动 4 步(上、下、左、右),所以总步数和 $n$ 成正比。用人话说就是:字符串多长,我们就处理多少次,每次处理花固定时间。
148+
- **空间复杂度**:$O(1)$。除了存储结果的指令序列外,我们只用了几个固定大小的变量,不随输入规模增大。
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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)$。需要存储所有用户的访问记录,以及所有可能的三元组模式。
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# [1134. 阿姆斯特朗数](https://leetcode.cn/problems/armstrong-number/)
2+
3+
- 标签:数学
4+
- 难度:简单
5+
6+
## 题目链接
7+
8+
- [1134. 阿姆斯特朗数 - 力扣](https://leetcode.cn/problems/armstrong-number/)
9+
10+
## 题目大意
11+
12+
**描述**:给定一个整数 $n$。
13+
14+
如果 $n$ 是一个 $k$ 位数,并且它每一位数字的 $k$ 次方之和正好等于它本身,那么 $n$ 就是阿姆斯特朗数。
15+
16+
举个例子:$153$ 是 3 位数,$1^3 + 5^3 + 3^3 = 1 + 125 + 27 = 153$,所以 153 就是阿姆斯特朗数。
17+
18+
**要求**:判断 $n$ 是不是阿姆斯特朗数,是就返回 `true`,不是就返回 `false`。
19+
20+
**说明**:
21+
22+
- $1 \le n \le 10^{8}$(最大 1 亿)。
23+
24+
**示例**:
25+
26+
- 示例 1:
27+
28+
```python
29+
输入:n = 153
30+
输出:true
31+
解释:
32+
153 是一个 3 位数,且 153 = 1^3 + 5^3 + 3^3。
33+
```
34+
35+
- 示例 2:
36+
37+
```python
38+
输入:n = 123
39+
输出:false
40+
解释:123 是一个 3 位数,且 123 != 1^3 + 2^3 + 3^3 = 36。
41+
```
42+
43+
## 解题思路
44+
45+
### 思路 1:逐位拆解 + 累加求和
46+
47+
这个问题的思路非常直接。可以把它想象成「数钱」的过程:拿到一张钞票,先看它有几个数字(是几张?),然后把每个数字分别拿出来,做它自己的几次方,加在一起,最后看看总和和原数是不是一样。
48+
49+
具体的步骤是:
50+
51+
1. **先看这个数有几位。** 比如 153 是 3 位数,那么每个数字就要算它的 3 次方。怎么算位数呢?最简单的方法就是把它转成字符串看长度。
52+
53+
2. **把每一位数字单独取出来。** 用一种叫做「取模和整除」的技巧:
54+
- 用 `n % 10` 可以拿到最后一位数字(比如 153 % 10 = 3)。
55+
- 用 `n // 10` 可以去掉最后一位(比如 153 // 10 = 15)。
56+
- 重复这个过程,就能从左到右取出每一位。
57+
58+
3. **对每一位做它的 k 次方,累加起来。** 比如 153,依次取出 3、5、1,计算 $3^3 + 5^3 + 1^3$。
59+
60+
4. **比较累加结果和原数。** 相等就是阿姆斯特朗数,不相等就不是。
61+
62+
**为什么这样就能判断?** 因为这就是阿姆斯特朗数的定义——逐位计算再求和。没有任何隐藏的技巧,按定义做就对了。
63+
64+
### 思路 1:代码
65+
66+
```python
67+
class Solution:
68+
def isArmstrong(self, n: int) -> bool:
69+
# 第 1 步:算出 n 是几位数
70+
# 把数字转成字符串,字符串的长度就是位数
71+
k = len(str(n))
72+
73+
# 第 2~3 步:逐位取出,计算 k 次方,累加起来
74+
total = 0 # 用来存放累加结果
75+
temp = n # 复制一份,因为后面要修改它
76+
77+
while temp > 0:
78+
digit = temp % 10 # 拿到最后一位数字(比如 153→3)
79+
total += digit ** k # 算它的 k 次方,加到总和里
80+
temp //= 10 # 去掉最后一位(比如 153→15),继续处理下一位
81+
82+
# 第 4 步:判断累加结果是否等于原数
83+
return total == n
84+
```
85+
86+
### 思路 1:复杂度分析
87+
88+
- **时间复杂度**:$O(\log n)$。用人话说就是:$n$ 有几位,我们就处理几次。$n$ 最大是 1 亿(8 位数),所以最多跑 8 遍循环,非常快。
89+
- **空间复杂度**:$O(1)$。只用了几个固定变量,不随 $n$ 的大小变化。

0 commit comments

Comments
 (0)