代码随想录|哈希|leetcode454,383,15,18

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int result = 0;
        Map<Integer, Integer> map = new HashMap<>();
        for(int i: nums1){
            for(int j: nums2){
                int sum = i+j;
                map.put(sum, map.getOrDefault(sum, 0)+1);

            }
        }

        for(int i: nums3){
            for(int j: nums4){
                result = result + map.getOrDefault(0-i-j,0); // 0-(c+d)=a+b
            }
        }
        return result;

    }
}

思路:

  1. 首先,使用一个哈希表 map 来存储数组 nums1nums2 中所有可能的两两之和以及对应的频率。这是通过双重循环实现的。

  2. 在填充哈希表的过程中,对于每个 i(来自 nums1)和 j(来自 nums2)的组合,我们计算它们的和,并使用 map.put(sum, map.getOrDefault(sum, 0) + 1); 来更新哈希表中的频率。

  3. 在第一步之后,已经得到了 nums1nums2 中所有可能的两两之和及其对应的频率。

  4. 再次使用双重循环遍历数组 nums3nums4 中的所有元素组合。对于每个 i(来自 nums3)和 j(来自 nums4)的组合,我们检查 -i-j 在哈希表中的频率(因为我们希望找到 a+b+c+d = 0 的组合,所以 a+b = -c-d)。我们将此频率添加到结果 result 中。

  5. 注意,这里的ij实际上分别对应于cd

  6. 最后,返回结果 result,它表示满足条件的元素组合的数量。

赎金信 

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        // shortcut
        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;
    }
}

 思路:

  1. 首先,检查ransomNote的长度是否大于magazine。如果是,我们可以立即返回false,因为一个较长的字符串不可能由一个较短的字符串构成。

  2. 定义一个record数组来存储26个小写字母的计数。该数组的大小是固定的,为26,对应26个小写字母。初始化所有值为0。

  3. 之后,我们遍历magazine中的所有字符,并更新它们在record数组中的计数。

  4. 接下来,我们遍历ransomNote中的所有字符,并减去相应的计数。

  5. 最后,我们检查record数组,看看是否有任何负值。如果存在负值,那意味着ransomNote中有一个字符出现的次数超过了它在magazine中的次数。在这种情况下,返回false。如果所有值都是非负的,那么返回true

三数之和 

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>(); //定义一个结果二维数组
        Arrays.sort(nums); //降序排序
        // 找出a + b + c = 0
        // a = nums[i], b = nums[left], c = nums[right]
        for(int i = 0; i < nums.length; i++){
            if(nums[i]>0){
                return result;
            }
            if(i > 0 && nums[i-1] == nums[i]){ //去重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){
                    left++;
                }else if(sum > 0){
                    right--;
                }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++;
                    }
                }
                left++;
                right--;
            }

        }
        return result;
    }
}

思路:

  1. 初始化:创建一个结果列表来存储所有的三数组合。

  2. 排序:排序数组可以简化问题,因为当数组是排序的,我们可以简单地移动指针来增加或减少三个数字的和。

  3. 遍历数组:固定数字a,并使用双指针找到bc

    • 如果a大于0,则break。因为三个正数的和不可能为0。
    • 如果当前数字和前一个数字相同(即重复),则跳过,以避免重复组合。
  4. 双指针:对于每个固定的a,使用双指针技术查找与其匹配的bc

    • left指针指向a之后的元素,right指针指向数组的最后一个元素。
    • 如果a + b + c < 0,则left指针向右移动。
    • 如果a + b + c > 0,则right指针向左移动。
    • 如果a + b + c == 0,我们找到了一个解,将其添加到结果列表中。然后移动指针并跳过所有重复的bc值。
  5. 返回结果:返回存储所有三数组合的结果列表。

  6. 对b和c进行去重,注意要先收集完结果再去重

四数之和 

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);
       
        for (int i = 0; i < nums.length; i++) {
		
            // nums[i] > target 直接返回, 剪枝操作
            if (nums[i] > 0 && nums[i] > target) {
                return result;
            }
		
            if (i > 0 && nums[i - 1] == nums[i]) {    // 对nums[i]去重
                continue;
            }
            
            for (int j = i + 1; j < nums.length; j++) {

                if (j > i + 1 && nums[j - 1] == nums[j]) {  // 对nums[j]去重
                    continue;
                }

                int left = j + 1;
                int right = nums.length - 1;
                while (right > left) {
		    // nums[k] + nums[i] + nums[left] + nums[right] > target 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]));
                        // 对nums[left]和nums[right]去重
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;

                        left++;
                        right--;
                    }
                }
            }
        }
        return result;
    }
}

 思路:

  1. 排序数组: 首先对数组进行排序。排序的主要目的有两个:一是方便后面的去重操作;二是为了使用双指针技术。

  2. 三重循环 + 双指针:

    • 外层循环 (i):遍历整个数组,寻找第一个数字。对于nums[i],有两个关键的剪枝操作:一是当nums[i]大于target且为正数时,直接返回结果,因为后续的数字都会更大,不可能存在合适的四元组;二是为了去重,如果当前的nums[i]和前一个数字相同,则跳过此次循环。

    • 第二层循环 (j):在i之后,遍历数组寻找第二个数字。和外层循环类似,这里也有去重的操作,即如果nums[j]和前一个数字相同,则跳过此次循环。

    • 双指针:固定了前两个数字后,使用双指针技术寻找后两个数字。定义leftright两个指针,left指向j的下一个位置,right指向数组的最后一个位置。然后根据四数之和与target的大小关系来移动指针,具体地:如果和小于target,则增加left;如果和大于target,则减小right;如果和等于target,则将这四个数字加入结果中,并且移动指针到下一个不重复的位置。

  3. 返回结果: 遍历完数组后,返回找到的所有四元组。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值