使用查找表的经典问题

例题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。
但是,这个题最最最难的一点就是,如何去重。毕竟它对顺序是没有要求的,那么就会出现使用不同的元素,但是得到的三元组却是相同的
所以,采取以下的方式:

  1. 对数组排序
  2. 从左向右遍历,对于每一个元素nums[i],使用双指针遍历查找i之后的元素,L指向左端,R指向右端。
  3. 如果 nums[i]>0,则三数之和必然无法等于 0,结束循环
  4. 如果 nums[i] == nums[i-1],则说明该数字重复,会导致结果重复,所以应该跳过
  5. 当 sum == 0 时,nums[L] == nums[L+1]则会导致结果重复,应该跳过,L++
  6. 当 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几个之前不熟悉的方法:

  1. String str = String.valueOf(strArr);可以将char[]转为String。
  2. 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;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值