⭐️引言⭐️
大家好,我是执梗。今天为大家讲解的是力扣经典的几数之和系列。有句话说得好——有人夜里看海,有人力扣第一题都做不出来。大家应该都知道力扣的第一题是两数之和。但其实两数之和只是一个起始,在进阶的还有三数之和,四数之和以及四数相加等等。今天我为大家将这一系列从易到难为大家整合好,其中详细解析各种方法,帮助大家总结其中规律,以达到举一反三的效果。建议大家收藏,方便训练。
同时在这为兄弟们推荐我的链表专题,帮助大家手撕链表,系统训练——链表专题
????欢迎关注????点赞????收藏
❤️ :热爱Java与算法学习,期待一起交流!
????作者水平很有限,如果发现错误,求告知,多谢!
⭐️导航助手⭐️
????1.刷题与观看须知
????2.逐步训练,鏖战力扣
???? 1.两数之和
????2.两数之和||——输入有序数组
????3.两数之和IV——输入BST
????4.三数之和
????5.四数之和
????6.四数相加ll
????3.刷题总结
????1.刷题与观看须知
几数几何系列是力扣最经典的系列套题之一,它的每道题解法都非常多样化。用到了各种各样的算法知识,比如暴力枚举、哈希、排序+双指针等等。但是,虽然很多人能做出来,但是却只会一种朴素的解法,这就感到沾沾自喜了。这类题一定要会做到举一反三触类旁通,希望大家每道题都能多样化解题,同时总结出这类题的规律。
????2.逐步训练,鏖战力扣
???? 1.两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
题目链接:两数之和
作为力扣的第一题,它是很多人成神的起点,这道题也有着自己的美丽。
题目分析:第一个想到肯定是暴力遍历,其次是利用哈希表。哈希表的解法也有不同类型,这里我们用的是Hashmap,用key存放值,value存放下标,在遍历数组nums[i]的同时寻找哈希表内是否有符合的数,有则返回两个下标,无则将nums[i]放入哈希表中。
1.暴力遍历
时间复杂度O(N^2):使用两层for循环,最坏的情况每两个数都要被遍历几次
空间复杂度O(1):常数级的数组空间
class Solution {
public int[] twoSum(int[] nums, int target) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return new int[0];
}
}
2.利用HashMap
时间复杂度O(N) :只遍历了一次数组
空间复杂度O(N):主要为哈希表的开销
class Solution {
public int[] twoSum(int[] nums, int target) {
int n=nums.length;
//key存放值,value存放下标
Map<Integer,Integer> map=new HashMap<>();
for(int i=0;i<n;i++){
//找到了
if(map.containsKey(target-nums[i])){
//返回下标
return new int[]{map.get(target-nums[i]),i};
}
//没找到放入map中
map.put(nums[i],i);
}
//遍历结束仍然未找到
return new int[0];
}
}
????2.两数之和||——输入有序数组
给定一个已按照 非递减顺序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
题目链接:两数之和||——输入有序数组
思路分析:这道题可以同上一道一样使用暴力求解以及哈希表,但那是针对与无序数组的。因为是一个排好序的数组,我们自然要想到双指针,从两头开始遍历。当两指针相加的元素大于target,右指针向左移动,当小于target时,让左指针右移。
时间复杂度:O(n),其中n是数组的长度,两个指针移动最多一共n次
空间复杂度:O(1)
class Solution {
public int[] twoSum(int[] numbers, int target) {
int n=numbers.length;
int left=0;
int right=n-1;
while(left<right){
int count=numbers[left]+numbers[right];
//找到了,但是下标要加1,因为题目要求
if(count==target){
return new int[]{left+1,right+1};
//加起来太大了,right--,这样count会变小
}else if(count>target){
right--;
//加起来太小了,left++,这样count会变大
}else{
left++;
}
}
return new int[0];
}
}
????3.两数之和IV——输入BST
给定一个二叉搜索树 root
和一个目标结果 k
,如果树中存在两个元素且它们的和等于给定的目标结果,则返回 true
。
题目链接:两数之和 IV - 输入 BST
题目分析:这道题与前面的两数之和类似,只不过是元素存放的形式变成了树,我们同样可以利用Hashset来遍历树。
方法:Hashset加遍历树。判断当前root时,set中是否存在k-root.val,如果存在直接返回true。不存在的话将此时的root.val存放进set中,同时去左子树和右子树中继续查找。
时间复杂度O(n),n为节点的数量,最坏的情况下,整棵树被遍历一次
空间复杂度O(n),最坏的情况下,set存储n个节点的值。
class Solution {
public boolean findTarget(TreeNode root, int k) {
Set<Integer> set=new HashSet<>();
return find(root,k,set);
}
public boolean find(TreeNode root,int k,Set set){
//如果root为空直接返回false
if(root==null) return false;
if(set.contains(k-root.val)){
return true;
}
//这个没找到就加入到set集合中
set.add(root.val);
//同时去左子树和右子树查找
return find(root.left,k,set)||find(root.right,k,set);
}
}
????4.三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
题目链接:三数之和
题目分析:这道题和两数之和看上去类似,但做法却不相同,难度也不在一个档次,朴素暴力的做法再最后几个例子会超时
方法1:朴素暴力(超时)
时间复杂度O(N^3)
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
int n=nums.length;
List<List<Integer>> list=new ArrayList<>();
for(int i=0;i<n-2;i++){
for(int j=n-1;j>i+1;j--){
int x=i+1;
while(x<j){
int count=nums[i]+nums[x]+nums[j];
if(count==0){
List<Integer> arr=new ArrayList<>();
arr.add(nums[i]);
arr.add(nums[x]);
arr.add(nums[j]);
if(!list.contains(arr)){
list.add(arr);
}
x++;
}else if(count>0){
break;
}else{
x++;
}
}
}
}
return list;
}
}
方法2:排序加双指针
思路:在为了方便去重和减少遍历次数,我们应该保证三指针满足i<j<k的关系。i我们通过for循环固定,j和k从两头向内移动。
时间复杂度O(n^2):数组排序O(nlogn),遍历数组O(n),双指针遍历O(n)。总体O(n^2)
空间复杂度O(1)
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//对数组排序
Arrays.sort(nums);
int n=nums.length;
List<List<Integer>> list=new ArrayList<>();
for(int i=0;i<n-2;i++){
//定一个i,移动两指针j,k,注意j是从末尾开始移动
int j=i+1;
int k=n-1;
while(j<k){
int count=nums[i]+nums[j]+nums[k];
//找到符合的
if(count==0){
List<Integer> arr=new ArrayList<>();
arr.add(nums[i]);
arr.add(nums[j]);
arr.add(nums[k]);
//为了去重
if(!list.contains(arr)){
list.add(arr);
}
j++;
k--;
//加起来太大,让k--从而和变小
}else if(count>0){
k--;
//加起来太小,让j++从而和变大
}else{
j++;
}
}
}
return list;
}
}
对上述代码可进行优化:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);//排序,nums变成递增数组
List<List<Integer>> res = new ArrayList<>();
//k < nums.length - 2是为了保证后面还能存在两个数字
for(int k = 0; k < nums.length - 2; k++){
if(nums[k] > 0) break;//若nums[k]大于0,则后面的数字也是大于零(排序后是递增的)
if(k > 0 && nums[k] == nums[k - 1]) continue;//nums[k]值重复了,去重
int i = k + 1, j = nums.length - 1;//定义左右指针
while(i < j){
int sum = nums[k] + nums[i] + nums[j];
if(sum < 0){
while(i < j && nums[i] == nums[++i]);//左指针前进并去重
} else if (sum > 0) {
while(i < j && nums[j] == nums[--j]);//右指针后退并去重
} else {
res.add(new ArrayList<Integer>(Arrays.asList(nums[k], nums[i], nums[j]);
while(i < j && nums[i] == nums[++i]);//左指针前进并去重
while(i < j && nums[j] == nums[--j]);//右指针后退并去重
}
}
}
return res;
}
}
????5.四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同 nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
题目链接:四数之和
题目思路:这道题是在三数之和的基础之上个进阶,也可以说是两数之和的进阶,只不过变成了四个数。我们不可能用4个for循环遍历,而且还得去重,肯定是会超时的。这时我们同样可以利用三数之和的性质,先将数组排序,利用四个指针i,p1,p2,j。同时保证i<p1<p2<j。每次固定i和j,判断四个下标相加的和 ,如果比target大,让p2--,如果比target小,让p1++。如果相等就放入到答案集合中。
方法: 排序加双指针
时间复杂度:O(n^3)
空间复杂度: O(logn)
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list=new ArrayList<>();
Arrays.sort(nums);
int n=nums.length;
//i得保证后面还得有三个位,所以<n-3
for(int i=0;i<n-3;i++){
//得保证和i之间还有两个位,所以>i+2
for(int j=n-1;j>i+2;j--){
int x=nums[i]+nums[j];
//两指针开始的位置
int p1=i+1;
int p2=j-1;
while(p1<p2){
int count=x+nums[p1]+nums[p2];
if(count==target){
List<Integer> arr=new ArrayList<>();
arr.add(nums[i]);
arr.add(nums[j]);
arr.add(nums[p1]);
arr.add(nums[p2]);
//去重
if(!list.contains(arr)){
list.add(arr);
}
p1++;
p2--;
}else if(count>target){
p2--;
}else{
p1++;
}
}
}
}
return list;
}
}
????6.四数相加ll
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
题目链接:四数相加ll
题目思路:这道题其实比上一道题四数之和要简单一些,不过它却也有自己比较难处理的地方。我们不可能用四个for循环遍历,因为即使能遍历不超时,去重的步骤也是很麻烦的。其实也相当于又是两数之和的进阶。我们可以将数组两两分组,A组和B组遍历相加,将结果作为key,value当做该结果出现的次数。然后再遍历C组和D组相加的和为count,去判断map中是否有和count相加为0的key,如果有则加上该key对应的value。这样相当于进行了两次O(n^2)的遍历,总体来说还是O(n^2)的世界复杂度
方法:分组+哈希表
时间复杂度O(n^2):两个两层for循环,总体来时间复杂度还是O(n^2)
空间复杂度O(n^2):哈希映射需要的空间,最坏的情况下存入的值个数位n^2,也就需要O(n^2)的空间
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Map<Integer,Integer> map=new HashMap<>();
int n=nums1.length;
//用来统计答案
int anser=0;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
int count=nums1[i]+nums2[j];
//已存在
if(map.containsKey(count)){
//让出现的次数加1,也就是value
map.put(count,map.get(count)+1);
}else{
//未存在,放入map中,value为1
map.put(count,1);
}
}
}
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
int count2=nums3[i]+nums4[j];
//找到匹配的
if(map.containsKey(-count2)){
//加上value,也就是出现的次数
anser+=map.get(-count2);
}
}
}
return anser;
}
}
????3.刷题总结
几数之和的题目都非常的干货,深度地考察了我们算法基础能力的实践应用。特别是对哈希能力的训练,因为哈希是经典的以空间换时间,如果我们不用哈希就会超时,而且去重也非常麻烦。其次是双指针的运用,因为几数之和的题目数据大部分都在数组中,在数组中找元素大部分都离不开双指针算法。大家做完这类型的题目一定要多总结其中的规律,从二数到三数到四数,它的变化是怎样,做法又如何变化,这样才能高效刷题,早日成神! 后续我也会出哈希专题系列以及双指针专题系列。
如果有用,希望兄弟们三连多多支持一下!!!感谢
文末惊喜: