|
| 1 | +## 题目地址(611. 有效三角形的个数) |
| 2 | + |
| 3 | +https://leetcode-cn.com/problems/valid-triangle-number/ |
| 4 | + |
| 5 | +## 题目描述 |
| 6 | + |
| 7 | +``` |
| 8 | +给定一个包含非负整数的数组,你的任务是统计其中可以组成三角形三条边的三元组个数。 |
| 9 | +
|
| 10 | +示例 1: |
| 11 | +
|
| 12 | +输入: [2,2,3,4] |
| 13 | +输出: 3 |
| 14 | +解释: |
| 15 | +有效的组合是: |
| 16 | +2,3,4 (使用第一个 2) |
| 17 | +2,3,4 (使用第二个 2) |
| 18 | +2,2,3 |
| 19 | +注意: |
| 20 | +
|
| 21 | +数组长度不超过1000。 |
| 22 | +数组里整数的范围为 [0, 1000]。 |
| 23 | +
|
| 24 | +``` |
| 25 | + |
| 26 | +## 前置知识 |
| 27 | + |
| 28 | +- 排序 |
| 29 | +- 双指针 |
| 30 | +- 二分法 |
| 31 | +- 三角形边的关系 |
| 32 | + |
| 33 | +## 暴力法(超时) |
| 34 | + |
| 35 | +### 思路 |
| 36 | + |
| 37 | +首先要有一个数学前提: `如果三条线段中任意两条的和都大于第三边,那么这三条线段可以组成一个三角形`。即给定三个线段 a,b,c,如果满足 a + b > c and a + c > b and b + c > a,则线段 a,b,c 可以构成三角形,否则不可以。 |
| 38 | + |
| 39 | +力扣中有一些题目是需要一些数学前提的,不过这些数学前提都比较简单,一般不会超过高中数学知识,并且也不会特别复杂。一般都是小学初中知识即可。 |
| 40 | + |
| 41 | +> 如果你在面试中碰到不知道的数学前提,可以寻求面试官提示试试。 |
| 42 | +
|
| 43 | +### 关键点解析 |
| 44 | + |
| 45 | +- 三角形边的关系 |
| 46 | +- 三层循环确定三个线段 |
| 47 | + |
| 48 | +### 代码 |
| 49 | + |
| 50 | +代码支持: Python |
| 51 | + |
| 52 | +```py |
| 53 | +class Solution: |
| 54 | + def is_triangle(self, a, b, c): |
| 55 | + if a == 0 or b == 0 or c == 0: return False |
| 56 | + if a + b > c and a + c > b and b + c > a: return True |
| 57 | + return False |
| 58 | + def triangleNumber(self, nums: List[int]) -> int: |
| 59 | + n = len(nums) |
| 60 | + ans = 0 |
| 61 | + for i in range(n - 2): |
| 62 | + for j in range(i + 1, n - 1): |
| 63 | + for k in range(j + 1, n): |
| 64 | + if self.is_triangle(nums[i], nums[j], nums[k]): ans += 1 |
| 65 | + |
| 66 | + return ans |
| 67 | +``` |
| 68 | + |
| 69 | +**复杂度分析** |
| 70 | + |
| 71 | +- 时间复杂度:$O(N ^ 3)$,其中 N 为 数组长度。 |
| 72 | +- 空间复杂度:$O(1)$ |
| 73 | + |
| 74 | +## 优化的暴力法 |
| 75 | + |
| 76 | +### 思路 |
| 77 | + |
| 78 | +暴力法的时间复杂度为 $O(N ^ 3)$, 其中 $N$ 最大为 1000。一般来说, $O(N ^ 3)$ 的算法在数据量 <= 500 是可以 AC 的。1000 的数量级则需要考虑 $O(N ^ 2)$ 或者更好的解法。 |
| 79 | + |
| 80 | +OK,到这里了。我给大家一个干货。 应该是其他博主不太会提的。原因可能是他们不知道, 也可能是他们觉得太小儿科不需要说。 |
| 81 | + |
| 82 | +1. 由于前面我根据数据规模推测到到了解法的复杂度区间是 $N ^ 2$, $N ^ 2 * logN$,不可能是 $N$ (WHY?)。 |
| 83 | +2. 降低时间复杂度的方法主要有: `空间换时间` 和 `排序换时间`(我们一般都是使用基于比较的排序方法)。而`排序换时间`仅仅在总体复杂度大于 $O(NlogN)$ 才适用(原因不用多说了吧?)。 |
| 84 | + |
| 85 | +这里由于总体的时间复杂度是 $O(N ^ 3)$,因此我自然想到了`排序换时间`。当我们对 nums 进行一次排序之后,我发现: |
| 86 | + |
| 87 | +- is_triangle 函数有一些判断是无效的 |
| 88 | + |
| 89 | +```py |
| 90 | + def is_triangle(self, a, b, c): |
| 91 | + if a == 0 or b == 0 or c == 0: return False |
| 92 | + # a + c > b 和 b + c > a 是无效的判断,因为恒成立 |
| 93 | + if a + b > c and a + c > b and b + c > a: return True |
| 94 | + return False |
| 95 | +``` |
| 96 | + |
| 97 | +- 因此我们的目标变为找到`a + b > c`即可,因此第三层循环是可以提前退出的。 |
| 98 | + |
| 99 | +```py |
| 100 | +for i in range(n - 2): |
| 101 | + for j in range(i + 1, n - 1): |
| 102 | + k = j + 1 |
| 103 | + while k < n and num[i] + nums[j] > nums[k]: |
| 104 | + k += 1 |
| 105 | + ans += k - j - 1 |
| 106 | +``` |
| 107 | + |
| 108 | +- 这也仅仅是减枝而已,复杂度没有变化。通过进一步观察,发现 k 没有必要每次都从 j + 1 开始。而是从上次找到的 k 值开始就行。原因很简单, 当 nums[i] + nums[j] > nums[k] 时,我们想要找到下一个满足 nums[i] + nums[j] > nums[k] 的 新的 k 值,由于进行了排序,因此这个 k 肯定比之前的大(单调递增性),因此上一个 k 值之前的数都是无效的,可以跳过。 |
| 109 | + |
| 110 | +```py |
| 111 | +for i in range(n - 2): |
| 112 | + k = i + 2 |
| 113 | + for j in range(i + 1, n - 1): |
| 114 | + while k < n and nums[i] + nums[j] > nums[k]: |
| 115 | + k += 1 |
| 116 | + ans += k - j - 1 |
| 117 | +``` |
| 118 | + |
| 119 | +由于 K 不会后退,因此最内层循环总共最多执行 N 次,因此总的时间复杂度为 $O(N ^ 2)$。 |
| 120 | + |
| 121 | +> 这个复杂度分析有点像单调栈,大家可以结合起来理解。 |
| 122 | +
|
| 123 | +### 关键点分析 |
| 124 | + |
| 125 | +- 排序 |
| 126 | + |
| 127 | +### 代码 |
| 128 | + |
| 129 | +```py |
| 130 | +class Solution: |
| 131 | + def triangleNumber(self, nums: List[int]) -> int: |
| 132 | + n = len(nums) |
| 133 | + ans = 0 |
| 134 | + nums.sort() |
| 135 | + for i in range(n - 2): |
| 136 | + if nums[i] == 0: continue |
| 137 | + k = i + 2 |
| 138 | + for j in range(i + 1, n - 1): |
| 139 | + while k < n and nums[i] + nums[j] > nums[k]: |
| 140 | + k += 1 |
| 141 | + ans += k - j - 1 |
| 142 | + return ans |
| 143 | +``` |
| 144 | + |
| 145 | +**复杂度分析** |
| 146 | + |
| 147 | +- 时间复杂度:$O(N ^ 2)$ |
| 148 | +- 空间复杂度:取决于排序算法 |
| 149 | + |
| 150 | +更多题解可以访问我的 LeetCode 题解仓库:https://github.com/azl397985856/leetcode 。 目前已经 30K star 啦。 |
| 151 | + |
| 152 | +关注公众号力扣加加,努力用清晰直白的语言还原解题思路,并且有大量图解,手把手教你识别套路,高效刷题。 |
| 153 | + |
| 154 | + |
0 commit comments