代码随想录:哈希表7-9

383.赎金信

题目

        给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。

代码

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] hash = new int [26]; //存储小写字母的哈希值

        //遍历r,字母出现一次,对应hash值-1
        for(int i=0;i < ransomNote.length();i++){
            hash[ransomNote.charAt(i) - 'a']--;
        }

        //遍历m,字母出现一次,对应hash值+1
        for(int i=0;i < magazine.length();i++){
            hash[magazine.charAt(i) - 'a']++;
        }

        //遍历hash,如果全部大于等于0,说明r属于s
        for(int i=0;i < hash.length;i++){
            if(hash[i] < 0){  //某个字母哈希值小于0,说明s不够
                return false;
            }
        }
        return true;

    }
}

总结

1.原理

(1)为什么用hash表

        题目的核心是判断r的每个小写字母是否在s中出现。其实就是r包含于s中。和字母异位词题目类似。

(2)为什么用数组hash

        小写字母长度受限

(3)算法流程

        初始化一个长度为26的int型hash数组,默认值为0。先变量字符串r,每一个字符,都让对应的hash值--。再遍历字符串s,每一个字符,都让对应的hash值++。最后,遍历hash数组,如果存在值小于0的,说明r比较多,s不够了,返回false。最后hash数组全大于等于0,返回true。

2.语法问题

(1)String长度:str.length()

(2)获取String第i个字符:str.charAt(i)

第二次刷题总结

1.算法没问题,就是对String进行遍历的时候,语法山上不能用char c: str的增强for,不然编译会出错,还是乖乖的写普通的for循环。

15.三数之和

题目

        给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

        你返回所有和为 0 且不重复的三元组。

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

代码(算法去重)

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();
        for(int i=0; i < nums.length - 2; i++){
            if(i > 0 && nums[i] == nums[i-1]){
                continue;
            }
            int j = i + 1;
            int k = nums.length - 1;
            while(j < k){
                int sum = nums[i]+nums[j]+nums[k];
                if(sum == 0){
                    //res.add(Stream.of(nums[i],nums[j],nums[k]).collect(Collectors.toList()));
                    res.add(Arrays.asList(nums[i],nums[j],nums[k]));
                    while(j < k && nums[j] == nums[j+1]){
                        j++;
                    }
                    while(j < k && nums[k] == nums[k-1]){
                        k--;
                    }
                    j++;
                    k--;
                }
                else if(sum > 0){
                    k--;
                }
                else{
                    j++;
                }
            } 
        }
        return res;
    }
}

代码(用set去重通不过版)

        这个代码是很拧巴写出来的,没有排序数组,直接硬生生的用set去重了。但是时间复杂度也很高,力扣上会超时。只是刚刚学了stream流,想操作一下列表,set的转换。

        注意:每一个元组输出是其实是递增有序的,如果按这个方法不对数组排序直接找,找到之后,也要把对应的这个list递增排序一下,再add到set中,不然set里面的list都是乱的。如下图。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Set<List<Integer>> set = new HashSet<>();
        for(int i=0; i < nums.length; i++){
            int j;
            int k; 
            for(j=i+1; j < nums.length; j++){
                for(k=j+1; k < nums.length; k++){
                    int sum = nums[i] + nums[j] + nums[k];
                    if(sum == 0){
                        List<Integer> list = Stream.of(nums[i],nums[j],nums[k]).collect(Collectors.toList());
                        Collections.sort(list); 
                        set.add(list);
                    }
                }     
            }
        }
        List<List<Integer>> res = set.stream().collect(Collectors.toList());
        return res;
    }
}

代码(用set去重通过版)

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Set<List<Integer>> set = new HashSet<>();
        Arrays.sort(nums);
        for(int i=0; i < nums.length; i++){
            int j = i + 1;
            int k = nums.length - 1; 
            while(j < k){
                int sum = nums[i] + nums[j] + nums[k];
                if(sum == 0){
                    List<Integer> list = Stream.of(nums[i],nums[j],nums[k]).collect(Collectors.toList()); 
                    set.add(list);
                    j++;
                    k--;
                }
                else if(sum > 0){
                    k--;
                }  
                else{
                    j++;
                }
            }
        }
        List<List<Integer>> res = set.stream().collect(Collectors.toList());
        return res;
    }
}

总结

1.原理

(1)为什么不能用hash法

        题目要求是找一个int数组中满足a+b+c=3的所有情况,且每个情况是唯一的。如果用hash法,类似于两数之和==target,可以用一个map哈希表,key存储ab之和,value存储对应a和b的值。然后,我们用两个for循环遍历a和b,在map哈希中寻找-(a+b)是否存在。

        但这里存在两个问题,第一,map的key是唯一了,可能存在a1+b1=0且a2+b2=0,但是map中key=0的情况只能存储一次,不能把所有ab的情况存下来。第二,结果要求三元组不重复,这里也不能保证。

