给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。
示例 1:
输入: [2,2,3,4] 输出: 3 解释: 有效的组合是: 2,3,4 (使用第一个 2) 2,3,4 (使用第二个 2) 2,2,3
注意:
- 数组长度不超过1000。
- 数组里整数的范围为 [0, 1000]。
我的解法:
最容易想到的是三个数满足条件:任意两边之和大于第三边。只要写一个三重循环遍历所有情况,就能得知答案。
int triangleNumber(vector<int>& nums) {
int n=nums.size();
int count=0;
for(int i=0;i<n-2;i++)
for(int j=i+1;j<n-1;j++)
for(int k=j+1;k<n;k++)
{
//任意两边之和大于第三边
if((nums[i]+nums[j]>nums[k])
&&(nums[j]+nums[k]>nums[i])
&&(nums[k]+nums[i]>nums[j]))
count++;
}
return count;
}
结果是正确的,但是效率太低了,测试耗时1800ms。
考虑它的另一个等价条件:最长边小于其余两边之和。在循环体中挑选最长的边,依然是件很复杂的事情,不如事先将数组排序,这样选出来的边无需比较就知道他们的长度关系了。这样做大大减少了条件判断的次数,然而时间复杂度仍旧是n立方的。
如果要把每种情况都考虑到,一一判断,那么至少需要判断C(n,3)次,时间复杂度必定是O(n^3)。但实际上不用求出每个满足条件的每种情况,只要求出满足条件的情况的数目。这样,在确定了两条次长边a,b之后,我们可以确定最长边的长度c区间:max(a,b)<c<a+b,进而确定这个区间中有多少个元素即可。
int triangleNumber(vector<int>& nums) {
//先排序
sort(nums.begin(),nums.end());
auto end=nums.end();
int count=0;
for(auto i=nums.begin();i<end-2;i++)
for(auto j=i+1;j<end-1;j++)
{
//寻找最大边可能在的区间[a,b)
auto a=j+1;
auto b=upper_bound(a,end,(*i)+(*j)-1);
count+=b-a;
}
return count;
}
时间复杂度是 O(n^2*logn),效率提高不少,测试耗时50ms。
暂时想不到更好的办法,去看了别人最快的算法实现,是这样的:
int triangleNumber(vector<int>& nums) {
vector<int> snums(nums);
sort(snums.begin(),snums.end());
int count = 0;
for(int n = nums.size(),k = n - 1;k > 1;k--){
int i = 0;
int j = k - 1;
while(i < j)
if(snums[i] + snums[j] > snums[k])
count += --j - i + 1;
else
i++;
}
return count;
}
揣摩了一下,他的思路是这样的:
在固定最长边c的情况下,对于每个次长边b,求出所有最短边a的可能总数。而b与a存在如下制约关系:
如果a和b是一组可能的解,那么所有x(其中x属于[a,b))和y(其中y属于[b,c))都是可能的解;
如果a和b是一组不可能的解,那么所有的x(其中x属于(0,a])和y(其中y属于[b,c))都是不可能的解。
再加上构思设计的算法,将时间复杂度降到了O(n^2)