242. 有效的字母异位词
解题思路
要判断两个字符串 s 和 t 是否是字母异位词(Anagram),核心定义是:
两个字符串包含的字符种类和每种字符的出现次数完全相同,只是顺序可能不同。
例如:
"anagram"和"nagaram"→ 是异位词 ✅"rat"和"car"→ 不是 ❌
🧠 关键观察:
- 如果两个字符串长度不同,一定不是异位词(可提前返回 false)。
- 只需关注 26 个小写字母(题目通常限定为小写英文字母)。
- 可以用一个计数数组 来统计每个字母的"净出现次数":
- 遍历
s:每个字符对应位置 +1 - 遍历
t:每个字符对应位置 -1
- 遍历
- 最终如果所有计数都为 0,说明两字符串字符频次完全一致 → 是异位词。
这种方法称为 "频次抵消法",时间高效,空间固定。
class Solution {
public:
bool isAnagram(string s, string t) {
// 1. 定义长度为26的整型数组,初始化为0
// record[i] 表示第 i 个字母('a'+i)的净频次
int record[26] = {0};
// 2. 遍历字符串 s,对每个字符进行 +1 计数
for (int i = 0; i < s.size(); i++) {
record[s[i] - 'a']++; // 将字符映射到 0~25 的索引
}
// 3. 遍历字符串 t,对每个字符进行 -1 抵消
for (int j = 0; j < t.size(); j++) {
record[t[j] - 'a']--;
}
// 4. 检查 record 数组是否全为 0
for (int i = 0; i < 26; i++) {
if (record[i] != 0) {
return false; // 只要有一个不为0,就不是异位词
}
}
return true; // 全为0,是异位词
}
};
349. 两个数组的交集
解题思路
核心思想:利用哈希集合(HashSet)实现快速查找和自动去重。
步骤分解:
- 将
nums1转为哈希集合nums_set- 目的:去重 + O(1) 平均查找
- 遍历
nums2中的每个元素num- 如果
num存在于nums_set中 → 说明是公共元素 - 将其加入结果集合
result_set(自动去重)
- 如果
- 将
result_set转为vector返回
💡 为什么用两个
unordered_set?
- 第一个 (
nums_set):快速判断是否在nums1中- 第二个 (
result_set):确保结果中无重复(即使nums2有多个相同交集元素)
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
// 1. 用 nums1 构建哈希集合(自动去重)
unordered_set<int> nums_set(nums1.begin(), nums1.end());
// 2. 用另一个集合存储交集结果(自动去重)
unordered_set<int> result_set;
// 3. 遍历 nums2
for (int num : nums2) {
// 如果 num 在 nums1 中出现过
if (nums_set.find(num) != nums_set.end()) {
result_set.insert(num); // 插入结果集(重复插入无效)
}
}
// 4. 将 unordered_set 转为 vector 返回
return vector<int>(result_set.begin(), result_set.end());
}
};
202. 快乐数
解题核心思路
关键观察:
非快乐数一定会进入循环(因为平方和有上界,状态有限)。
因此,问题转化为:
在计算平方和的过程中,是否出现重复值?
解决方案:
-
使用
unordered_set<int>记录所有出现过的平方和。 -
如果某次计算结果为
1→ 返回true -
如果某次结果已在集合中 → 出现循环 → 返回
falseclass Solution {
public:
int getsum(int n){
int sum=0;
while(n){
sum += (n%10)*(n%10);
n=n/10;
}return sum; } bool isHappy(int n) { unordered_set<int> res; while(1){ int sum = getsum(n); if(sum==1)return true; if(res.find(sum)!= res.end()) return false; else res.insert(sum); n = sum; } }};
1. 两数之和
解题核心思想:"用空间换时间"
暴力解法(O(n²)):
- 双重循环枚举所有
(i, j)对 → 效率低 ❌
哈希表优化(O(n))✅:
- 遍历数组时,记录每个数字及其下标
- 对当前
nums[i],计算need = target - nums[i] - 如果
need已在哈希表中 → 找到答案! - 否则,将
nums[i]存入哈希表,继续
💡 关键洞察:
当我们处理到
nums[i]时,只需要知道 前面是否出现过target - nums[i]。
vector<int> twoSum(vector<int>& nums, int target) {
int n = nums.size();
unordered_map<int, int> res; // key: 数值, value: 下标
for (int i = 0; i < n; i++) {
auto iter = res.find(target - nums[i]); // 查找 need = target - nums[i]
if (iter != res.end()) {
return {iter->second, i}; // 找到!返回 {之前下标, 当前下标}
} else {
res.insert(pair<int, int>(nums[i], i)); // 将当前数存入 map
}
}
return {}; // 理论上不会执行(题目保证有解)
}
454. 四数相加 II
解题核心思想:分组 + 哈希查找
❌ 暴力解法(不可行):
- 四重循环 → 时间复杂度 O(n⁴) ,当
n=200时,操作次数 ≈ 1.6×10⁹ → 超时 ❌
✅ 优化思路(你的方法):
将四数之和拆成两部分:
(A[i] + B[j]) + (C[k] + D[l]) == 0
⇒ A[i] + B[j] == - (C[k] + D[l])
步骤:
- 预处理 A+B 的所有可能和,并用哈希表记录每个和出现的次数。
- 遍历 C+D 的所有可能和 ,对每个
s = c + d,检查-s是否在哈希表中。- 如果存在,则说明有
umap[-s]种(a,b)组合能与当前(c,d)配对 → 累加到结果。
- 如果存在,则说明有
💡 这是典型的 "空间换时间" + "meet-in-the-middle" 思想。
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
// 1. 哈希表:key = a+b 的值,value = 该值出现的次数
unordered_map<int, int> umap;
// 2. 枚举 A 和 B 的所有组合,统计 a+b
for (int a : A) {
for (int b : B) {
umap[a + b]++; // 自动初始化为 0 后 +1
}
}
int count = 0;
// 3. 枚举 C 和 D 的所有组合
for (int c : C) {
for (int d : D) {
int target = -(c + d);
if (umap.find(target) != umap.end()) {
count += umap[target]; // 累加所有匹配的 (a,b) 数量
}
}
}
return count;
}
15. 三数之和
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int n = nums.size();
for (int i = 0; i < n; i++) {
if (i > 0 && nums[i] == nums[i - 1])
continue;
int left = i + 1, right = n - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0) {
res.push_back({nums[i], nums[left], nums[right]});
left++;
right--;
// 跳过重复值
while (left < right && nums[left] == nums[left - 1])
left++;
while (left < right && nums[right] == nums[right + 1])
right--;
}
else if (sum < 0) {
left++;
}
else {
right--;
}
}
}
return res;
}
};
18. 四数之和
暴力解法 vs 优化思路
❌ 暴力解法(O(n⁴)):
- 四重循环枚举所有
(i, j, k, l)组合 - 时间复杂度过高(n=200 时,操作数 ≈ 1.6 亿),会超时
✅ 优化方向:排序 + 双指针 + 剪枝 + 去重
这是解决 KSum 类问题(2Sum, 3Sum, 4Sum...) 的通用范式。
核心解题步骤(以 4Sum 为例)
步骤 1️⃣:排序数组
sort(nums.begin(), nums.end());
- 作用:
- 使双指针可行(有序才能根据和的大小移动指针)
- 便于去重(相同元素相邻)
步骤 2️⃣:固定前两个数,双指针找后两个数
- 外层循环:固定第一个数
nums[k] - 内层循环:固定第二个数
nums[i](i > k) - 然后在
[i+1, n-1]区间用 双指针 找c和d
相当于把 4Sum 拆成
(a + b) + (c + d) = target
步骤 3️⃣:关键优化 ------ 剪枝(Pruning)
避免无效搜索,大幅提升效率。
✂️ 剪枝 1:当前最小四元组 > target → 后续无解
if ((long)nums[k] + nums[k+1] + nums[k+2] + nums[k+3] > target) break;
- 因为数组已排序,
k固定时,最小和是接下来三个数 - 如果这个最小和都 > target,说明
k太大,直接跳出外层循环
✂️ 剪枝 2:当前最大四元组 < target → 当前 k/i 太小
if ((long)nums[k] + nums[n-3] + nums[n-2] + nums[n-1] < target) continue;
- 最大和是最后三个数
- 如果最大和都 < target,说明当前
k(或i)太小,跳过本次循环
💡 注意:必须加
(long)防止int溢出!
步骤 4️⃣:去重(Avoid Duplicates)
因为数组可能有重复元素,必须跳过相同值,避免重复四元组。
去重规则:
-
第一个数去重 :
if (k > 0 && nums[k] == nums[k-1]) continue; -
第二个数去重 :
if (i > k+1 && nums[i] == nums[i-1]) continue; -
第三、四个数去重(找到解后) :
left++; right--; while (left < right && nums[left] == nums[left-1]) left++; while (left < right && nums[right] == nums[right+1]) right++;
✅ 原则:每个位置只取"第一次出现"的值
步骤 5️⃣:双指针移动逻辑
long sum = (long)nums[k] + nums[i] + nums[left] + nums[right];
if (sum > target) {
right--; // 和太大,右指针左移
} else if (sum < target) {
left++; // 和太小,左指针右移
} else {
// 找到解,加入结果,并去重移动
}
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
int n = nums.size();
for (int k = 0; k < n; k++) {
// 一级剪枝:最小四元组 > target
if (k + 3 < n && (long)nums[k] + nums[k+1] + nums[k+2] + nums[k+3] > target) {
break;
}
// 最大四元组 < target,跳过(可选优化)
if ((long)nums[k] + nums[n-3] + nums[n-2] + nums[n-1] < target) {
continue;
}
if (k > 0 && nums[k] == nums[k-1]) continue;
for (int i = k + 1; i < n; i++) {
// 二级剪枝:最小四元组 > target
if (i + 2 < n && (long)nums[k] + nums[i] + nums[i+1] + nums[i+2] > target) {
break;
}
// 最大四元组 < target,跳过
if ((long)nums[k] + nums[i] + nums[n-2] + nums[n-1] < target) {
continue;
}
if (i > k + 1 && nums[i] == nums[i-1]) continue;
int left = i + 1, right = n - 1;
while (left < right) {
long sum = (long)nums[k] + nums[i] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
result.push_back({nums[k], nums[i], nums[left], nums[right]});
left++;
right--;
// 去重
while (left < right && nums[left] == nums[left - 1]) left++;
while (left < right && nums[right] == nums[right + 1]) right++;
}
}
}
}
return result;
}
};