优化题解:四步解码 LeetCode 特殊四元组难题
2023-10-20 02:45:14
统计特殊四元组:LeetCode 上的一道有趣题目
探索一种有趣而有效的解决方案
在 LeetCode 上,有着数不胜数的有趣题目,它们旨在挑战我们的编程技能和算法知识。今天,我们将深入探讨其中一道引人入胜的题目:统计特殊四元组 。这道题不仅考验我们的算法设计能力,还要求我们对数组和排序等数据结构有深入的理解。
题目概述
给你一个下标从 0 开始的整数数组 nums
,请返回满足以下条件的不同四元组 (a, b, c, d)
的数目:
a + b + c = d
a < b < c < d
例如,如果 nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
,那么满足条件的四元组有:
- (1, 2, 3, 6)
- (1, 2, 3, 7)
- (1, 2, 3, 8)
- (1, 2, 3, 9)
- (1, 2, 3, 10)
- (2, 3, 4, 9)
- (2, 3, 4, 10)
- (2, 3, 5, 10)
因此,我们应该返回 8
。
解题思路
乍一看,这道题似乎有些棘手,但仔细分析后,我们可以发现一个巧妙的解决方案。问题的关键在于利用数组的有序性。我们可以依次枚举每个元素作为四元组中的 a
,然后利用二分查找在剩余数组中寻找满足条件的 b
、c
和 d
。
具体步骤如下:
- 枚举
a
: 遍历数组,依次枚举每个元素作为四元组中的a
。 - 寻找
b
: 对于当前的a
,我们可以利用数组的有序性,通过二分查找找到满足条件的b
。 - 寻找
c
: 对于当前的a
和b
,我们可以同样利用二分查找找到满足条件的c
。 - 统计
d
: 对于当前的a
、b
和c
,我们可以通过简单的计算得到满足条件的d
。 - 统计结果: 重复以上步骤,对于每个
a
,统计满足条件的四元组数量,并将结果累加得到最终结果。
优化算法
为了进一步优化算法的效率,我们可以对数组进行预处理,以便在二分查找时能够快速找到满足条件的元素。具体来说,我们可以对数组进行排序,并保存每个元素的索引。这样,在二分查找时,我们就可以通过索引直接找到元素,而无需遍历整个数组。
代码实现
import java.util.Arrays;
class Solution {
public int countQuadruplets(int[] nums) {
int n = nums.length;
if (n < 4) {
return 0;
}
// 对数组进行排序
Arrays.sort(nums);
// 保存每个元素的索引
int[] indexes = new int[n];
for (int i = 0; i < n; i++) {
indexes[i] = i;
}
// 统计满足条件的四元组数量
int count = 0;
for (int i = 0; i < n - 3; i++) {
// 跳过重复元素
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 寻找满足条件的 b
int left = i + 1;
int right = n - 1;
int target = nums[i] + nums[i + 1] + nums[i + 2];
int indexB = binarySearch(nums, indexes, left, right, target);
if (indexB < 0) {
continue;
}
// 寻找满足条件的 c
left = indexB + 1;
right = n - 1;
target = nums[i] + nums[i + 1] + nums[i + 2] + nums[indexB];
int indexC = binarySearch(nums, indexes, left, right, target);
if (indexC < 0) {
continue;
}
// 统计满足条件的 d
int indexD = n - 1;
while (indexD >= 0 && nums[indexD] == nums[indexC]) {
indexD--;
}
count += indexD - indexC + 1;
}
return count;
}
// 二分查找
private int binarySearch(int[] nums, int[] indexes, int left, int right, int target) {
while (left <= right) {
int mid = (left + right) / 2;
if (nums[indexes[mid]] == target) {
return indexes[mid];
} else if (nums[indexes[mid]] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
}
常见问题解答
- 为什么需要对数组进行排序?
排序是为了利用数组的有序性进行二分查找。二分查找是一种高效的搜索算法,可以快速找到满足条件的元素。
- 为什么需要保存每个元素的索引?
保存每个元素的索引是为了在二分查找时能够快速找到元素。直接通过索引查找元素比遍历整个数组要高效得多。
- 在枚举
a
时,为什么需要跳过重复元素?
跳过重复元素是为了避免重复计算。如果我们不跳过重复元素,那么对于相同的 a
,我们会重复计算多次。
- 在寻找
b
时,为什么需要设置left = i + 1
?
因为 a < b
,所以我们只需要在 a
之后的元素中寻找 b
。
- 在统计
d
时,为什么需要循环递减indexD
?
因为 a < b < c < d
,所以 d
一定是数组中最大的元素。因此,我们可以从数组的末尾开始循环递减 indexD
,直到找到满足条件的 d
。