算法精讲:巧用双指针与单调性,高效求解「有效三角形个数」
在算法面试和日常开发中,如何高效地处理数组并找出满足特定条件的元素组合,是衡量开发者基本功的重要标尺。今天,我们将深入探讨一道经典题目——「有效三角形个数」,并揭示如何利用数组的单调性和双指针技巧,将时间复杂度从 O(N³) 优化至 O(N²),实现性能的飞跃。掌握此技巧,不仅能解决此类问题,更能深刻理解双指针算法在有序数据上的强大威力。
一、问题定义与暴力解法剖析
题目要求很简单:给定一个包含非负整数的数组,返回可以组成三角形三条边的不同三元组个数。这里的关键是“不去重”,即值相同的不同索引元素被视为不同的组合。
最直观的解法是三层嵌套循环,穷举所有三元组 (a, b, c),然后判断其能否构成三角形。三角形的判定依据是任意两边之和大于第三边,这需要三次判断(a+b>c, a+c>b, b+c>a)。

这种暴力解法的时间复杂度高达 O(N³),在数据量稍大时(如 N>1000)便难以承受。其核心瓶颈在于无差别的穷举和重复的判断逻辑。此时,一个关键的优化思路浮出水面:如果数组是有序的,判定条件能否简化?答案是肯定的。当我们固定最长边 c 时,只需判断较小的两边 a 和 b 之和是否大于 c 即可(a ≤ b ≤ c)。这得益于有序带来的单调性:若 a+b > c 成立,那么对于固定的 b,所有比当前 a 大的元素与 b 之和也必然大于 c。这正是我们优化的突破口。
二、排序奠基:引入单调性优化
优化第一步,对数组进行排序。排序的时间复杂度为 O(N log N),这是一笔非常划算的“投资”。排序后,数组呈现非递减的单调性,这为我们带来了两大好处:
- 简化判定逻辑:只需判断
nums[left] + nums[right] > nums[i]这一个条件。 - 启用双指针扫描:利用单调性,我们可以用两个指针在线性时间内扫描出所有满足条件的二元组,替代一层循环。

这种“先排序,后处理”的模式,在解决许多数组组合问题(如两数之和、三数之和)时都非常有效。它不仅适用于C++,在Java、Python、Go或TypeScript中,利用语言内置的高效排序函数,都能轻松实现这一预处理步骤。
[AFFILIATE_SLOT_1]三、双指针算法的核心逻辑与演绎
排序之后,进入算法的核心:外层循环固定最长边,内层使用双指针寻找较短的两边。我们设定外层循环变量 i 从数组末尾开始遍历,将 nums[i] 视为当前三角形的最大边(第三边)。
对于每个固定的 i,初始化两个指针:left = 0, right = i - 1。我们的目标是在 [left, right] 区间内,找到所有满足 nums[left] + nums[right] > nums[i] 的 (left, right) 对。

指针移动的贪心策略如下:
- 如果
nums[left] + nums[right] > nums[i]:这是一个关键发现!由于数组有序(单调递增),此时对于固定的right,所有从left到right-1的索引与right组合都满足条件。因此,一次性可以计入right - left个有效三元组。然后,将right左移一位,尝试更小的“第二大边”。 - 如果
nums[left] + nums[right] <= nums[i]:说明当前的两边之和太小,需要增大最小值,因此将left右移一位。
这个过程直到 left 与 right 相遇结束。对于每个 i,双指针的扫描是线性的 O(N),因此整体算法复杂度为 O(N²)。
四、代码实现与逐行解析
理解了算法思想,我们来看具体的代码实现。以下是基于上述思路的C++核心代码:
class Solution {
public:
int maxArea(vector& height) {
int left=0,right=height.size()-1,vm=0;//定义双指针
while(leftvm?v:vm;//如果算出来的体积比vm大,二者交换
if(height[left]
让我们对上期参考代码进行解析:首先进行排序,然后外层循环固定最大数。内层的 while 循环是双指针的经典模板。当满足条件时,通过 right - left 快速计数并移动 right;不满足时则移动 left。这段代码清晰体现了如何利用单调性避免无效计算。
为了加深理解,我们也回顾一下暴力解法作为对比:
class Solution {
public:
int triangleNumber(vector& nums) {
int n=nums.size(),sum=0;
sort(nums.begin(),nums.end());//这里先排序,不排序过不了
for(int i=0;inums[k])//排序之后只需要进行一次逻辑判断。
sum++;
return sum;
}
};
对比两者,高下立判。双指针解法通过排序和巧妙的指针移动,去除了大量不必要的枚举和判断。
[AFFILIATE_SLOT_2]
五、视觉化演示与常见误区
一图胜千言。下面的示意图清晰地展示了当固定最大边 nums[i] = 9 时,双指针 left 和 right 是如何协作,快速定位所有满足 nums[left] + nums[right] > 9 的组合的。

在实现和面试中,需要特别注意以下几个要点:
- ⚠️ 排序是前提:务必先排序,否则单调性假设不成立,算法失效。
- ⚠️ 指针移动的逻辑:理解为什么满足条件时是累加
right-left 并移动 right,这是利用单调性的精髓。 - ⚠️ 去重问题:本题明确要求不去重。若题目要求去重,则需要在指针移动时跳过重复值,这是另一个常见的变体。
- 溢出处理:在类似题目中,若数值很大,相加可能导致整数溢出,在某些语言中需要考虑使用长整型。

六、总结与延伸思考
通过「有效三角形个数」这道题,我们深入掌握了“排序 + 双指针”这一经典范式。其核心在于利用有序序列的单调性,将原本需要多重循环枚举的问题,转化为指针的线性扫描,从而大幅降低时间复杂度。

这个技巧的应用范围极广,例如:
- 两数之和(有序数组版):一左一右指针向中间逼近。
- 盛最多水的容器:同样是利用高度和宽度的单调性移动指针。
- 三数之和:固定一个数,转化为两数之和问题。
掌握其本质——通过单调性确定决策的单调方向,从而安全地缩小搜索空间——你就能在面对更多复杂问题时游刃有余。下期,我们将探讨另一道利用类似思想的题目:LCR 179. 查找总价格为目标值的两个商品,敬请期待!
最后,再次强调:“光看不练假把式”。强烈建议你关闭文章后,独立在代码编辑器(无论是VS Code、IntelliJ还是在线平台)中手敲一遍代码,并尝试用Java、Python等语言复现,才能真正内化这种重要的算法思维。
浙公网安备 33010602011771号