leetcode_查找相关问题

本文探讨了解决查找问题的思路,分为查找有无和查找对应关系两类,常用数据结构为set和map。文章列举了多个LeetCode相关问题,如两数之和、交集查找、字母异位词等,并提供了相应的解题思路,如使用set去重、map查找等。
摘要由CSDN通过智能技术生成

查找问题的思路

查找问题一般分为两类:

  • 查找有无,这样的查找问题多用set解决
  • 查找对应关系(键值对应),这样的问题多用map解决

在查找问题中,难点往往是分析出需要查找的是什么,然后将待查找的内容放入查找表中(set / map)

注意:在java中, set / map 的底层实现有两类,一类是基于红黑树实现的 TreeSet / TreeMap ,另一类是基于哈希表实现的 HashSet / HashMap, 在增删查的过程中基于哈希表实现的set/map的时间复杂度为O(1)性能上更优(TreeSet/TreeMap增删查的时间复杂度为O(lofn)), 但相比其在提高时间性能的过程中失去了数据的顺序性,在查找过程中若需要利用数据之间的顺序性时(如:数据集中的最大值最小值;某个元素的前驱和后继;某个元素的ceil和floor;某个元素的排位rank;选择某个排位的元素select 等),TreeSet/TreeMap才是首选。

 

leetcode相关问题

349. 两个数组的交集

给定两个数组,编写一个函数来计算它们的交集。

示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]

示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
说明:

输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。

思路:使用set去重并进行查找

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        ArrayList<Integer> list = new ArrayList<>();
        
        HashSet<Integer> set = new HashSet<>();
        for(int i=0; i<nums1.length; i++){
            set.add(nums1[i]);
        }
        
        for(int i=0; i<nums2.length; i++){
            if(set.contains(nums2[i])){
                list.add(nums2[i]);
                set.remove(nums2[i]);
            }
        }
        
        int[] res = new int[list.size()];
        for(int i=0; i<list.size(); i++){
            res[i] = list.get(i);
        }
        
        return res;
    }
}

 

350. 两个数组的交集 II

给定两个数组,编写一个函数来计算它们的交集。

示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]

示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
说明:

输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
我们可以不考虑输出结果的顺序。

思路:使用Map查找

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        ArrayList<Integer> list = new ArrayList<>();
        
        //将nums1写入查找表
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int e : nums1){
            if(!map.containsKey(e)){
                map.put(e, 1);
            }else{
                map.put(e, map.get(e)+1);
            }
        }
        
        for(int i=0; i<nums2.length; i++){
            if(map.containsKey(nums2[i])){
                list.add(nums2[i]);
                map.put(nums2[i], map.get(nums2[i])-1);
                if(map.get(nums2[i])<1){
                    map.remove(nums2[i]);
                }
            }
        }
        
        int[] res = new int[list.size()];
        for(int i=0; i<list.size(); i++){
            res[i] = list.get(i);
        }
        
        return res;
    }
}

 

242. 有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。

示例 1:
输入: s = "anagram", t = "nagaram"
输出: true

示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。

思路:使用map进行记录

class Solution {
    public boolean isAnagram(String s, String t) {
        if(s.length() != t.length()){
            return false;
        }
        
        HashMap<Character, Integer> map = new HashMap<>();       
        //将s放入查找表中
        for(char e : s.toCharArray()){
            if(!map.containsKey(e)){
                map.put(e, 1);
            }else{
                map.put(e, map.get(e)+1);
            }
        }
        
        for(int i=0; i<t.length(); i++){
            if(map.containsKey(t.charAt(i))){
                map.put(t.charAt(i), map.get(t.charAt(i))-1);
                if(map.get(t.charAt(i))<1){
                    map.remove(t.charAt(i));
                }
            }
        }        
        
        return map.isEmpty();
    }
}

 

202. 快乐数

编写一个算法来判断一个数是不是“快乐数”。

一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。

示例: 
输入: 19
输出: true

