例题1:两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
使用查找集的做法就是,将所有元素以及对应下标都放入map中,然后查找每一个target-a
是否存在于map,在的话返回a
以及target-a
的下标值。
但是,如果有两个相同的元素怎么办?为此,需要在放入map的时候便进行判断,如果在此时的查找表中找不到target-a
,就把a
放入map;如果找到了就返回它。
1.三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0
?请你找出所有满足条件且不重复的三元组。注意:答案中不可以包含重复的三元组。
思索了半天,怎么看也没法用查找集来解决。只是跟例题长得像罢了,但做法截然不同。
做法不同,但是有一点是一样的,那就是对于a,找到b和c,使得b+c==target-a。
但是,这个题最最最难的一点就是,如何去重。毕竟它对顺序是没有要求的,那么就会出现使用不同的元素,但是得到的三元组却是相同的。
所以,采取以下的方式:
- 对数组排序
- 从左向右遍历,对于每一个元素nums[i],使用双指针遍历查找
i
之后的元素,L指向左端,R指向右端。 - 如果 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(n2)
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
Arrays.sort(nums);
for(int i=0;i<nums.length-2;i++){
if(nums[i]>0) break;
if(i>0 && nums[i]==nums[i-1]) {
continue;
}
int l = i+1;
int r = nums.length-1;
while(l<r){
int sum = nums[i]+nums[l]+nums[r];
if(sum==0){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[l]);
list.add(nums[r]);
while(l<r && nums[l]==nums[l+1]) l++;
while(l<r && nums[r]==nums[r-1]) r--;
res.add(list);
l++;
r--;
}
else if(sum>0) r--;
else l++;
}
}
return res;
}
}
2.四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a +
b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。注意:
答案中不可以包含重复的四元组。
相比于三数之和,是不是可以只需要在第一层循环和对撞指针之间再加一层即可呢?
事实证明是可行的,只是这样时间复杂度便成了O(n3)。
class Solution {
public List<List<Integer>> fourSum(int[] nums,int target) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
Arrays.sort(nums);
for(int i=0;i<nums.length-3;i++){ //a
if(i>0 && nums[i]==nums[i-1]) {
continue;
}
for(int j=i+1;j<nums.length-2;j++){ //b
if(j>i+1 && nums[j]==nums[j-1]) continue;
int l = j+1;
int r = nums.length-1;
while(l<r){
int sum = nums[i]+nums[j]+nums[l]+nums[r];
// System.out.println(nums[i]+" "+nums[j]+" "+nums[l]+" "+nums[r]);
if(sum==target){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[l]);
list.add(nums[r]);
while(l<r && nums[l]==nums[l+1]) l++;
while(l<r && nums[r]==nums[r-1]) r--;
res.add(list);
l++;
r--;
}
else if(sum>target) r--;
else l++;
}
}
}
return res;
}
}
三、最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target
最接近。返回这三个数的和。假定每组输入只存在唯一答案。例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
思路还是和三数之和一样一样的,区别就在于这里不再是比较是否相等了,而是比较sum-target
谁的绝对值更小了。更小的则更新结果,优化的思路和之前也一样。
例题2:四数相加II
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
这个就很简单了,只需要使用两个双重循环,第一个双重循环遍历所有A、B的值,然后相加,把和以及对应的出现次数记录在一个map中。
为什么要记录次数?因为题目没说不允许重复,况且两个不同的组合相加的值也可能是一样的。
第二个双重循环用来遍历所有的C+D,然后查看-C-D
是否存在于map即可,存在则在总数中加上出现次数。
一、字母异位词分组
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”],
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
思路就是利用查找表map,其中字母异位词的特征就是,按照字母顺序排序后字符串是一样的。所以,按一致性,将排序后的字符串作为key,将每个对应的字母异位词放到一个List中作为value。
其中,这里用到了java几个之前不熟悉的方法:
String str = String.valueOf(strArr);
可以将char[]转为String。new ArrayList<>(map.values());
可以将map的值转为一个List。
代码如下:时间复杂度O(n)
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String,List<String>> map = new HashMap<>();
for(int i=0;i<strs.length;i++){
char[] strArr = strs[i].toCharArray();
Arrays.sort(strArr);
String str = String.valueOf(strArr);
if(map.containsKey(str)){
map.get(str).add(strs[i]);
}else{
List<String> list = new ArrayList<>();
list.add(strs[i]);
map.put(str,list);
}
}
return new ArrayList<>(map.values());
}
}
例题3:回旋镖的数量
给定平面上 n 对不同的点,“回旋镖” 是由点表示的元组 (i, j, k) ,其中 i 和 j 之间的距离和 i 和 k之间的距离相等(需要考虑元组的顺序)。
找到所有回旋镖的数量。你可以假设 n 最大为 500,所有点的坐标在闭区间 [-10000, 10000] 中。
思路,遍历每一个点 p ,找到p到其他点的所有距离,然后以距离作为key,以到p距离为key的点数作为value。每次找到一个新的点,都可以和所有其他距离相同的点各构成两个回旋镖。
需要双重循环,复杂度为O(n2)。
class Solution {
public int numberOfBoomerangs(int[][] points) {
int ans = 0;
for(int q=0;q<points.length;q++){
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int w=0;w<points.length;w++){
if(q!=w){
int dis = (points[q][0]-points[w][0])*(points[q][0]-points[w][0])+(points[q][1]-points[w][1])*(points[q][1]-points[w][1]);
if(map.containsKey(dis)){
int n = map.get(dis);
ans += 2*n;
map.replace(dis,n+1);
}else{
map.put(dis,1);
}
}
}
}
return ans;
}
}