day07 - 哈希表 part02

发布时间 2023-09-14 02:12:53作者: 笑忘书丶丶

力扣454. 四数相加II

思路:把四个数组分为两组,前两组的和 + 后两组的和 = 0;

利用哈希表,key为前两组的和,value为出现的次数,因为根据题意,只需输出有几种情况,因此value设置为出现的次数,然后用后两组的和的负数,作为key查找,如果找到了就count += value。

最后返回count

代码

int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
   unordered_map<int, int> map1;
   //unordered_map<int, int> map2;
   int count = 0;
   for (int i = 0; i < nums1.size(); i++)
  {
       for (int j = 0; j < nums2.size(); j++)
      {
           map1[nums1[i] + nums2[j]]++;
      }
  }
   for (int i = 0; i < nums3.size(); i++)
  {
       for (int j = 0; j < nums4.size(); j++)
      {
           if (map1.find(-(nums3[i] + nums4[j])) != map1.end())
          {
               count += map1[-(nums3[i] + nums4[j])];
          }
      }
  }
   return count;
}

力扣383. 赎金信

思路:先统计magazine,每个字母++;然后遍历ransomNote,每个字母--;

如果哈希表里的数值小于0,那么说明这个字母缺少直接返回false,否则返回true。

注意:如果先遍历ransomNote,再遍历magazine,那么需要完全遍历结束后,再判断哈希表里是否有值小于0,所以应该先遍历ransomNote。

代码

bool canConstruct(string ransomNote, string magazine) {
   int record[26] = {0};
   for (int i = 0; i < magazine.size(); i++)
  {
       record[magazine[i] - 'a']++;
  }
   for (int i = 0; i < ransomNote.size(); i++)
  {
       record[ransomNote[i] - 'a']--;
       if (record[ransomNote[i] - 'a'] < 0)
      {
           return false;
      }
  }
   return true;
}

力扣15. 三数之和

挺难的,思路+去重

思路:先排序,为后面做准备。然后遍历nums,i作为固定的,然后左右双指针,如果大于0,则收缩右指针,如果小于0则收缩左指针。重点在于去重!

初始情况,第一次循环,遍历nums,设置双指针。

image-20230914005710711

刚好等于0,记录结果{-2,0,2};

然后左右收缩

image-20230914005914150

 

此时也等于0,但是需要去重,去重的逻辑就是如果找到一个答案了,并且他左右指针的下一个正好和现在这个相等,就跳过下一个,为什么?因为i是固定的,如果left的下一个等于现在这个,那么right即使找到了,也必定是现在这个值,就必定重复,因此,直接跳过。这是左右指针的去重。

此外还有i的去重,如果i等于前一个,那么直接跳过。此记录为二刷,只做记录,不做发布。

代码如下:

vector<vector<int>> threeSum(vector<int>& nums) {
   sort(nums.begin(), nums.end());
   vector<vector<int>> result;
   for (int i = 0; i < nums.size(); i++)
  {
       if (nums[i] > 0)
      {
           break;
      }
       if (i > 0 && nums[i] == nums[i - 1])
      {
           continue;
      }
       int left = i + 1;
       int right = nums.size() - 1;
       while (left < right)
      {
           if (nums[i] + nums[left] + nums[right] == 0)
          {
               result.push_back({ 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++;
          }
           else if (nums[i] + nums[left] + nums[right] > 0)
          {
               right--;
          }
           else
          {
               left++;
          }
      }
  }
   return result;

}

力扣18. 四数之和

此题与三数之和无异,掌握此题,可以做7数之和,8数之和,随便。

三数之和,就是固定一个i,然后双指针,将On3复杂度降为On2,四数之和,便是固定两个数i,j,将On4复杂度降为On3,多套一层循环罢了。重点在剪枝,去重!每层循环都要剪枝。

代码:

vector<vector<int>> fourSum(vector<int>& nums, int target) {
   //此题与三数之和的区别在于,这题有时候会给数组里根本没有4个数。直接返回
   if (nums.size() < 4)
  {
       return {};
  }
   vector<vector<int>> result;
   sort(nums.begin(), nums.end());
   //第一层循环,固定i。
   for (int i = 0; i < nums.size(); i++)
  {
       //对i剪枝,因为上题target是0,这题不是,所以不能随便剪。如果前面是正数,并且第一个数大于target了,剪枝,why?
       //因为如果数组是【-4,-3,-2,-1】,target = -10;那么是可以凑出来的,但是nums[i] > target.
       //其实可以从尾部继续剪枝,最后一个数如果小于0,并且最后一个数小于target,那么剪枝,试了一下,时间提升不大,可能示例没那么多。
       if (nums[i] > target && nums[i] >= 0)
      {
           break;
      }
       //去重操作,如果i>0(保证不会出现i-1 < 0数组越界),并且重复,直接跳过
       if (i > 0 && nums[i] == nums[i - 1])
      {
           continue;
      }
       //第二层循环,固定j
       for (int j = i + 1; j < nums.size(); j++)
      {
           //如果两个固定的数大于target,并且大于0, 剪枝
           if (nums[j] + nums[i] > target && nums[j] + nums[i] >= 0)
          {
               break;
          }
           //去重。
           if (j > i + 1 && nums[j] == nums[j - 1])
          {
               continue;
          }
           int left = j + 1;
           int right = nums.size() - 1;
           while (left < right)
          {

               if ((long)nums[i] + nums[j] + nums[left] + nums[right] > target)
              {
                   right--;
              }
               else if ((long)nums[i] + nums[j] + nums[left] + nums[right] < target)
              {
                   left++;
              }
               else
              {
                   result.push_back({nums[i], nums[j], 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 result;
}

总结:都是双指针+固定其他数套路,只能通过双指针降低一次复杂度,每层循环都可以剪枝和去重,去重固定的数和去重后面的数逻辑不一样,注意写的位置。