解释: 
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

思路:通过set记录查找中间数,判断中间是否出现重复数字,若出现了重复数字则必然会进入循环,不可能是“快乐数”

class Solution {
    public boolean isHappy(int n) {
        if(n==1){
            return true;
        }
        
        HashSet<Integer> set = new HashSet<>();
        
        while(true){
            int next = getSum(n);
            if(next == 1){
                return true;
            }
            
            if(set.contains(next)){
                return false;
            }
            
            set.add(next);
            
            n=next;
        }
        
        
    }
    
    private int getSum(int n){
        int res = 0;    
        while(n!=0){
            res+=Math.pow(n%10, 2);
            n/=10;
        }
        return res;
    }
}

 

模式匹配问题:可以通过map的 K-V 解决

290. 单词模式(模式匹配问题)

给定一种 pattern(模式) 和一个字符串 str ,判断 str 是否遵循相同的模式。

这里的遵循指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应模式。

示例1:
输入: pattern = "abba", str = "dog cat cat dog"
输出: true

示例 2:
输入:pattern = "abba", str = "dog cat cat fish"
输出: false

示例 3:
输入: pattern = "aaaa", str = "dog cat cat dog"
输出: false

示例 4:
输入: pattern = "abba", str = "dog dog dog dog"
输出: false

说明:
你可以假设 pattern 只包含小写字母, str 包含了由单个空格分隔的小写字母。

思路:通过map的 K-V 进行模式匹配

class Solution {
    public boolean wordPattern(String pattern, String str) {
       
        //String的split方法支持正则表达式;
        //正则表达式\s表示匹配任何空白字符,+表示匹配一次或多次。
        String[] s = str.split("\\s+");
        
        if(pattern.length() != s.length){
            return false;
        }
                      
        HashMap<Character, String> map = new HashMap<>();
        for(int i=0; i<pattern.length(); i++){
            if(!map.containsKey(pattern.charAt(i))){
                if(map.containsValue(s[i])){
                    return false;
                }
                map.put(pattern.charAt(i), s[i]);
            }else{
                if(!map.get(pattern.charAt(i)).equals(s[i])){
                    return false;
                }            
            }                              
        }
        
        return true;               
    }
}

205. 同构字符串 (模式匹配问题  思路同290)

给定两个字符串 s 和 t,判断它们是否是同构的。

如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。

所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。

示例 1:
输入: s = "egg", t = "add"
输出: true

示例 2:
输入: s = "foo", t = "bar"
输出: false

示例 3:
输入: s = "paper", t = "title"
输出: true

class Solution {
    public boolean isIsomorphic(String s, String t) {
        if(s.length() != t.length()){
            return false;
        }
        
        HashMap<Character, Character> map = new HashMap<>();
        for(int i=0; i<s.length(); i++){
            if(!map.containsKey(s.charAt(i))){
                if(map.containsValue(t.charAt(i))){
                    return false;
                }
                map.put(s.charAt(i), t.charAt(i));
            }else{
                if(map.get(s.charAt(i))!=t.charAt(i)){
                    return false;
                }
            }
        }
        
        return true;
    }
}

 

451. 根据字符出现频率排序

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:
输入:
"tree"
输出:
"eert"
解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。

示例 2:
输入:
"cccaaa"
输出:
"cccaaa"
解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。

示例 3:
输入:
"Aabb"
输出:
"bbAa"
解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。

思路:通过Map记录频次, 将map转为set,根据value值排序,重新构建字符串

