简单题
202. 快乐数
编写一个算法来判断一个数 n
是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n
是 快乐数 就返回 true
;不是,则返回 false
。
示例 1:
- 输入: n = 19
- 输出: true
- 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1
示例 2:
- 输入: n = 2
- 输出: false
提示:
- 1 <= n <= 231 - 1
参考代码
|
|
算法步骤
初始化:
- 创建一个变量
sum
用于保存每次计算的平方和,初始为 0。 - 创建一个变量
digit
用于保存提取的每位数字,初始为 0。 - 使用哈希表
map
来记录已经出现过的平方和,初始化时将输入n
加入。
- 创建一个变量
循环处理:
- 使用
while (true)
无限循环,直到找到结果(快乐数或确定不是快乐数)。 - 在循环内,提取
n
的最后一位数字,并更新n
去掉最后一位。 - 计算当前数字的平方并累加到
sum
。
- 使用
条件判断:
- 如果
n
变为 0,表示已经处理完所有位:- 检查
sum
是否为 1,是则退出循环,返回true
。 - 如果当前的
sum
已经在哈希表中出现过,说明进入循环,返回false
。 - 否则,将当前
sum
记录进哈希表,并将sum
作为新的n
继续处理,同时重置sum
。
- 检查
- 如果
结束条件:
- 如果循环结束,说明
n
到达 1,返回true
。
- 如果循环结束,说明
注意点
- 循环检测:使用哈希表来记录已经出现过的平方和,有效避免无限循环。
- 重置和更新:在每次计算平方和后,将其作为新的
n
处理,并重置sum
,确保每轮计算的独立性。
205. 同构字符串
给定两个字符串 s
和 t
,判断它们是否是同构的。
如果 s
中的字符可以按某种映射关系替换得到 t
,那么这两个字符串是同构的。
每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
示例 1:
- 输入: s = “egg”, t = “add”
- 输出: true
示例 2:
- 输入: s = “foo”, t = “bar”
- 输出: false
示例 3:
- 输入: s = “paper”, t = “title”
- 输出: true
提示:
- 1 <=
s.length
<= 5 * 104 t.length == s.length
s
和t
由任意有效的 ASCII 字符组成
参考代码
|
|
主要步骤
建立映射关系:使用一个哈希表
map
来存储从字符串s
到t
的映射关系。遍历字符串:使用循环逐个检查
s
中的字符:- 如果字符还没有在映射中,添加字符
s[i]
的映射关系到t[i]
。 - 如果已经存在映射关系,检查当前字符的映射是否一致。
- 如果字符还没有在映射中,添加字符
反向映射检查:为了确保不同的字符不会映射到相同的字符,使用另一个哈希表
reversedMap
检查从t
到s
的反向映射关系。返回结果:如果所有的映射关系都通过了检查,则返回
true
;否则返回false
。
注意点
- 唯一性:同构的要求包括相同字符必须映射为相同字符,不同字符不能映射为相同字符。这两个条件的检查都必须通过,才能判定两个字符串是同构的。
290. 单词规律
给定一种规律 pattern
和一个字符串 s
,判断 s
是否遵循相同的规律。
这里的 遵循 指完全匹配,例如, pattern
里的每个字母和字符串 s
中的每个非空单词之间存在着双向连接的对应规律。
示例1:
- 输入: pattern = “abba”, s = “dog cat cat dog”
- 输出: true
示例 2:
- 输入: pattern = “abba”, s = “dog cat cat fish”
- 输出: false
示例 3:
- 输入: pattern = “aaaa”, s = “dog cat cat dog”
- 输出: false
提示:
- 1 <=
pattern.length
<= 300 pattern
只包含小写英文字母- 1 <=
s.length
<= 3000 s
只包含小写英文字母和' '
s
不包含 任何前导或尾随对空格s
中每个单词都被 单个空格 分隔
参考代码
|
|
主要思路
分词: 利用
istringstream
从字符串s
中逐个提取单词。正向映射: 使用一个哈希表
map
来存储单词与模式字符之间的映射关系。反向映射: 使用另一个哈希表
reversedMap
来确定模式字符与单词的反向映射。一致性检查: 在映射建立过程中,检查每个单词和字符之间的对应关系是否一致。
注意点
边界条件: 必须确保读取的单词数量与模式的长度一致,如果不一致则直接返回
false
。双向映射: 在建立映射时要同时考虑单向与反向映射,以确保没有冲突。例如,如果同一个字符可以对应多个不同的单词,则返回
false
。性能考虑: 使用哈希表进行查找,可以有效提高查找的效率。
387. 字符串中的第一个唯一字符
给定一个字符串 s
,找到 它的第一个不重复的字符,并返回它的索引 。如果不存在,则返回 -1
。
示例 1:
- 输入: s = “leetcode”
- 输出: 0
示例 2:
- 输入: s = “loveleetcode”
- 输出: 2
示例 3:
- 输入: s = “aabb”
- 输出: -1
提示:
- 1 <=
s.length
<= 105 s
只包含小写字母
参考代码
|
|
关键步骤
统计字符出现次数:
- 使用
unordered_map
记录每个字符在字符串中出现的次数。 - 遍历字符串,对于每个字符
c
,将其出现次数加一。
- 使用
查找第一个不重复字符:
- 再次遍历字符串,检查每个字符的出现次数。
- 如果某个字符只出现一次,返回该字符的索引。
返回结果:
- 如果遍历结束后没有找到不重复的字符,返回 -1。
594. 最长和谐子序列
和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1
。
给你一个整数数组 nums
,请你在所有可能的
子序列
中找到最长的和谐子序列的长度。
数组的 子序列 是一个由数组派生出来的序列,它可以通过删除一些元素或不删除元素、且不改变其余元素的顺序而得到。
示例 1:
- 输入: nums = [1,3,2,2,5,2,3,7]
- 输出: 5
- 解释: 最长和谐子序列是
[3,2,2,2,3]
。
示例 2:
- 输入: nums = [1,2,3,4]
- 输出: 2
- 解释: 最长和谐子序列是
[1,2]
,[2,3]
和[3,4]
,长度都为 2。
示例 3:
- 输入: nums = [1,1,1,1]
- 输出: 0
- 解释: 不存在和谐子序列。
提示:
- 1 <=
nums.length
<= 2 * 104 - -109 <=
nums[i]
<= 109
参考代码
|
|
代码思路
统计元素出现次数:
- 使用
map<int, int>
来存储每个元素及其出现的次数。遍历nums
数组,将每个数字的出现次数记录在映射中。
- 使用
检查映射中的元素个数:
- 如果映射中只有一个数字,则没有和谐子序列,直接返回 0。
遍历映射:
- 使用迭代器遍历映射,从第二个元素开始,检查前一个元素与当前元素的差值是否为 1。
- 如果差值为 1,计算这两个元素的出现次数之和,并更新
maxLen
。
- 如果差值为 1,计算这两个元素的出现次数之和,并更新
- 使用迭代器遍历映射,从第二个元素开始,检查前一个元素与当前元素的差值是否为 1。
返回结果:
- 最后返回找到的最长和谐子序列的长度
maxLen
。
- 最后返回找到的最长和谐子序列的长度
注意点
- 边界情况:在处理边界情况时,注意如果只有一个元素,和谐子序列的长度应直接返回 0。
- 迭代器的使用:使用
next(mapN.begin())
从第二个元素开始遍历,对应前一个元素和当前元素的比较逻辑。 - 注意变量的初始化:特别是
maxLen
和tempLen
的初始值应该设置为 0,以确保在后续逻辑中能够正确比较和更新。
599. 两个列表的最小索引总和
假设 Andy 和 Doris 想在晚餐时选择一家餐厅,并且他们都有一个表示最喜爱餐厅的列表,每个餐厅的名字用字符串表示。
你需要帮助他们找到索引和最小的共同喜爱的餐厅。 如果答案不止一个,则输出所有答案并且不考虑顺序。 你可以假设答案总是存在。
示例 1:
- 输入: list1 = [“Shogun”, “Tapioca Express”, “Burger King”, “KFC”],list2 = [“Piatti”, “The Grill at Torrey Pines”, “Hungry Hunter Steakhouse”, “Shogun”]
- 输出: [“Shogun”]
- 解释: 他们唯一共同喜爱的餐厅是“Shogun”。
示例 2:
- 输入: list1 = [“Shogun”, “Tapioca Express”, “Burger King”, “KFC”],list2 = [“KFC”, “Shogun”, “Burger King”]
- 输出: [“Shogun”]
- 解释: 他们共同喜爱且具有最小索引和的餐厅是“Shogun”,它有最小的索引和1(0+1)。
提示:
- 1 <=
list1.length
,list2.length
<= 1000 - 1 <=
list1[i].length
,list2[i].length
<= 30 list1[i]
和list2[i]
由空格' '
和英文字母组成。list1
的所有字符串都是 唯一 的。list2
中的所有字符串都是 唯一 的。
参考代码
|
|
主要步骤
哈希表存储:
- 通过
unordered_map
创建一个哈希表rest1
,用于存储 Andy 的餐厅及对应的索引,使得查找更加高效。
- 通过
填充哈希表:
- 遍历
list1
(Andy的餐厅列表),将餐厅名称作为键,索引作为值存入rest1
。
- 遍历
遍历 Doris 的列表:
- 通过循环遍历
list2
(Doris的餐厅列表),检查每个餐厅是否在rest1
中存在。 - 如果存在,计算该餐厅的索引和(即 Andy 和 Doris 的索引和)。
- 通过循环遍历
更新结果:
- 维护最小索引和的变量
indexSum
,并在找到更小的索引和时更新结果。 - 如果当前的索引和等于最小索引和,则将该餐厅添加到结果中。
- 维护最小索引和的变量
返回结果:
- 函数返回一个包含共同最爱餐厅的向量。
注意点
哈希表的使用:哈希表的使用使得餐厅的查找变得高效,避免了重复的遍历。
清空结果向量:在找到更小的索引和时,需要清空结果向量并添加新的餐厅。
使用
INT_MAX
:初始化最小索引和为INT_MAX
是一种常见的做法,用于确保可以找到最小值。
645. 错误的集合
集合 s
包含从 1
到 n
的整数。不幸的是,因为数据错误,导致集合里面某一个数字复制了成了集合里面的另外一个数字的值,导致集合 丢失了一个数字 并且 有一个数字重复 。
给定一个数组 nums
代表了集合 S
发生错误后的结果。
请你找出重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。
示例 1:
- 输入: nums = [1,2,2,4]
- 输出: [2,3]
示例 2:
- 输入: nums = [1,1]
- 输出: [1,2]
提示:
- 2 <=
nums.length
<= 104 - 1 <=
nums[i]
<= 104
参考代码
|
|
函数解析
输入和输出
- 输入:一个整数数组
nums
,表示错误的集合。 - 输出:一个包含重复整数和丢失整数的数组。
- 输入:一个整数数组
数据结构
- 使用一个大小为 10001 的
vector
,以 0 初始化,用于计数出现的数字。因为我们要检查的数字范围是 1 到 n,所以 10001 是合适的大小(vec[0]
未使用)。
- 使用一个大小为 10001 的
统计出现次数
- 遍历输入数组
nums
,使用计数数组vec
统计每个数字出现的次数。
- 遍历输入数组
查找丢失和重复的数字
- 再次遍历计数数组
vec
,检查每个数字的出现次数:- 如果一个数字的计数为 2,则该数字是重复的。
- 如果一个数字的计数为 0,则该数字是丢失的。
- 再次遍历计数数组
注意点
- 遍历优化:遍历计数数组时,使用条件
lost == -1 || repeat == -1
进行优化,一旦找到需要的数字即可停止,提升效率。 - 返回值处理:使用三元运算符简化返回值的处理,确保函数在找不到丢失或重复数字时返回空数组。
884. 两句话中的不常见单词
句子 是一串由空格分隔的单词。每个 单词 仅由小写字母组成。
如果某个单词在其中一个句子中恰好出现一次,在另一个句子中却 没有出现 ,那么这个单词就是 不常见的 。
给你两个 句子 s1
和 s2
,返回所有 不常用单词 的列表。返回列表中单词可以按 任意顺序 组织。
示例 1:
- 输入: s1 = “this apple is sweet”, s2 = “this apple is sour”
- 输出: [“sweet”,“sour”]
示例 2:
- 输入: s1 = “apple apple”, s2 = “banana”
- 输出: [“banana”]
提示:
- 1 <=
s1.length
,s2.length
<= 200 s1
和s2
由小写英文字母和空格组成s1
和s2
都不含前导或尾随空格s1
和s2
中的所有单词间均由单个空格分隔
参考代码
|
|
主要步骤
使用字符串流:
- 通过
istringstream
分别将两个句子s1
和s2
处理成单词流,便于逐个读取单词。
- 通过
哈希表记录单词出现次数:
- 使用
unordered_map
来存储每个单词及其在两个句子中出现的次数。哈希表的键是单词,值是该单词出现的次数。
- 使用
统计单词出现次数:
- 通过
while
循环读取单词,并对出现的单词在哈希表中进行计数。
- 通过
查找不常见单词:
- 遍历哈希表,找出出现次数为1的单词,并将其添加到结果列表中。
返回结果:
- 最后返回包含不常见单词的向量。
注意点
字符串流的用法:使用
istringstream
是处理字符串的一个很好的选择,它能够方便地将整个句子拆分为单个单词。哈希表的选择:使用
unordered_map
可以实现快速的查找和插入操作,适合用于存储单词及其计数。单词读取方式:在读取单词时,使用
||
可以在两个句子流中同时读取,但要注意边界条件,以避免读取到未知的状态。
1207. 独一无二的出现次数
给你一个整数数组 arr
,如果每个数的出现次数都是独一无二的,就返回 true
;否则返回 false
。
示例 1:
- 输入: arr = [1,2,2,1,1,3]
- 输出: true
- 解释: 在该数组中,1 出现了 3 次,2 出现了 2 次,3 只出现了 1 次。没有两个数的出现次数相同。
示例 2:
- 输入: arr = [1,2]
- 输出: false
示例 3:
- 输入: arr = [-3,0,1,-3,1,1,1,-3,10,0]
- 输出: true
提示:
- 1 <=
arr.length
<= 1000 - -1000 <=
arr[i]
<= 1000
参考代码
|
|
代码解析
数据结构:
unordered_map<int, int> numCount
:用于存储每个数字及其出现次数。unordered_set<int> unique
:用于存储唯一的出现次数。
统计出现次数:
- 遍历整数数组 arr
,通过 ++numCount[num]
统计每个数字的出现次数。
记录唯一出现次数:
- 遍历
numCount
,将每个数字的出现次数插入unique
集合中。由于集合的特性,重复的出现次数会被自动忽略。
- 遍历
比较大小:
- 最后,比较
unique
集合的大小与numCount
的大小是否相等。如果相等,则说明出现次数都是独一无二的,返回true
;否则返回false
。
- 最后,比较
注意点
- 使用 unordered_map 和 unordered_set:
unordered_map
提供平均 O(1) 的时间复杂度用于插入和查找,适合快速统计频率。unordered_set
也具有平均 O(1) 的时间复杂度,用于存储唯一值,避免重复。
中等题
3. 无重复字符的最长子串
给定一个字符串 s
,请你找出其中不含有重复字符的 最长 子串的长度。
示例 1:
- 输入: s = “abcabcbb”
- 输出: 3
- 解释: 因为无重复字符的最长子串是
"abc"
,所以其长度为 3。
示例 2:
- 输入: s = “bbbbb”
- 输出: 1
- 解释: 因为无重复字符的最长子串是
"b"
,所以其长度为 1。
示例 3:
- 输入: s = “pwwkew”
- 输出: 3
- 解释: 因为无重复字符的最长子串是
"wke"
,所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke"
是一个_子序列,_不是子串。
提示:
- 0 <=
s.length
<= 5 * 104 s
由英文字母、数字、符号和空格组成
参考代码
|
|
关键步骤
初始化:
- 使用
unordered_map<char, int> unique
来存储字符及其索引。 - 初始化
len
用于当前无重复字符的长度,maxLen
用于记录最大的长度。
- 使用
遍历字符串:
- 使用
for
循环遍历字符串的每个字符。 - 检查当前字符是否已经存在于哈希表中:
- 如果存在,更新
maxLen
如果当前长度len
大于maxLen
,并重置len
为 0。 - 将索引
i
更新为当前重复字符的索引,并清空哈希表。
- 如果存在,更新
- 如果不存在,增加当前无重复字符的长度
len
,并将当前字符及其索引存入哈希表。
- 使用
返回结果:
- 最后返回
maxLen
和len
中的较大值,以确保得到最长的无重复字符子串长度。
- 最后返回
注意点
索引跳转:当发现重复字符时,需要将索引
i
跳转到该字符上一次出现的位置,这一操作确保了不会错过字符串中的任何潜在最长子串。哈希表的清空:在发现重复字符后,需要清空
unique
,这样才能从当前索引重新开始统计下一段无重复字符的长度。边界条件:需要处理字符串长度小于或等于 1 的情况,直接返回字符串的长度。
204. 计数质数
给定整数 n
,返回 所有小于非负整数 n
的质数的数量 。
示例 1:
- 输入: n = 10
- 输出: 4
- 解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
- 输入: n = 0
- 输出: 0
示例 3:
- 输入: n = 1
- 输出: 0
提示:
- 0 <=
n
<= 5 * 106
参考代码
|
|
主要步骤
边界条件处理:
- 如果
n
为 0 或 1,直接返回 0,因为 0 和 1 都不是质数。
- 如果
初始化布尔向量:
- 使用
vector<bool> nums(n, true)
初始化一个布尔类型的向量,长度为n
,所有元素初始值为true
,表示默认所有数都是质数。
- 使用
埃拉托斯特尼筛法:
- 从 2 开始,遍历到
n/2
,对于每个质数i
,将其倍数标记为非质数(即false
)。 - 具体实现是通过一个
while
循环,不断将i
的倍数标记为false
,直到倍数超过n-1
。
- 从 2 开始,遍历到
统计质数数量:
- 遍历布尔向量
nums
,统计值为true
的元素数量。 - 由于 0 和 1 不是质数,最终结果需要减去 2。
- 遍历布尔向量
返回结果:
- 返回统计的质数数量。
注意点
边界条件:函数一开始就处理了
n
为 0 或 1 的情况,避免了后续不必要的计算。布尔向量的初始化:使用
vector<bool>
来标记质数和非质数,初始值为true
,表示默认所有数都是质数。埃拉托斯特尼筛法的实现:遍历范围是从 2 到
n/2
,因为大于n/2
的数不可能再有倍数小于n
。使用while
循环来标记非质数,确保所有倍数都被正确标记。统计质数数量:在统计质数数量时,需要减去 0 和 1 这两个非质数。
215. 数组中的第K个最大元素
给定整数数组 nums
和整数 k
,请返回数组中第 **k**
个最大的元素。
请注意,你需要找的是数组排序后的第 k
个最大的元素,而不是第 k
个不同的元素。
你必须设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
- 输入:
[3,2,1,5,6,4],
k = 2 - 输出: 5
示例 2:
- 输入:
[3,2,3,1,2,4,5,5,6],
k = 4 - 输出: 4
提示:
- 1 <=
k
<= nums.length <= 105 - -104 <=
nums[i]
<= 104
参考代码
|
|
代码分析
最小堆的使用:
- 在 C++ 中,使用
priority_queue<int, vector<int>, greater<int>>
来实现最小堆。greater<int>
表示我们希望实现一个最小堆的行为。
- 在 C++ 中,使用
遍历数组:
- 对数组中的每个元素
num
进行遍历。 - 将
num
加入最小堆。 - 如果堆的大小超过了
k
,则弹出堆顶元素(即当前堆中最小的元素),以确保堆中始终保存的是最大的k
个元素。
- 对数组中的每个元素
返回结果:
- 最后,堆顶元素
mHeap.top()
即为第k
个最大的元素。
- 最后,堆顶元素
注意点
排序与元素的关系:注意这里返回的是排序后的第
k
个最大元素,不要混淆为第k
个不同的最大元素。堆的特性:熟悉堆的基本操作(插入、删除、获取堆顶元素)是实现该算法的基础。
最小堆模板参数说明:
priority_queue<int, vector<int>, greater<int>>
中,int
是存储的元素类型,vector<int>
是底层容器的类型(用来存储堆中的元素),greater<int>
则用于指定堆的比较方式,确保堆顶始终是最小的元素,从而实现最小堆的特性。
347. 前 K 个高频元素
给你一个整数数组 nums
和一个整数 k
,请你返回其中出现频率前 k
高的元素。你可以按 任意顺序 返回答案。
示例 1:
- 输入: nums = [1,1,1,2,2,3], k = 2
- 输出: [1,2]
示例 2:
- 输入: nums = [1], k = 1
- 输出: [1]
提示:
- 1 <=
nums.length
<= 105 k
的取值范围是[1, 数组中不相同的元素的个数]
- 题目数据保证答案唯一,换句话说,数组中前
k
个高频元素的集合是唯一的
进阶: 你所设计算法的时间复杂度 必须 优于 O(n log n)
,其中 n
是数组大小。
参考代码
|
|
函数步骤
频率统计:
- 使用
unordered_map<int, int>
类型的frequency
来存储每个元素及其出现的频率。 - 遍历数组
nums
,对每个元素进行频率统计。
- 使用
优先队列:
- 使用
priority_queue
来维护频率最高的元素。 - 自定义比较函数,使得优先队列能够按照频率从高到低进行排序。
- 使用
维护队列大小:
- 遍历频率映射,将每个元素及其对应的频率压入优先队列。
- 如果队列的大小超过
k
,则移除频率最低的元素(即队头元素)。
提取结果:
- 最后,通过弹出优先队列中的元素,将频率最高的元素保存到结果数组中,并返回该数组。
注意点
使用
unordered_map
:- 频率统计使用了
unordered_map
,其查找和插入时间复杂度为 O(1),适合大量数据处理。
- 频率统计使用了
自定义比较函数:
- 自定义的比较函数在优先队列中起到关键作用,确保频率高的元素优先出队。
优先队列的容量管理:
- 在维护优先队列大小时,通过条件判断实现了只保留前
k
高频元素,可以有效提高空间利用率。
- 在维护优先队列大小时,通过条件判断实现了只保留前
380. O(1) 时间插入、删除和获取随机元素
实现RandomizedSet
类:
RandomizedSet()
初始化RandomizedSet
对象bool insert(int val)
当元素val
不存在时,向集合中插入该项,并返回true
;否则,返回false
。bool remove(int val)
当元素val
存在时,从集合中移除该项,并返回true
;否则,返回false
。int getRandom()
随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。
你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1)
。
示例:
输入:[“RandomizedSet”, “insert”, “remove”, “insert”, “getRandom”, “remove”, “insert”, “getRandom”][ [], [1], [2], [2], [], [1], [2], [] ]
输出:[null, true, false, true, 2, true, false, 2]
解释:RandomizedSet randomizedSet = new RandomizedSet();randomizedSet.insert(1); // 向集合中插入 1 。返回 true 表示 1 被成功地插入。randomizedSet.remove(2); // 返回 false ,表示集合中不存在 2 。randomizedSet.insert(2); // 向集合中插入 2 。返回 true 。集合现在包含 [1,2] 。randomizedSet.getRandom(); // getRandom 应随机返回 1 或 2 。randomizedSet.remove(1); // 从集合中移除 1 ,返回 true 。集合现在包含 [2] 。randomizedSet.insert(2); // 2 已在集合中,所以返回 false 。randomizedSet.getRandom(); // 由于 2 是集合中唯一的数字,getRandom 总是返回 2 。
提示:
- -231 <=
val
<= 231 - 1 - 最多调用
insert
、remove
和getRandom
函数 2 * 105 次 - 在调用
getRandom
方法时,数据结构中 至少存在一个 元素。
参考代码
|
|
1. 类的功能
RandomizedSet
类实现了一个数据结构,可以进行插入、删除和随机获取集合中的元素。所有操作的平均时间复杂度为 O(1)。
2. 成员变量
unordered_map<int, int> vai
:用于存储元素值与其在集合向量中的索引的映射,方便快速查找。vector<int> v
:用于存储集合中的元素,支持随机访问。
3. 构造函数
- 在构造函数中使用
srand(time(NULL))
设置随机数种子,以确保每次运行时随机数生成的不同。
4. 插入方法 insert(int val)
首先检查元素是否已经存在于集合中,若存在则返回
false
。若不存在,将元素添加到
vector
中,并更新哈希表,记录该元素的位置。返回
true
表示插入成功。
5. 移除方法 remove(int val)
首先检查要移除的元素是否存在,若不存在则返回
false
。获取集合中最后一个元素,用它的值替代要删除的元素,以保持操作的 O(1) 时间复杂度。
更新哈希表中最后一个元素对应的索引,并删除要移除元素的映射关系。
从
vector
中移除最后一个元素。返回
true
表示移除成功。
6. 随机获取方法 getRandom()
通过生成一个随机索引,返回
vector
中对应的元素。使用
rand()
方法产生随机数并对集合大小取余,确保返回的索引有效。
注意点
复杂度要求:所有操作必须满足平均时间复杂度为 O(1),这个要求使得实现的设计尤为重要。
数据结构选择:
- 使用
unordered_map
来实现快速查找(O(1)),对元素的插入和删除操作提供支持。 - 使用
vector
以支持随机访问元素,确保能有效实现getRandom()
方法。
- 使用
随机数种子:在构造函数中使用
srand()
设置随机种子,能够确保每次运行时随机数生成的不同。元素替换:在删除元素时,通过用集合中的最后一个元素替换被删除元素,避免了需要在中间位置进行元素移动的开销,从而保持时间复杂度为 O(1)。
451. 根据字符出现频率排序
给定一个字符串 s
,根据字符出现的 频率 对其进行 降序排序 。一个字符出现的 频率 是它出现在字符串中的次数。
返回 已排序的字符串 。如果有多个答案,返回其中任何一个。
示例 1:
- 输入: s = “tree”
- 输出: “eert”
- 解释: ’e’出现两次,‘r’和’t’都只出现一次。 因此’e’必须出现在’r’和’t’之前。此外,“eetr"也是一个有效的答案。
示例 2:
- 输入: s = “cccaaa”
- 输出: “cccaaa”
- 解释: ‘c’和’a’都出现三次。此外,“aaaccc"也是有效的答案。 注意"cacaca"是不正确的,因为相同的字母必须放在一起。
示例 3:
- 输入: s = “Aabb”
- 输出: “bbAa”
- 解释: 此外,“bbaA"也是一个有效的答案,但"Aabb"是不正确的。 注意’A’和’a’被认为是两种不同的字符。
提示:
- 1 <=
s.length
<= 5 * 105 s
由大小写英文字母和数字组成
参考代码
|
|
主要步骤
特殊情况处理:当输入字符串长度为1时,直接返回该字符串。
频率统计:
- 使用
unordered_map<char, int>
来记录每个字符及其频率。 - 遍历字符串,统计每个字符出现的次数。
- 使用
优先队列排序:
- 定义一个比较函数,按照频率进行排序(降序)。
- 使用
priority_queue
存储字符及其频率,以便能够快速获取频率最高的字符。
构建结果字符串:
- 从优先队列中取出字符,并根据其频率附加到结果字符串中。
- 重复直到优先队列为空。
注意点
哈希表的使用:
unordered_map
提供了常数时间复杂度的插入和查找操作,适合频率统计。优先队列的性质:优先队列自动保持元素的排序状态,因此可以高效地处理和获取频率最高的字符。
字符频率的处理:在处理字符频率时,采用两次
top()
操作获取字符及其频率,并使用循环将其添加至结果字符串,这是确保结果字符串正确构造的关键。
648. 单词替换
在英语中,我们有一个叫做 词根(root) 的概念,可以词根 后面 添加其他一些词组成另一个较长的单词——我们称这个词为 衍生词 (derivative)。例如,词根 help
,跟随着 继承词 "ful"
,可以形成新的单词 "helpful"
。
现在,给定一个由许多 词根 组成的词典 dictionary
和一个用空格分隔单词形成的句子 sentence
。你需要将句子中的所有 衍生词 用 词根 替换掉。如果 衍生词 有许多可以形成它的 词根,则用 最短 的 词根 替换它。
你需要输出替换之后的句子。
示例 1:
- 输入: dictionary = [“cat”,“bat”,“rat”], sentence = “the cattle was rattled by the battery”
- 输出: “the cat was rat by the bat”
示例 2:
- 输入: dictionary = [“a”,“b”,“c”], sentence = “aadsfasf absbs bbab cadsfafs”
- 输出: “a a b c”
提示:
- 1 <=
dictionary.length
<= 1000 - 1 <=
dictionary[i].length
<= 100 dictionary[i]
仅由小写字母组成。- 1 <=
sentence.length
<= 106 sentence
仅由小写字母和空格组成。sentence
中单词的总量在范围[1, 1000]
内。sentence
中每个单词的长度在范围[1, 1000]
内。sentence
中单词之间由一个空格隔开。sentence
没有前导或尾随空格。
参考代码
|
|
主要功能和步骤
词根字典的构建:
- 使用
unordered_map
存储词根及其长度,方便后续查找。
- 使用
句子处理:
- 通过
std::istringstream
对句子进行分词,逐个处理每个单词。
- 通过
前缀匹配:
- 对于每个单词,遍历字典中的每个词根,检查单词的前缀是否与词根匹配。
- 如果匹配,则用该词根替换原单词。
结果拼接:
- 将替换后的单词添加到结果字符串中,并在最后去掉多余的空格。
注意点
字符串处理:
std::istringstream
是处理字符串的一个便捷工具,可以方便地进行分词。
前缀替换逻辑:
- 注意判断条件的顺序,如果一个单词的多个词根都可以匹配,应根据长度优先选择最短的词根替换。
结果字符串处理:
- 最后返回结果时,需要去掉最后一个多余的空格,避免格式问题。
692. 前K个高频单词
给定一个单词列表 words
和一个整数 k
,返回前 k
个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。
示例 1:
- 输入: words = [“i”, “love”, “leetcode”, “i”, “love”, “coding”], k = 2
- 输出: [“i”, “love”]
- 解析: “i” 和 “love” 为出现次数最多的两个单词,均为2次。 注意,按字母顺序 “i” 在 “love” 之前。
示例 2:
- 输入: [“the”, “day”, “is”, “sunny”, “the”, “the”, “the”, “sunny”, “is”, “is”], k = 4
- 输出: [“the”, “is”, “sunny”, “day”]
- 解析: “the”, “is”, “sunny” 和 “day” 是出现次数最多的四个单词, 出现次数依次为 4, 3, 2 和 1 次。
注意:
- 1 <=
words.length
<= 500 - 1 <=
words[i]
<= 10 words[i]
由小写英文字母组成。k
的取值范围是 [1, 不同 words[i] 的数量]
进阶: 尝试以 O(n log k)
时间复杂度和 O(n)
空间复杂度解决。
参考代码
|
|
主要步骤
统计单词频率:使用
unordered_map
来记录每个单词的出现次数。自定义排序规则:通过 lambda 表达式定义一个比较函数,用于在优先队列中排序。
使用优先队列:通过
priority_queue
来维护出现频率最高的 k 个单词。构建结果:将优先队列中的元素弹出并存入结果向量,最后反转结果以确保频率从高到低。
注意点
哈希表的使用:
unordered_map
高效地存储和查找单词频率,时间复杂度为 O(n),其中 n 是单词列表的长度。优先队列的自定义排序:比较函数确保了正确的排序逻辑,优先考虑频率,其次考虑字典序。
保持优先队列大小:在加入新元素时,始终保持队列的大小不超过 k,避免内存浪费。
结果反转:在从优先队列中取出元素时,需要反转结果,否则将以低频优先的顺序输出。
718. 最长重复子数组
给两个整数数组 nums1
和 nums2
,返回 两个数组中 公共的 、长度最长的子数组的长度 。
示例 1:
- 输入: nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
- 输出: 3
- 解释: 长度最长的公共子数组是 [3,2,1] 。
示例 2:
- 输入: nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
- 输出: 5
提示:
- 1 <=
nums1.length
,nums2.length
<= 1000 - 0 <=
nums1[i]
,nums2[i]
<= 100
参考代码
|
|
主要步骤
哈希表初始化:
- 使用
unordered_map<int, deque<int>> umMap1
来存储nums1
中每个元素及其对应的索引。每个元素的索引存储在双端队列中,以便可以高效地遍历。
- 使用
遍历
nums2
:- 对于
nums2
中的每个元素,先检查该元素是否在nums1
中存在。如果不存在,跳过当前元素。 - 如果存在,通过哈希表获取所有对应的
nums1
中的索引。
- 对于
比较子数组:
- 对于
nums2[j]
在nums1
中的每个索引,初始化currentLen
为零,然后从这个索引开始,逐个比较nums1
和nums2
中的元素。只要两个数组的元素相同,就增加当前长度计数。 - 移动
nums1
和nums2
的索引,直到遇到不同的元素或到达数组结尾。
- 对于
更新最大长度:
- 每次找到一个公共子数组时,检查并更新记录的
maxLen
。
- 每次找到一个公共子数组时,检查并更新记录的
提前结束条件:
- 为了节省时间,如果
nums2
剩余的元素个数小于等于当前已找到的最大长度,则可以提前结束循环。
- 为了节省时间,如果
注意点
哈希表的使用: 利用哈希表快速查找和访问索引,可以显著提高算法效率。
双端队列的应用: 使用双端队列方便了索引的管理和遍历,确保每次我们可以快速访问到当前正在比较的索引。
720. 词典中最长的单词
给出一个字符串数组 words
组成的一本英语词典。返回 words
中最长的一个单词,该单词是由 words
词典中其他单词逐步添加一个字母组成。
若其中有多个可行的答案,则返回答案中字典序最小的单词。若无答案,则返回空字符串。
请注意,单词应该从左到右构建,每个额外的字符都添加到前一个单词的结尾。
示例 1:
- 输入: words = [“w”,“wo”,“wor”,“world”, “world”]
- 输出: “world”
- 解释: 单词"world"可由"w”, “wo”, “wor”, 和 “world"逐步添加一个字母组成。
示例 2:
- 输入: words = [“a”, “banana”, “app”, “appl”, “ap”, “apply”, “apple”]
- 输出: “apple”
- 解释: “apply” 和 “apple” 都能由词典中的单词组成。但是 “apple” 的字典序小于 “apply”
提示:
- 1 <=
words.length
<= 1000 - 1 <=
words[i].length
<= 30 - 所有输入的字符串
words[i]
都只包含小写字母。
参考代码
|
|
主要步骤
输入参数:
vector<string>& words
: 输入的字符串数组,代表一个英语词典。
特殊情况处理:
- 如果输入的单词数组只有一个单词,则直接返回空字符串。
排序:
- 对单词数组按照字典序进行排序,这样可以确保返回的单词是字典序最小的。
分类存储:
- 创建一个长度为 31 的双端队列数组
vec
,用来存储不同长度的单词,方便后续处理。
- 创建一个长度为 31 的双端队列数组
单词构建检查:
- 从最长的单词开始向前检查,寻找可以由更短的单词逐步构建的最长单词。
- 使用嵌套循环遍历每个长度的单词,并比较其前缀是否能在短单词列表中找到匹配。
返回结果:
- 当找到符合条件的单词时,立即返回;如果遍历完成后没有找到,则返回空字符串。
注意点
- 字典序:排序操作非常重要,因为它保证了在存在多个答案时,可以优先返回字典序最小的单词。
- 双端队列:使用双端队列 (
deque
) 以提高从两端插入和删除的效率,适合这个场景下的需求。 - 计数器的使用:
count
变量用来记录当前匹配的状态,只有在完全匹配时才返回结果。 - 前缀匹配逻辑:在进行前缀比较时,确保正确处理每个长度的单词,并适时更新临时队列,避免重复匹配。
970. 强整数
给定三个整数 x
、 y
和 bound
,返回 值小于或等于 bound
的所有 强整数 组成的列表 。
如果某一整数可以表示为 xi + yj ,其中整数 i >= 0
且 j >= 0
,那么我们认为该整数是一个 强整数 。
你可以按 任何顺序 返回答案。在你的回答中,每个值 最多 出现一次。
示例 1:
- 输入: x = 2, y = 3, bound = 10
- 输出: [2,3,4,5,7,9,10]
- 解释:2 = 20 + 303 = 21 + 304 = 20 + 315 = 21 + 317 = 22 + 319 = 23 + 3010 = 20 + 32
示例 2:
- 输入: x = 3, y = 5, bound = 15
- 输出: [2,4,6,8,10,14]
提示:
- 1 <=
x
,y
<= 100 - 0 <=
bound
<= 106
参考代码
|
|
代码实现思路
边界条件处理:
- 如果
bound
小于或等于 1,直接返回空向量。 - 如果
bound
小于x
和y
中的最小值,进一步处理:- 如果
bound
等于 2,返回包含bound
的列表。 - 否则返回空列表。
- 如果
- 如果
计算基数的次幂上限:
- 使用
unordered_map<int, int> uMap
存储每个基数的最大幂次。 - 对于每个基数
x
和y
,计算其各个次幂,直到大于bound
。
- 使用
计算强整数:
- 使用嵌套循环遍历
x
和y
的次幂。如果xn + yn
(强整数的值)小于等于bound
,则将该值插入到unordered_set<int> tempRes
中以确保唯一性。
- 使用嵌套循环遍历
返回结果:
- 最后将集合中的强整数转为向量
vector<int>
并返回。
- 最后将集合中的强整数转为向量
注意点
集合的使用:使用
unordered_map
存储幂次的上限可以有效避免重复计算。使用unordered_set
存储结果是为了去重,确保返回的强整数唯一。特殊值处理:在计算幂次时需要考虑基数为 1 的特殊情况,因为任何次幂都是 1。