2023-04-14 算法面试中常见的查找表问题

发布时间 2023-04-14 22:53:17作者: 空無一悟

2023-04-14 算法面试中常见的查找表问题

1 Set的使用

LeetCode349号问题:两个数组的交集

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

示例 1:

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

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

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

import java.util.*;

/***********************************************************
 * @note      : 
 * @author    : l00379880 梁山广
 * @version   : V1.0 at 2019/8/20 15:46
 ***********************************************************/
class Solution {

    /**
     * 用retainAll方法实现求交集
     */
    public int[] intersection(int[] nums1, int[] nums2) {
        Set<Integer> numSet = new HashSet<>();
        for (int i : nums1) {
            numSet.add(i);
        }

        Set<Integer> num2Set = new HashSet<>();

        for (int i : nums2) {
            num2Set.add(i);
        }

        // 求交集
        numSet.retainAll(num2Set);
        int[] result = new int[numSet.size()];
        int i = 0;
        for (Integer num : numSet) {
            result[i] = num;
            i++;
        }
        return result;
    }


    /**
     * 用自己的方法实现交集,核心是contains方法
     */
    public int[] intersection2(int[] nums1, int[] nums2) {

        TreeSet<Integer> record = new TreeSet<>();
        for (int num : nums1) {
            record.add(num);
        }

        TreeSet<Integer> resultSet = new TreeSet<>();
        for (int num : nums2) {
            if (record.contains(num)) {
                resultSet.add(num);
            }
        }

        int[] res = new int[resultSet.size()];
        int index = 0;
        for (Integer num : resultSet) {
            res[index++] = num;
        }

        return res;
    }


    public static void main(String[] args) {
        int[] nums1 = {4, 9, 5};
        int[] nums2 = {9, 4, 9, 8, 4};
        int[] result = new Solution().intersection2(nums1, nums2);
        System.out.println(Arrays.toString(result));
    }
}

2 Map的使用

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]
说明:

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

package Chapter04SetAndMap.IntersectionofTwoArrays2;

import java.util.*;

/***********************************************************
 * @note      : 
 * @author    : l00379880 梁山广
 * @version   : V1.0 at 2019/8/20 16:36
 ***********************************************************/
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        Map<Integer, Integer> num1Map = new HashMap<>();
        Set<Integer> num1Set = new HashSet<>();
        for (int i : nums1) {
            num1Map.merge(i, 1, Integer::sum);
            num1Set.add(i);
        }


        Map<Integer, Integer> num2Map = new HashMap<>();
        Set<Integer> num2Set = new HashSet<>();

        for (int i : nums2) {
            num2Map.merge(i, 1, Integer::sum);
            num2Set.add(i);
        }

        // 求交集
        num1Set.retainAll(num2Set);

        // num1Set就是交集的元素了
        List<Integer> resultList = new ArrayList<>();
        for (Integer num : num1Set) {
            int count = Math.min(num1Map.get(num), num2Map.get(num));
            for (int i = 0; i < count; i++) {
                resultList.add(num);
            }
        }

        int[] result = new int[resultList.size()];
        int i = 0;
        for (Integer num : resultList) {
            result[i] = num;
            i++;
        }
        return result;
    }

    public static void main(String[] args) {
        int[] nums1 = {1, 2, 2, 1};
        int[] nums2 = {2};
        int[] result = new Solution().intersect(nums1, nums2);
        System.out.println(Arrays.toString(result));
    }
}

3 Set和Map不同底层实现的区别

参考Part2Basic全课程,每部分实现地数组、链表和树可以用来作为Map的底层,不同的实现比较如下:
Set和Map不同底层实现的区别

哈希表虽然性能高,劣势是空间占用大,而且元素不能保持顺序~~

LeetCode上更多相似问题:242、202、290、205、451

242.有效的字母异位词

基于频率表的实现

class Solution {
    // 字母异位词:字母相同但是顺序不同的单词,,类似问题是438号问题
    public boolean isAnagram(String s, String t) {
        if("".equals(s) && "".equals(t)){
            return true;
        } 
        if("".equals(s) || "".equals(t) || s.length() != t.length()){
            return false;
        }
        int[] freq = new int[256];
        int len = s.length();
        for(int i = 0; i< len; i++){
            freq[s.charAt(i)] = freq[s.charAt(i)] + 1;
            freq[t.charAt(i)] = freq[t.charAt(i)] - 1;
        }
        for(int i = 0; i< len; i++){
            if(freq[s.charAt(i)] != 0){
                return false;
            }
        }
        return true;
    }
}

