给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解:
思路一:
暴力法:
- 两遍for循环,从头开始遍历元素与后边元素依次相加和target比较
- 当和target相等时, 则为最终结果
- 遍历到尾部都不同时,开始第二轮循环
- 第二个元素执行上述操作
- ….
- 循环到末尾仍然没有元素之和 == target时,return null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 暴力法 - 两层循环
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
int m = nums[i];
for (int j = i+1; j < nums.length; j++) {
int n = nums[j];
if (m + n == target){
int [] res = {m,n};
return res;
}
}
}
return null;
}
|
复杂度分析:
时间复杂度 : O(N ^ 2)
空间复杂度: O(1)
暴力破解虽然 ac,但是执行用时特别长,
思路二:
HashMap
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// 利用hashmap减少查询时间
/*
* HashMap中保存{差值 :index}
* 遍历, 如果map的key包含 取到的值。则取出i和 key对应的value
* */
public int[] twoSum1(int[] nums, int target) {
HashMap<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int m = nums[i];
if (map.containsKey(m)){
int [] res = {map.get(m),i};
return res;
}
map.put(target - m,i);
}
return null;
}
|
复杂度分析 :
时间复杂度 : O(N) 遍历一遍数组
空间复杂度 : O(N) 利用了额外的字典存储空间
题目进阶:
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解:
此题目与上边的题目非常类似, 唯一区别之处在于,本题中的数组是有序的
仍然可以用上题中,两种解法解决。
思路一:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/**
*
* 暴力法,两层循环
* 时间复杂度 : O(N ^ 2)
* 空间复杂度 : O(1)
*
* 代码逻辑简单,粗暴
*
* */
public int[] twoSum(int[] numbers, int target) {
for (int i = 0; i < numbers.length; i++) {
int m = numbers[i];
for (int j = i+1; j < numbers.length; j++) {
int n = numbers[j];
if (m + n == target){
int [] res = {m + 1,n + 1};
return res;
}
}
}
return null;
}
|
思路二:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
*
* 字典存储遍历过的值
*
* 用map 存储 <target - num, num>. 遍历下一个元素时,如果 map的key中包含。说明和为target。
* 遍历完毕,没找到,则返回空
*
* 时间复杂度 : O(N)
* 空间复杂度 : O(N)
*
* */
public int[] twoSum1(int[] numbers, int target) {
HashMap<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < numbers.length; i++) {
int num = numbers[i];
if (map.containsKey(num)) {
int[] res = {map.get(num),i};
return res;
}
map.put(target-numbers[i], num);
}
return null;
}
|
思路三:
以上两种解法中,我们完全没有用到有序数组这个条件。
那么,如何利用有序这个条件呢?
看到有序,首先想到的是不是可以用二分查找?
用二分查找进行了尝试,发现不太好实现。
- 因为一个最小的数字和一个最大的数字,和有可能刚好为target
- 二分方貌似不能缩小范围。
所以,我们利用本题中的双指针法解决问题
- 首先用两个指针 begian = 1, end = nums.length - 1; 分别指向数组的开头和末尾
- while循环(循环条件 begain < end),计算两个指针指向元素的和sum
- 当sum == target时,begain 和 end 即为最终结果
- 当sum > target时, begain –
- 当sum < target时, end ++
- 循环过程中,一直没有 sum == target 的情况,则返回null
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
/**
* 因为我们完全没有用到升序数组这个条件
* 因为时有序数组,所以想到使用二分的思想解决问题
*
* 但是试着写了一会代码,发现二分的思想貌似不能用
* 因为一个最小的数字 + 一个最大的数字。 有可能和刚好为 target
* 所以二分没办法缩小查找范围。不能用
*
* 所以我们利用,双指针法解决
* 两个指针分别指向数组的开头begain和结尾end
* 依次计算两个指针指向元素的和
* 如果刚好等于 target 则为最终结果
* 如果 大于 target。 则 end --; 继续循环
* 如果 小于 target。 则 begain ++; 继续循环
* 循环条件 begian < end
*/
public int[] twoSum2(int[] numbers, int target) {
int begain = 0, end = numbers.length - 1;
while (begain < end){
int num1 = numbers[begain];
int num2 = numbers[end];
if (num1 + num2 == target) {
int[] res = {begain, end};
return res;
}
if (num1 + num2 > target)
end --;
else
begain ++;
}
return null;
}
|
复杂度分析 :
时间复杂度 : O(N)
空间复杂度 : O(1)
进阶-三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解:
思路一:暴力法
- 最容易想到的方法-暴力法
- 三层循环,找到每一种不同数字可能的组合
- 当三个数字和为0时,添加到结果数组
- 时间复杂度 : O(N ^ 3)
- 空间复杂度 : O(1)
思路二:排序 + 双指针
- 先排序
- 排序后,两层循环数组
- 取到最外层某个数字num
- 开始计算其后边 n-1个元素的和 为 -num 的组合
- 因为已排序,所以可以用 头尾双指针法 计算后边 n-1个元素的和
- 头尾双指针 begain, end
- 当和 == -num时, 加入结果数组
- 当和 < -num时, begain++
- 当和 > -num时, end –
❗️❗️❗️
- 在确定此题目 排序 + 双指针 的解题思路后
- 本题难点在于,如何去除重复解
- 我没写好,copy的评论区的代码😅😅😅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
/**
*
* 先排序
* 排序后,数组两层循环
* 取到最外层某个数num
* 开始计算其后边 n - 1个元素的和为 -num 的组合
* 后边的操作 类似 排序版本 的两数之和
* 头尾双指针begain, end,
* 当和 == -num时, 加入结果数组
* 当和 < -num时, begain ++
* 当和 > -num时, end --
*
*
* */
public static List<List<Integer>> threeSum(int[] nums) {
// 先排序
Arrays.sort(nums);
List<List<Integer>> ls = new ArrayList<>();
// 排序以后的操作 跟 排序后的两数之和一样
for (int i = 0; i < nums.length - 2; i++) {
if (i == 0 || (i > 0 && nums[i] != nums[i - 1])) { // 跳过可能重复的答案
int l = i + 1, r = nums.length - 1, sum = 0 - nums[i];
while (l < r) {
if (nums[l] + nums[r] == sum) {
ls.add(Arrays.asList(nums[i], nums[l], nums[r]));
while (l < r && nums[l] == nums[l + 1]) l++;
while (l < r && nums[r] == nums[r - 1]) r--;
l++;
r--;
} else if (nums[l] + nums[r] < sum) {
while (l < r && nums[l] == nums[l + 1]) l++; // 跳过重复值
l++;
} else {
while (l < r && nums[r] == nums[r - 1]) r--;
r--;
}
}
}
}
return ls;
}
|
复杂度分析 :
时间复杂度 : Arrays.sort() 最优的快排,时间复杂度为 O(N * log N) 。 后边的两层循环 : O(N ^ 2)
所以整体时间复杂度 : O(N * logN) + O(N ^ 2) = O(N ^ 2)
空间复杂度 : 除结果数组以外,没有用到额外的存储空间,所以为 O(1)
三数之和再变种:
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
提示:
3 <= nums.length <= 10^3
-10^3 <= nums[i] <= 10^3
-10^4 <= target <= 10^4
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum-closest
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解:
- 本题目是15题 三数之和 的变种
- 15题中,我们采用 排序 + 双指针 的解法
- 本题中,貌似我们依然可以采用这种解法
- 只不过15题是 在遍历过程中, 将和 == target的数依次加入数组
- 本题中,只要保存最接近的值即可
- 这样想,貌似题目还简单了,因为不需要去除麻烦的重复
- 依然是排序
- 遍历排序后的数组 ,头尾双指针解题方法
- 求 三个数的和 和 target差值的绝对值
- 当最近求出的 差值的绝对值 < 之前求的结果, 则替换结果。
- 遍历过程中
- 当 sum == target时,差值0,直接return sum
- 当 sum > target时,right–,找是否有更接近的
- 当 sum < target时,left++, 找是否有更接近的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public static int threeSumClosest(int[] nums, int target) {
// 先排序 时间复杂度 O(N * log N)
Arrays.sort(nums);
int res = nums[0] + nums[1] + nums[2];
for (int i = 0; i < nums.length; i++) {
int num = nums[i];
int left = i + 1, right = nums.length - 1;
while (left < right){
int sum = nums[left] + nums[right] + num;
// 如果有相等的,直接返回0
if (sum == target) return sum;
if (Math.abs(sum - target) < Math.abs(res - target))
res = sum;
if (sum > target){
right --;
}else {
left ++;
}
}
}
return res;
}
|
复杂度分析 :
时间复杂度 : O(N ^ 2)
空间复杂度 : O(1)
进阶: 四数之和
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/4sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
|
题解:
leetcode 也是挺有意思,两数之和,两数之和I,两数之和II, 三数之和…现在又来一个四数之和
让我想起了 丑数I, 丑数II, 丑数III, 超级丑数
以后会来一个 N数之和 或者 超级N数之和的吧.
- 其实跟三数之和相比, 也没啥新意
- 一样的套路,先排序,后循环
- 无非是内部三层循环,还是两层循环的问题
- 但是做过三数之和的同学都知道,去重复的恶心…
- 去重复去的想吐..
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
// 先排序
Arrays.sort(nums);
List<List<Integer>> ls = new ArrayList<>();
// 排序以后的操作 跟 排序后的两数之和一样
for (int i = 0; i < nums.length - 3; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
for (int j = i + 1; j < nums.length - 2; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
int l = j + 1, r = nums.length - 1, sum = -nums[i] - nums[j];
while (l < r){
if (nums[l] + nums[r] == sum) {
ls.add(Arrays.asList(nums[i],nums[j], nums[l], nums[r]));
while (l < r && nums[l] == nums[l + 1]) l++;
while (l < r && nums[r] == nums[r - 1]) r--;
l++;
r--;
} else if (nums[l] + nums[r] < sum) {
while (l < r && nums[l] == nums[l + 1]) l++; // 跳过重复值
l++;
} else {
while (l < r && nums[r] == nums[r - 1]) r--;
r--;
}
}
}
}
return ls;
|
时间复杂度 : O(N ^ 3)
空间复杂度 : O(1)