代码随想录之哈希表

1、有效的字母异位词

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

注意:st 中每个字符出现的次数都相同,则称 st 互为字母异位词。

示例 1:

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

示例 2:

输入: s = "rat", t = "car"
输出: false

解:

①:利用数组充当map,构建两个数组分别存储字符串s和t中a~z出现的次数,比较两个数组
②:先将字符串转为数组,再排序,再转换为字符串,比较即可
/**
     * 检查两个字符串是否为互为字谜(anagram)。
     * 字谜指的是两个字符串在重新排列字符后可以相互转换的情况,即两个字符串中每个字符出现的次数都相同。
     *
     * @param s 第一个字符串
     * @param t 第二个字符串
     * @return 如果两个字符串互为字谜,返回true;否则返回false。
     */
    //有效字母的异位
    public boolean isAnagram(String s, String t) {
        // 初始化一个长度为26的数组来记录字母'a'到'z'在字符串s和t中出现的次数差异。
        int[] record = new int[26];
        
        // 遍历字符串s,统计每个字符出现的次数。
        for(int i = 0; i<s.length();  i++){
            record[s.charAt(i) - 'a']++;   //并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
        }
        
        // 遍历字符串t,对每个字符出现的次数进行调整,记录其与字符串s的差异。
        for(int i = 0; i<t.length();  i++){
            record[t.charAt(i) - 'a']--;   //并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
        }
        
        // 遍历记录数组,如果有任何一个字符的出现次数不为0,说明两个字符串不是字谜。
        for(int count : record){
            if(count != 0){
                return false;
            }
        }
        
        // 所有字符的出现次数都相等,两个字符串互为字谜。
        return true;
    }


2、两个数组的交集

给定两个数组 nums1nums2 ,返回它们的交集

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

示例 1:

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

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

解:

利用哈希表的set进行处理,构建两个hashset;
先将arr1的元素放进set1,在判断arr2中的元素是否存在于set1中;
如果存在,则放进set2,最后返回set2。
    /**
     * 计算两个数组的交集。
     * 交集中的每个元素都至少出现在两个数组中的一个,并且在结果中没有重复。
     * 
     * @param nums1 第一个数组,可以包含重复元素。
     * @param nums2 第二个数组,可以包含重复元素。
     * @return 一个包含两个数组交集的数组,没有重复元素。
     *         如果其中一个或两个数组为空,则返回一个空数组。
     */
   public int[] intersection(int[] nums1, int[] nums2) {
        //先判断空数组的情况
        if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
            return new int[0];
        }

        // 使用HashSet存储nums1中的元素,以便快速查找
        Set<Integer> set1 = new HashSet<>();  // 存储nums1中的元素
        Set<Integer> resSet = new HashSet<>(); //存储nums2与set1的交集

        // 将nums1中的元素添加到set1中
        for (int i : nums1) {
            set1.add(i);
        }
        // 遍历nums2,如果元素也在nums1中(即在set1中),则添加到resSet中
        for (int i : nums2) {
            if (set1.contains(i)) { //如果i存在于set1中,则加入resSet中
                resSet.add(i);
            }
        }

        // 方法1:通过stream流方式,将HashSet转换为数组
        //return resSet.stream().mapToInt(x -> x).toArray();

        // 方法2:另外申请一个数组存放setRes中的元素,最后返回数组
        int[] arr = new int[resSet.size()];
        int j = 0;
        // 遍历resSet,将元素填充到arr中
        for (int i : resSet) {
            arr[j++] = i;
        }
        return arr;
    }

3、快乐数

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

「快乐数」 定义为:

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

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

输入:n = 2
输出:false

解:

无限循环即求和的结果sum会反复出现,用hash表存储sum;
每次循环判断set中是否存在sum,如果存在则返回false。
    /**
     * 检查一个数是否为“快乐数”。
     * “快乐数”是在一系列操作后,其值最终变为1的数。
     * 操作是将给定的数替换为其各个位数的平方和。
     * 
     * @param n 要检查的数
     * @return 如果n是快乐数,则返回true;否则返回false。
     */
    //快乐数
    public static boolean isHappy(int n) {
        // 使用HashSet存储已经出现过的数,以检查是否存在循环。
        //构建一个hash表判断当前的sum是否存在与表中,存在则返回false
        Set<Integer> set1 = new HashSet<>();
        // 当当前数不为1时,继续进行下一轮迭代。
        while (n != 1) {
            // 计算当前数各个位数的平方和,作为下一个数。
            n = getSum(n);
            // 如果当前数已经在HashSet中出现过,则存在循环,不是快乐数。
            if (set1.contains(n)) {
                return false;
            }
            // 将当前数添加到HashSet中,标记为已经出现。
            set1.add(n);
        }
        // 如果迭代结束时,数为1,则是快乐数。
        return true;
    }
    //获得n对应的每一位的sum
    public static int getSum(int n) {
        int sum = 0;
        while (n > 0) {
            int temp = n % 10;
            sum += temp * temp;
            n /= 10;
        }
        return sum;
    }


4、两数之和

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

假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

解:

①:暴力双层遍历解
②:使用hash的map,降低空间复杂度;
从arr中依次取出元素,判断temp = target-arr[i]是否存在map的key中;
不存在则将数组放入map中,map的key对应arr[i], value对应i;
存在则返回数组:{i,map.get(temp)}。
    /**
     * 寻找数组中两个数之和等于特定目标值的那两个数的索引。
     * 该方法通过两层循环遍历数组,寻找满足条件的两个数的索引。
     * 第一层循环从数组的第一个元素开始,第二层循环从当前元素的下一个元素开始,以避免检查相同的元素。
     * 如果找到满足条件的两个数,方法立即返回这两个数的索引。
     * 如果没有找到满足条件的两个数,方法最终返回一个包含-1的数组,表示未找到。
     *
     * @param nums 输入的整数数组。
     * @param target 目标值,即需要找到的两个数之和。
     * @return 包含两个数的索引的数组。如果找不到满足条件的两个数,返回包含-1的数组。
     */
    //两数之和 时间复杂度o*o
    public static int[] twoSum(int[] nums, int target) {
        // 初始化一个长度为2的数组,用于存储两个数的索引。
        int[] arr = new int[2];
        // 第一层循环遍历数组中的每个元素。
        for (int i = 0; i < nums.length; i++) {
            // 计算与当前元素配对的目标值。
            int n = target - nums[i];
            // 第二层循环从当前元素的下一个元素开始,寻找与n相等的元素。
            for (int j = i + 1; j < nums.length; j++) {
                // 如果找到与n相等的元素,说明找到了满足条件的两个数。
                if (nums[j] == n) {
                    // 将这两个数的索引存储到结果数组中。
                    arr[0] = i;
                    arr[1] = j;
                    // 返回结果数组。
                    return arr;
                }
            }
        }
        // 如果没有找到满足条件的两个数,返回一个包含-1的数组。
        return arr;
    }
    /**
     * 使用哈希表查找数组中是否存在两个数之和等于特定目标值。
     * 这个方法通过遍历数组并使用哈希表来检查是否存在一个数与当前元素相加等于目标值。
     * 如果存在,方法会立即返回这两个数的索引。
     * 
     * @param nums 输入的整数数组。
     * @param target 目标值,我们寻找两个数的和等于这个值。
     * @return 如果存在两个数的和等于目标值,则返回包含这两个数索引的数组;否则返回一个空数组。
     */
    //两数之和,哈希map实现
    //因为最后需要返回下标,所以将数组下标存储在Value中,数组的值存储在Key中进行比较
    public static int[] twoSumMap(int[] nums, int target) {
        // 初始化一个哈希表来存储遍历过的数字及其索引。
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            // 计算与当前元素配对的目标元素。
            // 遍历当前元素,并在map中寻找是否有匹配的key
            int temp = target - nums[i];
            // 检查哈希表中是否已存在这个目标元素。
            if (map.containsKey(temp)) {
                // 如果存在,说明找到了两个数的和等于目标值,返回它们的索引。
                return new int[]{i, map.get(temp)};
            }
            // 将当前元素及其索引添加到哈希表中,以供后续查找使用。
            map.put(nums[i], i);
        }
        // 如果没有找到符合条件的两个数,返回一个空数组。
        return new int[0];
    }

5、四数之和Ⅱ

给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例 2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

解:

四个数组求四数之和为target,返回的是下标元组,因此不需要去重
先遍历前两个数组,求一个target,以及次数,用map存储,key存储target,value存储次数;
再对num3和num4进行双重遍历,寻找判断-target2,是否存在于map中;
如果存在,则累加map的value次数
    /**
     * 计算四个数组中所有元素的四元组之和等于指定目标值的数量。
     * 该方法通过构建一个映射来存储两个数组的元素和,然后利用这个映射来快速查找另外两个数组的元素和的相反数在映射中的出现次数。
     * 这种方法避免了对所有可能的四元组进行直接遍历,降低了时间复杂度。
     *
     * @param nums1 第一个数组,包含整数元素。
     * @param nums2 第二个数组,包含整数元素。
     * @param nums3 第三个数组,包含整数元素。
     * @param nums4 第四个数组,包含整数元素。
     * @return 返回满足条件的四元组数量。
     */
    //四个数组求四数之和为target,返回的是下标元组,因此不需要去重
    public static int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        // 初始化计数器为0,用于记录满足条件的四元组数量。
        int count = 0;
        // 获取第一个数组的长度,用于后续循环遍历。
        int n = nums1.length;
        // 创建一个HashMap,用于存储两个数组元素和的频次。
        HashMap<Integer, Integer> map1 = new HashMap<>();
        // 遍历第一个数组和第二个数组的所有组合,计算它们的和,并将和及其出现次数存储到HashMap中。
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                int target = nums1[i] + nums2[j];
                // 将元素和target及其出现次数存储到map1中,如果target已存在,则更新其出现次数。
                //获取map1中,target出现的次数
                map1.put(target, map1.getOrDefault(target, 0) + 1);
            }
        }
        // 遍历第三个数组和第四个数组的所有组合,计算它们的和的相反数,并在HashMap中查找这个相反数的出现次数。
        for (int k = 0; k < n; k++) {
            for (int l = 0; l < n; l++) {
                int target2 = -nums3[k] - nums4[l];
                // 累加找到的相反数在HashMap中出现的次数到count中。
                //将map1中的次数累加到count中
                count += map1.getOrDefault(target2, 0);
            }
        }
        // 返回满足条件的四元组数量。
        return count;
    }

6、赎金信

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

输入:ransomNote = "aa", magazine = "ab"
输出:false

示例 3:

输入:ransomNote = "aa", magazine = "aab"
输出:true

解:

如果是判断字母的,都可以考虑构建一个0~25的映射a~z的哈希表判断次数,再进行比较次数。
构建int[26],分别存储两个string中字母出现的次数,再循环遍历,用ransonNote次数-magazine次数,如果出现小于0的情况,直接返回false即可。
    /**
     * 判断ransomNote字符串是否可以用magazine字符串中的字符组成。
     * 该方法通过统计每个字母的出现次数来判断,使用数组来简化操作。
     *
     * @param ransomNote 赎金信字符串,可能包含任意字符。
     * @param magazine 杂志字符串,用于检查是否包含赎金信中的字符。
     * @return 如果magazine中的字符可以组成ransomNote,则返回true;否则返回false。
     */    
	//赎金信 构建26的哈希表
    public static boolean canConstruct(String ransomNote, String magazine) {
        // 初始化一个长度为26的数组,用于统计字母'a'到'z'在ransomNote中的出现次数。
        int[] arr = new int[26];
        
        // 遍历ransomNote字符串,统计每个字符的出现次数。
        for (char c : ransomNote.toCharArray()) {
            arr[c - 'a']++;
        }
        
        // 遍历magazine字符串,减少每个字符的出现次数。
        for (char c : magazine.toCharArray()) {
            arr[c - 'a']--;
        }
        //检查数组中的每个值,如果有任何一个值小于0,说明magazine中缺少某个字符。
        //如果数组中存在负数,说明ransomNote字符串中存在magazine中没有的字符
        for (int i : arr) {
            if (i < 0) {
                return false;
            }
        }
        // 如果数组中的所有值都大于等于0,说明magazine中的字符可以组成ransomNote。
        return true;
	}

7、三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

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

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

解:

返回具体的值,可以考虑双指针法,难点在于判断边界去重。双指针法的关键在于排序后,无法返回下标
①双指针法: 先排序,再构建初始的i,然后left和right,sum = arr[i] + arr[left] + arr[right]
如果sum<0;left++;
如果sum>0;right--;
如果sum=0;保存当前{arr[i], arr[left], arr[right]};
	判断去重,arr[left]==arr[left+1]以及arr[right]==arr[right-1]进行收缩
		不相等的话直接left++; right--;
    /**
     * 寻找数组中所有不重复的三元组,这些三元组的和为零。
     * 
     * @param nums 输入的整数数组
     * @return 返回一个列表,包含所有和为零的不重复三元组
     */
    //三数之和,返回三数的二维列表
    public static List<List<Integer>> threeSum(int[] nums) {
        // 初始化结果列表
        List<List<Integer>> lists = new ArrayList<>();
        //排序数组
        Arrays.sort(nums);
        // 遍历数组,寻找所有可能的三元组
        for (int i = 0; i < nums.length - 2; i++) {
            // 如果当前元素大于0,说明后续不会有和为负数的三元组,直接返回结果
            if (nums[i] > 0) {
                breaks;
            }
            // 如果当前元素与前一个元素相同,跳过当前元素,避免重复的三元组
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            // 设置左指针和右指针
            int left = i + 1;
            int right = nums.length - 1;
            // 使用双指针技术,寻找和为零的三元组
            while (left < right) {
                // 计算当前三元组的和
                int sum = nums[i] + nums[left] + nums[right]; //双指针
                // 如果和大于0,右指针向左移动
                if (sum > 0) {
                    right--;
                }
                // 如果和小于0,左指针向右移动
                else if (sum < 0) {
                    left++;
                }
                // 如果和等于0,找到一个符合条件的三元组
                else {
                    //将这组数据加入lists中用于返回
                    lists.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    // 移动左指针,跳过重复的元素
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    // 移动右指针,跳过重复的元素
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    // 两边指针同时向中间移动,继续寻找下一个可能的三元组
                    //两边同时收缩!!!
                    left++;
                    right--;
                }
            }
        }
        // 返回结果列表
        return lists;
    }

8、四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

提示:

  • 1 <= nums.length <= 200
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109

解:

    /**
     * 寻找数组中所有不重复的四元组,这些四元组的和为目标值。
     * @param nums 输入的整数数组
     * @param target 目标和
     * @return 返回一个列表,包含所有符合条件的四元组
     */
    //四数之和,返回四数的二维列表
    public static List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> lists = new ArrayList<>();
        // 对数组进行排序,以便后续使用双指针技术
        Arrays.sort(nums);
        // 遍历数组,固定第一个数
        for (int i = 0; i < nums.length - 3; i++) {
            // 如果当前数与目标和的差大于等于目标和本身,说明后续组合不可能达到目标和,提前返回
            if (nums[i] > target && (nums[i] >= 0 || target >= 0)) {
                return lists;
            }
            // 如果当前数与前一个数相同,跳过以避免重复解
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            // 遍历数组,固定第二个数
            for (int j = i + 1; j < nums.length - 2; j++) {
                // 如果当前两个数的和与目标和的差大于等于目标和本身,说明后续组合不可能达到目标和,跳出内层循环
                if (nums[i] + nums[j] > target && (nums[i] + nums[j] >= 0 || target >= 0)) {
                    break;
                }
                // 如果当前数与前一个数相同,跳过以避免重复解
                if (j-1 > i && nums[j] == nums[j - 1]) {
                    continue;
                }
                // 使用双指针,左指针指向j后的第一个数,右指针指向数组最后一个数
                int left = j + 1;
                int right = nums.length - 1;
                // 当左指针小于右指针时,执行循环
                while (left < right) {
                    // 计算当前四个数的和
                    int sum = nums[i] + nums[j] + nums[left] + nums[right]; //双指针
                    // 如果和大于目标和,右指针向左移动
                    if (sum > target) {
                        right--;
                    }
                    // 如果和小于目标和,左指针向右移动
                    else if (sum < target) {
                        left++;
                    }
                    // 如果和等于目标和,将四个数加入结果列表
                    else {
                        lists.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        // 移动左指针,跳过重复的数
                        while (left < right && nums[left] == nums[left + 1]) {
                            left++;
                        }
                        // 移动右指针,跳过重复的数
                        while (left < right && nums[right] == nums[right - 1]) {
                            right--;
                        }
                        // 左右指针同时向内移动,继续寻找下一个解
                        // 两边同时收缩!!!
                        left++;
                        right--;
                    }
                }
            }
        }
        return lists;
    }

  • 13
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值