目录
- 零、题目描述
- 一、为什么这道题值得你花几分钟看懂?
- 二、从暴力枚举到双指针:思路的进化
-
- [1. 暴力枚举:直观但低效的尝试](#1. 暴力枚举:直观但低效的尝试)
- [2. 数学简化:三角形条件的优化](#2. 数学简化:三角形条件的优化)
- [3. 双指针:固定最大边,搜索有效对](#3. 双指针:固定最大边,搜索有效对)
- 三、双指针实现:固定一端,收缩两端
-
- [1. 算法步骤](#1. 算法步骤)
- [2. 代码实现](#2. 代码实现)
- 四、代码解析与复杂度分析
-
- [代码走读(示例1:`nums = [2,2,3,4]`)](#代码走读(示例1:
nums = [2,2,3,4]
)) - 复杂度分析
- [代码走读(示例1:`nums = [2,2,3,4]`)](#代码走读(示例1:
- 五、常见问题与注意事项
-
- [1. 为什么排序后能保证正确性?](#1. 为什么排序后能保证正确性?)
- [2. 数组中存在 0 会影响结果吗?](#2. 数组中存在 0 会影响结果吗?)
- [3. 双指针的移动逻辑为什么正确?](#3. 双指针的移动逻辑为什么正确?)
- 六、举一反三
零、题目描述
题目链接:有效三角形的个数
题目描述:
示例 1:
输入:nums = [2,2,3,4]
输出:3
解释:有效的组合是:
2,3,4 (使用第一个 2)
2,3,4 (使用第二个 2)
2,2,3
示例 2:输入:nums = [4,2,3,4]
输出:4
提示:1 <= nums.length <= 1000
0 <= nums[i] <= 1000
一、为什么这道题值得你花几分钟看懂?
有效三角形的个数问题,是双指针算法在"三重循环优化"中的经典应用。它的价值在于:让你学会如何通过排序和条件转化,将高复杂度的枚举问题降维打击。
这道题看似需要枚举所有可能的三元组(暴力解法的思路),但通过三角形三边关系的数学特性,我们能发现一个关键规律:对于排序后的数组,若较小的两边之和大于最大边,则三者可组成三角形。这个规律让双指针有了用武之地------通过固定最大边,用双指针寻找符合条件的较小两边,将时间复杂度从 O(n³) 降至 O(n²)。
搞定这道题,你会掌握:
- 如何将数学定理转化为算法优化的依据
- 双指针在"固定一端,搜索两端"场景中的应用
- 排序在数组优化问题中的辅助作用
这种"用数学简化问题,用指针降低复杂度"的思维,对解决组合计数类问题至关重要。
二、从暴力枚举到双指针:思路的进化
1. 暴力枚举:直观但低效的尝试
最容易想到的思路是:枚举所有可能的三元组组合,判断是否满足三角形三边关系,统计符合条件的个数。
暴力解法思路:
cpp
// 伪代码
int count = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
for (int k = j + 1; k < n; k++) {
if (nums[i] + nums[j] > nums[k] &&
nums[i] + nums[k] > nums[j] &&
nums[j] + nums[k] > nums[i]) {
count++;
}
}
}
}
return count;
问题分析:
- 时间复杂度:O(n³),当 n 为 1000 时,运算次数高达 10⁹,必然超时
- 空间复杂度:O(1),只使用常数个变量
- 核心缺陷:存在大量重复判断,且三边关系的三个条件中有冗余
2. 数学简化:三角形条件的优化
三角形三边关系的本质是:任意两边之和大于第三边 。但当数组排序后(假设 a ≤ b ≤ c
),这个条件可以简化为:a + b > c。
证明:
- 因为
a ≤ b ≤ c
,所以a + c > b
(c ≥ b 且 a ≥ 0,故 a + c ≥ b,当 a > 0 时成立) - 同理
b + c > a
也一定成立 - 因此只需判断最小的两边之和是否大于最大边(a + b > c)
这个简化让我们的判断条件从三个减少为一个,为后续优化奠定基础。
3. 双指针:固定最大边,搜索有效对
基于排序和简化的条件,我们可以设计更高效的算法:
- 先对数组排序(从小到大)
- 固定最大边 c(即数组中的某个元素 nums[i])
- 用双指针寻找所有满足
a + b > c
的 (a, b) 对(其中 a 和 b 是 c 左侧的元素,且 a ≤ b < c)
为什么固定最大边?
- 排序后,数组右侧的元素更大,适合作为最大边 c
- 固定 c 后,只需在左侧元素中寻找符合条件的 a 和 b,缩小搜索范围
三、双指针实现:固定一端,收缩两端
1. 算法步骤
- 对数组
nums
排序(从小到大) - 初始化计数变量
count = 0
- 从右向左遍历数组,固定最大边
c = nums[i]
(i 从 n-1 开始,至少为 2,因为需要三个元素) - 对每个 i,设置双指针:
left = 0
(指向最小元素),right = i - 1
(指向 c 左侧的最大元素) - 当
left < right
时,循环:- 若
nums[left] + nums[right] > nums[i]
:说明从 left 到 right-1 的所有元素与 right 搭配,都满足和大于 c(因为数组递增),因此计数count += right - left
,并将 right 左移(缩小范围) - 否则:说明当前 left 太小,将 left 右移(增大 a 的值)
- 若
- 遍历结束后,返回 count
2. 代码实现
cpp
class Solution {
public:
int triangleNumber(vector<int>& nums) {
int answer = 0;
// 先对数组排序,为双指针创造条件
sort(nums.begin(), nums.end());
// 从右向左固定最大边 c = nums[i]
for (int i = nums.size() - 1; i >= 2; i--) {
int left = 0; // 左指针指向最小元素
int right = i - 1; // 右指针指向最大边左侧的元素
// 双指针搜索符合条件的 a 和 b
while (left < right) {
// 若 a + b > c,说明 [left, right-1] 与 right 搭配均有效
if (nums[left] + nums[right] > nums[i]) {
answer += right - left;
right--; // 缩小右边界,寻找更小的 b
} else {
left++; // 增大左边界,寻找更大的 a
}
}
}
return answer;
}
};
四、代码解析与复杂度分析
代码走读(示例1:nums = [2,2,3,4]
)
- 排序后:
[2,2,3,4]
- 固定最大边 i=3(值为4):
- left=0(值为2),right=2(值为3)
- 2 + 3 > 4 → 有效,计数 answer += 2-0=2(即 (2,3) 和 (2,3) 两个组合 注:用的不同的2)
- right-- → right=1
- left=0 < right=1,判断 2 + 2 = 4 → 不满足,left++ → left=1
- left 不再小于 right,结束本轮
- 固定最大边 i=2(值为3):
- left=0,right=1(值为2)
- 2 + 2 > 3 → 有效,计数 answer += 1-0 = 1
- right-- → right=0,循环结束
- 总计数为 2 + 1 = 3,与示例结果一致
复杂度分析
复杂度类型 | 具体值 | 说明 |
---|---|---|
时间复杂度 | O(n²) | 排序耗时 O(n log n),外层循环 O(n),内层双指针循环 O(n),总体由 O(n²) 主导 |
空间复杂度 | O(log n) | 排序算法的栈空间开销(取决于具体实现) |
五、常见问题与注意事项
1. 为什么排序后能保证正确性?
排序后,我们固定最大边 c,并在左侧寻找 a 和 b(a ≤ b < c)。此时只需判断 a + b > c,因为:
- 由于 a ≤ b ≤ c,a + c > b 和 b + c > a 必然成立
- 所有可能的三元组都会被枚举,因为我们遍历了所有可能的最大边 c
2. 数组中存在 0 会影响结果吗?
不会。因为 0 无法参与组成三角形(例如 0,0,0 或 0,1,1 都不满足两边之和大于第三边),但算法会自动过滤这些情况:
- 若 a=0,b 为正数,c ≥ b,则 a + b = b ≤ c → 不满足条件,left 会右移
- 最终 0 不会被计入有效计数
3. 双指针的移动逻辑为什么正确?
- 当
nums[left] + nums[right] > c
时:因为数组递增,left 到 right-1 之间的所有元素与 right 搭配,和都会大于 c(例如 left+1 对应的元素 ≥ left,故 left+1 + right ≥ left + right > c),因此直接计数right - left
个 - 当
nums[left] + nums[right] ≤ c
时:当前 left 太小,即使与最大的 b(right)搭配都不满足,因此必须右移 left 以增大 a 的值
六、举一反三
这道题的"固定一端 + 双指针"思路可推广到:
- 三数问题:如寻找三数之和为目标值、三数乘积最大等
- 区间计数问题:如统计数组中满足某种条件的区间对数量
下一题预告 :LeetCode LCR 179. 查找总价格为目标值的两个商品!这道题是经典的"两数之和"变种,会让你看到双指针在"有序数组中寻找目标对"的妙用------如何通过首尾指针的移动,快速定位和为目标值的两个元素,将时间复杂度从 O(n²) 优化到 O(n)。明天的解析会带你拆解其中的逻辑,让你进一步熟悉双指针的灵活应用!
最后欢迎大家在评论区分享你的代码或思路,咱们一起交流探讨~ 🌟 要是有大佬有更精妙的思路或想法,恳请在评论区多多指点批评,我一定会虚心学习,并且第一时间回复交流哒!

✨ 如果你觉得这些思路对你有启发,不妨动动手指:
- 🌟 点个赞,让这份思考被更多人看见
- ⭐ 收个藏,下次遇到类似问题能快速找回灵感
- 👀 加个关注,后续更新的解题干货绝对不会让你迷路
毕竟,在算法的世界里,互相照亮的每一步,都走得更有力量呀~这是封面原图,咱们下篇题解再见!😉