454.四数相加II
- 标签:哈希表
- 难度:5.0
类比两数之和,这题的思路就比较简单了。
先求其中的两个数组都能组成哪些和,存在HashMap里,其中key是都有哪些和,value是这个和出现的次数。
再求剩下的两个数组能组成哪些和,每求出一个和,找key里是否存在它的相反数,如果存在,则累加value。
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
// 创建一个map,其中key是前两个数组都有哪些和,value是这个和出现的次数
Map<Integer,Integer> map1 = new HashMap();
// 遍历前两个数组,求和,放进map里
for(int i = 0; i < nums1.length; i++){
for(int j = 0; j < nums2.length; j++){
int sum = nums1[i] + nums2[j];
// 如果map里有这个和,则value++
// 没有,就把这个和放进去,value置为1
if(map1.containsKey(sum)){
map1.put(sum,map1.get(sum) + 1);
}else{
map1.put(sum,1);
}
}
}
// res存满足要求的元组个数
int res = 0;
// 遍历后两个数组,求和,找map里是否有值为和的相反数的
for(int i = 0; i < nums3.length; i++){
for(int j = 0; j < nums4.length; j++){
int sum = nums3[i] + nums4[j];
// 如果有,累加res
if(map1.containsKey(0-sum)){
res += map1.get(0-sum);
}
}
}
return res;
}
383.赎金信
- 标签:哈希表
- 难度:4.5
这个题和有效的字母异位词很像,不同的是这次我选用了HashMap。当然代码还有优化的空间,因为我忘了HashMap的API了,但不得不承认整体写法比较繁琐。这种简单的题就不要杀鸡用牛刀了,直接开一个长度26的数组用作哈希表。
来看代码随想录对HashMap和数组的选择:
一些同学可能想,用数组干啥,都用map完事了,其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!
方法一:HashMap(不推荐)
public boolean canConstruct(String ransomNote, String magazine) {
Map<Character,Integer> magazineMap = new HashMap();
for(int i = 0; i < magazine.length(); i++){
Character magazineChar = magazine.charAt(i);
if(!magazineMap.containsKey(magazineChar)){
magazineMap.put(magazineChar,1);
}else{
magazineMap.put(magazineChar,magazineMap.get(magazineChar) + 1);
}
}
for(int i = 0; i < ransomNote.length(); i++){
Character ransomNoteChar = ransomNote.charAt(i);
if(!magazineMap.containsKey(ransomNoteChar)){
return false;
}
if(magazineMap.get(ransomNoteChar) > 0){
magazineMap.put(ransomNoteChar,magazineMap.get(ransomNoteChar) - 1);
}else{
return false;
}
}
return true;
}
方法二:用数组做哈希表
public boolean canConstruct(String ransomNote, String magazine) {
// 定义一个哈希映射数组
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.三数之和
- 标签:双指针
- 难度:6.0
这个题类比两数之和的逻辑。两数之和是把其中一个数加入到哈希表里,再次遍历数组的同时查找哈希表。三数之和在此基础上,引入了双指针的思想,固定其中一个元素,让另两个元素所在的指针不断移动。
这个方法不是很好想,写起来也有很多细节要注意。一个重要的问题就在于去重的实现。代码随想录提到:
如果我们的写法是 这样:
if (nums[i] == nums[i + 1]) { // 去重操作 continue; }
那就我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。
我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!
所以这里是有两个重复的维度。
那么应该这么写:
if (i > 0 && nums[i] == nums[i - 1]) { continue; }
这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到结果集里。
public List<List<Integer>> threeSum(int[] nums) {
// 保存结果的数组
List<List<Integer>> res = new ArrayList<>();
int i = 0;
// 先排序
Arrays.sort(nums);
// 固定第一个指针
for(i = 0; i < nums.length; i++){
// 如果最小的那个数都大于0,那直接return空数组
if(nums[i] > 0){
return res;
}
// 如果和上一个数相同,则跳过,防止重复
if(i > 0 && nums[i] == nums[i-1]){
continue;
}
// 左右指针
int left = i+1;
int right = nums.length - 1;
// 当右大于左的时候,调整左右指针
while(right > left){
// 如果三个指针之和大于0,则向左调整右指针
if(nums[i] + nums[left] + nums[right] > 0){
right--;
// 如果三个指针之和小于0,则向右调整左指针
}else if(nums[i] + nums[left] + nums[right] < 0){
left++;
// 如果三个指针之和等于0
}else{
// 加入结果集
res.add(Arrays.asList(nums[i], 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 res;
}
18.四数之和
- 标签:双指针
- 难度:6.5
这是七天以来第一道难度超过6.0的题目。和上道题一样,难点还是在去重和剪枝,要类比来看。
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++) {
// 区别一:target不知道是大于还是小于0,所以剪枝操作改为nums[i] > target
if (nums[i] > 0 && nums[i] > target) {
return result;
}
if (i > 0 && nums[i - 1] == nums[i]) {
continue;
}
// 区别二:四数之和要固定两个指针
for (int j = i + 1; j < nums.length; j++) {
if (j > i + 1 && nums[j - 1] == nums[j]) {
continue;
}
int left = j + 1;
int right = nums.length - 1;
while (right > left) {
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 (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
left++;
right--;
}
}
}
}
return result;
}
15.三数之和双指针法就是将原本暴力O(n3)的解法,降为O(n2)的解法,四数之和双指针法就是将原本暴力O(n4)的解法,降为O(n3)的解法。