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

85 阅读2分钟

454. 四数相加

链接

题目链接

文章链接

第一想法

查看是否和为0,用于查找,所以第一想到了哈希表,用map还是set,因为数字之和可能有多种组合,所以用map,可以方便记录和数字出现的次数。同时要写两个两层的for循环,这样的时间复杂度是O(n2)O(n^2),如果一个写三层for循环,一个写一层,那么时间复杂度为O(n3)O(n^3),所以写两个两层的for循环,代码如下:

function fourSumCount(nums1: number[], nums2: number[], nums3: number[], nums4: number[]): number {
  let map:Map<number, number>=new Map()
  let res:number=0
  for(let i=0;i<nums1.length;i++){ //用于存储nums1和nums2数组两个数字相加之和
      for(let j=0;j<nums2.length;j++){
          if(map.has(nums1[i]+nums2[j])){
              map.set(nums1[i]+nums2[j],map.get(nums1[i]+nums2[j])+1) //记录和相同时出现的次数
          }else{
              map.set(nums1[i]+nums2[j],1)
          }
      }
  }
    for(let i=0;i<nums3.length;i++){
      for(let j=0;j<nums4.length;j++){
          if(map.has(0-(nums3[i]+nums4[j]))){//查询是否四个数加起来为0
             res+=map.get(0-(nums3[i]+nums4[j])) 
          }
      }
  }
  return res
};

看完文章后的想法

文章的思路总体上和自己的想法是一致的,所以在此不进行再次赘述,其他语言代码可以查看代码随想录

思考

这道题不难,唯一需要想清楚的是要这道题是要找到等于0的个数,而不是要找出这四个的下标是多少,所以不需要四层for循环,挨个尝试是否为0.只需要找到数字相加之和是否为0就可以了,为了使时间复杂度最低,四数相加可以转换为二数相加就可以完美解决这道题。

383. 赎金信

链接

题目链接

文章链接

第一想法

这道题和242有效的字母异位词类型差不多,所以一下子就想到了用哈希表,这道题用数组,因为只有26个字母,很简单,唯一的区别是magazine中的字母要包含ransomNote的字母,也就是ransomNote是magazine的子集,代码如下:

function canConstruct(ransomNote: string, magazine: string): boolean {
     let arr:number[]=new Array(26).fill(0)
     for(let i=0;i<ransomNote.length;i++){ //先循环哪个要看最终判断条件是哪个
         arr[ransomNote[i].charCodeAt(0)-'a'.charCodeAt(0)]++
     }
    for(let i=0;i<magazine.length;i++){
         arr[magazine[i].charCodeAt(0)-'a'.charCodeAt(0)]--
     }
     return !arr.some(item=>item>0) //先循环ransomNote,如果循环完magazine后,arr中存在大于0的数,说明ransomNote中的某个字母个数大于magazine中对应字母的个数
};

看完文章后的想法

文章的想法和自己的想法几乎是完全一致的,只不过文章先循环的是magazine,所以最终条件也不一样,下列代码为代码随想录中的代码:

function canConstruct(ransomNote: string, magazine: string): boolean {
    let helperArr: number[] = new Array(26).fill(0);
    let base: number = 'a'.charCodeAt(0);
    let index: number;
    for (let i = 0, length = magazine.length; i < length; i++) {
        helperArr[magazine[i].charCodeAt(0) - base]++;
    }
    for (let i = 0, length = ransomNote.length; i < length; i++) {
        index = ransomNote[i].charCodeAt(0) - base;
        helperArr[index]--;
        if (helperArr[index] < 0) {
            return false;
        }
    }
    return true;
};

思考

这道题的主要思路和242有效的字母异位词相似,只不过一个是子集,应该是相等,所以也没有多思考时间按,这道题10多分钟就搞定了,还是比较简单的。

15. 三数之和

链接

文章链接

题目链接

第一想法

第一感觉这不是和四数相加2很像,但是发现结果不同,而且还要去重,有点麻烦,但是还是决定用哈希算法尝试一下(去重就是麻烦),代码如下:

function threeSum(nums: number[]): number[][] {
    let map1:Map<number,number[][]>=new Map() //用来存储元素
    let map2:Map<number,number[][]>=new Map() //用来存储下标
    let set=new Set()
    let res=[]
 for(let i=0;i<nums.length;i++){
     for(let j=i+1;j<nums.length;j++){
         if(map1.has(nums[i]+nums[j])){
             map1.get(nums[i]+nums[j]).push([nums[i],nums[j]])
             map2.get(nums[i]+nums[j]).push([i,j])
             map1.set(nums[i]+nums[j],map1.get(nums[i]+nums[j]))
             map2.set(nums[i]+nums[j],map2.get(nums[i]+nums[j]))  
         }else{
             map1.set(nums[i]+nums[j],[[nums[i],nums[j]]])
             map2.set(nums[i]+nums[j],[[i,j]])
         }
     }
 }
 for(let i=0;i<nums.length;i++){
     if(!map1.has(0-nums[i])) continue
     let arr:number[][]= map2.get(0-nums[i])
     let arr1:number[][]= map1.get(0-nums[i]) //元素
     for(let j=0;j<arr.length;j++){
         if(arr[j].includes(i)) continue
         let arr2:number[]=Array.from(arr1[j])
         arr2.push(nums[i])
         if(!set.has(arr2.sort().join(" "))){ //这里是用来去重的
             set.add(arr2.sort().join(" "))
             res.push(Array.from(arr2))
         }
     }
 } 
 return res
};

