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:
- The length of the given array won't exceed 1000.
- 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)
这个题目整体还是有很多种方法的,比较好理解的还是用二分查找的方法。最后一种设计很巧妙,学习了。