进一步优化,缩小数组范围,性能最高:

class Solution {
    // 字母异位词:字母相同但是顺序不同的单词,,类似问题是438号问题
    public boolean isAnagram(String s, String t) {
        if("".equals(s) && "".equals(t)){
            return true;
        } 
        if("".equals(s) || "".equals(t) || s.length() != t.length()){
            return false;
        }
        int[] freq = new int[26];
        int len = s.length();
        for(int i = 0; i< len; i++){
            int sInt = s.charAt(i) - 'a';
            int tInt = t.charAt(i) - 'a';
            freq[sInt] = freq[sInt] + 1;
            freq[tInt] = freq[tInt] - 1;
        }
        for(int i = 0; i< len; i++){
            if(freq[s.charAt(i) - 'a'] != 0){
                return false;
            }
        }
        return true;
    }
}

基于HashMap的实现

class Solution {
    /**
     * 字母异位词:字母相同但是顺序不同的单词,,类似问题是438号问题
     */
    public boolean isAnagram(String s, String t) {
        if ("".equals(s) && "".equals(t)) {
            return true;
        }
        if ("".equals(s) || "".equals(t) || s.length() != t.length()) {
            return false;
        }
        int len = s.length();
        // 哈希表,类似用来存频率
        Map<Character, Integer> cMap = new HashMap<>();
        for (int i = 0; i < len; i++) {
            char sC = s.charAt(i);
            char tC = t.charAt(i);
            if (cMap.get(sC) == null) {
                cMap.put(sC, 1);
            } else {
                cMap.put(sC, cMap.get(sC) + 1);
            }
            if (cMap.get(tC) == null) {
                cMap.put(tC, -1);
            } else {
                cMap.put(tC, cMap.get(tC) - 1);
            }
        }
        for (Character c : cMap.keySet()) {
            if (cMap.get(c) != 0) {
                return false;
            }
        }
        return true;
    }
}

202.快乐数

关键是观察规律

import java.util.HashSet;
import java.util.Set;

class Solution {
    int getSquareSum(int n) {
        int sum = 0;
        while (n != 0) {
            sum += (n % 10) * (n % 10);
            n = n / 10;
        }
        return sum;
    }

    /**
     * 若一个数是快乐数,最终变换会回到1,因此确定循环终结条件;
     * 若不是快乐数,会进入死循环,如何终至死循环,将每次变换过后的值存入HashSet中,判断是否出现过重复值,出现则return false;
     */
    public boolean isHappy(int n) {
        Set<Integer> set = new HashSet<>();
        while (true) {
            n = getSquareSum(n);
            if (n == 1) {
                return true;
            }
            if (set.contains(n)) {
                // 出现重复值了,直接退出
                return false;
            } else {
                set.add(n);
            }
        }
    }

    public static void main(String[] args) {
        // 19->82->100->1
        int n = 19;
        System.out.println(new Solution().isHappy(n));
    }
}

290.单词规律

class Solution {
    public boolean wordPattern(String pattern, String str) {
        if("".equals(pattern) || "".equals(str)){
            return false;
        }
        String[] words = str.split(" ");
        int len = pattern.length();
        if(len != words.length){
            return false;
        }
        Set<Character> setPattern = new HashSet<>();
        Set<String> setWord = new HashSet<>();
        for(int i = 0; i < len; i++){
            setPattern.add(pattern.charAt(i));
            setWord.add(words[i]);
        }
        if(setPattern.size() != setWord.size()){
            return false;
        }
        Map<Character, String> map = new HashMap<>();
        for(int i = 0; i < len; i++){
            Character c = pattern.charAt(i);
            String word = map.get(c);
            if(word == null){
                map.put(c, words[i]);
            }else {
                // 映射在之前就存在了,但是之前对应的关系和现在不符,直接返回false
                if(!word.equals(words[i])){
                    return false;
                }
            }
        }
        return true;
    }
}

205.同构字符串

和290几乎完全相同,单词边字符而已

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