(2)为什么用双指针法

        因为这里最后返回的只是abc的数值,不用返回其对应的下标,所以可以对数组进行排序,然后用双指针。

(3)双指针法算法流程

        首先,对nums进行升序排序。然后,用for循环遍历nums,索引i代表a,相当于a先固定,再去[i+1,length-1]区间里边找所有满足b+c==-a 的情况。

        那怎么找呢。我们将left指向i+1,right指向length-1。当left<right时,循环计算sum=a+b+c。如果sum大于0,说明c太大了,就让right--。如果sum小于0,说明b太小了,left++。如果sum等于0,就把当前的abc加入list列表。

(4)如何去重

        首先如何对a去重,就是刚进入i的for循环之后,判断nums[i]和nums[i-1]的值是否一样,如果一样,用continue跳出当前for循环。因为当索引=i,a固定时,我们会在[i+1,length-1]区间里边找所有满足b+c==-a 的情况。而在上一轮索引=i-1时,我们会在[i,length-1]区间里边找所有满足b+c==-a 的情况,这里的情况肯定能把下一轮包括进去,所以可以直接continue。注意,要求i不能是0。

        其次,如何对b和c去重,就是当left和right正好指向符合要求的b和c,即a+b+c==0时,我们会把当前的情况add进结果。然后我们要循环判断如果nums[left]==nums[left+1],要让left++,如果nums[right]==nums[right-1],要让right++,也就是把b和c出现相同值的情况给跳过。

2.注意点

(1)首先要对b和c进行去重时,循环里面的left < right条件不能省略,不然right和left一直移动,可能会导致下标越界。

(2)对b和c的while循环去重结束, right--和left++不能漏掉, 因为去重只是让b和c指向最后一个相同的元素,而不是新的元素,因此right和left还要指向新的元素,然后继续不断靠近。

3.优化点

(1)进入for循环后,可以提前剪枝判断,如果nums[i] > 0,可以直接return result,因为如果a大于0,因为递增排序了,a+b+c必然大于0

(2)for循环的区间可以缩小到nums.length - 2,是因为a最多指向倒数第三个元素,后边两个元素要留给b和c。

4.语法

(1)如何对int数组进行排序:Arrays.sort(nums)

(2)如何把a,b,c转为list:Arrays.asList(nums[i], nums[left], nums[right])

第二次刷题总结

1.这次写的代码更简洁一点了,附在下面。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> result = new ArrayList<>();

        for(int i=0; i < nums.length; i++){
            if(i > 0 && nums[i] == nums[i - 1]){
                continue;
            }

            int j = i + 1;
            int k = nums.length - 1;

            while(j < k){
                int sum = nums[i] + nums[j] + nums[k];
                if(sum == 0){
                   result.add(Arrays.asList(nums[i],nums[j],nums[k]));

                   while(j < k && nums[j] == nums[j+1]){
                    j++;
                   }
                   while(j < k && nums[k] == nums[k-1]){
                    k--;
                   }
                   j++;
                   k--;
                }
                else if(sum > 0){
                    k--;
                }
                else{
                    j++;
                }
            } 
        }
        return result;

    }
}

2.用的是双指针,i固定a,用j和k去确定b和c。不要忘记要sort排序。然后考虑清楚各种去重的情况就行。

18.四数之和

题目

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

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

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

代码(算法去重)

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {

        List<List<Integer>> result = new ArrayList<>();   //保存结果集

        Arrays.sort(nums);  //递增排序

        //遍历a,a最多指向倒数第四个元素
        for(int i = 0; i < nums.length - 3; i++){ 

            //对a去重,如果当前迭代轮的a和前一轮一样,这一轮直接跳过
            if(i > 0 && nums[i] == nums[i - 1]){
                continue;
            }
            //遍历b,b最多指向倒数第三个元素
            for(int j = i + 1; j < nums.length - 2; j++){

                //对b去重,如果当前迭代轮的b和前一轮一样,也跳过
                if(j > i + 1 && nums[j] == nums[j - 1]){
                    continue;
                }

                int left = j + 1;
                int right = nums.length - 1;
                while(left < right){
                    //错误写法:int sum = nums[i] + nums[j] + nums[left] + nums[right];  这里int求和会越界
                    Long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
                    if(sum > target){
                        right--;
                    }
                    else if(sum < target){
                        left++;
                    }
                    else{
                        result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        while(left < right && nums[right] == nums[right - 1]){
                            right--;
                        }
                        while(left < right && nums[left] == nums[left + 1]){
                            left++;
                        }
                        left++;
                        right--;
                    }
                }
            }
        }
        return result;
    }
}

