1. 分糖果问题解析与优化策略
1.1 问题重述与核心约束
分糖果问题要求我们为站成一排的n个孩子分配糖果,满足两个核心条件:
- 每个孩子至少获得1颗糖果
- 相邻孩子中评分更高的必须获得更多糖果
这个问题看似简单,但隐藏着几个关键难点:
- 相邻关系需要考虑左右两侧的评分比较
- 需要满足全局最优解(最少糖果总数)
- 可能存在评分相等的特殊情况
1.2 贪心算法的双遍历解法
最经典的解法是采用两次遍历的贪心策略,其核心思想是将复杂条件分解为两个独立规则:
左规则处理
从左向右遍历,确保每个孩子与其左侧邻居满足:
cpp复制if (ratings[i] > ratings[i-1]) {
candies[i] = candies[i-1] + 1;
}
右规则处理
从右向左遍历,处理右侧邻居关系:
cpp复制if (ratings[i] > ratings[i+1]) {
candies[i] = max(candies[i], candies[i+1] + 1);
}
复杂度分析
- 时间复杂度:O(n) 两次线性遍历
- 空间复杂度:O(n) 需要额外存储左右规则结果
1.3 实际编码中的优化技巧
在实现时可以进行以下优化:
- 合并计算:第二次遍历时直接累加结果,减少一次数组遍历
- 边界处理:使用哨兵值简化边界条件判断
- 内存优化:用单个变量替代右规则数组
优化后的核心代码段:
cpp复制int candy(vector<int>& ratings) {
int n = ratings.size();
vector<int> left(n, 1);
for (int i = 1; i < n; ++i) {
if (ratings[i] > ratings[i-1]) {
left[i] = left[i-1] + 1;
}
}
int right = 1, total = left[n-1];
for (int i = n-2; i >= 0; --i) {
if (ratings[i] > ratings[i+1]) {
right++;
} else {
right = 1;
}
total += max(left[i], right);
}
return total;
}
1.4 常见错误与验证用例
开发者常犯的错误包括:
- 忽略相等评分的处理
- 边界条件处理不当(首尾元素)
- 贪心策略的单向性缺陷
验证用例建议:
python复制测试用例1:[1,0,2] → 5
测试用例2:[1,2,2] → 4
测试用例3:[1,3,4,5,2] → 11
测试用例4:[1,1,1] → 3
测试用例5:[5,4,3,2,1] → 15
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 接雨水问题的多维解法
2.1 问题本质与数学模型
接雨水问题可以抽象为:对于每个位置i,找到其左右两侧的最高柱子,形成的"水槽"高度为:
code复制water[i] = min(leftMax[i], rightMax[i]) - height[i]
2.2 动态规划标准解法
预处理数组构建
- leftMax数组:记录每个位置左侧的最大高度
- rightMax数组:记录右侧最大高度
预处理过程:
cpp复制// 从左向右构建leftMax
leftMax[0] = height[0];
for (int i = 1; i < n; ++i) {
leftMax[i] = max(leftMax[i-1], height[i]);
}
// 从右向左构建rightMax
rightMax[n-1] = height[n-1];
for (int i = n-2; i >= 0; --i) {
rightMax[i] = max(rightMax[i+1], height[i]);
}
复杂度分析
- 时间复杂度:O(n) 三次线性遍历
- 空间复杂度:O(n) 两个辅助数组
2.3 双指针优化方案
动态规划解法可以进一步优化为O(1)空间:
cpp复制int trap(vector<int>& height) {
int left = 0, right = height.size() - 1;
int leftMax = 0, rightMax = 0;
int ans = 0;
while (left < right) {
if (height[left] < height[right]) {
height[left] >= leftMax ?
(leftMax = height[left]) : ans += (leftMax - height[left]);
++left;
} else {
height[right] >= rightMax ?
(rightMax = height[right]) : ans += (rightMax - height[right]);
--right;
}
}
return ans;
}
2.4 实际应用中的注意事项
- 边界处理:空数组或单元素返回0
- 数值溢出:对于极大height值的情况
- 算法选择:
- 面试场景:推荐双指针解法
- 工程场景:动态规划更易理解和维护
3. 两数之和的高效实现
3.1 基础解法与优化思路
暴力解法O(n²)的优化路径:
- 排序+双指针 → O(nlogn)
- 哈希表存储 → O(n)
3.2 哈希表实现细节
核心代码解析:
cpp复制vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> numMap;
for (int i = 0; i < nums.size(); ++i) {
int complement = target - nums[i];
if (numMap.count(complement)) {
return {numMap[complement], i};
}
numMap[nums[i]] = i;
}
return {};
}
关键点说明:
- 边遍历边存储,避免重复处理
- 使用count判断存在性比find更简洁
- 处理负数和非整数情况(根据题目约束)
3.3 变种问题解决方案
常见变种及应对策略:
- 多解情况:返回所有解而非首解
- 数值不唯一:需要额外判断索引不同
- 大数据量:考虑分块处理或外部排序
4. 快乐数问题的数学本质
4.1 算法终止性证明
关键数学结论:
- 任何数最终都会进入循环或收敛到1
- 三位数以上数字经过操作位数必然减少
- 最大可能值为243(对于3位数)
4.2 实现模式检测
两种检测循环的方法:
- 哈希表记录历史值
- 快慢指针法(类似链表环检测)
快慢指针实现:
cpp复制bool isHappy(int n) {
auto next = [](int num) {
int sum = 0;
while (num) {
int d = num % 10;
num /= 10;
sum += d * d;
}
return sum;
};
int slow = n, fast = next(n);
while (fast != 1 && slow != fast) {
slow = next(slow);
fast = next(next(fast));
}
return fast == 1;
}
4.3 数学优化方向
预计算优化策略:
- 预先计算1-243的结果缓存
- 识别所有已知的循环模式
- 位运算优化平方计算
5. 存在重复元素II的滑动窗口解法
5.1 哈希表标准解法
核心算法流程:
- 维护value到index的映射
- 检查距离约束条件
- 更新最新位置信息
5.2 滑动窗口优化
固定窗口大小的实现:
cpp复制bool containsNearbyDuplicate(vector<int>& nums, int k) {
unordered_set<int> window;
for (int i = 0; i < nums.size(); ++i) {
if (i > k) {
window.erase(nums[i-k-1]);
}
if (window.count(nums[i])) {
return true;
}
window.insert(nums[i]);
}
return false;
}
复杂度对比:
- 哈希表法:最坏O(n)空间
- 滑动窗口:严格O(k)空间
5.3 工程实践建议
- 数据规模较小时:哈希表实现更简单
- 超大数组场景:滑动窗口内存效率更高
- 并行化处理:可分块处理子区间