class Solution {
    public boolean isIsomorphic(String s, String t) {
        if ("".equals(s) && "".equals(t)) {
            return true;
        }
        if ("".equals(s) || "".equals(t)) {
            return false;
        }
        int len = s.length();
        if (len != t.length()) {
            return false;
        }
        Set<Character> setS = new HashSet<>();
        Set<Character> setT = new HashSet<>();
        for (int i = 0; i < len; i++) {
            setS.add(s.charAt(i));
            setT.add(t.charAt(i));
        }
        if (setS.size() != setT.size()) {
            return false;
        }
        Map<Character, Character> map = new HashMap<>();
        for (int i = 0; i < len; i++) {
            Character cS = s.charAt(i);
            Character cSMap = map.get(cS);
            Character cT = t.charAt(i);
            if (cSMap == null) {
                map.put(cS, cT);
            } else {
                // 映射在之前就存在了,但是之前对应的关系和现在不符,直接返回false
                if (!cT.equals(cSMap)) {
                    return false;
                }
            }
        }
        return true;
    }

    public static void main(String[] args) {
        String s = "egg";
        String t = "add";
        System.out.println(new Solution().isIsomorphic(s, t));
    }
}

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

class Solution {
    public String frequencySort(String s) {
        StringBuilder sb = new StringBuilder();
        int[] freq = new int[256];
        int len = s.length();
        for(int i = 0; i < len; i++){
            char c = s.charAt(i);
            freq[c] += 1;
        }

        for(int i = 0; i < len; i++){
            // max记录频率值最大的索引(字符的ASCII值,即在freq中的下标)
            int max = 0;
            for(int j = 0; j < 256; j++) {
                if(freq[j] > freq[max]){
                    max = j;
                }
            }
            // 插入频率个字符
            for(int m = 0; m < freq[max]; m++){
                sb.append((char)max);
            }
            // 一定记得清零
            freq[max] = 0;
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        String s = "cccaaa";
        System.out.println(new Solution().frequencySort(s));
    }
}

4 Two Sum的问题

LeetCode第1号问题

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

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

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

第一种解法:把整个数组当做查找表

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 值和索引组成Map
        Map<Integer, Integer> mapValIndex = new HashMap<>();
        int len = nums.length;
        for (int i = 0; i < len; i++) {
            // 重复的元素不要二次加入
            mapValIndex.putIfAbsent(nums[i], i);
        }
        int[] result = new int[2];
        for (int i = 0; i < len; i++) {
            Integer index = mapValIndex.get(target - nums[i]);
            if (index != null && index != i) {
                // 注意顺序问题, 当nums=[3,3],target=6时会体现Math函数的作用
                result[0] = Math.min(i, index);
                result[1] = Math.max(i, index);
                // 找到就退出,因为用例保证了答案是唯一地了
                break;
            }
        }
        return result;
    }

    public static void main(String[] args) {
        int[] nums = {7, 3, 3};
        int target = 6;
        int[] result = new Solution().twoSum(nums, target);
        System.out.println(Arrays.toString(result));
    }
}

第2种解法:把遍历过程前面的元素作为查找表,顺便还能保证顺序
把遍历过程前面的元素作为查找表

class Solution {
      public int[] twoSum(int[] nums, int target) {
        // 值和索引组成Map
        Map<Integer, Integer> mapValIndex = new HashMap<>();
        int len = nums.length;
        for (int i = 0; i < len; i++) {
            Integer index = mapValIndex.get(target - nums[i]);
            if (index != null) {
                // 找到就退出,因为用例保证了答案是唯一地了
                return new int[]{index, i};
            }
            // 加入当前节点
            mapValIndex.put(nums[i], i);
        }
        return new int[0];
    }
}