结果运行,发现——寄,通过不了,感觉是内存太大了。不过也通过了308个测试用例。

image.png

看完文章后的想法

看完文章后,给我的感觉是,没想到,这里还能用双指针,没想到。但是这道题的重点是,要提前把数组排序用于去重!!!!这个是最重要的,但是我没想到,建议看看代码随想录的这篇文章,确实好。代码如下:

function threeSum(nums: number[]): number[][] {
  nums.sort((a,b)=>a-b) //进行排序
  let res:number[][]=[]
  //要满足 a+b+c==0
  for(let i=0;i<nums.length;i++){
      if(nums[i]>0) return res //因为排序后,如果第一个数大于0,则相加肯定大于0
      if(i>0&&nums[i]==nums[i-1]) continue //对a去重
      let left:number=i+1
      let right:number=nums.length-1
      while(right>left){
          if(nums[i]+nums[left]+nums[right]>0) right--
           else if(nums[i]+nums[left]+nums[right]<0) left++
           else{
               res.push([nums[i],nums[left],nums[right]])
               left++
               right--
               while(nums[right]==nums[right+1]) right-- //对c去重
               while(nums[left]==nums[left-1]) left++//对b去重
           }
      }
  }
  return res
};

思考

通过这道题我发现了个误区,如果题目属于哈希表这个范围的话,我就只会按照哈希表来想,不会从其他的地方来想,这道题如果用双指针来想,确实简单但是要想清去重的思路以及剪枝。

18. 四数之和

链接

文章链接

题目链接

第一想法

刚刚做完三数之和,所以第一眼决定不用哈希表,决定用双指针,唯一的区别就是不需要判断nums[i]>target了,因为target可能是负数,代码如下:

function fourSum(nums: number[], target: number): number[][] {
  let res:number[][]=[]
  nums.sort((a,b)=>a-b)
  //要满足a+b+c+d==0
  for(let i=0;i<nums.length;i++){
      //这里不能有nums[i]>target了,因为target可能是负数
      if(i>0&&nums[i]==nums[i-1]) continue
      for(let j=i+1;j<nums.length;j++){
          //这里要判断j>i+1的原因是防止nums[i]==nums[j]时这种情况被去除
          if((j-i)>1&&nums[j]==nums[j-1]) continue
          let left:number=j+1
          let right:number=nums.length-1
          while(right>left){
              if(nums[i]+nums[j]+nums[left]+nums[right]>target) right--
              else if(nums[i]+nums[j]+nums[left]+nums[right]<target) left++
              else {
                  res.push([nums[i],nums[j],nums[left],nums[right]])
                  left++
                  right--
                  while(left<right&&nums[left]==nums[left-1]) left++
                  while(left<right&&nums[right]==nums[right+1]) right--
              } 
              
          }
      }
  }
  return res
};

看完文章后的想法

文章和我用的方法一样,想法也类似,下面为代码随想录的代码:

function fourSum(nums: number[], target: number): number[][] {
    nums.sort((a, b) => a - b);
    let first: number = 0,
        second: number,
        third: number,
        fourth: number;
    let length: number = nums.length;
    let resArr: number[][] = [];
    for (; first < length; first++) {
        if (first > 0 && nums[first] === nums[first - 1]) {
            continue;
        }
        for (second = first + 1; second < length; second++) {
            if ((second - first) > 1 && nums[second] === nums[second - 1]) {
                continue;
            }
            third = second + 1;
            fourth = length - 1;
            while (third < fourth) {
                let total: number = nums[first] + nums[second] + nums[third] + nums[fourth];
                if (total === target) {
                    resArr.push([nums[first], nums[second], nums[third], nums[fourth]]);
                    third++;
                    fourth--;
                    while (nums[third] === nums[third - 1]) third++;
                    while (nums[fourth] === nums[fourth + 1]) fourth--;
                } else if (total < target) {
                    third++;
                } else {
                    fourth--;
                }
            }
        }
    }
    return resArr;
};

总结

今天前两道题比较简单,但是第三道题比较难,没有想到,同时尝试用了哈希表来解决三数之和,发现能写,但是非常难写,去重比较麻烦。第三题写完后再写第四题,会立即想到用双指针,说明对于三数、四数、五数等都可以用双指针,就能用时四小时.