有效三角形的个数 图解题解
这道题到底在问什么
- 输入
- nums=[2,2,3,4]
- 输出
- 3
- 输入
- nums=[4,2,3,4]
- 输出
- 4
- 输入
- nums=[1,1,3]
- 输出
- 0(1+1 不大于 3)
先想最直接的笨办法
每个能当三元组最大边的位置(k 从 2 到 5)都固定过一遍、各跑一趟双指针,累计 11 个有效三角形,就是答案。回头看:排序 + 固定最大边 + 对撞双指针,把暴力枚举三元组的 O(n³) 压到了 O(n²)。(动画第 29 步)
最优解:一步一步想明白
- 3记住「排序→固定最大边→对撞双指针:和够大就一次加 r−l 个、r 左移;不够就 l 右移」,下面每帧都在套它。
- 4第一步排序。原数组是 [8, 2, 6, 4, 3, 5],排好序变成 [2, 3, 4, 5, 6, 8]。排序是后面双指针的前提:有了顺序,固定最右边那个就是当前最大边,左边的数也单调,指针才能稳稳对撞。
- 5固定最大的那条边 nums[5]=8(黄色)。只要在它左边能找到两条边、和严格大于 8,就凑成一个三角形。左指针 l 从最左、右指针 r 紧挨最大边,往中间夹。
- 6当前两条边是 nums[0]=2 和 nums[4]=6,它们的和是 8。下一步看这个和是不是严格大于最大边 8。
- 78 没有大于 8,凑不成三角形。问题出在最小的那条边 nums[0]=2 太短,把它换掉:l 右移一格到 1,去试更大的最小边。
- 8当前两条边是 nums[1]=3 和 nums[4]=6,它们的和是 9。下一步看这个和是不是严格大于最大边 8。
- 99 严格大于 8,成立!这里有个关键省时:既然最小的 nums[1]=3 配 nums[4]=6 都够大,那把 l 换成 1 到 3 之间任何一个更大的数,配 nums[4] 和最大边一样成立,所以一次就能记下 r−l=3 个三角形。记完让 r 左移一格,去试更小的右边。
- 10当前两条边是 nums[1]=3 和 nums[3]=5,它们的和是 8。下一步看这个和是不是严格大于最大边 8。
- 118 没有大于 8,凑不成三角形。问题出在最小的那条边 nums[1]=3 太短,把它换掉:l 右移一格到 2,去试更大的最小边。
- 12当前两条边是 nums[2]=4 和 nums[3]=5,它们的和是 9。下一步看这个和是不是严格大于最大边 8。
- 139 严格大于 8,成立!这里有个关键省时:既然最小的 nums[2]=4 配 nums[3]=5 都够大,那把 l 换成 2 到 2 之间任何一个更大的数,配 nums[3] 和最大边一样成立,所以一次就能记下 r−l=1 个三角形。记完让 r 左移一格,去试更小的右边。
- 14固定最大的那条边 nums[4]=6(黄色)。只要在它左边能找到两条边、和严格大于 6,就凑成一个三角形。左指针 l 从最左、右指针 r 紧挨最大边,往中间夹。
- 15当前两条边是 nums[0]=2 和 nums[3]=5,它们的和是 7。下一步看这个和是不是严格大于最大边 6。
- 167 严格大于 6,成立!这里有个关键省时:既然最小的 nums[0]=2 配 nums[3]=5 都够大,那把 l 换成 0 到 2 之间任何一个更大的数,配 nums[3] 和最大边一样成立,所以一次就能记下 r−l=3 个三角形。记完让 r 左移一格,去试更小的右边。
- 17当前两条边是 nums[0]=2 和 nums[2]=4,它们的和是 6。下一步看这个和是不是严格大于最大边 6。
- 186 没有大于 6,凑不成三角形。问题出在最小的那条边 nums[0]=2 太短,把它换掉:l 右移一格到 1,去试更大的最小边。
- 19当前两条边是 nums[1]=3 和 nums[2]=4,它们的和是 7。下一步看这个和是不是严格大于最大边 6。
- 207 严格大于 6,成立!这里有个关键省时:既然最小的 nums[1]=3 配 nums[2]=4 都够大,那把 l 换成 1 到 1 之间任何一个更大的数,配 nums[2] 和最大边一样成立,所以一次就能记下 r−l=1 个三角形。记完让 r 左移一格,去试更小的右边。
- 21固定最大的那条边 nums[3]=5(黄色)。只要在它左边能找到两条边、和严格大于 5,就凑成一个三角形。左指针 l 从最左、右指针 r 紧挨最大边,往中间夹。
- 22当前两条边是 nums[0]=2 和 nums[2]=4,它们的和是 6。下一步看这个和是不是严格大于最大边 5。
- 236 严格大于 5,成立!这里有个关键省时:既然最小的 nums[0]=2 配 nums[2]=4 都够大,那把 l 换成 0 到 1 之间任何一个更大的数,配 nums[2] 和最大边一样成立,所以一次就能记下 r−l=2 个三角形。记完让 r 左移一格,去试更小的右边。
- 24当前两条边是 nums[0]=2 和 nums[1]=3,它们的和是 5。下一步看这个和是不是严格大于最大边 5。
- 255 没有大于 5,凑不成三角形。问题出在最小的那条边 nums[0]=2 太短,把它换掉:l 右移一格到 1,去试更大的最小边。
- 26固定最大的那条边 nums[2]=4(黄色)。只要在它左边能找到两条边、和严格大于 4,就凑成一个三角形。左指针 l 从最左、右指针 r 紧挨最大边,往中间夹。
- 27当前两条边是 nums[0]=2 和 nums[1]=3,它们的和是 5。下一步看这个和是不是严格大于最大边 4。
- 285 严格大于 4,成立!这里有个关键省时:既然最小的 nums[0]=2 配 nums[1]=3 都够大,那把 l 换成 0 到 0 之间任何一个更大的数,配 nums[1] 和最大边一样成立,所以一次就能记下 r−l=1 个三角形。记完让 r 左移一格,去试更小的右边。
- 29每个能当三元组最大边的位置(k 从 2 到 5)都固定过一遍、各跑一趟双指针,累计 11 个有效三角形,就是答案。回头看:排序 + 固定最大边 + 对撞双指针,把暴力枚举三元组的 O(n³) 压到了 O(n²)。
⚠️ 容易写错的地方
✗ 错:逐个枚举三元组再判定
✓ 对:排序后固定最大边、双指针对撞
暴力 O(n³) 在 n=1000 时太慢;双指针把内层压成线性
✗ 错:命中时只 ans++
✓ 对:命中要 ans += r−l 一次加一批
较小边从 l 到 r−1 共这么多个,各自配 r、k 都成立,漏加就少数一大片
✗ 错:三条不等式都去判
✓ 对:排序后只需查最小两边和 > 最大边
最大边固定后,另两个不等式必然成立,只剩一个要查
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def triangleNumber(self, nums: List[int]) -> int:
nums.sort()
ans = 0
for k in range(len(nums) - 1, 1, -1):
l, r = 0, k - 1
while l < r:
if nums[l] + nums[r] > nums[k]:
ans += r - l
r -= 1
else:
l += 1
return ansC++
#include <algorithm>
#include <vector>
using namespace std;
class Solution {
public:
int triangleNumber(vector<int>& nums) {
sort(nums.begin(), nums.end());
int ans = 0;
for (int k = nums.size() - 1; k >= 2; --k) {
int l = 0, r = k - 1;
while (l < r) {
if (nums[l] + nums[r] > nums[k]) { ans += r - l; r--; }
else l++;
}
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int triangleNumber(int[] nums) {
Arrays.sort(nums);
int ans = 0;
for (int k = nums.length - 1; k >= 2; k--) {
int l = 0, r = k - 1;
while (l < r) {
if (nums[l] + nums[r] > nums[k]) { ans += r - l; r--; }
else l++;
}
}
return ans;
}
}复杂度
时间
O(n²)
排序 O(n log n);固定每条最大边各跑一趟双指针,合起来 O(n²)
空间
O(log n)~O(n)
只用几个指针,额外开销主要在排序:C++/Java 通常 O(log n),Python 的 Timsort 最坏 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 有效三角形的个数 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么固定最大边、而不是固定最小边?+
固定最大边后,「最小两边之和 > 最大边」是唯一要查的条件,且左边区间随最大边单调,双指针能对撞着扫;若固定最小边,要查的不等式和单调性都不顺,双指针不好用。
和「三数之和」类题有什么共性?+
都是「排序 + 双指针」把一层枚举降成线性。区别在判定条件和命中后的处理:三数之和找等于目标、命中后两指针同时收;本题找和大于阈值、命中后一次性累加 r−l 个再收右指针。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 有效三角形的个数 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。