废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是两数之和和三数之和,使用哈希这个基本的数据结构来实现
两数之和【EASY】
照例先从简单往难搞
题干
java
输入:
[3,2,4],6
返回值:
[2,3]
说明:
因为 2+4=6 ,而 2的下标为2 , 4的下标为3 ,又因为 下标2 < 下标3 ,所以返回[2,3]
java
输入:
[20,70,110,150],90
返回值:
[1,2]
说明:
20+70=90
解题思路
哈希 实现。从题中给出的有效信息:找出下标对应的值相加为target,数组中存在唯一解,故此 可以使用 直接遍历或者 hash表来解答,直接双重循环遍历时间复杂度太高,而上篇Blog我们应该还记得:【算法训练-模拟】模拟设计LRU缓存结构
代码实现
基本数据结构 :数组
辅助数据结构 :哈希表
算法 :迭代
技巧 :哈希查找
java
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param numbers int整型一维数组
* @param target int整型
* @return int整型一维数组
*/
public int[] twoSum (int[] numbers, int target) {
// 1 用哈希表记录出现过的数字,key为数字,value为下标
Map<Integer, Integer> map = new HashMap<>();
int[] result = new int[2];
for (int i = 0; i < numbers.length; i++) {
// 得出需要的差值是多少
int needVal = target - numbers[i];
if (map.containsKey(needVal)) {
// 如果map存储了差值则记录两数
result[0] = map.get(needVal);
result[1] = i + 1;
} else {
// 如果map未存储差值则将当前值存储
map.put(numbers[i], i + 1);
}
}
return result;
}
}
复杂度分析
时间复杂度O(N) :遍历了数组;空间复杂度O(N) ,定义了哈希结构存储数据。相比于双重循环这种方式是典型的空间换时间
三数之和【MID】
好的,难度升级,进入三数之和:
java
输入:
[0]
返回值:
[]
java
输入:
[-2,0,1,1,2]
返回值:
[[-2,0,2],[-2,1,1]]
java
输入:
[-10,0,10,20,-10,-40]
返回值:
[[-10,-10,20],[-10,0,10]]
解题思路
排序+双指针实现。关键点:三元组按照升序排列;不能出现重复三元组。值得注意的是,虽然这里可以继续用哈希+双重循环把两数之和转为三数之和,但没有必要,因为我们之所以用哈希主要是为了找下标,而题目没有要求给出下标,只要给出值的三元组即可,所以这里用哈希反而增加了空间复杂度,并且也没有降低时间复杂度。
代码实现
基本数据结构 :数组
辅助数据结构 :无
算法 :迭代、快速排序
技巧 :双指针
使用Java实现,特别注意的是基准值、左指针和右指针都需要避免重复的情况
java
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param num int整型一维数组
* @return int整型ArrayList<ArrayList<>>
*/
public ArrayList<ArrayList<Integer>> threeSum (int[] num) {
// 1 定义返回结果
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
// 排除不满足条件的num
if (num == null || num.length < 3) {
return result;
}
// 2 要求非降序排列,且为了应用左右指针及去重考虑,先对整个数组升序排列
Arrays.sort(num);
// 3 遍历数组,以其中一个数为基准,双指针寻找另外两个数
for (int i = 0; i < num.length - 2; i++) {
// 减少无效遍历,去掉无效情况,如果第一个数都大于0,那总和一定大于0
if (num[i] > 0) {
break;
}
// 排除基准数的重复值
if (i > 0 && num[i] == num[i - 1]) {
continue;
}
int left = i + 1, right = num.length - 1;
int target = -num[i];
while (left < right) {
if (num[left] + num[right] == target) {
// 存放满足条件的三元组并移动指针
result.add(new ArrayList<>(Arrays.asList(num[i], num[left], num[right])));
// 基准数固定,指针移动,左指针向右,右指针必须向左【左+右】为固定值,如果左值不变,右值也不变就重复了,如果左值变,右值就不可能和原来一致,所以必须向左寻求新值。
left++;
right--;
// 由于数组已经排序,所以可能出现重复三元组的数据就在相邻位置,所以需要越过相邻位置的重复三元组
while (left < right && num[left] == num[left - 1]) {
left++;
}
while (left < right && num[right] == num[right + 1]) {
right--;
}
} else if (num[left] + num[right] > target) {
// 大于目标值,右指针向左减小总值
right--;
} else {
// 小于目标值,左指针向右扩大总值
left++;
}
}
}
return result;
}
}
复杂度分析
时间复杂度:O(n log n) 【排序】+ O(n^2) 【外层for循环和内层while循环】
空间复杂度:O(1),排除结果的数据结构