相关的题目:

  • 15. 三数之和

    a+b = -c,转化为Two Sum的问题,但是注意这里的TwoSum可能不止有唯一解

    给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
    注意:答案中不可以包含重复的三元组。
    例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
    
    满足要求的三元组集合为:
    [
      [-1, 0, 1],
      [-1, -1, 2]
    ]
    
    /***********************************************************
     * @Description : LeetCode第15号问题:三数之和
     * https://leetcode-cn.com/problems/3sum/
     * @author      : 梁山广(Liang Shan Guang)
     * @date        : 2020/1/16 22:31
     * @email       : liangshanguang2@gmail.com
     ***********************************************************/
    package Chapter03.ThreeSum;
    
    import java.util.*;
    
    class Solution {
        /**
         * 这里的Two Sum和LeetCode第一题不同,这里一个target可能对应多个结果
         *
         * @param nums   要检索的数组
         * @param target 要求的目标值
         * @return 符合条件的下标对
         */
        public List<int[]> twoSum(int[] nums, int target) {
            List<int[]> resultList = new ArrayList<>();
            // 值和索引组成Map
            Map<Integer, Integer> mapValIndex = new HashMap<>();
            int len = nums.length;
            for (int i = 0; i < len; i++) {
                Integer index = mapValIndex.get(target - nums[i]);
                if (index != null) {
                    // 找到了一个满足条件地
                    resultList.add(new int[]{index, i});
                }
                // 加入当前节点
                mapValIndex.put(nums[i], i);
            }
            return resultList;
        }
    
        /**
         * 三个数字之和
         *
         * @param nums 数组
         * @return 所有满足三个数之和的元素对
         */
        public List<List<Integer>> threeSum(int[] nums) {
            // 这个排序很重要!!可能省掉下面大量的Collections.sort()
            Arrays.sort(nums);
            Set<List<Integer>> result = new HashSet<>();
            if (nums.length < 3) {
                return new ArrayList<>();
            }
            int len = nums.length;
            for (int i = 2; i < len; i++) {
                // 时间优化
                if (nums[i] == nums[i - 1] && nums[i] == nums[i - 2]) {
                    if (nums[i] == 0) {
                        // 把三个0加进去
                        result.add(new ArrayList<>(Arrays.asList(0, 0, 0)));
                    }
                    // 三个连续相等地,第3个开始就不用看了
                    continue;
                }
                // 注意twoSum返回地是nums的索引下标
                List<int[]> twoSumResultList = twoSum(Arrays.copyOfRange(nums, 0, i), -nums[i]);
                for (int[] twoSumResult : twoSumResultList) {
                    // 找到的两个索引不能和当前索引相等
                    if (twoSumResult.length == 2) {
                        // 这个是set,会自动去重
                        result.add(Arrays.asList(nums[twoSumResult[0]], nums[twoSumResult[1]], nums[i]));
                    }
                }
            }
            return new ArrayList<>(result);
        }
    
        /**
         * 输出顺序不重要,结果对就行~~
         * nums = {-1, 0, 1, 2, -1, -4}   ===> [ [-1, 0, 1], [-1, -1, 2]]
         * {-4, -2, -2, -2, 0, 1, 2, 2, 2, 3, 3, 4, 4, 6, 6}  ==> [[-4,-2,6],[-4,0,4],[-4,1,3],[-4,2,2],[-2,-2,4],[-2,0,2]]
         */
        public static void main(String[] args) {
            int[] nums = {-4, -2, -2, -2, 0, 1, 2, 2, 2, 3, 3, 4, 4, 6, 6};
            List<List<Integer>> lists = new Solution().threeSum(nums);
            System.out.println(lists);
        }
    }
    
  • 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]
    ]
    
    /***********************************************************
     * @Description : LeetCode第18号问题:四数之和
     * https://leetcode-cn.com/problems/4sum/
     * @author      : 梁山广(Liang Shan Guang)
     * @date        : 2020/1/16 23:46
     * @email       : liangshanguang2@gmail.com
     ***********************************************************/
    package Chapter03.FourSum;
    
    import java.util.*;
    
    class Solution {
        /**
         * 这里的Two Sum和LeetCode第一题不同,这里一个target可能对应多个结果
         *
         * @param nums   要检索的数组
         * @param target 要求的目标值
         * @return 符合条件的元素列表
         */
        public List<List<Integer>> twoSum(int[] nums, int target) {
            List<List<Integer>> resultList = new ArrayList<>();
            // 值和索引组成Map
            Map<Integer, Integer> mapValIndex = new HashMap<>();
            int len = nums.length;
            for (int i = 0; i < len; i++) {
                Integer index = mapValIndex.get(target - nums[i]);
                if (index != null) {
                    // 找到了一个满足条件地
                    resultList.add(new ArrayList<>(Arrays.asList(nums[index], nums[i])));
                }
                // 加入当前节点
                mapValIndex.put(nums[i], i);
            }
            return resultList;
        }
    
        /**
         * 三个数字之和
         *
         * @param nums 数组
         * @return 所有满足三个数之和的元素对
         */
        public List<List<Integer>> threeSum(int[] nums, int target) {
            // 这个排序很重要!!可能省掉下面大量的Collections.sort()
            Arrays.sort(nums);
            Set<List<Integer>> result = new HashSet<>();
            if (nums.length < 3) {
                return new ArrayList<>();
            }
            int len = nums.length;
            for (int i = 2; i < len; i++) {
                // 时间优化
                if (nums[i] == nums[i - 1] && nums[i] == nums[i - 2]) {
                    if (nums[i] + nums[i - 1] + nums[i - 2] == target) {
                        // 把三个加进去
                        result.add(new ArrayList<>(Arrays.asList(nums[i], nums[i - 1], nums[i - 2])));
                    }
                    // 三个连续相等地,第3个开始就不用看了
                    continue;
                }
                // 注意twoSum返回地是nums的索引下标
                List<List<Integer>> twoSumResultList = twoSum(Arrays.copyOfRange(nums, 0, i), target - nums[i]);
                for (List<Integer> twoSumResult : twoSumResultList) {
                    // 找到的两个索引不能和当前索引相等
                    if (twoSumResult.size() == 2) {
                        // 这个是set,会自动去重
                        result.add(Arrays.asList(twoSumResult.get(0), twoSumResult.get(1), nums[i]));
                    }
                }
            }
            return new ArrayList<>(result);
        }
    
    
        public List<List<Integer>> fourSum(int[] nums, int target) {
            // 这个排序很重要!!可能省掉下面大量的Collections.sort()
            Arrays.sort(nums);
            Set<List<Integer>> result = new HashSet<>();
            if (nums.length < 4) {
                return new ArrayList<>();
            }
            int len = nums.length;
            for (int i = 3; i < len; i++) {
                // 时间优化
                if (nums[i] == nums[i - 1] && nums[i] == nums[i - 2] && nums[i] == nums[i - 3]) {
                    if (nums[i] + nums[i - 1] + nums[i - 2] + nums[i - 3] == target) {
                        // 把三个加进去
                        result.add(new ArrayList<>(Arrays.asList(nums[i], nums[i - 1], nums[i - 2], nums[i - 3])));
                    }
                    // 四个连续相等地,第4个开始就不用看了
                    continue;
                }
                // 注意twoSum返回地是nums的索引下标
                List<List<Integer>> threeSumResultList = threeSum(Arrays.copyOfRange(nums, 0, i), target - nums[i]);
                for (List<Integer> threeSumResult : threeSumResultList) {
                    // 找到的两个索引不能和当前索引相等
                    if (threeSumResult.size() == 3) {
                        // 这个是set,会自动去重
                        result.add(Arrays.asList(threeSumResult.get(0), threeSumResult.get(1), threeSumResult.get(2), nums[i]));
                    }
                }
            }
            return new ArrayList<>(result);
        }
    
        /**
         * 输出顺序不重要,结果对就行~~
         * nums = {-1, 0, 1, 2, -1, -4}   ===> [ [-1, 0, 1], [-1, -1, 2]]
         * {-4, -2, -2, -2, 0, 1, 2, 2, 2, 3, 3, 4, 4, 6, 6}  ==> [[-4,-2,6],[-4,0,4],[-4,1,3],[-4,2,2],[-2,-2,4],[-2,0,2]]
         */
        public static void main(String[] args) {
            int[] nums = {0, 0, 0, 0};
            int target=1;
            List<List<Integer>> lists = new Solution().fourSum(nums, target);
            System.out.println(lists);
        }
    }
    
  • 16.最接近的三数之和
    给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
    例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
    
    与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
    
    /***********************************************************
     * @Description : LeetCode第16号问题:最接近的三数之和
     * https://leetcode-cn.com/problems/3sum-closest/
     * @author      : 梁山广(Liang Shan Guang)
     * @date        : 2020/1/17 00:19
     * @email       : liangshanguang2@gmail.com
     ***********************************************************/
    package Chapter03.ThreeSumClosest;
    
    import java.util.*;
    
    class Solution {
    
        /**
         * 这里的Two Sum和LeetCode第一题不同,这里一个target可能对应多个结果
         *
         * @param nums   要检索的数组
         * @param target 要求的目标值
         * @return 符合条件的元素列表
         */
        public List<List<Integer>> twoSum(int[] nums, int target) {
            List<List<Integer>> resultList = new ArrayList<>();
            // 值和索引组成Map
            Map<Integer, Integer> mapValIndex = new HashMap<>();
            int len = nums.length;
            for (int i = 0; i < len; i++) {
                Integer index = mapValIndex.get(target - nums[i]);
                if (index != null) {
                    // 找到了一个满足条件地
                    resultList.add(new ArrayList<>(Arrays.asList(nums[index], nums[i])));
                }
                // 加入当前节点
                mapValIndex.put(nums[i], i);
            }
            return resultList;
        }
    
        /**
         * 三个数字之和
         *
         * @param nums 数组
         * @return 所有满足三个数之和的元素对
         */
        public List<List<Integer>> threeSum(int[] nums, int target) {
            // 这个排序很重要!!可能省掉下面大量的Collections.sort()
            Arrays.sort(nums);
            Set<List<Integer>> result = new HashSet<>();
            if (nums.length < 3) {
                return new ArrayList<>();
            }
            int len = nums.length;
            for (int i = 2; i < len; i++) {
                // 时间优化
                if (nums[i] == nums[i - 1] && nums[i] == nums[i - 2]) {
                    if (nums[i] + nums[i - 1] + nums[i - 2] == target) {
                        // 把三个加进去
                        result.add(new ArrayList<>(Arrays.asList(nums[i], nums[i - 1], nums[i - 2])));
                    }
                    // 三个连续相等地,第3个开始就不用看了
                    continue;
                }
                // 注意twoSum返回地是nums的索引下标
                List<List<Integer>> twoSumResultList = twoSum(Arrays.copyOfRange(nums, 0, i), target - nums[i]);
                for (List<Integer> twoSumResult : twoSumResultList) {
                    // 找到的两个索引不能和当前索引相等
                    if (twoSumResult.size() == 2) {
                        // 这个是set,会自动去重
                        result.add(Arrays.asList(twoSumResult.get(0), twoSumResult.get(1), nums[i]));
                    }
                }
            }
            return new ArrayList<>(result);
        }
    
        public int threeSumClosest(int[] nums, int target) {
            int len = nums.length;
            if (len < 3) {
                return -1;
            }
            if (len == 3) {
                return nums[0] + nums[1] + nums[2];
            }
            Arrays.sort(nums);
            int minTarget = nums[0] + nums[1] + nums[2];
            int maxTarget = nums[len - 3] + nums[len - 2] + nums[len - 1];
            if (target >= maxTarget) {
                return maxTarget;
            }
            if (target <= minTarget) {
                return minTarget;
            }
            // 临时的targetTmp和target的距离,从最小开始找,只要找到一个就退出,即是最小的distance
            int distance = 0;
            int maxDistance = Math.max(maxTarget - target, target - minTarget);
            // target一定在minTarget和maxTarget之间了
            while (distance < maxDistance) {
                if (target + distance <= maxTarget && threeSum(nums, target + distance).size() != 0) {
                    return target + distance;
                }
                if (target - distance >= minTarget && threeSum(nums, target - distance).size() != 0) {
                    return target - distance;
                }
                distance++;
            }
            return -1;
        }
    
        public static void main(String[] args) {
            int[] nums = {-1, 2, 1, -4};
            int target = 1;
            System.out.println(new Solution().threeSumClosest(nums, target));
        }
    }
    

