454. 四数相加 II
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 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
解释:
两个元组如下:
- (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
- (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
class Solution {public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {Map<Integer, Integer> countAB = new HashMap<Integer, Integer>();for (int u : A) {for (int v : B) {countAB.put(u + v, countAB.getOrDefault(u + v, 0) + 1);}}int ans = 0;for (int u : C) {for (int v : D) {if (countAB.containsKey(-u - v)) {ans += countAB.get(-u - v);}}}return ans;}
}
这段代码定义了一个名为 Solution
的类,其中包含一个方法 fourSumCount
。这个方法用于解决四数之和问题:给定四个整数数组 A
, B
, C
, 和 D
,计算有多少个由一个来自 A
和一个来自 B
的数以及一个来自 C
和一个来自 D
的数构成的四元组,它们的和为 0。
逻辑分析:
-
初始化哈希表(Map):首先,创建一个哈希表
countAB
用于存储数组A
和B
中元素两两两两数之和的频次。键为和的值,值为该和出现的次数。 -
计算
A
和B
的组合和:遍历数组A
和B
中的所有元素,计算它们的和,并在哈希表countAB
中累积这些和的计数,使用getOrDefault
方法来简化计数累加逻辑,如果键不存在则默认值为0,然后加1。 -
计算四数和为0的组合数:接着,遍历数组
C
和D
的元素,对于每一对u
和v
,检查countAB
是否包含-u - v
这个键(即如果存在一个来自A
和B
的和与u
和v
之和为相反数),如果存在,则累加该和在countAB
中的频次到答案ans
。 -
返回结果:最后,返回累计的满足条件的四数之和为0的组合总数
ans
。
代码特点与优化:
- 空间换时间:通过预计算
A
和B
的所有组合和及其频次,将原本需要四重循环的问题转换为两重循环加上查找,显著提高了效率。 - 哈希表(Map)使用:利用哈希表的高效查找特性,简化了寻找特定和的操作,从原本可能的线性查找降为接近常数时间。
- 代码简洁性:虽然逻辑清晰,但在实际应用中可能还需考虑边界条件处理(如输入数组长度为0的情况),以及优化点(如适当处理大数以避免溢出问题)。
综上所述,这段代码提供了一种有效且相对高效求解四数之和为0的组合数目的方法,体现了哈希表在优化搜索问题中的应用。
383. 赎金信
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
示例 1:
输入:ransomNote = “a”, magazine = “b”
输出:false
示例 2:
输入:ransomNote = “aa”, magazine = “ab”
输出:false
示例 3:
输入:ransomNote = “aa”, magazine = “aab”
输出:true
class Solution {public boolean canConstruct(String ransomNote, String magazine) {if (ransomNote.length() > magazine.length()) {return false;}int[] cnt = new int[26];for (char c : magazine.toCharArray()) {cnt[c - 'a']++;}for (char c : ransomNote.toCharArray()) {cnt[c - 'a']--;if(cnt[c - 'a'] < 0) {return false;}}return true;}
}
这段代码定义了一个名为 Solution
的类,其中包含一个方法 canConstruct
。这个方法用于判断能否通过重新排列给定的杂志字符串 magazine
中的字符,来拼写出勒索要赎金信字符串 ransomNote
。如果可以,则返回 true
;如果不可以,则返回 false
。
逻辑分析:
-
长度检查:首先,进行一个简单的长度检查,如果赎金信的长度大于杂志字符串长度,显然无法构造,直接返回
false
。 -
字符计数数组初始化:声明一个长度为26的小写英文字母表数组
cnt
,用于计数杂志字符串中每个字符出现的次数。这里利用了小写字母’a’到’z’在ASCII码表中连续的特性,减去’a’后即可映射到数组的索引。 -
计算杂志字符频次:遍历杂志字符串
magazine
的字符,逐个对应回字符在数组cnt
中计数加1。利用了字符与其在ASCII码的关系,减去 ‘a’ 来索引数组位置。 -
检查赎金信字符可用性:接着,遍历赎金信
ransomNote
的字符,逐个检查该字符在cnt
数组中的计数是否足够。如果有足够的字符,就将计数减1;如果不够(即计数小于0),说明该字符在杂志中数量不足,直接返回false
。 -
返回结果:如果成功遍历完赎金信所有字符且未返回,说明可以构造出来,最后返回
true
。
代码特点:
- 空间效率:使用固定大小的计数数组而非哈希表减少了空间消耗,因为只考虑了小写字母,局限了字符范围。
- 时间效率:两重循环,时间复杂度为O(len(ransomNote) + len(magazine)),其中len表示字符串长度,因为分别遍历了两次字符串。
- 简洁性:直接利用字符编码特性进行索引映射,减少了额外的字符判断和映射逻辑。
综上所述,此方法通过直接计数字符频次并检查是否足够来判断是否能构造赎金信,是一种直观且效率较高的实现方式。
15. 三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != 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 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
class Solution {public List<List<Integer>> threeSum(int[] nums) {int n = nums.length;Arrays.sort(nums);List<List<Integer>> ans = new ArrayList<List<Integer>>();// 枚举 afor (int first = 0; first < n; ++first) {// 需要和上一次枚举的数不相同if (first > 0 && nums[first] == nums[first - 1]) {continue;}// c 对应的指针初始指向数组的最右端int third = n - 1;int target = -nums[first];// 枚举 bfor (int second = first + 1; second < n; ++second) {// 需要和上一次枚举的数不相同if (second > first + 1 && nums[second] == nums[second - 1]) {continue;}// 需要保证 b 的指针在 c 的指针的左侧while (second < third && nums[second] + nums[third] > target) {--third;}// 如果指针重合,随着 b 后续的增加// 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环if (second == third) {break;}if (nums[second] + nums[third] == target) {List<Integer> list = new ArrayList<Integer>();list.add(nums[first]);list.add(nums[second]);list.add(nums[third]);ans.add(list);}}}return ans;}
}
这段代码是用于解决“三数之和”问题的Java实现,给定一个整数数组 nums
,找出数组中所有和为0的三个数的组合,并返回这些组合的列表。以下是代码的逻辑解析:
-
初始化:
- 首先对数组
nums
进行排序,便于后续的双指针操作。 - 初始化一个空的结果列表
ans
,用于存储满足条件的三元组。
- 首先对数组
-
外层循环枚举 a:
-
用变量
first
作为外层循环变量,从0遍历到数组长度。 -
避度处理重复值:若当前
first
不是第一个元素且与前一个元素值相同,则跳过,避免重复解。 -
初始化
third
指针为数组最后一个元素,目标值target
为-nums[first]
,以使a + b + c = 0
。
-
-
中层循环枚举 b:
- 用变量
second
作为中层循环变量,从first + 1
开始遍历。 - 处理重复值:若
second
不是first + 1
且与前一个元素值相同,则跳过,避免重复解。 - 维护
third
指针位置,使其满足nums[second] + nums[third] <= target
,不断向左移动直到符合条件或second == third
。 - 若
second
与third
重合,说明后续增加的second
不可能再满足条件,直接跳出循环。
- 用变量
-
检查并添加解:
- 当
nums[second] + nums[third] == target
成立时,说明找到了一个解。 - 将
nums[first]
,nums[second]
,nums[third]
加入临时列表list
,然后将list
添加到结果列表ans
中。
- 当
-
返回结果:
- 遍历完成后返回结果列表
ans
。
- 遍历完成后返回结果列表
这段代码通过排序和双指针技巧高效地解决了三数之和问题,时间复杂度主要受排序影响为O(nlogn),空间复杂度为O(n)(最坏情况下存储所有解)。注意,由于代码中存在若干处逻辑错误(如循环更新语句末尾的 ++
应替换为 ;
),修正后才能正常运行。
18. 四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
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]]
class Solution {public List<List<Integer>> fourSum(int[] nums, int target) {List<List<Integer>> quadruplets = new ArrayList<List<Integer>>();if (nums == null || nums.length < 4) {return quadruplets;}Arrays.sort(nums);int length = nums.length;for (int i = 0; i < length - 3; i++) {if (i > 0 && nums[i] == nums[i - 1]) {continue;}if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {break;}if ((long) nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {continue;}for (int j = i + 1; j < length - 2; j++) {if (j > i + 1 && nums[j] == nums[j - 1]) {continue;}if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {break;}if ((long) nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {continue;}int left = j + 1, right = length - 1;while (left < right) {long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];if (sum == target) {quadruplets.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));while (left < right && nums[left] == nums[left + 1]) {left++;}left++;while (left < right && nums[right] == nums[right - 1]) {right--;}right--;} else if (sum < target) {left++;} else {right--;}}}}return quadruplets;}
}
这段代码是一个Java实现,用于解决寻找数组中所有和为目标值的四数组合(4Sum)的问题。给定一个整数数组 nums
和一个目标整数 target
,如果 nums
中存在四个元素 a, b, c, 和 d 使得 a + b + c + d 的和等于 target
,那么找出所有这样的四元组。返回一个二维列表,其中每个列表表示一个四元组。
解析代码逻辑:
-
初始化: 创建一个空的
quadruplets
列表,用于存放所有满足条件的四元组。如果输入数组为空或长度小于4,直接返回空列表。 -
排序: 对
nums
进行排序,便于使用双指针法减少搜索空间。 -
三层循环:
- 外层循环 (
i
): 遍历数组,避免重复,若当前数与前一个相同则跳过。 - 中层循环 (
j
): 从i+1
开始,同样避免重复,计算四个数之和的上下界,提前剪枝。 - 双指针法 (
left
,right
): 在j+1
和length-1
之间,寻找另外两个数,使得四数之和等于target
。若找到,添加四元组到结果列表并跳过重复值,否则根据和调整指针。
- 外层循环 (
-
返回结果: 最后返回所有满足条件的四元组列表
quadruplets
。
关键点:
- 排序 是基础,使得双指针法可行。
- 剪枝策略 减少不必要的循环,提高效率。
- 避免重复 通过跳过相同元素,确保结果唯一性。
注意:代码中有部分地方需要修正以确保正确性,例如 quadruplets.add(Arrays.asList(...))
应该为 quadruplets.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]))
。此外,确保所有逻辑分支和边界条件正确处理。