目录
[一. 题目解析](#一. 题目解析)
[二 .讲解算法原理](#二 .讲解算法原理)
[1. 判断方法:](#1. 判断方法:)
[2. 提升](#2. 提升)
[3. 最优解法](#3. 最优解法)
[4. 总结步骤](#4. 总结步骤)
[三. 编写代码](#三. 编写代码)
[1. 步骤精讲](#1. 步骤精讲)
2[. 完整代码](#. 完整代码)
一. 题目解析
给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。
题目的意思是:给了我们一个数组nums,让我们在数组中任意挑选三个数,看这三个数能否组成一个三角形,然后对于整个数组,有几种能够组成三角形的挑法
哪怕组合重复,也要统计到有效组合中的,因为选法不同 ,如示例一
二 .讲解算法原理
1. 判断方法:
如果有三个数a,b,c,判断这三个数是否能构成三角形,利用任意两边之和大于第三边的条件:
方法一:
(a+b>c)&&(a+c>b)&&(b+c>a),这种方法虽然简单,但是要判断三次;
方法二:
我们有一种更加优秀的方法,仅仅需要判断一次,就能看这三个数能否构成三角形:
我们只要知道a,b,c 的三个数的大小顺序,那么只要判断a+b是否大于c,即可判断a,b,c能否构成三角形。
这时候,有uu就会有疑问,不就是多判断两次吗?时间复杂度能多到哪去?
接下来,我们来讲通过第二种判断方法的解法,就会发现,利用第二种判断方法,时间复杂度会有质的提升。
因为这个题目给我们的数组是无序的,我们想用第二种判断方法判断,就必须先给数组排序。
2. 提升
暴力解法
暴力枚举所有的三个数:
上图是一个伪代码,只是用来展示暴力枚举的思路 ,通过暴力枚举的解法的总时间复杂度,来给大家解释为什么要优化判断方法.
提升点一
暴力解法的时间复杂度也非常好看,三层for()循环,时间复杂度就是O(N^3)。
这时候我们在来看常数级别,如果暴力解法中:
check()方法用的是第一种判断方法,那么时间复杂度会达到O(3*N^3);
check()方法用的是第二种判断方法,我们要把对数组排序的时间复杂度也算入总的复杂度中,排序的时间复杂度为O(N*logN);那么时间复杂度会达到O(N^3+N*logN)。
因此,我们通过暴力枚举,运用不同的判断方法,会发现对时间复杂度的优化是巨大的。
提升点二
在使用第二种判断方法时,因为我们的数组就已经有序了,我们就能通过单调性,作出更多的优化 ;排完序后的数组,对于我们来说,更容易想到优化的点,如二分算法。但是对于这道题,二分算法并不是最优秀的优化方法,我们就先不讲;
我们利用最优秀的解法来解决这道题目, 就是利用单调性,使用双指针算法来解决问题。
3. 最优解法
如上文,我们已经说了,就是通过判断方法二,数组已经排好序,利用单调性,使用双指针算法来解决问题:
我们先固定最大的数组元素,也就是数组最后一位元素,我们设被固定的数为 c ;
我们开始枚举剩下的两个数,但是不是胡乱枚举,否则时间复杂度又是O(N^3);
所以再定义两个指针 left,right 分别指向数组第一个元素和倒数第二个元素,设left指的数是a,right指的数是b;
我们观察a+b,可以得出两种情况: a+b>c 或者 a+b<=c ;
我们来看看,能不能通过数组有序,写出一些优化方法。
情况一:a+b>c
如果 a+b>c ,那么a,b 之间的所有数字都可以替换 a( left指针可以指向 a b 之间的任意一个数),此时这种情况下的a b c都可以构成三角形;
也就是通过a+b>c这一次比较,就发现了好几种符合题意的情况,刚好是(right-left)种情况。
得到(right-left)种情况后,right所指向的元素就没有再枚举的必要了,让right--
情况二: a+b<=c
对于上图,我们在移动 right 指针后,会出现 a+b<=c 的情况,这时候,a 和 b之间的所有数都小于 b ,要想继续找能构成三角形的 a,b,c,我们就要令left++
直到 left 和 right 相遇,说明固定10的所有情况都已经找到:
这时候,我们就需要固定的指针向左移动,去枚举固定第二个最大数时,能构成三角形的所有情况
4. 总结步骤
- 优化判断方法,排序;
- 定义 ret 记录数组所有能构成三角形的情况个数,作为返回的结果;
- 固定最大的数;
- 在最大的数的左区间内,使用双指针算法,快速统计出符合要求的三元组个数;
- 时间复杂度:第一步固定一个数,需要一层循环,第二步双指针,也需要一层循环,所以时间复杂度为O(N^2),是远远优于暴力枚举的策略的。
三. 编写代码
1. 步骤精讲
1.优化判断方法,排序:
2.定义 ret 记录数组所有能构成三角形的情况个数,作为返回的结果:
- 固定最大的数:
注意,这里 i 固定最后一个元素,所以 i 从 n - 1 的位置开始,又因为前面还要定义两个指针,这两个指针所指的值,与要固定的值,是不能重复的;所以可以少枚举两次,i>=2 而不是 i>=0
4. 在最大的数的左区间内,使用双指针算法,快速统计出符合要求的三元组个数:
left ,right两个指针,必须是在固定最大数之后,再定义;
所以left ,right 要在第一层for循环内定义,因为每次循环都要改变;
right=i-1 表示 right 在固定好数后,从该数的左边开始枚举
2. 完整代码
java
public int triangleNumber(int[] nums) {
int n =nums.length;
Arrays.sort(nums);
int ret=0;
for(int i=n-1; i>=2;i--){
int left=0 , right= i-1;
while(left<right){
if(nums[left]+nums[right]>nums[i]){
ret+=right-left;
right--;
}else{
left++;
}
}
}
return ret;
}