代码随想录算法训练营第七天 | 454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和

454.四数相加II

题目链接:https://leetcode.cn/problems/4sum-ii/
文档讲解:https://programmercarl.com/0454.%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0II.html
视频讲解:https://www.bilibili.com/video/BV1Md4y1Q7Yh

思路

  • 将数组分为ab和cd两个,先用两个for循环遍历求出ab数组的和,作为key放入map中,value存放出现的次数。
  • 接着遍历求出cd的和,如果他的负数存在于map中,则在res上加上value。

代码

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

        for (int i = 0; i < nums1.length; i++) {
            for (int j = 0; j < nums1.length; j++) {
                int sum = 0 - (nums3[i] + nums4[j]);
                res += map.getOrDefault(sum, 0);
            }
        }

        return res;
    }
}

分析:
时间复杂度:O(n2),空间复杂度:O(n2)。
空间复杂度: O(n2),最坏情况下a和b的值各不相同,相加产生的数字个数为 n2

383. 赎金信

题目链接:https://leetcode.cn/problems/ransom-note/
文档讲解:https://programmercarl.com/0383.%E8%B5%8E%E9%87%91%E4%BF%A1.html#%E6%80%9D%E8%B7%AF
视频讲解:无

思路

  • 这道题和前面的有效字母异位词一样都是小写字母,要判断ransomNote中的字母是否在magazine中出现过,所以可以用数组实现哈希表。
  • 首先遍历ransomNote,将结果放在数组中,接着遍历magazine,在数组对应的位置-1。最后判断如果有大于零的值,说明magazine中的字母不够ransomNote使用,返回false。

代码

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] hash = new int[26];
        for (int i = 0; i < ransomNote.length(); i++) {
            hash[ransomNote.charAt(i) - 'a']++;
        }
        for (int i = 0; i < magazine.length(); i++) {
            hash[magazine.charAt(i) - 'a']--;
        }
        for (int i = 0; i < hash.length; i++) {
            if (hash[i] > 0) return false;
        }
        return true;
    }
}

我的代码和卡哥代码不太一样的是,卡哥的代码是先遍历magazine,然后将后面两个for循环合并成一个,只要遇到ransomNote中字母对应的数组值小于零,就返回false。

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] hash = new int[26];
        for (int i = 0; i < magazine.length(); i++) {
            hash[magazine.charAt(i) - 'a']++;
        }
        for (int i = 0; i < ransomNote.length(); i++) {
            hash[ransomNote.charAt(i) - 'a']--;
            if (hash[ransomNote.charAt(i) - 'a'] < 0) return false;
        }
        
        return true;
    }
}

分析:时间复杂度:O(n),空间复杂度:O(1)。

15. 三数之和

题目链接:https://leetcode.cn/problems/3sum/
文档讲解:https://programmercarl.com/0015.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE
视频讲解:https://www.bilibili.com/video/BV1GW4y127qo

思路

  • 这道题需要去重,如果用哈希法,哈希法在数组中寻找第三个值的时候是没有什么顺序的,所以去重比较困难,会遇到很多不好处理的小细节。

所以这道题使用的是双指针法。需要提前对数组进行排序。 在这里插入图片描述

如果三数之和大于0,right- -;如果三数之和小于0,left++。当找到一组数,要下一组数时,要进行去重。而去重是nums[i]和他的前一个nums[i - 1]比较,如果是和nums[i + 1]比较,就相当于是在和nums[left]比较,因为在定义中left = i + 1,比如[-1, -1, 2]。

代码

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 2; i++) {
            if (nums[i] > 0) return res;
            // nums[i]去重
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            int left = i + 1;
            int right = nums.length - 1;
            while (left < right) {
                if (nums[i] + nums[left] + nums[right] > 0) {
                    right--;
                } else if (nums[i] + nums[left] + nums[right] < 0) {
                    left++;
                } else {
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    // 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 res;
    }
}

分析:时间复杂度:O(n2),空间复杂度:O(1)。

总结

又认真看视频梳理了一遍逻辑,去重部分比一刷的时候清晰很多。但是在ArrayList的使用上还是掌握的不太好。比如新建List的时候应该是new ArrayLIst<>();将结果加入res时需要先Arrays.asList(),再add进List中。下面总结一下Java中集合框架底层数据结构。
首先是Collection接口下面的集合。

List

  • ArrayListObject[]数组。
  • VectorObject[]数组,现在很少使用。
  • LinkedList :双向链表,一般都使用ArrayList。

Set

  • HashSet:无序,唯一。基于HasfMap实现,底层采用HasfMap来保存元素。
  • LinkedHashSetLinkedHashSetHashSet的子类,并且其内部是通过LinkedHashMap来实现的。
  • TreeSet:有序,唯一。红黑树。

Queue

  • PriorityQueueObjecti[]数组来实现大顶堆或小顶堆。定义方式:
	PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]); //小顶堆
    PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]); //大顶堆
  • DelayQueuePriorityQueue
  • ArrayDeque:可动态扩容双向数组。
    接着是Map接口下面的集合。