class Solution {
    public String frequencySort(String s) {
        
        HashMap<Character, Integer> map = new HashMap<>();
        //tag
        // for(char e : s.toCharArray()){
        //     if(map.containsKey(e)){
        //         map.put(e, map.get(e)+1);
        //     }else{
        //         map.put(e, 1);
        //     }
        // }
        
        //tag的更优雅的写法
        for(char e : s.toCharArray()){
            map.put(e, map.getOrDefault(e, 0)+1);
        }
        
        
        //将map的Map.Entry对象(k-v pair对)提出成set结构,然后将set转成list,最后使用Collections的sort方法对list进行排序。
        ArrayList<Map.Entry<Character, Integer>> list = new ArrayList<>(map.entrySet());
        
        Collections.sort(list, new Comparator<Map.Entry<Character, Integer>>(){           
            public int compare(Map.Entry<Character, Integer> o1, Map.Entry<Character, Integer> o2){
                return o2.getValue()-o1.getValue();
            }            
        });
        //更优雅的写法 使用lambda表达式实现比较器的比较定义。
        // Collections.sort(list, (o1, o2) -> (o2.getValue() - o1.getValue()));
        
        StringBuilder sb = new StringBuilder();       
        for(Map.Entry<Character, Integer> e : list){
            for(int i=0; i<e.getValue(); i++){
                sb.append(e.getKey());
            }       
        }
        
        return sb.toString();               
    }
}

 

1. 两数之和

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

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

思路:不同于167号问题,这里给定的数组是无序的,无法直接使用对撞指针的思路解决。要想使用对撞指针的方法首先需要对数组进行一次排序。大的思路是确定一个数nums[i],然后从剩下的数中寻找target-nums[i], 关键是这个寻找的过程,可以使用for循环遍历(暴力法),可以使用二分搜索(需要先排序),也可以使用查找表

暴力解法

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] result=new int[2];
        for(int i=0;i<nums.length;i++){         
            for(int j=i+1;j<nums.length;j++){
                if(target-nums[i]==nums[j]){
                    result[0]=i;
                    result[1]=j;               
                    return result;
                }
            }         
        }
        return result;
    }     
}

使用map作为查找表,时间性能大大提升

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

 

15. 三数之和

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

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

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

思路:首先对数组进行排序,然后把三个数求和的解就转化为两个数相加等于某个数。求解两个数相加等于某个数,用双指针法即可。
关键在于处理重复的问题,由于排完序了,所以重复问题的处理只需要判断相邻两个是不是相等,如果相等移动指针即可。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        
        Arrays.sort(nums);
        
        for(int i=0; i<nums.length-2; i++){
            //由于nums已经按升序排好序,若第一个数大于0,则三数之和不可能等于0
            if(nums[i]>0){
                break;
            }
            
            //去重
            if(i>0 && nums[i]==nums[i-1]){
                continue;
            }
            
            //对撞指针
            int l=i+1, r=nums.length-1;
            int sum = 0-nums[i];
            
            while(l<r){
                if(nums[l]+nums[r] == sum){
                    res.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){
                    r--;
                }else{
                    l++;
                }
            }
        }
        
        return res;
    }
}


18. 四数之和

给定一个包含 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]
]

思路:类似于三数之和,只不过多加了一重循环 

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        
        //Arrays.sort() 可以用于对基本类型的数组排序
        //Collections.sort() 用于对List进行排序  
        //Arrays.asList() 可以将包裹类型的array转换为list, 但不可将基本类型的array转换为list
        Arrays.sort(nums);
        
        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;
                int temp = target-nums[i]-nums[j];
                
                while(l<r){
                    if(nums[l]+nums[r] == temp){
                        res.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] > temp){
                        r--;
                    }else{
                        l++;
                    }
                }             
            }
        }
        
        return res;        
    }
}

 

16. 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

思路:先排序, 然后 fix 一个数进行遍历, 然后内部使用双指针

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        int res=0;
        int dis=Integer.MAX_VALUE;  //记录三数字和与目标值的距离
        
        Arrays.sort(nums);
        
        for(int i=0; i<nums.length-2; i++){
          
            //对撞指针
            int l=i+1, r=nums.length-1;                 
            while(l<r){
                int sum = nums[i]+nums[l]+nums[r];
                if(Math.abs(sum-target)<dis){
                    dis = Math.abs(sum-target);
                    res = sum;
                }
                
                if(l<r && sum<target){                
                    l++;
                }else if(l<r && sum>target){                  
                    r--;
                }else{
                    return target;
                }              
            }
        }
        
        return res;
    }
}

 