5 灵活选择键值 454号问题 4Sum

解题思路:把C+D的组合放入查找表中,通过查找A+B是否等于-(C+D)

给定四个包含整数的数组列表 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 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 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
/***********************************************************
 * @Description : 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0
 * 解题思路:把C+D的组合放入查找表中,通过查找A+B是否等于-(C+D)
 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2019/8/20 23:52
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter04.TwoSum2;

import java.util.HashMap;
import java.util.Map;

/*
 *时间复杂度是:O(n^2)
 *空间复杂度是:O(n^2)
 */
class Solution {
    public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
        Map<Integer, Integer> record = new HashMap<>();
        for (int i = 0; i < C.length; i++) {
            for (int j = 0; j < D.length; j++) {
                if (record.get(C[i] + D[j]) == null) {
                    record.put(C[i] + D[j], 1);
                } else {
                    record.put(C[i] + D[j], record.get(C[i] + D[j]) + 1);
                }
            }
        }
        int result = 0;
        for (int i = 0; i < A.length; i++) {
            for (int j = 0; j < B.length; j++) {
                if (record.containsKey(-A[i] - B[j])) {
                    result += record.get(-A[i] - B[j]);
                }
            }
        }
        return result;
    }

    public static void main(String[] args) {
        int[] A = {1, 2};
        int[] B = {-2, -1};
        int[] C = {-1, 2};
        int[] D = {0, 2};
        int result = new Solution().fourSumCount(A, B, C, D);
        System.out.println(result);
    }
}

