1 两数之和
两数之和 ---- 题目链接
要求:
给定一个整数组nums和一个目标值target,请你在该数组中找出和为目标值的那两个整数,
并返回他们的数组下标。
假设每种输入只会对应一个答案,但是不能重复利用这个数组中同样的元素
1.1 双层for(枚举):
枚举在数据中所有的不同的两个下标的组合
逐个检查他们所对应的数的和是否等于target
- 时间复杂度:O(n²),这里n为数组长度
- 空间复杂度:O(1),只用到常数个临时变量
public int[] twoSum(int[] nums,int target){
int[] array=new int[2];//也可以不定义返回匿名数组
for (int i = 0; i <nums.length ; i++) {
for(int j=i+1;j<nums.length-i;j++){
if (nums[i]+nums[j]==target){
array[0]=i;
array[1]=j;
}
}
}
/*[0]+[1]=2+5 [0]+[2]=2+7=9 [0]+[3]=2+6
[1]+[2]=5+7 [1]+[3]=5+6
[2]+[3]=7+6
*
* */
return array;
}
1.1.2 返回匿名数组,但要创建抛出异常对象
public static int[] twoSums(int[] m,int n){
for (int i = 0; i < m.length; i++) {
for (int j=i+1;j<m.length;j++){
if (m[i]+m[j]==n){
return new int[]{i,j};
}
}
}
throw new IllegalArgumentException("未找到");
}
测试
public static void main(String[] args) {
int[] a={2,5,7,6};
int[] arr=twoSums(a,9);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
1.2 HashMap
空间换取时间,少一层for循环
查表
空间复杂度:O(n),哈希表中最多需要存n-1个键值对
循环查询值,若不存在map.get( )==null;则存入map中
public int[] twoSum(int[] nums, int target) {
//hashmap 初始化时,尽量指定初始值大小,虽然hashmap是可以自动扩容的,但是不指定会频繁消耗资源
int len=nums.length;
HashMap<Integer, Integer> map = new HashMap<>(len-1);
int[] res=new int[2];
for (int i = 0; i < len; i++) {//i=0; i=1; i=2;
int dif=target-nums[i]; //dif=7; dif=4; dif=2;
if (map.get(dif)!=null){ //=null; =null; !=null; map.get(key)=0
res[0]=map.get(dif); // res[0]=0;
res[1]=i; // res[1]=2;
return res; // res={0,2};结束
}
map.put(nums[i],i); //put(2,0); put(5,1)
}
return res;
}
2 三数之和
三数之和 ---- 题目链接
要求
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?
请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例2:
输入:nums = [ ]
输出:[ ]
示例3:
输入:nums = [0]
输出:[]
2.1 排序+Set+双指针
1.先对数组进行排序,升序,Arrays.sort( )
2.先固定一个数比如数组中第一个数nums[0],然后再用两个数分别指向后面剩余的两端,
L=i+1;
R=n-1; (n是数组长度)
3.循环条件while(L<R)
1)若和=0,则把他们放入Set集合中 (Set去重),缩小范围L++ R–
2)如果当前三个所指向数组中的数的和<0,则L++;
(因为数组是先按照从小到大排序的,R是最大的,那么前面两个数和R之前的数相加也是<0的,所以L++;)
3)反之,若和>0,R- -
public static List<List<Integer>> threeS2(int[] nums){
Arrays.sort(nums);//排序
int n=nums.length;
Set<List<Integer>> res = new HashSet<>();//利用Set去重
for (int i = 0; i < n; i++) {
//定义两个数分别指向i+1和数组末端
int l=i+1;
int r=n-1;
while (l<r){
if (nums[i]+nums[l]+nums[r]==0){
res.add(Arrays.asList(nums[i],nums[l],nums[r]));//若符合则添加到res
l++;
r--;
}else if (nums[i]+nums[l]+nums[r]<0){
l++;
}else{
r--;
}
}
}
ArrayList<List<Integer>> ans = new ArrayList<>();
ans.addAll(res);
return ans;
}
虽然利用set去重简化了代码量,但最后set要放入list ,较耗时
public static void main(String[] args) {
int[] a={-1,0,1,2,-1,-4};
List<List<Integer>> an=threeS2(a);
for (int i = 0; i <an.size() ; i++) {
System.out.println(an.get(i));
}
}
代码运行解析
index i | 0 | 1 | 2 | 3 | 4 | 5 | action | goto |
---|---|---|---|---|---|---|---|---|
-4 | -1 | -1 | 0 | 1 | 2 | |||
i=0 | L=1 | R=5 | -4+(-1)+2<0 | L++ | ||||
i=0 | L=2 | R=5 | -4+(-1)+2<0 | L++ | ||||
i=0 | L=3 | R=5 | -4+0+2<0 | L++ | ||||
i=0 | L=4 | R=5 | -4+1+2<0 | L++ | ||||
i=0 | L=5=R | L<R=false | i++ | |||||
i=1 | L=2 | R=5 | -1+(-1)+2=0 | L++,R- - | ||||
i=1 | L=3 | R=4 | -1+0+1=0 | L++,R- - | ||||
i=1 | R=3 | L=4 | L<R=false | i++ | ||||
i=2 | L=3 | R=5 | -1+0+2>0 | R- - | ||||
i=2 | L=3 | R=4 | -1+0+1=0 | L++,R- - | ||||
i=2 | R=3 | L=4 | L<R=false | i++ | ||||
i=3 | L=4 | R=5 | 0+1+2>0 | R- - | ||||
i=3 | L=4=R | L<R=false | i++ | |||||
i=4 | L=5,R=5 | L<R=false | i++ | |||||
i=5,R=5,L=6 | L<R=false | end |
一些方法说明:
1.Arrays.asList()
方法
asList方法接收一个泛型化的可变参数列表
API中的说明
2.2 排序+枚举+双指针
注意去重
public List<List<Integer>> threeSum1_1(int[] nums) {
int len=nums.length;
List<List<Integer>> res=new ArrayList<>(len);
Arrays.sort(nums);
//测试集合长度小于3 或 排序后 最大值<0 直接返回空集合
if(len<3||nums[len-1]<0){
return res;
}
for (int i = 0; i < len-2; i++) {
if(nums[i]>0){
break; // 最小值大于0直接跳出
}
//满足边界条件 且 当前下标值与前一个值相同,跳出进行下一轮循环
if (i>0&&nums[i]==nums[i-1]){//去重
continue;
}
int l=i+1;
int r=len-1;
while (l<r) {
int sum=nums[i] + nums[l] + nums[r];
if (sum == 0) {
res.add(Arrays.asList(nums[i], nums[l], nums[r]));
//缩小范围
l++;
r--;
while (l<r&&nums[l]==nums[l-1]){
l++;//去重
}
while (l<r&&nums[r]==nums[r+1]){
r--;//去重
}
} else if (sum < 0) {
l++;
} else {
r--;
}
}
}
return res;
}
3 四数之和
要求:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
3.1 四层for (超时)
/*
方法1. 时间复杂度较高 耗时
*/
public static List<List<Integer>> fourSum1(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
for (int k = j + 1; k < nums.length; k++) {
for (int m = k + 1; m < nums.length; m++) {
if (nums[i] + nums[j] + nums[k] + nums[m] == target) {
List<Integer> ans = new ArrayList<>();
Collections.addAll(ans, nums[i], nums[j], nums[k], nums[m]);
Collections.sort(ans);
if (!res.contains(ans)) {
res.add(ans);
}
}
}
}
}
}
return res;
}
3.2 双层循环和双指针
/*
方法2. 利用双层循环和双指针
*/
//判断时每次循环都计算四数之和是否符合要求
public List<List<Integer>> fourSum2(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
int len = nums.length;
Arrays.sort(nums);
for (int i = 0; i < len - 3; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for (int j = i + 1; j < len - 2; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
int l = j + 1;
int r = len - 1;
while (l < r) {
int sum = nums[i] + nums[j] + nums[l] + nums[r];
if (sum == target) {
ArrayList<Integer> tmp = new ArrayList<>();
Collections.addAll(tmp, nums[i], nums[j], nums[l], nums[r]);
res.add(tmp);
l++;
r--;
while (l < r && nums[l] == nums[l - 1]) {
l++;
}
while (l < r && nums[r] == nums[r + 1]) {
r--;
}
} else if (sum < target) {
l++;
} else {
r--;
}
}
}
}
return res;
}
3.3 优化
/*
优化
*/
public List<List<Integer>> fourSum(int[] nums, int target) {
int len = nums.length;
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
if (len < 4) {
return res;
}
long min4 = (long) nums[0] + nums[1] + nums[2] + nums[3]; //最小的四数之和
long max3 = (long) nums[len - 1] + nums[len - 2] + nums[len - 3];
long sum3 = 0;
for (int i = 0; i < len - 3; i++) {
if (min4 > target) {//最小的4个数的和大于目标值
break;
}
if (i + 4 < len) {
min4 = min4 - nums[i] + nums[i + 4];//向右滑动更新区间和
}
if (i > 0 && nums[i] == nums[i - 1]) {//去重
continue;
}
if (max3 + nums[i] < target) {//第一个元素+最大的三个数的和小于目标值
continue;
}
//如果第一个数确定,并加上后面三个连续数的和小于target,结束循环
sum3 = (long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3];
for (int j = i + 1; j < len - 2; j++) {
if (sum3 > target) {
break;
}
if (j + 3 < len) {
sum3 = sum3 - nums[j] + nums[j + 3];//更新区间和
}
if (j > i + 1 && nums[j] == nums[j - 1]) {//去重
continue;
}
int l = j + 1;
int r = len - 1;
/*第二层循环时,可以确定nums[i]+nums[j]
可以先计算出前两数和,再判断 双指针指向的和是否满足要求
这样 先确定已知的两个和 再判断 双指针的和 可以比每次都计算出四个数之和要稍快些
*/
int t = target - nums[i] - nums[j];
while (l < r) {
int cur = nums[l] + nums[r];
if (cur < t) {
l++;
} else if (cur > t) {
r--;
} else {
List<Integer> ans = new ArrayList<>();
Collections.addAll(ans, nums[i], nums[j], nums[l], nums[r]);
res.add(ans);
l++;
r--;
//去重 不越界且 当 l 等于它之前(左边)的一个数,说明已经被枚举过了
while (l < r && nums[l] == nums[l - 1]) {
l++;
}
//去重 不越界且 当 r 等于它右边的一个数,说明已经被枚举过了
while (l < r && nums[r] == nums[r + 1]) {
r--;
}
}
}
}
}
return res;
}