454. 四数相加 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 = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2

解释:
两个元组如下:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0

 

思路:使用查找表,通过一个map记录A,B所有组合及其频次,时间复杂度为O(n^2)

class Solution {
    public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
        int res = 0;
        
        //通过一个map记录数组A, B组合的结果及其频次
        HashMap<Integer, Integer> map = new HashMap<>();
        for(int i=0; i<A.length; i++){
            for(int j=0; j<B.length; j++){
                int sum = A[i]+B[j];
                map.put(sum, map.getOrDefault(sum, 0)+1);
            }
        }
        
        //遍历C, D, 查找map求解
        for(int k=0; k<C.length; k++){
            for(int l=0; l<D.length; l++){
                if(map.containsKey(0-C[k]-D[l])){
                    res+=map.get(0-C[k]-D[l]);
                }
            }
        }
        
        return res;
    }
}

 

49. 字母异位词分组

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]
说明:
所有输入均为小写字母。
不考虑答案输出的顺序。

思路:查找表法,通过一个map记录, 将每个字符串对应字符数组经排序后得到的字符串作为该字符串的key

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        //通过一个map记录, 将每个字符串对应字符数组经排序后得到的字符串作为该字符串的key
        HashMap<String, List<String>> map = new HashMap<>();
        for(String s : strs){            
            //找出字符串的键, 所有的异位词共用同一个键
            char[] c = s.toCharArray();
            Arrays.sort(c);
            String key = new String(c);
            
            if(!map.containsKey(key)){
                List<String> list = new ArrayList<>();
                list.add(s);
                map.put(key, list);
            }else{
                List<String> list = map.get(key);
                list.add(s);
                map.put(key, list);              
            }
        }
        
        //Map.values()方法只是返回了一个Collection集合, 这里是Collection<List<String>>
        //在ArrayList中,有一个构造函数,可以接受一个集合类型的参数,然后返回一个list
        return new ArrayList<List<String>>(map.values());               
    }
}

447. 回旋镖的数量

给定平面上 n 对不同的点,“回旋镖” 是由点表示的元组 (i, j, k) ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。

找到所有回旋镖的数量。你可以假设 n 最大为 500,所有点的坐标在闭区间 [-10000, 10000] 中。

示例:
输入:
[[0,0],[1,0],[2,0]]
输出:
2

解释:
两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]

思路:由于每一个点都可能是回旋镖的顶点 (三个点中的第一个点),需要对每个点都分别进行考虑。对每一个点,构造一个查询表,K为其它点到该点的距离(避免浮点误差,使用距离的平方),V为该距离出现的频次

class Solution {
    public int numberOfBoomerangs(int[][] points) {
        int res = 0;
        //由于每一个点都可能是回旋镖的顶点 (三个点中的第一个点),需要对每个点都分别进行考虑
        //对每一个点,构造一个查询表,K为其它点到该点的距离(避免浮点误差,使用距离的平方),V为该距离出现的频次
        for(int i=0; i<points.length; i++){
            HashMap<Integer, Integer> map = new HashMap<>();
            
            for(int j=0; j<points.length; j++){
                if(i!=j){                    
                    int dist = (int)Math.pow((points[i][0]-points[j][0]), 2)+(int)Math.pow((points[i][1]-points[j][1]), 2);                         
                    map.put(dist, map.getOrDefault(dist, 0)+1);              
                }           
            }
            
            //计算以i为顶点的回旋镖的个数
            for(int d : map.keySet()){
                int nums = map.get(d);
                if(nums>=2){
                    res+=nums*(nums-1);  //有nums个点到点i的距离相等
                }               
            }
        }
        
        return res;        
    }
}

 

149

 

滑动窗口+查找表

219

217

 

220

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值