类似问题:49. 字母异位词分组

下面的实现行用时 :13 ms, 在所有 Java 提交中击败了66.68%的用户;内存消耗 :40.9 MB, 在所有 Java 提交中击败了96.06%的用户

核心优化:数组或者字符串长度不要单独赋值

// 教训:len不要单独赋值
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        List<List<String>> result = new ArrayList<>();
        Map<String, List<String>> anagramsMap = new HashMap<>();
        for (int i = 0; i < strs.length; i++) {
            char[] chs = strs[i].toCharArray();
            Arrays.sort(chs);
            String strSorted = new String(chs);
            if (anagramsMap.get(strSorted)==null){
                List<String> anagrams = new ArrayList<>();
                anagrams.add(strs[i]);
                anagramsMap.put(strSorted, anagrams);
            }else {
                List<String> anagrams = anagramsMap.get(strSorted);
                anagrams.add(strs[i]);
                anagramsMap.put(strSorted,anagrams);
            }
        }

        for (String key : anagramsMap.keySet()) {
            result.add(anagramsMap.get(key));
        }
        return result;
    }
}

4.6 灵活选择键值:LeetCode447号问题-回旋镖的数量

选择距离作为键

class Solution {
    /**
     * 获取距离,暂时不开方,只要能表示距离的唯一值即可
     */
    int getDistance(int[] p1, int[] p2) {
        return (p2[0] - p1[0]) * (p2[0] - p1[0]) + (p2[1] - p1[1]) * (p2[1] - p1[1]);
    }

