算法day06-哈希表篇(2)
目录
- 四数相加II
- 赎金信
- 三数之和
- 四数之和
一、四数相加II

主要思路:
-
分组+哈希表优化:
-
将四个数组分成两组,
nums1和nums2为一组,nums3和nums4为另一组。 -
首先计算
nums1和nums2中所有可能的元素对的和,并用哈希表记录每个和出现的次数。 -
然后计算
nums3和nums4中所有可能的元素对的和的相反数(即-(nums3[k] + nums4[l])),并在哈希表中查找该值是否存在。如果存在,说明存在对应的nums1[i] + nums2[j]使得总和为 0,此时将哈希表中的计数累加到结果中。
-
-
复杂度分析:
-
时间复杂度: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)
二、赎金信

该代码用于判断 ransomNote 是否能由 magazine 中的字符构成(每个字符只能用一次)。思路是:
-
统计
magazine的字符频率(用HashMap存储)。 -
遍历
ransomNote的字符,检查是否在magazine中有足够的字符可用:-
如果某个字符不存在或数量不足,返回
false。 -
否则,减少该字符的剩余可用数量。
-
-
如果全部字符都能匹配,返回
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] 内使用双指针 left 和 right 进行查找。
-
如果当前三数之和
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+剪枝去重的方式,不仅能解决这类和为目标的组合问题,也能迁移到很多其他类似题上。尤其三数之和是很适合练习双指针写法和去重逻辑的。
总的来说,刷这几题对我帮助挺大,尤其是在理解“组合类问题怎么拆解”、“怎么控制重复解”、“怎么用哈希表提升效率”等方面,希望这篇总结也能对你有点启发。
浙公网安备 33010602011771号