我的LeetCode代码仓:https://github.com/617076674/LeetCode
原题链接:https://leetcode-cn.com/problems/4sum/description/
题目描述:
知识点:哈希表、对撞双指针
思路一:暴力解法
先排序,再四重循环遍历求解。循环次数实在是有点多,我都不知道怎么给循环变量命名了,好在LeetCode中并没有超时,还是获得了通过。
时间复杂度为O(n ^ 4),其中n为nums数组的长度。空间复杂度为O(1)。
JAVA代码:
public class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> listList = new ArrayList<>();
int n = nums.length;
Arrays.sort(nums);
for(int i = 0; i < n; i++) {
if(i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for(int j = i + 1; j < n; j++) {
if(j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
for(int k = j + 1; k < n; k++) {
if(k > j + 1 && nums[k] == nums[k - 1]) {
continue;
}
for(int m = k + 1; m < n; m++) {
if(m > k + 1 && nums[m] == nums[m - 1]) {
continue;
}
if(nums[i] + nums[j] + nums[k] + nums[m] == target) {
List<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[k]);
list.add(nums[m]);
listList.add(list);
}
}
}
}
}
return listList;
}
}
LeetCode解题报告:
思路二:用哈希表存储数组中的每一个元素及其出现的次数
我们用一个哈希表来存储数组中出现的所有元素及其出现的次数。
我们总共分五种情况:
(1)四个数都相同。
(2)三个数相同。
(3)两个数相同,另外两个数也相同。
(4)两个数相同,另外两个数不相同。
(5)四个数都不相同
JAVA代码:
public class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> listList = new ArrayList<>();
int n = nums.length;
HashMap<Integer, Integer> hashMap = new HashMap<>();
for(int i = 0; i < n; i++) {
if(hashMap.containsKey(nums[i])) {
hashMap.put(nums[i], hashMap.get(nums[i]) + 1);
}else {
hashMap.put(nums[i], 1);
}
}
//有4个相同的数的情况
if(target % 4 == 0 && hashMap.containsKey(target / 4) && hashMap.get(target / 4) >= 4) {
addToListList(target / 4, target / 4, target / 4, target / 4, listList);
}
ArrayList<Integer> arrayList = new ArrayList<>();
for(Integer integer : hashMap.keySet()) {
arrayList.add(integer);
}
Collections.sort(arrayList);
//有3个相同的数的情况
for(int i = 0; i < arrayList.size(); i++) {
for(int j = i + 1; j < arrayList.size(); j++) {
if(arrayList.get(i) * 3 + arrayList.get(j) == target && hashMap.get(arrayList.get(i)) >= 3) {
addToListList(arrayList.get(i), arrayList.get(i), arrayList.get(i), arrayList.get(j), listList);
}
if(arrayList.get(i) + arrayList.get(j) * 3 == target && hashMap.get(arrayList.get(j)) >= 3) {
addToListList(arrayList.get(i), arrayList.get(j), arrayList.get(j), arrayList.get(j), listList);
}
}
}
//2个数两两相同的情况
for(int i = 0; i < arrayList.size(); i++) {
for(int j = i + 1; j < arrayList.size(); j++) {
if(arrayList.get(i) * 2 + arrayList.get(j) * 2 == target && hashMap.get(arrayList.get(i)) >= 2 && hashMap.get(arrayList.get(j)) >= 2) {
addToListList(arrayList.get(i), arrayList.get(i), arrayList.get(j), arrayList.get(j), listList);
}
}
}
for(int i = 0; i < arrayList.size(); i++) {
for(int j = i + 1; j < arrayList.size(); j++) {
for(int k = j + 1; k < arrayList.size(); k++) {
//有2个数相同,另外两个数不相同的情况
if(arrayList.get(i) * 2 + arrayList.get(j) + arrayList.get(k) == target && hashMap.get(arrayList.get(i)) >= 2) {
addToListList(arrayList.get(i), arrayList.get(i), arrayList.get(j), arrayList.get(k), listList);
}
if(arrayList.get(i) + arrayList.get(j) * 2 + arrayList.get(k) == target && hashMap.get(arrayList.get(j)) >= 2) {
addToListList(arrayList.get(i), arrayList.get(j), arrayList.get(j), arrayList.get(k), listList);
}
if(arrayList.get(i) + arrayList.get(j) + arrayList.get(k) * 2 == target && hashMap.get(arrayList.get(k)) >= 2) {
addToListList(arrayList.get(i), arrayList.get(j), arrayList.get(k), arrayList.get(k), listList);
}
//没有相同数的情况
int num = target - arrayList.get(i) - arrayList.get(j) - arrayList.get(k);
if(num > arrayList.get(k) && hashMap.containsKey(num)) {
addToListList(arrayList.get(i), arrayList.get(j), arrayList.get(k), num, listList);
}
}
}
}
return listList;
}
private void addToListList(int num1, int num2, int num3, int num4, List<List<Integer>> listList) {
List<Integer> list = new ArrayList<>();
list.add(num1);
list.add(num2);
list.add(num3);
list.add(num4);
listList.add(list);
}
}
LeetCode解题报告:
思路三:将思路一中最内层的双层循环用对撞双指针来实现
本题其实和LeetCode015——三数之和是一模一样的,只不过外层又多了一层循环。
由于我们把最内层的双重循环用对撞双指针来实现,因此时间复杂度优化到了O(n ^ 3),其中n为nums数组的长度。空间复杂度为O(1)。
JAVA代码:
public class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> listList = new ArrayList<>();
int n = nums.length;
Arrays.sort(nums);
for(int i = 0; i < n - 3; i++) {
if(i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for(int j = i + 1; j < n - 2; j++) {
if(j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
int left = j + 1;
int right = n - 1;
while(left < right) {
if(nums[i] + nums[j] + nums[left] + nums[right] == target) {
List<Integer> list = new ArrayList<>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[left]);
list.add(nums[right]);
listList.add(list);
left++;
right--;
while(left < right && nums[left] == nums[left - 1]) {
left++;
}
while(left < right && nums[right] == nums[right + 1]) {
right--;
}
}else if(nums[i] + nums[j] + nums[left] + nums[right] < target) {
left++;
while(left < right && nums[left] == nums[left - 1]) {
left++;
}
}else {
right--;
while(left < right && nums[right] == nums[right + 1]) {
right--;
}
}
}
}
}
return listList;
}
}
LeetCode解题报告:
思路四:两两成对考虑,并有Set集合来过滤除去重复元素
用一个哈希表存储数组中两个数的和,以及形成这个和可能的索引组合的List。虽然我们的Set集合能够自动帮我们过滤掉重复的List,但是过滤的前提是我们得到的List必须是有序的。比如(0, 1, -1, 0)和(0, 0, -1, 1)这一组数据,对于Set集合来说是不重复的,而(-1, 0, 0, 1)和(-1, 0, 0, 1)这组数据,对于Set集合来说才是重复的。
在形成哈希表的时候,我们能够保证第一个索引小于第二个索引,但是当我们在哈希表中寻找target - key的时候,如何保证两个索引数组形成的List是有序的呢?我们只需要让第一个数组的较大索引小于第二个数组的较小索引即可。
这个思路的时间复杂度分析挺复杂的。首先,排序过程的时间复杂度一定是O(nlogn),其中n为nums数组的长度。而形成哈希表的时间复杂度是O(n ^ 2)级别的。而遍历哈希表形成结果的过程的时间复杂度不好算,这和每两个数的和对应的List的大小有关。而空间复杂度还是很明了的,我们存储了一个哈希表,而哈希表的键存的是两个数的和,这两个数的组合可能产生的和是O(n ^ 2)级别的,因此空间复杂度是O(n ^ 2)级别的。
JAVA代码:
public class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Set<List<Integer>> listSet = new HashSet<>();
int n = nums.length;
Arrays.sort(nums);
HashMap<Integer, List<Integer[]>> hashMap = new HashMap<>();
for(int i = 0; i < n; i++) {
for(int j = i + 1; j < n; j++) {
int num = nums[i] + nums[j];
Integer[] pair = {i, j};
if(hashMap.containsKey(num)) {
hashMap.get(num).add(pair);
}else {
List<Integer[]> list = new ArrayList<>();
list.add(pair);
hashMap.put(num, list);
}
}
}
for(Integer integer : hashMap.keySet()) {
if(hashMap.containsKey(target - integer)) {
List<Integer[]> list1 = hashMap.get(integer);
List<Integer[]> list2 = hashMap.get(target - integer);
for(Integer[] pair1 : list1) {
int index1 = pair1[0];
int index2 = pair1[1];
for(Integer[] pair2 : list2) {
int index3 = pair2[0];
int index4 = pair2[1];
if(index2 < index3) {
List<Integer> list = new ArrayList<>();
list.add(nums[index1]);
list.add(nums[index2]);
list.add(nums[index3]);
list.add(nums[index4]);
listSet.add(list);
}
}
}
}
}
return new ArrayList<>(listSet);
}
}
LeetCode解题报告: