classSolution: defis_triangle(self, a, b, c): if a == 0or b == 0or c == 0: returnFalse if a + b > c and a + c > b and b + c > a: returnTrue returnFalse deftriangleNumber(self, nums: List[int]) -> int: n = len(nums) ans = 0 for i in range(n - 2): for j in range(i + 1, n - 1): for k in range(j + 1, n): if self.is_triangle(nums[i], nums[j], nums[k]): ans += 1
defis_triangle(self, a, b, c): if a == 0or b == 0or c == 0: returnFalse # a + c > b 和 b + c > a 是无效的判断,因为恒成立 if a + b > c and a + c > b and b + c > a: returnTrue returnFalse
因此我们的目标变为找到a + b > c即可,因此第三层循环是可以提前退出的。
1 2 3 4 5 6
for i in range(n - 2): for j in range(i + 1, n - 1): k = j + 1 while k < n and num[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1
这也仅仅是减枝而已,复杂度没有变化。通过进一步观察,发现 k 没有必要每次都从 j + 1 开始。而是从上次找到的 k 值开始就行。原因很简单, 当 nums[i] + nums[j] > nums[k] 时,我们想要找到下一个满足 nums[i] + nums[j] > nums[k] 的 新的 k 值,由于进行了排序,因此这个 k 肯定比之前的大(单调递增性),因此上一个 k 值之前的数都是无效的,可以跳过。
1 2 3 4 5 6
for i in range(n - 2): k = i + 2 for j in range(i + 1, n - 1): while k < n and nums[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1
由于 K 不会后退,因此最内层循环总共最多执行 N 次,因此总的时间复杂度为 $O(N ^ 2)$。
这个复杂度分析有点像单调栈,大家可以结合起来理解。
关键点分析
排序
代码
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution: deftriangleNumber(self, nums: List[int]) -> int: n = len(nums) ans = 0 nums.sort() for i in range(n - 2): if nums[i] == 0: continue k = i + 2 for j in range(i + 1, n - 1): while k < n and nums[i] + nums[j] > nums[k]: k += 1 ans += k - j - 1 return ans
classSolution: defmaxSumDivThree(self, nums: List[int]) -> int: self.res = 0 defbacktrack(temp, start): total = sum(temp) if total % 3 == 0: self.res = max(self.res, total) for i in range(start, len(nums)): temp.append(nums[i]) backtrack(temp, i + 1) temp.pop(-1)
backtrack([], 0)
return self.res
减法 + 排序
减法的核心思想是,我们求出总和。如果总和不满足题意,我们尝试减去最小的数,使之满足题意。
思路
这种算法的思想,具体来说就是:
我们将所有的数字加起来,我们不妨设为 total
total 除以 3,得到一个余数 mod, mod 可能值有 0,1,2.
同时我们建立两个数组,一个是余数为 1 的数组 one,一个是余数为 2 的数组 two
如果 mod 为 0,我们直接返回即可。
如果 mod 为 1,我们可以减去 one 数组中最小的一个(如果有的话),或者减去两个 two 数组中最小的(如果有的话),究竟减去谁取决谁更小。
如果 mod 为 2,我们可以减去 two 数组中最小的一个(如果有的话),或者减去两个 one 数组中最小的(如果有的话),究竟减去谁取决谁更小。
由于我们需要取 one 和 two 中最小的一个或者两个,因此对数组 one 和 two 进行排序是可行的,如果基于排序的话,时间复杂度大致为 $O(NlogN)$,这种算法可以通过。
classSolution: defmaxSumDivThree(self, nums: List[int]) -> int: one = [] two = [] total = 0
for num in nums: total += num if num % 3 == 1: one.append(num) if num % 3 == 2: two.append(num) one.sort() two.sort() if total % 3 == 0: return total elif total % 3 == 1and one: if len(two) >= 2and one[0] > two[0] + two[1]: return total - two[0] - two[1] return total - one[0] elif total % 3 == 2and two: if len(one) >= 2and two[0] > one[0] + one[1]: return total - one[0] - one[1] return total - two[0] return0
减法 + 非排序
思路
上面的解法使用到了排序。 我们其实观察发现,我们只是用到了 one 和 two 的最小的两个数。因此我们完全可以在线形的时间和常数的空间完成这个算法。我们只需要分别记录 one 和 two 的最小值和次小值即可,在这里,我使用了两个长度为 2 的数组来表示,第一项是最小值,第二项是次小值。
classSolution: defmaxSumDivThree(self, nums: List[int]) -> int: one = [float('inf')] * 2 two = [float('inf')] * 2 total = 0
for num in nums: total += num if num % 3 == 1: if num < one[0]: t = one[0] one[0] = num one[1] = t elif num < one[1]: one[1] = num if num % 3 == 2: if num < two[0]: t = two[0] two[0] = num two[1] = t elif num < two[1]: two[1] = num if total % 3 == 0: return total elif total % 3 == 1and one: if len(two) >= 2and one[0] > two[0] + two[1]: return total - two[0] - two[1] return total - one[0] elif total % 3 == 2and two: if len(one) >= 2and two[0] > one[0] + one[1]: return total - one[0] - one[1] return total - two[0] return0
for num in nums: if num % 3 == 0: state = [state[0] + num, state[1] + num, state[2] + num] if num % 3 == 1: a = max(state[2] + num, state[0]) b = max(state[0] + num, state[1]) c = max(state[1] + num, state[2]) state = [a, b, c] if num % 3 == 2: a = max(state[1] + num, state[0]) b = max(state[2] + num, state[1]) c = max(state[0] + num, state[2]) state = [a, b, c] return state[0]