[leetcode] Valid Triangle Number

Given an array consists of non-negative integers, your task is to count the number of triplets chosen from the array that can make triangles if we take them as side lengths of a triangle.

Example 1:

Input: [2,2,3,4]
Output: 3
Explanation:
Valid combinations are: 
2,3,4 (using the first 2)
2,3,4 (using the second 2)
2,2,3

Note:

  1. The length of the given array won't exceed 1000.
  2. The integers in the given array are in the range of [0, 1000].

分析:题目翻译一下:要求在一个数组中找到三个数的组合,使得这三个数任意两数之和大于第三个数,不同位置重复的数字视为不同组合,求有多少种组合。
首先,第一印象三层循环,因为要挑出三个数字。用个小技巧就是先排序,然后只需要比较较小两个数的和是否大于第三个数就行了。代码如下:
 1 class Solution {
 2     public int triangleNumber(int[] nums) {
 3         Arrays.sort(nums);
 4         int count = 0;
 5         for ( int i = 0 ; i < nums.length - 2 ; i ++ ){
 6             for ( int j = i + 1 ; j < nums.length - 1 ; j ++ ){
 7                 int doublesum = nums[i]+nums[j];
 8                 for ( int z = j + 1 ; z < nums.length ; z ++ ){
 9                     if ( nums[z] < doublesum ) count++;
10                 }
11             }
12         }
13         return count;
14     }
15 }

      运行时间235ms,很差的算法,时间复杂度为O(n^3)。但是这个是我们下一步改进的基础。


思路二:上面一个算法中,我们比较关键的一步操作时先对数组进行排序,排序之后的数组是从小到大,然后我们找数字的时候,也就是第三层循环,可以直接用binarysearch方法去找到不满足条件最小的数字,就不用一个一个去遍历了。例如[1,3,4,5,8,9],如果选择前两个数字是3和4,那么第三个数字不能超过7,那我们就用binarysearch方法搜索7的位置,然后减去4的位置就是在这种情况下的值。也就是说利用这种思想可以把时间复杂度降到O(n^2)。查了一下binarysearch的api,发现很不好用,还不如自己写一个二分查找。代码如下:

 1 class Solution {
 2     public int triangleNumber(int[] nums) {
 3         Arrays.sort(nums);
 4         int count = 0;
 5         for ( int i = 0 ; i < nums.length - 2 ; i ++ ){
 6             for ( int j = i + 1 ; j < nums.length - 1 ; j ++ ){
 7                 int doublesum = nums[i] + nums[j];
 8                 //二分查找
 9                 int low = j + 1;
10                 int high = nums.length;
11                 while ( low < high ){
12                     int mid = ( low + high ) / 2 ;
13                     if ( nums[mid] < doublesum ) low = mid + 1;
14                     else high = mid;
15                 }
16                 count += low - j - 1;
17             }
18         }
19         return count;
20     }
21 }

      这种思路运行时间43ms,击败26.16%的提交。神了奇了,都缩短到O(n^2*longn)了,为啥还是不行。。。莫非还有O(n*longn)的算法存在?


思路三:参考了solution的解法,我的理解是一层循环+滑动窗口。滑动窗口比较关键,从i+1到nums.length-1进行滑动窗口,每个窗口内应该包含的是使得三角形成立的最大情况。

比如[2,5,6,7,9]

i=0:1、left从5开始,right从6开始,首先right向又移动,一旦nums[i]+nums[left]>=nums[right],right停止一定,此时[left,right]窗口中包含right-left-1个解。

        2、left向右移动一位,right又向右移动,重复上面的步骤。

具体的滑动窗口法在在另一个文章中有详细介绍。这里代码实现如下:

 1 class Solution {
 2     public int triangleNumber(int[] nums) {
 3         Arrays.sort(nums);
 4         int count = 0;
 5         for ( int i = 0 ; i < nums.length - 2 ; i ++ ){
 6             for ( int left = i + 1 ; left < nums.length - 1 ; left ++ ){
 7                 int right = left + 1;
 8                 while (right < nums.length && nums[i] + nums[left] > nums[right])  right++;
 9                 count += right - left - 1;
10             }
11         }
12         return count;
13     }
14 }

       运行时间66ms,还是不够好。其实时间复杂度还是O(n^2*logn)。


思路4:从大到小搜索,因为两数之和要大于第三个数,不妨从nums.length-1到2,下面问题就转变成在有序数组中求两个数之和大于指定数的个数了。

例如:[2,5,6,7,8],当nums[i]=8时,因为2+7>8,所以278、578、678都可以,count+=right-left,然后right左移;2+6<=8,所以left右移,继续上面的判断。想法非常巧妙。

代码如下:

 1 class Solution {
 2     public int triangleNumber(int[] nums) {
 3         Arrays.sort(nums);
 4         int count = 0;
 5         for ( int i = nums.length - 1 ; i > 1 ; i -- ){
 6             int target = nums[i];
 7             int low = 0;
 8             int high = i-1;
 9             while ( low < high ){
10                 if ( nums[low] + nums[high] > target ){
11                     count += high - low;
12                     high--;
13                 }else {
14                     low ++;
15                 }
16             }
17         }
18         return count;
19     }
20 }

      运行时间18ms,时间复杂度o(n*longn)

      这个题目整体还是有很多种方法的,比较好理解的还是用二分查找的方法。最后一种设计很巧妙,学习了。

posted @ 2018-07-19 17:56  Lin.B  阅读(135)  评论(0编辑  收藏  举报