    public int numberOfBoomerangs(int[][] points) {
        int count = 0;
        for (int i = 0; i < points.length; i++) {
            // 到i的距离和相等距离点的个数组成的映射
            Map<Integer, Integer> mapDistancePoints = new HashMap<>();
            for (int j = 0; j < points.length; j++) {
                int distance = getDistance(points[i], points[j]);
                if (mapDistancePoints.get(distance) == null) {
                    mapDistancePoints.put(distance, 1);
                } else {
                    mapDistancePoints.put(distance, mapDistancePoints.get(distance) + 1);
                }
            }
            // 统计到points[i]距离相等的点数
            for (Integer distance : mapDistancePoints.keySet()) {
                int cnt = mapDistancePoints.get(distance);
                // 指定的举例值至少有一个元素地,所以这里不需要判空
                if (cnt > 1) {
                    // 因为考虑顺序,所以求组合而不是排列A-N-2 = N * (N - 1)
                    count += cnt * (cnt - 1);
                }
            }
        }
        return count;
    }
}

类似问题:LeetCode第149号问题:直线上最多的点数

坑非常多,同时锻炼价值也很大,用例考察地角度非常多。执行用时 :14 ms, 在所有 Java 提交中击败了81.61%的用户;内存消耗 :36 MB, 在所有 Java 提交中击败了60.83%的用户

import java.util.HashMap;
import java.util.Map;

class Solution {

    public int maxPoints(int[][] points) {
        int max = 0;
        if (points.length < 3) {
            return points.length;
        }
        for (int i = 0; i < points.length; i++) {
            // 到i的斜率和斜率相等的点的个数组成的映射
            Map<Double, Integer> mapKPoints = new HashMap<>();
            // 和points[i]的横纵坐标都相同的点,初始至少有自己
            int sameCount = 1;
            for (int j = i + 1; j < points.length; j++) {
                // 斜率可能是浮点数
                double k = Integer.MAX_VALUE;
                if (points[i][1] != points[j][1]) {
                    k = (points[i][0] - points[j][0]) * 1.0 / (points[i][1] - points[j][1]);
                } else {
                    if (points[i][0] == points[j][0]) {
                        // 横纵坐标都相等,那么这样的点可以加到任何一条和i相连的直线上
                        sameCount++;
                        continue;
                    }
                }
                // 下面这一行是为了解决用例[[4,0],[4,-1],[4,5]] ,Java中0.0和-0.0是不同的,https://blog.csdn.net/ouy5933/article/details/72461239
                k += 0.0;
                if (mapKPoints.get(k) == null) {
                    mapKPoints.put(k, 1);
                } else {
                    mapKPoints.put(k, mapKPoints.get(k) + 1);
                }
            }
            // 统计到points[i]距离相等的点数
            for (Double k : mapKPoints.keySet()) {
                int cnt = mapKPoints.get(k);
                // 要算上当前点points[i],所以要cnt+1
                max = Math.max(cnt + sameCount, max);
            }
            // 全部都是一个点的用例:[[1,1],[1,1],[1,1]]
            max = Math.max(sameCount, max);
        }
        return max;
    }