代码(set去重通过版)

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);

        Set<List<Integer>> set = new HashSet<>();
        
        for(int i=0; i < nums.length - 3; i++){
            for(int j=i+1; j < nums.length - 2; j++){
                int k = j + 1;
                int l = nums.length - 1;
                while(k < l){
                    long sum = (long)nums[i] + nums[j] + nums[k] + nums[l];
                    if(sum == target){
                        set.add(Stream.of(nums[i], nums[j], nums[k], nums[l]).collect(Collectors.toList()));
                        k++;
                        l--;
                    }
                    else if(sum > target){
                        l--;
                    }
                    else{
                        k++;
                    }
                }
            }
        }
        List<List<Integer>> res = set.stream().collect(Collectors.toList());
        return res;
    }
}

总结

1.原理

(1)为什么不能用哈希表

        如果使用哈希表,需要用两个for循环变量nums,用map型的hash表存储已经遍历过的a和b,key表示ab之和,value存储单独的a和b的值。每次遍历计算a+b,,然后到map中寻找是否有key=-(a+b)的情况。

        和三数之和类似,这里也有两个问题。

        第一,map的key是唯一的,只能存储一种a+b之和的key,而对应的所有a,b的单独值的情况不能存储。第二,要求返回的四元组是不重复的,这样的hash表结构无法保证无重复

(2)双指针算法流程

        首先对nums数字进行升序排序。

        然后用第一层for循环遍历nums,i指向元素a,用第二次for循环遍历nums,j指向元素b。相当于两层for循环先把a和b的值固定了,然后在[j+1,length-1]区间寻找所有满足条件的c和d。

        那怎么找c和d呢?我们令left=j+1,指代c,令right=nums.length-1,指代d。当left<right时,计算sum = a+b+c+d,同理,sum大于target,就让right--,sum小于target,就让left--,sum等于target,说明ab固定时找到符合条件的c和d了,把abcd加入结果列表中。

(3)如何去重

        如何去a去重,就是在进入第一层for循环后,先判断nums[i]和nums[i-1]的值是否一样,如果一样,用continue跳出当前for循环。因为当索引=i,a固定时,我们会在[i+1,length-1]区间里边找所有满足条件的bcd的情况。而在上一轮索引=i-1时,我们会在[i,length-1]区间里边找所有满足条件的bcd的情况,这里的情况肯定能把下一轮包括进去,所以可以直接continue。注意,要求i要大于0。(这里和三数之和类似)

        如何对b去重,就是在进入第二次for循环后,先判断nums[j]和nums[j-1]的值是否一样,如果一样,用continue跳出当前for循环。原理和对a去重一致,不过注意j的下标从i+1开始,所以这里要求j要大于i+1,即第一个j不需要判断去重。

        如何对c和d去重,原理和三数之和完全一样。相当于此时cd的区间是[j+1,nums.length-1],在次区间找如何要求的c和d。就是当left和right正好指向符合要求的c和d,即a+b+c+d==0时,我们会把当前的情况add进结果。然后我们要循环判断如果nums[left]==nums[left+1],要让left++,如果nums[right]==nums[right-1],要让right++,也就是把c和d出现相同值的情况给跳过。

2.和三数之和有什么一样

        相比于三数之和,只有一层for循环,只用i固定a。这里多加了一层for循环,用j对b进行固定。然后在区间[j+1,nums.length-1]找符合要求的cd。

        因为这里四数之和==target而不是0,所以如果nums[i] > 0,不能直接return result进行剪枝了。比如target=-10,可能a=-4,b=-3,c=-2,d=-1。

3.优化点

(1)对a的遍历终止条件是i < nums.length - 3,因为a最多指向倒数第四个元素

(2)对b的遍历终止条件是j < nums.length - 2,因为a最多指向倒数第三个元素

4.注意点

(1)对a去重时:i > 0 && nums[i] == nums[i - 1],有i > 0的条件不能漏掉,因为第一个a是不需要去重判断的

(2)对b去重时:j > i + 1 && nums[j] == nums[j - 1],有j > i + 1的条件不能漏掉,而且j的初始值是i+1而不是0。

(3)特别注意,这里四数之和计算的sum一定要用long型,不然会越界:Long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];

第二次刷题总结

1.有一个很栓q的点,因为四数之和sum可能会越界,需要用long型进行存储,(long)nums[i] + nums[j] + nums[l] + nums[r],但是如果写成(long)(nums[i] + nums[j] + nums[l] + nums[r])居然会通过不了。

        核心原因是:因为如果后边加了括号,括号内优先计算,四个int先相加,就直接越界了。而后边不加括号,是先把nums[i]先转为long,然后后边int相加会自动提升为long,就不会越界了。

        下面附上chatgpt对这个强制转换的解释,说的挺清楚的。

  • 34
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

守岁白驹hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值