题目链接:第454题.四数相加II
思路:刚拿到题时想到了昨天做的那个同一数组求出两数之和为目标值的 两数下标(思路是创建一个map,然后遍历该数组,求出目标值减去当前值的那个数,然后看那个数是否在map的键中,如果在就返回键值对中的值(下标),如果不在就把当前数的键值对存放到map里)。拿到这个题目它是四个不同的数组,并且要求求出有多少个这样不同的元祖(并且不需要去重),还是有点点懵的(昨天那个题规定了只有一组答案)。所以决定先看一下卡老师的视频。
看了视频之后,明白如果嵌套四层循环做这个题时间复杂度太高,需要n^4。应该把前两个数组看成一对然后遍历求出每个元素之和和它们对应的次数,然后把后两个数组看成一对遍历查询,这样的话时间复杂度就是n^2。为什不能把第一个单独放在一起,后三个放一起?同理,这样时间复杂度还是太高,n^3。
代码如下:
class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
//定义存储次数的变量
int res=0;
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
//统计两个数组中的元素之和,同时统计出现的次数,放入map
for(int i:nums1){
for(int j:nums2){
int sum=i+j;
//getOrDefault方法是接口中提供的一个用于查询Map类型中指定元素的方法,它的参数是一个Key,返回的是对应的value,如果查询不到,就返回默认值。getOrDefault(Object key, V defaultValue)
map.put(sum,map.getOrDefault(sum,0)+1);
}
}
for(int k:nums3){
for(int l:nums4){
//刚开始这里理解的很费劲,其实完全想复杂了,例如A、B两数组里和为5的一共有三组,C、D里面只有和为-5才满足条件,如果和为-5有两组,那么无非就是两次3累加不就行了呗,3+3=6;如果A和B数组里还有一组和为7的,C和D里面正好有一组和为-7的,那么总次数就是6+1=7次
res+=map.getOrDefault(0-k-l,0);
}
}
return res;
}
}
时间复杂度: O(n^2)
空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2
题目链接:383. 赎金信
思路:这道题目和242.有效的字母异位词 (opens new window)很像,字母异位词要求字符串a 和 字符串b是否可以相互组成,而此题要求a能不能由b里面的字符构成。
题目来源:“为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思” 这里也说明杂志里面的字母不可重复使用。
思路和之前242题完全一致,只不过242那个题最后判断的是数组里的元素是否为0,如果是说明完全一样返回true;而此题需要判断是否<=0,如果<=0则说明赎金信里的元素都在magazine里面,返回true。因为题目所只有小写字母,那可以采用空间换取时间的哈希策略,用一个长度为26的数组还记录magazine里字母出现的次数。然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。一些同学可能想,用数组干啥,都用map完事了,其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!
代码如下:
class Solution {
public boolean canConstruct(String ransomNote, String magazine) {
if(ransomNote.length()>magazine.length()){
return false;
}
//定义一个哈希映射数组
int[] record=new int[26];
//遍历ransomNote字符串
//toCharArray()将字符串转换为字符数组
for(char c:ransomNote.toCharArray()){
record[c-'a']++;
}
// 上述遍历代码也可以写成像242题中给出的常规形式:
// for(int i=0;i<ransomNote.length();i++){
// record[ransomNote.charAt(i)-'a']++;
// }
for(char c:magazine.toCharArray()){
record[c-'a']--;
}
//遍历数组,如果数组中存在正数,则说明ransomNote字符串总存在magazine中没有的字符
for(int i:record){
if(i>0)
return false;
}
return true;
}
}
时间复杂度: O(n)
空间复杂度: O(1)
题目链接:第15题. 三数之和
思路:本题虽然和两数之和很像,也能用哈希法,但用哈希法会非常麻烦,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码。此题应该用双指针法解题,建议先观看卡老师的视频。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//创建一个二维数组,list 是java中集合的一种,可以动态扩容,底层是数组结构,因为它动态扩容的特性很好用,很多时候就直接用来代替数组了
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;
}
//下面的去重操作为什么不写成nums[i] == nums[i + 1],这是因为举个例子,假设数组里面只有-1、-1、2这三个元素,刚开始i=-1、left=-1、right=2,如果写成这种格式的话,第一个-1直接就被continue结束循环了,相当于把结果集里的元素也直接给删除了
if (i > 0 && nums[i] == nums[i - 1]) {
// 去重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) {
right--;
} else if (sum < 0) {
left++;
} else {
//该方法是将数组转化成List集合的方法
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++;
right--;
left++;
}
}
}
return result;
}
}
时间复杂度: O(n^2)
空间复杂度: O(1)