    /***
     * 典型用例:
     *
     * {{1, 1}, {2, 2}, {3, 3}}  ==>3
     * {{0, 0}}   ==> 1
     * {{0, 0},{0, 0}}  ==> 2
     * {{0, 0},{1, 1},{0, 0}}  ==> 3
     * [[1,1],[1,1],[2,2],[2,2]]  ==> 4
     *  {{-4, 1}, {-7, 7}, {-1, 5}, {9, -25}}  ==》 3  这里例子终点考察斜率是浮点类型
     *  [[4,0],[4,-1],[4,5]]  ==>  3  考察 Java中0.0和-0.0是不同的
     */
    public static void main(String[] args) {
        int[][] points = {{4, 0}, {4, -1}, {4, 5}};
        System.out.println(new Solution().maxPoints(points));
    }
}

4.7 查找表+滑动数组:219号问题,Contains Duplicate II

使用Set巧妙控制滑动窗口

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k。
示例 1:
  输入: nums = [1,2,3,1], k = 3
  输出: true

示例 2:
  输入: nums = [1,0,1,1], k = 1
  输出: true
示例 3:
  输入: nums = [1,2,3,1,2,3], k = 2
  输出: false
/***********************************************************
 * @Description : 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k
 * 思路:控制一个宽度为不大于k的窗口(record)不断滑动,检测到有相等的两个不同下标的就返回true
 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2019/8/21 08:16
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter04.ContainDuplicate;

import java.util.HashSet;
import java.util.Set;

/**
 * 时间复杂度O(n),空间复杂度O(k)
 */
public class Solution {
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        Set<Integer> record = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            if (record.contains(nums[i])) {
                // 前面加入的元素nums[j]包含已经包含nums[i]了,
                return true;
            }
            record.add(nums[i]);
            // 保持record中不超过k个元素,即i和j的索引最大差值不大于k,超过k时抹掉最左边的元素即可
            if (record.size() == k + 1) {
                record.remove(nums[i - k]);
            }
        }
        // 遍历到最后还找不到就返回false
        return false;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 1};
        int k = 3;
        boolean contain = new Solution().containsNearbyDuplicate(nums, k);
        System.out.println(contain);
    }
}

相似的问题:217.存在重复元素,相似的思路,只是不用维护窗口了

class Solution {
    public boolean containsDuplicate(int[] nums) {
        Set<Integer> set = new HashSet();
        for(int num : nums){
            if(set.contains(num)){
                return true;
            }
            set.add(num);
        }
        return false;
    }
}

4.8 二分搜索树底层实现的顺序性 220号问题 Contain Duplicate III

给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ。

示例 1:
输入: nums = [1,2,3,1], k = 3, t = 0
输出: true
示例 2:

输入: nums = [1,0,1,1], k = 1, t = 2
输出: true
示例 3:

输入: nums = [1,5,9,1,5,9], k = 2, t = 3
输出: false

在上一节的基础上加一个判断即可:在大小为k的窗口中找值差不大于t的即可

220号问题1
220号问题2

/***********************************************************
 * @Description : 给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ。

 * @author      : 梁山广(Liang Shan Guang)
 * @date        : 2019/8/21 08:16
 * @email       : liangshanguang2@gmail.com
 ***********************************************************/
package Chapter04.ContainDuplicate;

import java.util.TreeSet;

/**
 * 时间复杂度O(n),空间复杂度O(k)
 */
public class Solution {
    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        TreeSet<Integer> record = new TreeSet<>();
        for (int i = 0; i < nums.length; i++) {
            // 在长度为k的窗口内存在一个下标为j的元素nums[j],使得abs(nums[i] - nums[j]) < t,推导出 nums[i]-t <= nums[j] <= nums[i] + t
            // 这里的nums[j]就取最接近窗口内大于"nums[i]-t"的最小值,如果这个值也小于"nums[i]+t",说明j满足题干条件,返回true即可
            if (record.ceiling(nums[i] - t) != null && record.ceiling(nums[i] - t) <= nums[i] +  t){
                // 前面加入的元素包含nums[k了]
                return true;
            }
            record.add(nums[i]);
            // 保持record中不超过k个元素,即i和j的索引最大差值不大于k,超过k时抹掉最左边的元素即可
            if (record.size() == k + 1) {
                record.remove(nums[i - k]);
            }
        }
        // 遍历到最后还找不到就返回false
        return false;
    }

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 1};
        int k = 3;
        int t = 0;
        boolean contain = new Solution().containsNearbyAlmostDuplicate(nums, k, t);
        System.out.println(contain);

    }
}