1. 项目概述
作为一名有多年C++开发经验的程序员,我最近在系统性地刷LeetCode Hot100题目。这个系列笔记记录了我的解题思路和心得,今天分享的是数组类题目的第一轮实战总结。选择数组题作为起点,是因为这类题目往往能很好地训练基础算法思维,也是面试中的高频考点。
本篇文章将详细解析五道经典数组题目:两数之和、移动零、盛最多水的容器、三数之和、无重复字符的最长子串。这些题目涵盖了哈希表、双指针、滑动窗口等核心算法技巧,难度从简单到中等递进,非常适合用来打牢算法基础。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心题目解析
2.1 两数之和(Two Sum)
这是LeetCode的第一题,也是很多人的算法入门题。题目要求在一个整数数组中找到两个数,使它们的和等于给定的目标值,并返回这两个数的索引。
暴力解法:最直观的方法是双重循环遍历所有可能的数对组合,时间复杂度O(n²)。这在数据量小的时候可行,但对于大规模数据效率太低。
哈希表优化:我们可以利用哈希表(在C++中可以用unordered_map)来存储已经遍历过的数字及其索引。这样对于每个元素nums[i],我们只需要检查哈希表中是否存在target - nums[i]即可。这种方法将时间复杂度降低到O(n),空间复杂度也是O(n)。
cpp复制vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> num_map;
for (int i = 0; i < nums.size(); ++i) {
int complement = target - nums[i];
if (num_map.find(complement) != num_map.end()) {
return {num_map[complement], i};
}
num_map[nums[i]] = i;
}
return {};
}
注意:使用哈希表时要注意处理重复元素的情况。题目保证有且仅有一个解,所以不需要考虑多个解的情况。
2.2 移动零(Move Zeroes)
这道题要求将数组中的所有0移动到数组末尾,同时保持非零元素的相对顺序。这是一个典型的原地操作数组的问题。
双指针技巧:我们可以使用快慢指针的方法。慢指针指向当前需要放置非零元素的位置,快指针遍历数组。当快指针遇到非零元素时,就将其与慢指针位置的元素交换,然后两个指针都向前移动。
cpp复制void moveZeroes(vector<int>& nums) {
int slow = 0;
for (int fast = 0; fast < nums.size(); ++fast) {
if (nums[fast] != 0) {
swap(nums[slow], nums[fast]);
++slow;
}
}
}
优化点:当快慢指针指向同一个元素时(即前面没有0),可以避免不必要的交换操作。但实际测试中这种优化对性能提升不大,因为swap操作本身已经很高效。
2.3 盛最多水的容器(Container With Most Water)
这道题给出一个高度数组,要求找出两条线,使得它们与x轴构成的容器能容纳最多的水。这是一个典型的双指针问题。
解题思路:容器的面积由两个因素决定:宽度(两指针的距离)和高度(两指针中较矮的那个)。初始时,我们将指针放在数组的两端,计算当前面积并记录最大值。然后移动较矮的那个指针,因为移动较高的指针不可能得到更大的面积(宽度在减小,而高度受限于较矮的线)。
cpp复制int maxArea(vector<int>& height) {
int left = 0, right = height.size() - 1;
int max_area = 0;
while (left < right) {
int current_area = min(height[left], height[right]) * (right - left);
max_area = max(max_area, current_area);
if (height[left] < height[right]) {
++left;
} else {
--right;
}
}
return max_area;
}
时间复杂度:O(n),因为每个元素最多被访问一次。这种解法比暴力法的O(n²)高效得多。
2.4 三数之和(3Sum)
这道题要求在数组中找到所有不重复的三元组,使得它们的和为0。这是两数之和问题的扩展,但解法要复杂得多。
排序+双指针:首先对数组进行排序(O(nlogn)),然后固定一个数,在剩下的部分使用类似两数之和的双指针方法寻找另外两个数。需要注意去重处理。
cpp复制vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() && nums[i] <= 0; ++i) {
if (i > 0 && nums[i] == nums[i-1]) continue; // 去重
int left = i + 1, right = nums.size() - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum < 0) {
++left;
} else if (sum > 0) {
--right;
} else {
result.push_back({nums[i], nums[left], nums[right]});
// 去重处理
while (left < right && nums[left] == nums[left+1]) ++left;
while (left < right && nums[right] == nums[right-1]) --right;
++left;
--right;
}
}
}
return result;
}
注意事项:
- 必须先排序,这是双指针能正确工作的前提
- 去重是关键,要跳过相同的元素
- 当固定数大于0时可以提前终止,因为三数之和不可能为0
2.5 无重复字符的最长子串(Longest Substring Without Repeating Characters)
这道题要求在字符串中找到不含有重复字符的最长子串的长度。这是滑动窗口算法的经典应用。
滑动窗口解法:使用哈希表记录字符最后一次出现的位置。维护一个滑动窗口,当遇到重复字符时,将窗口左边界移动到max(left, 上次出现位置+1)。
cpp复制int lengthOfLongestSubstring(string s) {
unordered_map<char, int> char_map;
int max_len = 0;
int left = 0;
for (int right = 0; right < s.size(); ++right) {
char c = s[right];
if (char_map.find(c) != char_map.end()) {
left = max(left, char_map[c] + 1);
}
char_map[c] = right;
max_len = max(max_len, right - left + 1);
}
return max_len;
}
优化点:如果字符串只包含ASCII字符,可以用固定大小的数组代替哈希表,性能会更好:
cpp复制int lengthOfLongestSubstring(string s) {
vector<int> char_index(128, -1);
int max_len = 0;
int left = 0;
for (int right = 0; right < s.size(); ++right) {
char c = s[right];
if (char_index[c] >= left) {
left = char_index[c] + 1;
}
char_index[c] = right;
max_len = max(max_len, right - left + 1);
}
return max_len;
}
3. 解题技巧总结
3.1 双指针的常见模式
- 对撞指针:如盛水容器问题,指针从两端向中间移动
- 快慢指针:如移动零问题,一个指针遍历,一个指针标记位置
- 滑动窗口:如无重复字符子串问题,动态调整窗口边界
3.2 哈希表的应用场景
- 快速查找:如两数之和问题,用哈希表存储已遍历元素
- 记录状态:如无重复字符子串问题,记录字符最后出现位置
- 计数统计:某些需要统计频率的问题
3.3 排序预处理的价值
- 使双指针成为可能:如三数之和问题
- 简化去重逻辑:排序后相同元素会相邻
- 利用有序性剪枝:如三数之和中当固定数>0时可提前终止
4. 常见错误与调试技巧
4.1 边界条件处理
- 空数组或单元素数组
- 所有元素相同的情况
- 无解的情况(如两数之和无解)
4.2 去重陷阱
- 三数之和中容易漏掉去重代码
- 结果集中出现重复解
- 过早跳过元素导致漏解
4.3 指针移动逻辑
- 双指针移动条件判断错误
- 指针越界访问
- 移动指针后未及时更新相关状态
5. 性能优化建议
- 空间换时间:合理使用哈希表等数据结构
- 提前终止:当找到解或确定无解时可提前退出循环
- 减少不必要操作:如移动零问题中避免相同位置的交换
- 选择合适的数据结构:如ASCII字符串用数组代替哈希表
6. 扩展练习建议
- 两数之和的变种:三数之和、四数之和
- 滑动窗口的变种:最小覆盖子串、找到字符串中所有字母异位词
- 双指针的其他应用:归并两个有序数组、链表的快慢指针问题
刷题时建议先自己思考解法,写出代码并测试,然后再看最优解。对于每道题,要理解其核心思想并能举一反三。记录错题和典型解法,定期复习巩固。
