哈希映射
- 1. Two Sum
- 167. Two Sum II - Input Array Is Sorted
- 454. 4Sum II
- 15. 3Sum
- 18. 4Sum
- 49. Group Anagrams
1. Two Sum
Given an array of integers nums
and an integer target
, return indices of the two numbers such that they add up to target
.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
You can return the answer in any order.
Example 1:
Input: nums = [2,7,11,15], target = 9
Output: [0,1]
Explanation: Because nums[0] + nums[1] == 9, we return [0, 1]
.
Example 2:
Input: nums = [3,2,4], target = 6
Output: [1,2]
Example 3:
Input: nums = [3,3], target = 6
Output: [0,1]
Constraints:
2 <= nums.length <= 10^4
-10^9 <= nums[i] <= 10^9
-10^9 <= target <= 10^9
- Only one valid answer exists.
Follow-up: Can you come up with an algorithm that is less than O(n2)
time complexity?
思路
使用HashMap
本题其实有四个重点:
- 为什么会想到用哈希表
- 哈希表为什么用map
- 本题map是用来存什么的
- map中的key和value用来存什么的
把这四点想清楚了,本题才算是理解透彻了。
C++解法
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
std::unordered_map <int,int> map;
for(int i = 0; i < nums.size(); i++) {
// 遍历当前元素,并在map中寻找是否有匹配的key
auto iter = map.find(target - nums[i]);
if(iter != map.end()) {
return {iter->second, i};
}
// 如果没找到匹配对,就把访问过的元素和下标加入到map中
map.insert(pair<int, int>(nums[i], i));
}
return {};
}
};
- 时间复杂度: O(n)
- 空间复杂度: O(n)
Java解法
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> numMap = new HashMap<>();
int length = nums.length;
for(int i = 0; i < length; i++){
int complement = target - nums[i];
if(numMap.containsKey(complement)){
return new int[]{i, numMap.get(complement)};
}
numMap.put(nums[i], i);
}
return null;
}
}
167. Two Sum II - Input Array Is Sorted
Given a 1-indexed array of integers numbers
that is already sorted in non-decreasing order, find two numbers such that they add up to a specific target
number. Let these two numbers be numbers[index1]
and numbers[index2]
where 1 <= index1 < index2 <= numbers.length
.
Return the indices of the two numbers, index1
and index2
, added by one as an integer array [index1, index2]
of length 2.
The tests are generated such that there is exactly one solution. You may not use the same element twice.
Your solution must use only constant extra space.
Example 1:
Input: numbers = [2,7,11,15], target = 9
Output: [1,2]
Explanation: The sum of 2 and 7 is 9. Therefore, index1 = 1, index2 = 2. We return [1, 2].
Example 2:
Input: numbers = [2,3,4], target = 6
Output: [1,3]
Explanation: The sum of 2 and 4 is 6. Therefore index1 = 1, index2 = 3. We return [1, 3].
Example 3:
Input: numbers = [-1,0], target = -1
Output: [1,2]
Explanation: The sum of -1 and 0 is -1. Therefore index1 = 1, index2 = 2. We return [1, 2].
Constraints:
2 <= numbers.length <= 3 * 10^4
-1000 <= numbers[i] <= 1000
numbers
is sorted in non-decreasing order.-1000 <= target <= 1000
- The tests are generated such that there is exactly one solution.
思路
方法一:在2Sum基础上修改索引即可,效率低下
方法二:双指针,左右指针,如果使用二分搜索确保numbers[right]<target
程序效率会更高。
C++解法
方法一:
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
unordered_map<int, int> map;
for(int i = 0; i < numbers.size(); i++){
int temp = target - numbers[i];
auto iter = map.find(temp);
if(iter != map.end()){
return {iter->second, i + 1};
}
map.insert(pair<int, int>(numbers[i], i + 1));
}
return {};
}
};
Java解法
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while(left <= right){
if(numbers[left] + numbers[right] > target){
right--;
}else if(numbers[left] + numbers[right] < target){
left++;
}else{
return new int[]{left + 1, right + 1};
}
}
return null;
}
}
454. 4Sum II
Given four integer arrays nums1
, nums2
, nums3
, and nums4
all of length n
, return the number of tuples (i, j, k, l)
such that:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
Example 1:
Input: nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
Output: 2
Explanation:
The two tuples are:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
Example 2:
Input: nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
Output: 1
Constraints:
n == nums1.length
n == nums2.length
n == nums3.length
n == nums4.length
1 <= n <= 200
-228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228
思路
把四个数组变成两个数组,然后仿照2Sum问题找(a+b)和0-(c+d)。分步相乘。
本题不考虑去重的问题。
本题其实有四个重点:
- 为什么会想到用哈希表?
- 哈希表为什么用map?
- 本题map是用来存什么的?
- map中的key和value用来存什么的?
把这四点想清楚了,本题才算是理解透彻了。
map用来存前两个数组元素之和及频率
C++解法
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> map;
int count = 0;
for(int a : nums1){
for(int b : nums2){
map[a + b]++;
}
}
for(int c : nums3){
for(int d : nums4){
auto iter = map.find(0 - c - d);
if(iter != map.end()){
count += iter->second;
}
}
}
return count;
}
};
Java解法
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Map<Integer, Integer> map = new HashMap<>();
int count = 0;
// 将 nums1 和 nums2 的所有和放入 map 中
for (int a : nums1) {
for (int b : nums2) {
map.put(a + b, map.getOrDefault(a + b, 0) + 1);
}
}
// 遍历 nums3 和 nums4,查找使得总和为 0 的组合
for (int c : nums3) {
for (int d : nums4) {
// 查找相应的负数和
count += map.getOrDefault(-(c + d), 0);
}
}
return count;
}
}
15. 3Sum
Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]]
such that i != j
, i != k
, and j != k
, and nums[i] + nums[j] + nums[k] == 0
.
Notice that the solution set must not contain duplicate triplets.
Example 1:
Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]
Explanation:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0.
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0.
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0.
The distinct triplets are [-1,0,1] and [-1,-1,2].
Notice that the order of the output and the order of the triplets does not matter.
Example 2:
Input: nums = [0,1,1]
Output: []
Explanation: The only possible triplet does not sum up to 0.
Example 3:
Input: nums = [0,0,0]
Output: [[0,0,0]]
Explanation: The only possible triplet sums up to 0.
Constraints:
3 <= nums.length <= 3000
-10^5 <= nums[i] <= 10^5
思路
因为结果要去重,这里不方便使用哈希法。
收获一个结果后再进行去重操作。
哈希解法
两层for循环就可以确定 两个数值,可以使用哈希法来确定 第三个数 0-(a+b) 或者 0 - (a + c) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。
把符合条件的三元组放进vector中,然后再去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。
去重的过程不好处理,有很多小细节,如果在面试中很难想到位。
时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。
大家可以尝试使用哈希法写一写,就知道其困难的程度了。
双指针
其实这道题目使用哈希法并不十分合适,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码。
而且使用哈希法在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2),也是可以在leetcode
上通过,但是程序的执行时间依然比较长 。
接下来我来介绍另一个解法:双指针法,这道题目使用双指针法 要比哈希法高效一些,那么来讲解一下具体实现的思路。
动画效果如下:
拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]
。
接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0
就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0
说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
时间复杂度:O(n^2)。
去重逻辑的思考
a的去重
说到去重,其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right]
a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。
但这里有一个问题,是判断 nums[i]
与 nums[i + 1]
是否相同,还是判断 nums[i]
与 nums[i-1]
是否相同。
有同学可能想,这不都一样吗?
其实不一样!
都是和nums[i]
进行比较,是比较它的前一个,还是比较它的后一个。
如果我们的写法是 这样:
if (nums[i] == nums[i + 1]) { // 去重操作
continue;
}
那我们就把三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。
我们要做的是不能有重复的三元组,但三元组内的元素是可以重复的!
所以这里是有两个重复的维度。
那么应该这么写:
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
这么写就是当前使用nums[i]
,我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到结果集里。
这是一个非常细节的思考过程。
b与c的去重
很多同学写本题的时候,去重的逻辑多加了对right 和left 的去重:(代码中注释部分)
while (right > left) {
if (nums[i] + nums[left] + nums[right] > 0) {
right--;
// 去重 right
while (left < right && nums[right] == nums[right + 1]) right--;
} else if (nums[i] + nums[left] + nums[right] < 0) {
left++;
// 去重 left
while (left < right && nums[left] == nums[left - 1]) left++;
} else {
}
}
但细想一下,这种去重其实对提升程序运行效率是没有帮助的。
拿right去重为例,即使不加这个去重逻辑,依然根据 while (right > left)
和 if (nums[i] + nums[left] + nums[right] > 0)
去完成right-- 的操作。
多加了 while (left < right && nums[right] == nums[right + 1]) right--;
这一行代码,其实就是把 需要执行的逻辑提前执行了,但并没有减少 判断的逻辑。
最直白的思考过程,就是right还是一个数一个数的减下去的,所以在哪里减的都是一样的。
所以这种去重是可以不加的。 仅仅是把去重的逻辑提前了而已。
既然三数之和可以使用双指针法,我们之前讲过的1.两数之和,可不可以使用双指针法呢?
如果不能,题意如何更改就可以使用双指针法呢?
两数之和 就不能使用双指针法,因为1.两数之和要求返回的是索引下标, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。
如果1.两数之和要求返回的是数值的话,就可以使用双指针法了。
C++解法
哈希法C++代码:
class Solution {
public:
// 在一个数组中找到3个数形成的三元组,它们的和为0,不能重复使用(三数下标互不相同),且三元组不能重复。
// b(存储)== 0-(a+c)(检索)
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); i++) {
// 如果a是正数,a<b<c,不可能形成和为0的三元组
if (nums[i] > 0)
break;
// [a, a, ...] 如果本轮a和上轮a相同,那么找到的b,c也是相同的,所以去重a
if (i > 0 && nums[i] == nums[i - 1])
continue;
// 这个set的作用是存储b
unordered_set<int> set;
for (int k = i + 1; k < nums.size(); k++) {
// 去重b=c时的b和c
if (k > i + 2 && nums[k] == nums[k - 1] && nums[k - 1] == nums[k - 2])
continue;
// a+b+c=0 <=> b=0-(a+c)
int target = 0 - (nums[i] + nums[k]);
if (set.find(target) != set.end()) {
result.push_back({nums[i], target, nums[k]}); // nums[k]成为c
set.erase(target);
}
else {
set.insert(nums[k]); // nums[k]成为b
}
}
}
return result;
}
};
- 时间复杂度: O(n^2)
- 空间复杂度: O(n),额外的 set 开销
双指针法C++代码如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
// 错误去重a方法,将会漏掉-1,-1,2 这种情况
/*
if (nums[i] == nums[i + 1]) {
continue;
}
*/
// 正确去重a方法
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
// 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
/*
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
*/
if (nums[i] + nums[left] + nums[right] > 0) right--;
else if (nums[i] + nums[left] + nums[right] < 0) left++;
else {
result.push_back(vector<int>{nums[i], nums[left], nums[right]});
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
return result;
}
};
- 时间复杂度: O(n^2)
- 空间复杂度: O(1)
Java解法
(版本一) 双指针
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.length; i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
return result;
}
}
(版本二) 使用哈希集合
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// 如果第一个元素大于零,不可能凑成三元组
if (nums[i] > 0) {
return result;
}
// 三元组元素a去重
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
HashSet<Integer> set = new HashSet<>();
for (int j = i + 1; j < nums.length; j++) {
// 三元组元素b去重
if (j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2]) {
continue;
}
int c = -nums[i] - nums[j];
if (set.contains(c)) {
result.add(Arrays.asList(nums[i], nums[j], c));
set.remove(c); // 三元组元素c去重
} else {
set.add(nums[j]);
}
}
}
return result;
}
}
Python解法
(版本一) 双指针
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
for i in range(len(nums)):
# 如果第一个元素已经大于0,不需要进一步检查
if nums[i] > 0:
return result
# 跳过相同的元素以避免重复
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = len(nums) - 1
while right > left:
sum_ = nums[i] + nums[left] + nums[right]
if sum_ < 0:
left += 1
elif sum_ > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
# 跳过相同的元素以避免重复
while right > left and nums[right] == nums[right - 1]:
right -= 1
while right > left and nums[left] == nums[left + 1]:
left += 1
right -= 1
left += 1
return result
(版本二) 使用字典
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
# 找出a + b + c = 0
# a = nums[i], b = nums[j], c = -(a + b)
for i in range(len(nums)):
# 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if nums[i] > 0:
break
if i > 0 and nums[i] == nums[i - 1]: #三元组元素a去重
continue
d = {}
for j in range(i + 1, len(nums)):
if j > i + 2 and nums[j] == nums[j-1] == nums[j-2]: # 三元组元素b去重
continue
c = 0 - (nums[i] + nums[j])
if c in d:
result.append([nums[i], nums[j], c])
d.pop(c) # 三元组元素c去重
else:
d[nums[j]] = j
return result
Go解法
(版本一) 双指针
func threeSum(nums []int) [][]int {
sort.Ints(nums)
res := [][]int{}
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for i := 0; i < len(nums)-2; i++ {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
n1 := nums[i]
if n1 > 0 {
break
}
// 去重a
if i > 0 && n1 == nums[i-1] {
continue
}
l, r := i+1, len(nums)-1
for l < r {
n2, n3 := nums[l], nums[r]
if n1+n2+n3 == 0 {
res = append(res, []int{n1, n2, n3})
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
for l < r && nums[l] == n2 {
l++
}
for l < r && nums[r] == n3 {
r--
}
} else if n1+n2+n3 < 0 {
l++
} else {
r--
}
}
}
return res
}
(版本二) 哈希解法
func threeSum(nums []int) [][]int {
res := make([][]int, 0)
sort.Ints(nums)
// 找出a + b + c = 0
// a = nums[i], b = nums[j], c = -(a + b)
for i := 0; i < len(nums); i++ {
// 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if nums[i] > 0 {
break
}
// 三元组元素a去重
if i > 0 && nums[i] == nums[i-1] {
continue
}
set := make(map[int]struct{})
for j := i + 1; j < len(nums); j++ {
// 三元组元素b去重
if j > i + 2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2] {
continue
}
c := -nums[i] - nums[j]
if _, ok := set[c]; ok {
res = append(res, []int{nums[i], nums[j], c})
// 三元组元素c去重
delete(set, c)
} else {
set[nums[j]] = struct{}{}
}
}
}
return res
}
Rust解法
#![allow(unused)] fn main() { // 哈希解法 use std::collections::HashSet; impl Solution { pub fn three_sum(nums: Vec<i32>) -> Vec<Vec<i32>> { let mut result: Vec<Vec<i32>> = Vec::new(); let mut nums = nums; nums.sort(); let len = nums.len(); for i in 0..len { if nums[i] > 0 { break; } if i > 0 && nums[i] == nums[i - 1] { continue; } let mut set = HashSet::new(); for j in (i + 1)..len { if j > i + 2 && nums[j] == nums[j - 1] && nums[j] == nums[j - 2] { continue; } let c = 0 - (nums[i] + nums[j]); if set.contains(&c) { result.push(vec![nums[i], nums[j], c]); set.remove(&c); } else { set.insert(nums[j]); } } } result } } }
#![allow(unused)] fn main() { // 双指针法 use std::cmp::Ordering; impl Solution { pub fn three_sum(nums: Vec<i32>) -> Vec<Vec<i32>> { let mut result: Vec<Vec<i32>> = Vec::new(); let mut nums = nums; nums.sort(); let len = nums.len(); for i in 0..len { if nums[i] > 0 { return result; } if i > 0 && nums[i] == nums[i - 1] { continue; } let (mut left, mut right) = (i + 1, len - 1); while left < right { match (nums[i] + nums[left] + nums[right]).cmp(&0){ Ordering::Equal =>{ result.push(vec![nums[i], nums[left], nums[right]]); left +=1; right -=1; while left < right && nums[left] == nums[left - 1]{ left += 1; } while left < right && nums[right] == nums[right+1]{ right -= 1; } } Ordering::Greater => right -= 1, Ordering::Less => left += 1, } } } result } } }
18. 4Sum
Given an array nums
of n
integers, return an array of all the unique quadruplets [nums[a], nums[b], nums[c], nums[d]]
such that:
0 <= a, b, c, d < n
a
,b
,c
, andd
are distinct.nums[a] + nums[b] + nums[c] + nums[d] == target
You may return the answer in any order.
Example 1:
Input: nums = [1,0,-1,0,-2,2], target = 0
Output: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
Example 2:
Input: nums = [2,2,2,2,2], target = 8
Output: [[2,2,2,2]]
Constraints:
1 <= nums.length <= 200
-10^9 <= nums[i] <= 10^9
-10^9 <= target <= 10^9
思路
四数之和,和15.三数之和是一个思路,都是使用双指针法, 基本解法就是在15.三数之和的基础上再套一层for循环。
但是有一些细节需要注意,例如: 不要判断nums[k] > target
就返回了,三数之和 可以通过 nums[i] > 0
就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1]
,target
是-10
,不能因为-4 > -10
而跳过。但是我们依旧可以去做剪枝,逻辑变成nums[i] > target && (nums[i] >=0 || target >= 0)
就可以了。
15.三数之和的双指针解法是一层for循环num[i]
为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0
。
四数之和的双指针解法是两层for循环nums[k] + nums[i]
为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target
的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。
那么一样的道理,五数之和、六数之和等等都采用这种解法。
对于15.三数之和双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。
之前我们讲过哈希表的经典题目:454.四数相加II,相对于本题简单很多,因为本题是要求在一个集合中找出四个数相加等于target,同时四元组不能重复。
而454.四数相加II是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0
就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于本题还是简单了不少!
C++解法
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
for (int k = 0; k < nums.size(); k++) {
// 剪枝处理
if (nums[k] > target && nums[k] >= 0) {
break; // 这里使用break,统一通过最后的return返回
}
// 对nums[k]去重
if (k > 0 && nums[k] == nums[k - 1]) {
continue;
}
for (int i = k + 1; i < nums.size(); i++) {
// 2级剪枝处理
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
break;
}
// 对nums[i]去重
if (i > k + 1 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
// nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出
if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) {
right--;
// nums[k] + nums[i] + nums[left] + nums[right] < target 会溢出
} else if ((long) nums[k] + nums[i] + nums[left] + nums[right] < target) {
left++;
} else {
result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
// 对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
}
return result;
}
};
- 时间复杂度: O(n^3)
- 空间复杂度: O(1)
二级剪枝的部分:
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
break;
}
可以优化为:
if (nums[k] + nums[i] > target && nums[i] >= 0) {
break;
}
因为只要 nums[k] + nums[i] > target
,那么 nums[i]
后面的数都是正数的话,就一定不符合条件了。
不过这种剪枝其实有点小绕,大家能够理解 文章给的完整代码的剪枝就够了。
Java解法
import java.util.*;
public class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums); // 排序数组
List<List<Integer>> result = new ArrayList<>(); // 结果集
for (int k = 0; k < nums.length; k++) {
// 剪枝处理
if (nums[k] > target && nums[k] >= 0) {
break;
}
// 对nums[k]去重
if (k > 0 && nums[k] == nums[k - 1]) {
continue;
}
for (int i = k + 1; i < nums.length; i++) {
// 第二级剪枝
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
break;
}
// 对nums[i]去重
if (i > k + 1 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
long sum = (long) nums[k] + nums[i] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
result.add(Arrays.asList(nums[k], nums[i], nums[left], nums[right]));
// 对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
}
return result;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {1, 0, -1, 0, -2, 2};
int target = 0;
List<List<Integer>> results = solution.fourSum(nums, target);
for (List<Integer> result : results) {
System.out.println(result);
}
}
}
Python解法
(版本一) 双指针
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
n = len(nums)
result = []
for i in range(n):
if nums[i] > target and nums[i] > 0 and target > 0:# 剪枝(可省)
break
if i > 0 and nums[i] == nums[i-1]:# 去重
continue
for j in range(i+1, n):
if nums[i] + nums[j] > target and target > 0: #剪枝(可省)
break
if j > i+1 and nums[j] == nums[j-1]: # 去重
continue
left, right = j+1, n-1
while left < right:
s = nums[i] + nums[j] + nums[left] + nums[right]
if s == target:
result.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
left += 1
right -= 1
elif s < target:
left += 1
else:
right -= 1
return result
(版本二) 使用字典
class Solution(object):
def fourSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[List[int]]
"""
# 创建一个字典来存储输入列表中每个数字的频率
freq = {}
for num in nums:
freq[num] = freq.get(num, 0) + 1
# 创建一个集合来存储最终答案,并遍历4个数字的所有唯一组合
ans = set()
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
for k in range(j + 1, len(nums)):
val = target - (nums[i] + nums[j] + nums[k])
if val in freq:
# 确保没有重复
count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val)
if freq[val] > count:
ans.add(tuple(sorted([nums[i], nums[j], nums[k], val])))
return [list(x) for x in ans]
Go解法
func fourSum(nums []int, target int) [][]int {
if len(nums) < 4 {
return nil
}
sort.Ints(nums)
var res [][]int
for i := 0; i < len(nums)-3; i++ {
n1 := nums[i]
// if n1 > target { // 不能这样写,因为可能是负数
// break
// }
if i > 0 && n1 == nums[i-1] { // 对nums[i]去重
continue
}
for j := i + 1; j < len(nums)-2; j++ {
n2 := nums[j]
if j > i+1 && n2 == nums[j-1] { // 对nums[j]去重
continue
}
l := j + 1
r := len(nums) - 1
for l < r {
n3 := nums[l]
n4 := nums[r]
sum := n1 + n2 + n3 + n4
if sum < target {
l++
} else if sum > target {
r--
} else {
res = append(res, []int{n1, n2, n3, n4})
for l < r && n3 == nums[l+1] { // 去重
l++
}
for l < r && n4 == nums[r-1] { // 去重
r--
}
// 找到答案时,双指针同时靠近
r--
l++
}
}
}
}
return res
}
49. Group Anagrams
Given an array of strings strs
, group the anagrams together. You can return the answer in any order.
Example 1:
Input: strs = ["eat","tea","tan","ate","nat","bat"]
Output: [["bat"],["nat","tan"],["ate","eat","tea"]]
Explanation:
- There is no string in strs that can be rearranged to form
"bat"
. - The strings
"nat"
and"tan"
are anagrams as they can be rearranged to form each other. - The strings
"ate"
,"eat"
, and"tea"
are anagrams as they can be rearranged to form each other.
Example 2:
Input: strs = [""]
Output: [[""]]
Example 3:
Input: strs = ["a"]
Output: [["a"]]
Constraints:
1 <= strs.length <= 10^4
0 <= strs[i].length <= 100
strs[i]
consists of lowercase English letters.
思路
- 头文件引入:引入必要的标准库,如
<iostream>
,<vector>
,<unordered_map>
, 和<algorithm>
。 - 类定义:定义一个名为
Solution
的类,其中包含groupAnagrams
方法,接受一个字符串向量并返回一个字符串向量的向量。 - unordered_map:使用
std::unordered_map
来代替 Java 的HashMap
。 - 字符串排序:利用
std::sort
对字符串进行排序以创建一个唯一的键。 - 结果存储:通过遍历哈希表,将分组的单词存储到结果向量中。
C++解法
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
class Solution {
public:
std::vector<std::vector<std::string>> groupAnagrams(std::vector<std::string>& strs) {
std::unordered_map<std::string, std::vector<std::string>> map;
for (const auto& str : strs) {
std::string key = str; // Copy the string to a key
// Sort the string to create a key
std::sort(key.begin(), key.end());
// Add the original string to the map using the sorted key
map[key].push_back(str);
}
// Prepare the result using a vector of vectors
std::vector<std::vector<std::string>> result;
for (const auto& pair : map) {
result.push_back(pair.second);
}
return result;
}
};
Java解法
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<String, List<String>>();
for (String str : strs) {
char[] array = str.toCharArray();
Arrays.sort(array);
String key = new String(array);
List<String> list = map.getOrDefault(key, new ArrayList<String>());
list.add(str);
map.put(key, list);
}
return new ArrayList<List<String>>(map.values());
}
}