算法day06-哈希表篇(2)

目录

  1. 四数相加II
  2. 赎金信
  3. 三数之和
  4. 四数之和

一、四数相加II

454. 四数相加 II - 力扣(LeetCode)

   主要思路:

  1. 分组+哈希表优化

    • 将四个数组分成两组,nums1 和 nums2 为一组,nums3 和 nums4 为另一组。

    • 首先计算 nums1 和 nums2 中所有可能的元素对的和,并用哈希表记录每个和出现的次数。

    • 然后计算 nums3 和 nums4 中所有可能的元素对的和的相反数(即 -(nums3[k] + nums4[l])),并在哈希表中查找该值是否存在。如果存在,说明存在对应的 nums1[i] + nums2[j] 使得总和为 0,此时将哈希表中的计数累加到结果中。

  2. 复杂度分析

    • 时间复杂度:O(N²),其中 N 是数组的长度。我们需要遍历 nums1 和 nums2 的所有组合(O(N²)),以及 nums3 和 nums4 的所有组合(O(N²)),哈希表的插入和查询操作均为 O(1)。

    • 空间复杂度:O(N²),哈希表最多存储 N² 个不同的和。

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int n = nums1.length;
        Map<Integer, Integer> map = new HashMap<>();
        for(int n1 : nums1){        //先把1和2组中的和存储到map中
            for(int n2 : nums2){
                int sum = n1+n2;
                map.put(sum, map.getOrDefault(sum,0)+1);
            }
        }
        int count = 0;
        for(int n1: nums3){         //看3和4组的和是否在map中出现过,出现过几次
            for(int n2: nums4){
                int target = - n1 - n2;
                if(map.containsKey(target)){
                    count += map.get(target);
                }
            }
        }
        return count;
    }
}//时间复杂度:O(N^2)
//空间复杂度:O(N^2)

二、赎金信

  383. 赎金信 - 力扣(LeetCode)

  该代码用于判断 ransomNote 是否能由 magazine 中的字符构成(每个字符只能用一次)。思路是:

  1. 统计 magazine 的字符频率(用 HashMap 存储)。

  2. 遍历 ransomNote 的字符,检查是否在 magazine 中有足够的字符可用:

    • 如果某个字符不存在或数量不足,返回 false

    • 否则,减少该字符的剩余可用数量。

  3. 如果全部字符都能匹配,返回 true

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        char[] cc = magazine.toCharArray();
        Map<Character,Integer> map = new HashMap<>();
        for(char c : cc){
            map.put(c, map.getOrDefault(c,0)+1);        //记录每个字符出现的次数
        }
        for(char c : ransomNote.toCharArray()){
            if(!map.containsKey(c)){    
              return false;  
                
            }else{               //若r中出现的字符在m中出现过
                map.put(c, map.getOrDefault(c,0)-1);        //每出现一次就消耗一次
                if(map.getOrDefault(c,0)<0){
                    return false;
                }
            }
            
        }
        return true;
    }
}
//时间复杂度:O(N+M)
//空间复杂度:O(M)(可以优化成O(1))

三、三数之和

  地址:https://leetcode.cn/problems/3sum/

   主要思路:先对数组 nums 进行排序,然后枚举每一个元素作为三元组的第一个数。对于每个固定的数 nums[i],跳过重复值以避免重复解。接下来在其右侧区间 [i+1, nums.length-1] 内使用双指针 leftright 进行查找。

  • 如果当前三数之和 sum < 0,说明需要更大的数,移动左指针 left++

  • 如果 sum > 0说明需要数,移动指针 `right

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums); // 排序
        
        for (int i = 0; i < nums.length - 2; i++) {
            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];
                if (sum == 0) {
                    result.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--;
                } else if (sum < 0) {
                    left++; // 需要更大的数
                } else {
                    right--; // 需要更小的数
                }
            }
        }
        
        return result;
    }
}

四、四数之和

  地址:https://leetcode.cn/problems/4sum/

   主要思路:这道题可以用dfs来做,依次对每个数选或不选,就可以选到所有的情况。但这里有一个问题是,选出的path之间不能重复(顺序不同但数字一样也看成重复)。

class Solution {
    static List<List<Integer>> res;
    static List<Integer> path;
    int[] visited;
    public List<List<Integer>> fourSum(int[] nums, int target) {
        res = new ArrayList<>();
        path = new ArrayList<>();
        visited = new int[nums.length];
        Arrays.sort(nums);
        dfs(nums, target, 0, 0);
        
        return res;
    }

    public void dfs(int[] nums, int target, int start, long sum){
        if(path.size()==4){
            if(sum == target){
                res.add(new ArrayList<>(path));
            }
            return;
        }

        for(int i=start; i<nums.length; i++){
            if(i > start && nums[i] == nums[i-1]){
                continue;
            }
            path.add(nums[i]);
            dfs(nums, target, i+1, sum+nums[i]);
            path.remove(path.size()-1);
        }
    }
}
//时间复杂度:O(N^4)
//空间复杂度:O(N)

 

总结

  这篇博客主要是对几道经典的“和类问题”进行梳理,包括四数相加 II、赎金信、三数之和、四数之和。虽然每道题具体做法不同,但其实背后的解题思路是相通的。

像四数相加 II 是典型的“分组 + 哈希优化”,思路比较巧,通过先处理前两个数组的所有组合和,再查找后两个数组对应的反向值,能大大减少时间复杂度。赎金信用的是最基础的哈希计数思想,但也是很多字符串题的基础套路,属于必掌握的部分。

  三数之和和四数之和这两个题就更有代表性了,用排序+双指针或者 DFS+剪枝去重的方式,不仅能解决这类和为目标的组合问题,也能迁移到很多其他类似题上。尤其三数之和是很适合练习双指针写法和去重逻辑的。

  总的来说,刷这几题对我帮助挺大,尤其是在理解“组合类问题怎么拆解”、“怎么控制重复解”、“怎么用哈希表提升效率”等方面,希望这篇总结也能对你有点启发。

posted @ 2025-04-29 23:25  筱倩  阅读(107)  评论(0)    收藏  举报