【力扣】数据之和系列
Leetcode 0015 三数之和
题目描述:Leetcode 0015 三数之和
分析
-
本题的考点:双指针。
-
本题的暴力解法是三种循环枚举三个数,时间复杂度是 O ( n 3 ) O(n^3) O(n3)的,不可取。
-
为了使用双指针,首先对数组进行排序,然后我们可以用
i
枚举三个数中的其中一个,然后使用双指针j、k
找到另外两个数。这里假设i < j < k
。 -
对于当前考虑的
nums[i]
,此时可以看成定值,我们初始化j = i+1, k = nums.size()-1
,对于nums[j]
来说,我们需要找到一个最小的k
,使得有 n u m s [ i ] + n u m s [ j ] + n u m s [ k ] ≥ 0 nums[i]+nums[j]+nums[k]\ge 0 nums[i]+nums[j]+nums[k]≥0。 -
则当
j
增大时,k
必须减小,否则两者都增大的话,三者之和会增大,而我们要找到三者之和为0的数据,不符合要求。这类似于对撞指针。 -
另外还要考虑不能出现重复答案,因为是排序后的数组,当我们遍历到相同元素直接跳过即可。
代码
- C++
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> res;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] > 0) break; // 后面两个数一定大于等于nums[i],可以直接退出
if (i && nums[i] == nums[i - 1]) continue;
// 双指针算法
for (int j = i + 1, k = nums.size() - 1; j < k; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
while (j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= 0) k--;
if (nums[i] + nums[j] + nums[k] == 0) {
res.push_back({nums[i], nums[j], nums[k]});
}
}
}
return res;
}
};
- Java
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) break; // 后面两个数一定大于等于nums[i],可以直接退出
if (i > 0 && nums[i] == nums[i - 1]) continue;
// 双指针算法
for (int j = i + 1, k = nums.length - 1; j < k; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
while (j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= 0) k--;
if (nums[i] + nums[j] + nums[k] == 0) {
res.add(get(nums[i], nums[j], nums[k]));
}
}
}
return res;
}
private List<Integer> get(int a, int b, int c) {
List<Integer> res = new ArrayList<>();
res.add(a); res.add(b); res.add(c);
return res;
}
}
- Python
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
for i in range(len(nums)):
if nums[i] > 0:
break
if i > 0 and nums[i] == nums[i - 1]:
continue
j = i + 1; k = len(nums) - 1
while j < k:
if j > i + 1 and nums[j] == nums[j - 1]:
j += 1
continue
while j < k - 1 and nums[i] + nums[j] + nums[k - 1] >= 0:
k -= 1
if nums[i] + nums[j] + nums[k] == 0:
res.append([nums[i], nums[j], nums[k]])
j += 1
return res
时空复杂度分析
-
时间复杂度: O ( n 2 ) O(n^2) O(n2),
n
为数组长度。因为外层循环中每次循环都会执行一次双指针算法,双指针算法时间复杂度是 O ( n ) O(n) O(n)的,因此总体时间复杂度为 O ( n 2 ) O(n^2) O(n2)的。 -
空间复杂度: O ( 1 ) O(1) O(1)。
Leetcode 0016 最接近的三数之和
分析
-
本题的考点:双指针。
-
本题的暴力解法是三种循环枚举三个数,时间复杂度是 O ( n 3 ) O(n^3) O(n3)的,不可取。
-
为了使用双指针,首先对数组进行排序,然后我们可以用
i
枚举三个数中的其中一个,然后使用双指针j、k
找到另外两个数。这里假设i < j < k
。 -
对于当前考虑的
nums[i]
,此时可以看成定值,我们初始化j = i+1, k = nums.size()-1
,对于nums[j]
来说,我们需要找到一个最小的k
,使得有 n u m s [ i ] + n u m s [ j ] + n u m s [ k ] ≥ t a r g e t nums[i]+nums[j]+nums[k]\ge target nums[i]+nums[j]+nums[k]≥target;因为k
是最小的一个,所以一定有 n u m s [ i ] + n u m s [ j ] + n u m s [ k − 1 ] < t a r g e t nums[i]+nums[j]+nums[k-1] < target nums[i]+nums[j]+nums[k−1]<target。这样最接近target
的两种情况都能枚举到。 -
则当
j
增大时,k
必须减小,否则两者都增大的话,三者之和会增大,而我们要找到三者之和为0的数据,不符合要求。这类似于对撞指针。 -
因为我们要存储当前三数之和与
target
的差值以及三数之和,因此可以使用pair
。
代码
- C++
class Solution {
public:
int threeSumClosest(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
pair<int, int> res(INT_MAX, INT_MAX); // (三数之和与target的差值的绝对值, 三数之和)
for (int i = 0; i < nums.size(); i++)
for (int j = i + 1, k = nums.size() - 1; j < k; j++) {
while (j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= target) k--;
int s = nums[i] + nums[j] + nums[k];
res = min(res, make_pair(abs(s - target), s));
if (j < k - 1) {
s = nums[i] + nums[j] + nums[k - 1];
res = min(res, make_pair(abs(s - target), s));
}
}
return res.second;
}
};
- Java
class Solution {
static class MyPair {
int x, y;
public MyPair(int x, int y) {
this.x = x; this.y = y;
}
}
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
MyPair res = new MyPair(Integer.MAX_VALUE, Integer.MAX_VALUE);
for (int i = 0; i < nums.length; i++)
for (int j = i + 1, k = nums.length - 1; j < k; j++) {
while (j < k - 1 && nums[i] + nums[j] + nums[k - 1] >= target) k--;
int s = nums[i] + nums[j] + nums[k];
if (Math.abs(s - target) < res.x) {
res.x = Math.abs(s - target);
res.y = s;
}
if (j < k - 1) {
s = nums[i] + nums[j] + nums[k - 1];
if (Math.abs(s - target) < res.x) {
res.x = Math.abs(s - target);
res.y = s;
}
}
}
return res.y;
}
}
- Python
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
nums.sort()
res = [1e9, 1e9]
for i in range(len(nums)):
j = i + 1; k = len(nums) - 1
while j < k:
while j < k - 1 and nums[i] + nums[j] + nums[k - 1] >= target:
k -= 1
s = nums[i] + nums[j] + nums[k]
res = min(res, [abs(s - target), s])
if j < k - 1:
s = nums[i] + nums[j] + nums[k - 1]
res = min(res, [abs(s - target), s])
j += 1
return int(res[1])
时空复杂度分析
-
时间复杂度: O ( n 2 ) O(n^2) O(n2),
n
为数组长度。因为外层循环中每次循环都会执行一次双指针算法,双指针算法时间复杂度是 O ( n ) O(n) O(n)的,因此总体时间复杂度为 O ( n 2 ) O(n^2) O(n2)的。 -
空间复杂度: O ( 1 ) O(1) O(1)。
Leetcode 0018 四数之和
题目描述:Leetcode 0018 四数之和
分析
-
本题的考点:双指针。
-
这一题和Leetcode 0015 三数之和的解法一样,只不过需要枚举两个元素,另外两个元素使用双指针寻找。
代码
- C++
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());
vector<vector<int>> res;
for (int i = 0; i < nums.size(); i++) {
if (i && nums[i] == nums[i - 1]) continue;
for (int j = i + 1; j < nums.size(); j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
// 双指针算法
for (int k = j + 1, u = nums.size() - 1; k < u; k++) {
if (k > j + 1 && nums[k] == nums[k - 1]) continue;
while (k < u - 1 && nums[i] + nums[j] + nums[k] + nums[u - 1] >= target) u--;
if (nums[i] + nums[j] + nums[k] + nums[u] == target) {
res.push_back({nums[i], nums[j], nums[k], nums[u]});
}
}
}
}
return res;
}
};
- Java
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
for (int j = i + 1; j < nums.length; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
for (int k = j + 1, u = nums.length - 1; k < u; k++) {
if (k > j + 1 && nums[k] == nums[k - 1]) continue;
while (k < u - 1 && nums[i] + nums[j] + nums[k] + nums[u - 1] >= target) u--;
if (nums[i] + nums[j] + nums[k] + nums[u] == target) {
res.add(get(nums[i], nums[j], nums[k], nums[u]));
}
}
}
}
return res;
}
private List<Integer> get(int a, int b, int c, int d) {
List<Integer> res = new ArrayList<>();
res.add(a); res.add(b); res.add(c); res.add(d);
return res;
}
}
- Python
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
res = []
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i - 1]:
continue
for j in range(i + 1, len(nums)):
if j > i + 1 and nums[j] == nums[j - 1]:
continue
k = j + 1; u = len(nums) - 1
while k < u:
if k > j + 1 and nums[k] == nums[k - 1]:
k += 1
continue
while k < u - 1 and nums[i] + nums[j] + nums[k] + nums[u - 1] >= target:
u -= 1
if nums[i] + nums[j] + nums[k] + nums[u] == target:
res.append([nums[i], nums[j], nums[k], nums[u]])
k += 1
return res
时空复杂度分析
-
时间复杂度: O ( n 3 ) O(n^3) O(n3)。两重循环+双指针。
-
空间复杂度: O ( 1 ) O(1) O(1)。