Map

  • HashMap:JDK1.8之前是由数组+链表组成的,数组是主体,链表是为了解决哈希冲突(拉链法)。JDK1.8之后,当链表长度大于阈值,会将链表转化为红黑树(如果数组长度小于64,会优先选择数组扩容)。
  • LinkedHashMap:继承自HashMap,所以底层和HashMap差不多。另外,LinkedHashMap在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的顺序插入。
  • Hashtable:数组+链表组成。数组是主体,链表是为了解决哈希冲突而存在。Hashtable是线程安全的。
  • TreeMap:红黑树。

18. 四数之和

题目链接:https://leetcode.cn/problems/4sum/
文档讲解:https://programmercarl.com/0018.%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.html#%E7%AE%97%E6%B3%95%E5%85%AC%E5%BC%80%E8%AF%BE
视频讲解:https://www.bilibili.com/video/BV1DS4y147US

思路

  • 跟三数之和相比,需要固定两个指针k和i,然后移动left和right。分别对k循环和i循环进行剪枝和去重。由于是自己指定target,所以去重的时候要先定在大于0的情况下。

代码

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        for (int k = 0; k < nums.length; k++) {
            if (nums[k] > target && nums[k] > 0 && target > 0) return res;
            if (k > 0 && nums[k] == nums[k - 1]) continue;
            for (int i = k + 1; i < nums.length; i++) {
                if (nums[i] > target && nums[i] > 0 && target > 0) return res;
                if (i > k + 1 && nums[i] == nums[i - 1]) continue;
                int left = i + 1, right = nums.length - 1;
                while (left < right) {
                	// 不强转为long会溢出
                    if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) right--;
                    else if ((long) nums[k] + nums[i] + nums[left] + nums[right] < target) left++;
                    else {
                        res.add(Arrays.asList(nums[k], 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--; 
                    }
                }
            }
        }
        return res;
    }
}

分析:
时间复杂度:O(n3),空间复杂度:O(1)。

哈希表章节总结

数组作为哈希表

242.有效字母的异位词和383.赎金信都用到了数组作为哈希表。因为这两道题都是限制在小写字母的范围内,范围有限,且需要得到字母是否出现过。
上面两道题目用Map确实可以,但使用Map的空间消耗要比数组大一些,因为Map要维护红黑树或者符号表,而且还要做哈希函数的运算。所以数组更加简单直接有效。

set作为哈希表

349.两个数组的交集和202.快乐数使用了Set作为哈希表。因为题目中没有限制数值的大小,所以不能使用数组。

Map作为哈希表

  • 1.两数之和是在一个数组中找到两个数之和为target的数,由于返回的是下标,我们不仅要知道某个数是否存在,还需要知道他的下标,这时就可以用Map。key存储值,value存储下标。然后遍历数组并存放到Map中,并在Map中查找另一个数是否存在。
  • 15.三数之和判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ,并返回所有的三元组,不能重复。由于使用哈希法的去重效率太低,所以选择了双指针法,先对数组进行排序,接着固定第一个数的下标i,然后left指向i + 1,right指向末尾,逐渐向中间靠拢。需要注意对i、left、right的去重以及对i的剪枝。
  • 18.四数之和判断是否存在四个元素,之和等于target,返回所有的四元组。相比于三数之和,需要固定k和i,然后对k和i都进行剪枝,其余操作和三数之和差不多。
  • 454.四数相加II。他和四数之和的不同点在于是从四个数组中分别取一个数,他们的和等于0,并返回四元组的数量。这里面的四元组就是可以重复的,因为他们可能来自不同的数组。所以使用Map存储ab的和,再用cd去Map里面找对应的数。Map中key存放ab的和的值,value存放出现次数。当找到符合的四元组,则res += value。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值