【双指针】专题:LeetCode 611题解——有效三角形的个数
有效三角形的个数
- 一、题目链接
- 二、题目
- 三、算法原理
- 1、判断是否是三角形的方法
- 2、解法
- 四、时间复杂度
- 五、编写代码
一、题目链接
有效三角形的个数
二、题目
三、算法原理
1、判断是否是三角形的方法
常规方法:三条边分别是a、b、c,要同时满足a + b > c、a + c > b、b + c > a才能构成三角形,但是要判断三次。
优化方法:仅需判断一次。已知三条边的大小顺序a <= b <= c(说明要提前对数组里的数据排好序),只要两个较小边之和大于最大一条边c就能构成三角形:a + b > c。优化后的方法相较于上面的普通方法少判断了a + c > b、b + c > a,不用判断的理由是c已经是最大边了,肯定有c >= a、c >= b,这样c再加上a肯定大于b,同理,c再加上b肯定大于a。
2、解法
解法一:暴力枚举。
三层for循环分别固定三个数。(伪代码:看懂就行,只是个思路,不能作为最终代码提交。)
若选择常规方法判断是否是三角形,时间复杂度是O(3n^3)
,前面的3表示判断三次;若选择优化方法判断是否是三角形,先对数组排好序,时间复杂度是O(nlogn + n^3)
(nlogn是排序算法的时间复杂度,因为只需判断一次,所以是n^3)。提升点1:3n^3 > nlogn + n^3
,优化方法对暴力枚举的时间复杂度提升非常大。
提升点2:优化后,数组已经有序,可以利用已经有序的单调性作很多的优化。
解法二:利用单调性,使用双指针算法来解决问题。
步骤:
- 数组已有序(升序),固定最大数c
- 在最大数c的左区间内,使用双指针算法,快速统计出符合要求的三元组的个数
- 情况1:a + b > c,right - left,right - -。情况2:a + b <= c,left++
分析过程:
数组已有序(升序),先固定最大数c,再枚举剩下的两个数a、b。不是胡乱枚举,要不时间复杂度又是O(n^3)了。
找最大数c的左区间内的最小值和最大值(分别对应左区间中的第一个数和最后一个数),分别用指针left、right指着。
所以,只有两种情况:情况1:a + b > c
、情况2:a + b <= c
。
2 + 9 > 10中了情况1,说明构成三角形。left和right中间这些数都 >= 当前left指向的数,那么这些数与right指向的数相加肯定大于c,肯定都满足构成三角形的条件,这一下子就有5个有效三角形的个数(个数求法:下标相减right - left):
因为9与这些数相加都大于c,所以9用完了,可以right - -。计算新区间中组成三角形三条边的三元组个数:
2 + 5 < 10中了情况2,不满足三角形的条件,当前left和right中间这些数都小于right所指,那么这些数与left所指相加肯定也 <= c(别的示例有等于的情况),这样就不用计算,直接left++:
直到两个指针相遇,结束计算。把最大数换成9,再在9的左区间找最大值和最小值,重复过程。把所有数都固定完一遍就能得到结果。
画图分析到最后发现c在下标为1处时就没有必要再计算了。在下标为0处就更没有必要了。其实写成 i >= 0也没有关系,写成上面分析的样子更标准一点:
四、时间复杂度
分析解法二的时间复杂度:
数组已有序(升序),固定最大数c,从右向左每一个数都可以成为固定的最大数c,那就是O(n)。两个双指针相向移动,遍历一遍数组就可以计算出三角形个数,也是O(n)。循环嵌套,O(n^2),该解法比优化后的暴力枚举还要好。
五、编写代码
不要在for循环外面定义left、right,因为在循环内部会改变它们的。是每次固定完一个数之后才出现一个左右区间,所以双指针要定义在循环内部。
if里有多个语句想偷懒写成一行,语句中间记住是用逗号分隔开,用分号分隔会导致if语句断掉。
class Solution {
public:int triangleNumber(vector<int>& nums) {sort(nums.begin(), nums.end());int sum = 0, n = nums.size();for (int i = n - 1; i > 1; --i){int left = 0, right = i - 1;while (left < right){if ((nums[left] + nums[right]) > nums[i]) sum += (right - left), right--;else left++;}}return sum;}
};