一、题目
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。
请你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
二、解题思路
-
排序数组:首先,我们需要对数组进行排序。这样可以帮助我们在后续步骤中更有效地找到满足条件的三元组。
-
遍历数组:然后,我们遍历数组,对于每个元素 nums[i],我们使用两个指针,一个指向当前元素的下一个元素(左指针),另一个指向数组的最后一个元素(右指针)。
-
双指针法:对于当前元素 nums[i],我们尝试找到两个其他元素,使得这三个元素的和为0。我们移动左指针和右指针,使得左指针指向的元素和右指针指向的元素的和加上当前元素 nums[i] 是否等于0。如果和小于0,我们向右移动左指针;如果和大于0,我们向左移动右指针。这样我们可以保证在每一步中,我们都在寻找和为0的三元组。
-
去重:在找到满足条件的三元组后,我们需要确保返回的结果中不包含重复的三元组。这可以通过在添加三元组到结果列表之前检查是否已经存在相同的三元组来实现。
-
返回结果:最后,我们返回包含所有满足条件的三元组的列表。
三、具体代码
import java.util.ArrayList;
import java.util.List;class Solution {public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> result = new ArrayList<>();if (nums == null || nums.length < 3) {return result;}// Step 1: Sort the arrayArrays.sort(nums);// Step 2: Iterate through the arrayfor (int i = 0; i < nums.length - 2; i++) {// To avoid duplicates, we skip the same elementif (i > 0 && nums[i] == nums[i - 1]) {continue;}// Step 3: Use two pointersint left = i + 1;int right = nums.length - 1;while (left < right) {int sum = nums[i] + nums[left] + nums[right];// Check if the sum is 0if (sum == 0) {// Add the triplet to the result set and move both pointersresult.add(Arrays.asList(nums[i], nums[left], nums[right]));// Skip duplicateswhile (left < right && nums[left] == nums[left + 1]) {left++;}while (left < right && nums[right] == nums[right - 1]) {right--;}left++;right--;} else if (sum < 0) {// If sum is less than 0, move left pointer to the rightleft++;} else {// If sum is greater than 0, move right pointer to the leftright--;}}}return result;}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
- 排序数组:
O(n log n)
,因为数组排序通常使用快速排序、归并排序等,其平均时间复杂度为O(n log n)
。 - 遍历数组:
O(n)
,因为我们只需要遍历数组一次。 - 双指针法:在最坏的情况下,对于数组中的每个元素,双指针可能需要遍历剩余的数组元素,这在最坏情况下是
O(n^2)
。但是,由于我们在找到和为0的三元组后会移动指针,所以实际的时间复杂度会小于O(n^2)
。具体来说,每次移动左指针或右指针时,我们都会跳过重复的元素,这减少了实际的比较次数。
综合考虑,时间复杂度可以近似为 O(n^2)
,因为排序是预处理步骤,而双指针法是主要的时间消耗部分。
2. 空间复杂度
- 结果列表:在最坏的情况下,如果数组中的所有元素都满足三元组和为0的条件,那么结果列表的大小将是
O(n^2)
。这是因为我们可能会添加n * (n - 1) / 2
个三元组到结果列表中(假设数组中的元素都是唯一的)。 - 辅助空间:除了结果列表,我们还需要额外的空间来存储排序后的数组索引(即左指针和右指针),这在最坏情况下是
O(1)
。
所以,空间复杂度是 O(n^2)
(结果列表)加上 O(1)
(指针),可以简化为 O(n^2)
。
请注意,这里的分析是基于最坏情况的假设。在实际应用中,由于数组中可能存在负数、正数和零,且它们的分布可能不均匀,实际的时间复杂度可能会有所不同。
五、总结知识点
1. 数组排序:
- 使用
Arrays.sort(nums)
方法对整数数组进行排序。这是 Java 标准库中的一个方法,用于对数组进行快速排序。
2. 数组遍历:
- 使用
for
循环遍历数组,这是 Java 中常见的迭代数组元素的方法。
3. 条件判断:
- 在遍历过程中,使用
if
语句跳过重复的元素,以避免在结果中出现重复的三元组。
4. 双指针技巧:
- 使用两个指针(
left
和right
)来遍历数组的剩余部分,这是一种高效的算法技巧,用于在有序数组中寻找特定条件的元素组合。
5. 列表操作:
- 使用
ArrayList
和Arrays.asList()
方法来创建和添加列表元素。ArrayList
是 Java 中的一个动态数组,可以动态地添加和删除元素。
6. 去重处理:
- 在添加三元组到结果列表之前,通过移动指针来跳过重复的元素,这是一种常见的去重策略。
7. 逻辑控制:
- 使用
while
循环结合条件判断(if-else
)来控制指针的移动,这是解决这类问题的关键逻辑。
8. 返回结果:
- 最后,将包含所有满足条件的三元组的列表返回给调用者。
9. 时间复杂度和空间复杂度分析:
- 虽然代码中没有直接展示,但理解时间复杂度和空间复杂度对于优化算法和评估算法效率是非常重要的。
10. 代码风格:
- 代码遵循了一定的命名规范和结构,如方法名
threeSum
清晰地表达了其功能,变量名如result
、left
、right
等也直观地反映了它们的用途。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。