1、四数相加
暴力:n的4次方
思路
对数组 ABCD 两两进行求和,对于 AB 两数组,求和后将值保存在哈希结构中,然后对 CD 数组求和后,到哈希结构中寻找是否有出现符合与其相加为 0 的值,如果存在,则将 AB 中相加等于该值的个数加入到总体的计数中
- a+b存入map,key是值,value是出现次数,去map中找0-(c+d),如果有匹配的就把次数取出来加上
- 用map,这题不用去重,根据key找出现次数,两两相加
- map.put(sum1, map.getOrDefault(sum1, 0) + 1)获得多次数,getOrDefault(sum1,0), get(sum1)没有默认0!
- map的API containsKey
- 注意map喜欢用for each的循环
// 注意点:1、判断元素是否在集合中出现过用哈希表,要value要次数用map
// 2、次数不是加一,是加value
Map<Integer, Integer> map = new HashMap<>();
int sum1 = 0, sum2 = 0, result = 0;
for (int i : nums1){
for (int j : nums2){
sum1 = i + j;
//map用put存入,这个是叠加,getOrDefault(sum1,0),get(sum1)没有默认0!,不能直接put
map.put(sum1, map.getOrDefault(sum1, 0) + 1);
}
}
for (int i : nums3){
for (int j : nums4){
sum2 = 0 - (i + j);
if (map.containsKey(sum2)){
result += map.get(sum2);
}
}
}
return result;
}
2、赎金信
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
思路
- 存入一个,另外一个扣数量,如果出现负数,说明不能构成
- 注意顺序别反了!! 先存大的减小的;反的话肯定会超啊
- 1、全是小写字母,用hash数组
- 2、可以将字符串转成char数组,也可以遍历charAt得到每一位
- 3、一个++一个--最后看看有没有负的即可
public boolean canConstruct(String ransomNote, String magazine) {
//加一个size判断,先考虑异常情况
if (ransomNote.length() > magazine.length()) {
return false;
}
//小写字母用数组存
//存入一个,另外一个扣数量,如果出现负数,说明不能构成
//注意顺序别反了!! 先存大的减小的;反的话肯定会超啊
int[] res = new int[26];
// for (char i : magazine.toCharArray()){
// res[i - 'a']++;
// }
// for (char i : ransomNote.toCharArray()){
// res[i - 'a']--;
// }
for (int i = 0; i < magazine.length(); i++) {
res[magazine.charAt(i) - 'a'] ++;
}
for (int i = 0; i < ransomNote.length(); i++) {
res[ransomNote.charAt(i) - 'a'] --;
}
for (int i : res){
if (i < 0){
return false;
}
}
return true;
}
写法二
public boolean canConstruct(String ransomNote, String magazine) {
//小写字母用数组存
//存入一个,另外一个扣数量,如果出现负数,说明不能构成
//注意顺序别反了!! 先存大的减小的;反的话肯定会超啊
int[] array = new int[26];
for (char i : magazine.toCharArray()) {
array[i - 'a']++;
}
for (char i : ransomNote.toCharArray()) {
//对应字符转数字的位置+1
array[i - 'a']--;
}
for (int i : array) {
if (i < 0) {
return false;
}
}
return true;
}
写法三:用map做
存入存出
map不能直接用foreach,用存入map的原始东西来遍历,比如数组啥的
public boolean canConstruct(String ransomNote, String magazine) {
//用map做下
Map<Character, Integer> map = new HashMap<>();
for (char i : magazine.toCharArray()) {
map.put(i, map.getOrDefault(i, 0) + 1);
}
for (char i : ransomNote.toCharArray()) {
map.put(i, map.getOrDefault(i, 0) - 1);
}
for (char i : ransomNote.toCharArray()) {//我只关心ran那个能不能全部抵消
if (map.get(i) < 0) {
return false;
}
}
return true;
}
3、 三数之和----经典双指针
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
思路
- 1、第一反应哈希表 但是不重复不好办 改双指针
- 2、i遍历数组,left = i + 1, right = size - 1,三个相加和0比大小(前提是排序)
- 3、细节去重 i的去重注意不能和i+1比;left和right去重
- 4、2个if剪枝:当nums【i】 > 0时;当nums[i] == nums[i - 1]时
代码注意点
- list的new后面是ArrayList<>()
- 排序:Array.sort(nums)
- 数组注意空指针的限制,一个是i>0,一个是left > right
- 对i的去重只能nums[i] == nums[i - 1],因为往后移如果和前面相同就不用再比了,如果是i+1就变成数组内部比较了
- 2维list的添加有两种方法:1、result.add(Arrays.asList(...)) 2、new一个一维数组list,在list里add进去后,再add到二维数组result里
- 当sum = 0的情况添加到数组后,左右指针都要向中心移动一位,别忘了
- 当left和right向中间移动遇到相同值的情况,while判断条件里也要left
if (i > 0 && nums[i] == nums[i - 1]) continue; //大于0的这个条件得放前面
public List<List<Integer>> threeSum(int[] nums) {
//不重复不用哈希,三指针搞上
//i遍历,left = i + 1, right = size - 1,三个相加和0比大小(前提是排序)
//细节去重 i的去重注意不能和i+1比;left和right去重
List<List<Integer>> result = new ArrayList<>();//arraylist
Arrays.sort(nums);//排序
for (int i = 0; i < nums.length; i++){
if (nums[i] > 0) return result;//说明后面不可能有了,直接返回
if (i > 0 && nums[i] == nums[i - 1]) continue;//要加i>0限制
int left = i + 1;
int right = nums.length - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) right--;
else if (sum < 0) left++;
else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// List<Integer> list = new ArrayList<>();
// list.add(nums[i]);
// list.add(nums[left]);
// list.add(nums[right]);
// result.add(list);
while (left < right && nums[right] == nums[right -1]) right--;
while (left < right && nums[left] == nums[left + 1]) left++;
//别忘了更新啊,相等之后肯定两个都要动一位才有可能实现结果
right--;
left++;
}
}
}
return result;
}
时间复杂度o(n2)
4、 四数之和
思路
- 三数之和的翻版,多套一个for
- 2for算和,while两指针比较
- 2个for都要去重,注意j限制条件跟着i变,不再是0了!! 但是不要随便剪枝了,要两个条件: >target和 >0
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
//三数之和的翻版,多套一个for
//剪枝 >target >0
//2for算和,while两指针比较
//2个for都要去重
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
//注意target可以是任意值,不能随便剪枝
if (nums[i] > target && nums[i] > 0) return result;//剪枝
if (i > 0 && nums[i] == nums[i - 1]) continue;//去重
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] > target && nums[j] > 0) continue;//这只能说跳出这个循环不能返回结果了
if (j > i + 1 && nums[j] == nums[j - 1]) continue;//j不能再用0限制了,要根据i变
int left = j + 1, right = nums.length -1;
while (left < right) {
long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) right--;
if (sum < target) left++;
if (sum == target){
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++;
right--;
left++;
}
}
}
}
return result;
}
}
🈶溢出判断,所以四个数相加的和要转化为long型的
可以不用剪纸
5、总结
对于continue和break在三数之和的区别
continue是下一个i 还存在有可能的情况, break是无论后面多少个i我们是确定不会再出现这样的情况了
数组作为哈希表
在242.有效的字母异位词 (opens new window)中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的!这道题目包含小写字母,那么使用数组来做哈希最合适不过。
在383.赎金信 (opens new window)中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组!
set作为哈希表
在349. 两个数组的交集 (opens new window)中我们给出了什么时候用数组就不行了,需要用set。这道题目没有限制数值的大小,就无法使用数组来做哈希表了。 在202.快乐数 (opens new window)中,我们再次使用了unordered_set来判断一个数是否重复出现过
map作为哈希表
在1.两数之和 (opens new window)中map正式登场。
来说一说:使用数组和set来做哈希法的局限。数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。map是一种
<key, value>
的结构,本题可以用key保存数值,用value在保存数值所在的下标。所以使用map最为合适。不可用哈希表
四数相加为四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑重复问题,而18. 四数之和 (opens new window),15.三数之和 (opens new window)是一个数组(集合)里找到和为0的组合,可就难很多了!
!!记住,遇到需要判断一个元素是否出现过的场景应该第一时间想到哈希法!!
拜拜~~