1. 两数之和

题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

1
2
3
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

1
2
输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

1
2
输入:nums = [3,3], target = 6
输出:[0,1]

题解

1. 暴力

两次枚举,需要两次遍历,时间复杂度O(n²)

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < n; i++) { // 枚举 i
for (int j = i + 1; j < nums.length; j++) { // 枚举 i 右边的 j
if (nums[i] + nums[j] == target) { // 满足要求
return new int[]{i, j}; // 返回两个数的下标
}
}
}
// 题目保证有解,循环中一定会 return
// 所以这里无需 return,毕竟代码不会执行到这里
}
}

ACM模式:

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
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

int n = scanner.nextInt();
int target = scanner.nextInt();

int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = scanner.nextInt();
}

// 两数之和
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (nums[i] + nums[j] == target) {
System.out.println(i + " " + j);
return;
}
}
}
}
}

2. 双指针

由于题目所给数组已经有序排列,则可以从两端开始取数,如果两端取数的和大于目标,则说明:和为目标值的两个元素一定在两端范围内,时间复杂度O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int[] twoSum(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (true) {
if ((nums[right] + nums[left]) > target){
right--;
}
else if((nums[right] + nums[left]) < target){
left++;
}
else{
return new int[]{left, right};
}
}
}
}

3. HashMap

使用HashMap的key作为元素值,value作为数组下标

把元素存到HashMap中,去查找HashMap中与元素相加等于target的key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.HashMap;

class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> map = new HashMap<>();
for(int i = 0; i < nums.length; i++){
int need = target - nums[i];
if(map.containsKey(need)){
return new int[]{map.get(need), i};
}
map.put(nums[i], i);
}
return new int[]{-1,-1};
}
}

心得

很多涉及到「两个变量」的题目,都可以枚举其中一个变量,把它当成常量看待,从而转化成「一个变量」的问题。

代码实现时,通常来说「枚举右,寻找左」是更加好写的。

2. 三数之和

题目描述

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

示例 1:

1
2
3
4
5
6
7
8
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
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 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

1
2
3
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

1
2
3
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

题解

  1. 输出的顺序和三元组的顺序并不重要。则可以规定 i < j < k
  2. 答案中不可以包含重复的三元组。只需要跳过重复的枚举数(确保三个数字中必有一个不同)
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
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums); // Java数组排序
int n = nums.length;
List<List<Integer>> list = new ArrayList();
for(int i = 0; i < nums.length-2; i++){
if (i > 0 && nums[i] == nums[i-1]) {
continue;
}
// 优化1:如果num[i] 和他之后最小的两个数相加大于零,则他和之后任意两数相加都大于0
if ((nums[i] + nums[i+1] + nums[i+2]) > 0) {
break;
}
// 优化2:如果nums[i] 和他之后最大的两个数相加小于零,则他和之后任意两数相加都小于0,可以直接跳过这个i
if ((nums[i] + nums[n-1] + nums[n-2]) < 0) {
continue;
}

int j = i + 1;
int k = n - 1;
while(j < k) {
int s = nums[i] + nums[j] + nums[k];
if (s > 0) {
k--;
}
else if (s < 0) {
j++;
}
else {
list.add(Arrays.asList(nums[i], nums[j], nums[k]));
j++;
while(j < k && nums[j] == nums[j-1]) {
j++;
}
k--;
while(k > j && nums[k] == nums[k+1]) {
k--;
}
}
}
}
return list;
}
}

思路

  • 首先对数组进行排序,排序后固定一个数 nums[i],再使用左右指针指向 nums[i]后面的两端,数字分别为 nums[L] 和 nums[R],计算三个数的和 sum 判断是否满足为 0,满足则添加进结果集
  • 如果 nums[i]大于 0,则三数之和必然无法等于 0,结束循环
  • 如果 nums[i] == nums[i−1],则说明该数字重复,会导致结果重复,所以应该跳过
  • 当 sum == 0 时,nums[L] == nums[L+1] 则会导致结果重复,应该跳过,L++
  • 当 sum == 0 时,nums[R] == nums[R−1] 则会导致结果重复,应该跳过,R−−
    时间复杂度:O(n ²),n 为数组长度