题目描述

✅ 611. 有效三角形的个数

image-20260928200307347

题意分析

给定非负整数数组,从三个不同下标各取一个数作为边长,统计能组成非退化三角形的组合数量。同一组三个下标只计一次,数值相同但下标不同的元素仍分别参与组合。

三角形要求任意两边之和严格大于第三边。把三条边排序为 a <= b <= c 后,只需判断 a + b > c:它同时保证最小边为正,另外两条不等式便自然成立。长度为零的边或两短边之和恰好等于长边,都不能计入。

解法:排序后固定最长边双指针

核心思路

[!blue]

先将数组升序排序。固定最长边下标 k 后,另外两个下标只能来自 [0, k),问题转为统计其中有多少对数的和严格大于 nums[k]。每个三元组都恰好属于其最右下标对应的一轮,不会在不同 k 之间重复计数。

用 left = 0、right = k - 1 表示尚待统计的候选范围。如果 nums[left] + nums[right] > nums[k],由于数组升序,第一条边取任何 left <= i < right 都满足条件。因此固定当前 right 的这些组合可以一次计入 right - left 个,再将 right 左移,处理更小的第二条边。

如果两者之和不够大,当前 left 配上范围内最大的 right 都失败,配上任何更靠左、更小的第二条边也一定失败。因此可以排除当前 left,令它右移,而不会漏掉答案。

这两个分支每次都完整处理一个边界:成功时处理当前 right 对应的全部可行第一条边,失败时排除当前 left 对应的全部剩余组合。一直收缩到 left == right,当前最长边的统计就完成了。不同下标的重复值需要照常保留,不使用三数之和中按数值去重的逻辑。

解题步骤

  1. 将数组升序排序,初始化答案为零。
  2. 从 k = n - 1 到 2 枚举最长边,令 left = 0、right = k - 1。
  3. 当 left < right 时,比较两短边之和与 nums[k]。
  4. 若严格大于,累加 right - left 并将 right 减一;否则将 left 加一。
  5. 汇总各个最长边对应的数量,返回答案。元素不足三个时,外层循环自然不执行。

代码实现

class Solution {
    public int triangleNumber(int[] nums) {
        java.util.Arrays.sort(nums);
        int ans = 0;

        for (int k = nums.length - 1; k >= 2; k--) {
            int left = 0;
            int right = k - 1;

            while (left < right) {
                if (nums[left] + nums[right] > nums[k]) {
                    // 当前最小候选已能成三角形,其右侧到右端之前的值都同样可行。
                    ans += right - left;
                    right--;
                } else {
                    left++;
                }
            }
        }

        return ans;
    }
}
import "sort"

func triangleNumber(nums []int) int {
    sort.Ints(nums)
    ans := 0

    for k := len(nums) - 1; k >= 2; k-- {
        left, right := 0, k-1
        for left < right {
            if nums[left]+nums[right] > nums[k] {
                // 当前最小候选已能成三角形,其右侧到右端之前的值都同样可行。
                ans += right - left
                right--
            } else {
                left++
            }
        }
    }
    return ans
}

复杂度分析

  • 时间复杂度:$O(n^2)$。排序需要 $O(n\log n)$;固定每个 k 后,两指针合计移动 $O(k)$ 次,全部扫描合计为平方量级。
  • 空间复杂度:双指针本身为 $O(1)$,整体取决于排序实现。Go 排序只递归较短分区,栈空间为 $O(\log n)$;Java 的排序合并路径可能分配 $O(n)$ 临时数组,保守按 $O(n)$ 计。实现依据见 Go 排序源码 和 OpenJDK 排序源码。

关键点总结

[!green]

  • 固定最右下标,就固定了最长边,并让每个下标三元组只归入一轮。
  • 命中条件后批量累加 right - left,是把三重枚举降为双重扫描的关键。
  • 排序后的单调性分别证明了成功时左移 right、失败时右移 left 的安全性。
  • 统计的是下标组合,重复数值不能去重;当前实现会直接排序并修改输入数组。

易错点总结

[!yellow]

  • 条件必须是严格大于,使用 >= 会把退化成线段的组合也算作三角形。
  • 命中时只加一,会漏掉当前 right 与整个可行前缀的组合,应加 right - left。
  • 命中后同时移动 left,会漏掉它与下一个较小 right 的组合;当前分支只处理完了 right。
  • 未排序就使用这些指针移动规则,无法保证被跳过的组合都已统计或确定无效。
  • 必须保持 left < right < k,否则可能把同一个数组位置当作两条边使用。

相似题目

题目 难度 关联与区别
976. 三角形的最大周长 简单 同样排序后使用两短边和大于长边,本题累计全部组合,原题只取最大周长。
259. 较小的三数之和 中等 同样固定一项后用双指针批量计数,本题条件是两边和大于第三边,原题三数和小于目标。
15. 三数之和 中等 排序后固定部分元素,再用左右指针收缩候选;本题把三角不等式转为两数和比较,该题固定一个数寻找零和三元组。
16. 最接近的三数之和 中等 排序后固定部分元素,再用左右指针收缩候选;本题把三角不等式转为两数和比较,该题根据与目标的距离更新最接近的和。
18. 四数之和 中等 排序后固定部分元素,再用左右指针收缩候选;本题把三角不等式转为两数和比较,该题多固定一个数寻找四元组。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/03455614
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!