资源引用:
leetcode题目:
454.四数相加Ⅱ(454. 四数相加 II - 力扣(LeetCode))
383.赎金信(383. 赎金信 - 力扣(LeetCode))
15.三数之和(15. 三数之和 - 力扣(LeetCode))
18.四数之和(18. 四数之和 - 力扣(LeetCode))
例行碎碎念:
今天也追赶上了一些进度,虽然生病感冒,但今天很好的坚持了从早到晚的复习,秉承开源的精神我也将自己的复习资料整理出来分享给其他同学,这也给了我一些成就感!在晚上虽然很疲惫,但还是坚持完成了一天的代码随想录,哈希表的利弊变得更加清晰,数组的小而美也令我认知刷新,更重要的是自己能够很好的复用先前学到的技巧(尤其是双指针),继续加油!
454.四数相加Ⅱ(454. 四数相加 II - 力扣(LeetCode))
题目分析:
四个长度为n的整数数组,分别从中取元素组成四项元组,问有多少元组的项之和为0,最后返回符合条件的元组数,这是讨论是否存在的问题,并返回存在数量的问题,需要存储key为元素值,value为元素个数,考虑使用HashMap。
解题思路:
四个数组的长度均为n,则元组最多有n^4个。边界条件是n为零,即四个数组均为空时,直接返回0。
关键在于是四个map,还是value是四个下标。
若采用四个下标的形式进行存储,则时间复杂度极高,与暴力解法没有差别,且key值不能重复,该解法无法实现。
考虑使用类似二分法的思想,将num1和num2作为一个组合,将num3和num4作为一个组合,每个组合分别具有n^2个元组,时间复杂度从n^4缩减到n^2,第一个组合用map1存储,key为num1和num2中元素和的不同情况,value为不同情况存在的组合数;第二个组合用map2存储同理。对map1中的每个key值,从map2中检查是否存在其相反数,若存在,则将二者的value值相乘并加到返回值res上。
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
if(nums1.length==0 || nums2.length==0 || nums3.length==0 || nums4.length==0){
return 0;
}
Map<Integer,Integer> map1 = new HashMap<>();
Map<Integer,Integer> map2 = new HashMap<>();
int res=0;
for(int i : nums1){
for(int j : nums2){
int sum1 = i+j;
int new_value=0;
if(map1.containsKey(sum1)){
new_value = map1.get(sum1)+1;
}else{
new_value = 1;
}
map1.put(sum1,new_value);
}
}
for(int k : nums3){
for(int l : nums4){
int sum2 = k+l;
int new_value=0;
if(map2.containsKey(sum2)){
new_value = map2.get(sum2)+1;
}else{
new_value = 1;
}
map2.put(sum2,new_value);
}
}
for (Map.Entry<Integer, Integer> entry : map1.entrySet()) {
int sum1 = entry.getKey();
if(map2.containsKey(-sum1)){
res+=(entry.getValue()*map2.get(-sum1));
}
}
return res;
}
}
进一步简化:
c和d遍历时无需放入哈希表中,为减少开销,若存在(c,d)组合的两数之和的相反数存在于map1中,直接取map1对应的value加到计数res上即可。
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
if(nums1.length==0 || nums2.length==0 || nums3.length==0 || nums4.length==0){
return 0;
}
Map<Integer,Integer> map1 = new HashMap<>();
int res=0;
for(int i : nums1){
for(int j : nums2){
int sum1 = i+j;
int new_value=0;
if(map1.containsKey(sum1)){
new_value = map1.get(sum1)+1;
}else{
new_value = 1;
}
map1.put(sum1,new_value);
}
}
for(int k : nums3){
for(int l : nums4){
int sum2 = k+l;
if(map1.containsKey(-sum2)){
res+=map1.get(-sum2);
}
}
}
return res;
}
}
再进一步简化:
map.getOrDefault(sum, 0) 的使用:如果存在key为sum的值则返回其value,否则返回默认值(此处参数将其规定为0)
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
int res = 0;
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//统计两个数组中的元素之和,同时统计出现的次数,放入map
for (int i : nums1) {
for (int j : nums2) {
int sum = i + j;
map.put(sum, map.getOrDefault(sum, 0) + 1);
}
}
//统计剩余的两个元素的和,在map中找是否存在相加为0的情况,同时记录次数
for (int i : nums3) {
for (int j : nums4) {
res += map.getOrDefault(0 - i - j, 0);
}
}
return res;
}
}
383.赎金信(383. 赎金信 - 力扣(LeetCode))
题目分析:
两个字符串,判断ransomNote中的字符是否全部包含在magazine中,且每个字符是否有其一一对应,即magazine中的每个字符只能在ransomNote,由于是存在问题且不仅需要匹配元素值还要匹配个数,考虑用HashMap解决。
解题思路:
map的键值对存储magazine中的元素值及其出现次数,遍历ransomNote中的每个字符,如果该字符在map中存在,若其value值不为0则将其对应的value值减一,若其value值为0则直接返回false;若该字符不存在,也直接返回false。最终返回true。
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
Map<Character,Integer> map = new HashMap<>();
for(char i : magazine.toCharArray()){
map.put(i,map.getOrDefault(i,0)+1);
}
for(char j : ransomNote.toCharArray()){
if(map.getOrDefault(j,0) == 0){
return false;
}else{
map.put(j,map.getOrDefault(j,0)-1);
}
}
return true;
}
}
反思总结:
灵活运用了for-each循环和map.getOrDefault()方法
然而,map的空间消耗和哈希函数的调用消耗导致:不论是时间层面还是空间层面,该题使用map都不如使用数组,使用数组更加简单有效!
也不要忘了控制边界条件!
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
// 边界条件
if (ransomNote.length() > magazine.length()) {
return false;
}
// 定义一个哈希映射数组
int[] record = new int[26];
// 遍历
for(char c : magazine.toCharArray()){
record[c - 'a'] += 1;
}
for(char c : ransomNote.toCharArray()){
record[c - 'a'] -= 1;
}
// 如果数组中存在负数,说明ransomNote字符串中存在magazine中没有的字符
for(int i : record){
if(i < 0){
return false;
}
}
return true;
}
}
15.三数之和(15. 三数之和 - 力扣(LeetCode))
题目分析:
由于这题要返回的是元组本身,故使用哈希表很不方便,carl哥提示用双指针法。
该题仅有一个数组nums,若长度小于3则直接返回空的List<List<Interger>> res。
关键在于怎样在遍历过程中保证i,j,k两两不相等,且返回元组不可重复。
妙解:
为了保证元组不可重复,需要在遍历过程中避免重复使用值相同的元素,所以将数组排序!
初始化:假设已经从小到大排序,下标i从0开始遍历,left=i+1,right=nums.length-1(1.注意针对i的“去重” )
寻址:若三数之和大于0,说明偏大,right左移1位;若三数之和小于0,说明偏小,left右移1位。重复“寻址”过程,直到left=right为止,在这过程中遇到三数和为0的添入List(全程注意去重!)。
关于去重:
1.i的去重:
由于已经经过排序,如果可以减少对nums[i]相同情况的遍历,但应当注意判断条件应为nums[i] != nums[i-1],而非nums[i] != nums[i+1],因为元组不能重复,但元组内的元素值可以重复!
2.三数之和为0时,left和right相遇前的去重:
参考i的去重,可知应为nums[left]!=nums[left-1],nums[right]!=nums[right+1]吗?注意处理下标防止越界——right+1存在越界风险,且由于left和right不是锚定数所以前后去重无本质区别,考虑方便实现即可。
总结反思:
1.双指针法一定要排序!
2.边界条件处理:①数组长度检查②排序后,nums[i]为正数时,则三数之和不可能为0,直接返回当前result。
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; i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
return result;
}
}
18.四数之和(18. 四数之和 - 力扣(LeetCode))
题目分析:
/*与454.四数相加Ⅱ不同之处在于:①在同一个数组中取数,由于下标不可重复,因此相较于对4个数组操作的难度增大②返回值是满足条件的四元组,而非元组数*/
在15.三数之和的基础上仍然使用双指针。
解题思路:
先将数组排序,a从0下标开始遍历,注意剪枝和去重
嵌套循环b从a+1开始遍历,注意剪枝和去重
初始化left=b+1,right=nums.length-1,
循环不变量是right>left。
b在nums.length-3处为最后一次,a在nums.lengh-4处为最后一次。
其他步骤参考15.三数之和。
tips:同样注意检查剪枝条件:①nums数组长度不小于4②此题target并非确定的值,需要进一步约束剪枝条件为nums[i] > target && (nums[i] >=0 || target >= 0)
总结反思:
1.双指针法就是将类似N数之和的问题的时间复杂度从O(n^N)降为O(n^(N-1))
2.注意数值大小,为防止溢出,需要使用长整型long记录sum
import java.util.*;
public class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums); // 排序数组
List<List<Integer>> result = new ArrayList<>(); // 结果集
for (int k = 0; k < nums.length-3; k++) {
// 剪枝处理
if (nums[k] > target && nums[k] >= 0) {
break;
}
// 对nums[k]去重
if (k > 0 && nums[k] == nums[k - 1]) { //注意当k为0时k-1越界,故做此处理
continue;
}
for (int i = k + 1; i < nums.length-2; i++) {
// 第二级剪枝
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
break;
}
// 对nums[i]去重
if (i > k + 1 && nums[i] == nums[i - 1]) { //与k为0时的处理类似,但重点是i为k+1时不必去重
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
long sum = (long) nums[k] + nums[i] + nums[left] + nums[right];
if (sum > target) {
right--;
} else if (sum < target) {
left++;
} else {
result.add(Arrays.asList(nums[k], nums[i], nums[left], nums[right]));
// 对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
}
return result;
}
}
标签:四数,15,nums,int,随想录,value,right,new,left
From: https://blog.csdn.net/csy031117/article/details/143581236