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
ArrayList:Object[]数组。Vector:Object[]数组,现在很少使用。LinkedList:双向链表,一般都使用ArrayList。
Set
HashSet:无序,唯一。基于HasfMap实现,底层采用HasfMap来保存元素。LinkedHashSet:LinkedHashSet是HashSet的子类,并且其内部是通过LinkedHashMap来实现的。TreeSet:有序,唯一。红黑树。
Queue
PriorityQueue:Objecti[]数组来实现大顶堆或小顶堆。定义方式:
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]); //小顶堆
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]); //大顶堆
DelayQueue:PriorityQueue。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。
187

被折叠的 条评论
为什么被折叠?



