力扣刷题总结 哈希表(2)

🔥博客主页: A_SHOWY
🎥系列专栏力扣刷题总结录 数据结构  云计算

1.两数之和easy

map哈希,因为要返回下标

15.三数之和mid哈希很难,因为要考虑去重,双指针法更优
18.四数之和mid和三数之和相似,使用双指针,注意剪枝区别

        哈希表的第二部分主要总结几个使用哈希表非常复杂的问题,当哈希表需要考虑的去重问题非常复杂的时候,双指针法是更优的选择 。

(1)1.两数之和

1. 两数之和icon-default.png?t=N7T8https://leetcode.cn/problems/two-sum/核心思想是把遍历的数值存放到map里面(因为是要返回数组下标),具体思路可以看下面这个文章。力扣刷题总结 哈希表(1)-CSDN博客

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    unordered_map<int,int> map;
    for(int i = 0; i < nums.size(); i++)
    {
        int s = target - nums[i];
        auto iter = map.find(s);
        if(iter != map.end()) return{iter -> second,i};
        else map.insert(pair<int,int>(nums[i],i));
    }
    return {};
    }
};

(2)15.三数之和

15. 三数之和icon-default.png?t=N7T8https://leetcode.cn/problems/3sum/

方法一:哈希法

两层for循环就可以确定 a 和b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组,要去重,a去重,b去重,a+b也要去重,细节特别多。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(),nums.end());
for(int i = 0; i < nums.size(); i++)
{
    if(nums[i] > 0) return result;
//对a去重 
    if(i > 0 && nums[i] == nums[i-1]) {continue;}
    unordered_set<int> set;
    for(int j = i+1; j < nums.size(); j++)
    {
        if(j > i+2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2]) {continue;}
        int c = 0 -nums[i] - nums[j];
        if(set.find(c) != set.end()) {result.push_back({nums[i],nums[j],c});
        set.erase(c);}//去重c
        else {set.insert(nums[j]);}
    }
}
return result;
    }
};

可以看出超出了时间限制,而且里面的去重操作非常繁琐

1.首先是对a去重没什么坑,从对b去重开始,对b去重,一般人可能想到我只需要判断和前一个不一样不就行了吗,为什么还要和前两个不一样 

if(j > i+2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2]) 

这就是这道题麻烦的地方,要考虑一组数前面是{0,0,0......}的情况

i   j   i+2   i+3
0   0   0     1    2    3

打个比方形如如上这种对应关系,假如我们只判断i+2等于i+1,那就把0,0,0这种情况漏过去了。 

i   j   i+2   i+3
0   0   0     0    2    3

 那假如是这种情况i+3也是0了,那么就0,0,0重复了才需要删掉.

 如果我们只判断nums[j] == nums[j-1]例如下面的样例就会错误,当连续三个的时候,就能判断不会重复,这很难想到!

方法二:双指针法(更优)

需要排序,因为输出的不需要输出下标,看上面的图,我们的目标是求三数之和,同时要满足不能出现相同的三元组,首先需要对数组进行排序。满足a+b+c = 0的话让i从第一位开始代表a,left在i+1,right在末尾。核心思路是,首先是i进行遍历,如果nums【i】+nums【left】+ nums【right】<0 的时候,就让left++,大于0的时候就right--,当等于0的时候就输出,但是这道题目尽管在使用双指针法的时候有很多很细节的点需要注意。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
         vector<vector<int>> result;
         sort(nums.begin(),nums.end());//先进行整体排序
         for(int i = 0; i < nums.size(); 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.size() - 1;
        while(left < right)//为什么是等于
    //这里不能进行第二次去重,因为会把{0,0,0}这种情况考虑不到
    {
        if(nums[i] + nums[left] + nums[right] > 0) right --;
       else if(nums[i] + nums[left] + nums[right] < 0) left ++;
       else{
           result.push_back(vector<int>{nums[i],nums[left],nums[right]});//使用了push_back方法向名为result的vector容器中添加一个新的元素,这个新元素是一个vector<int>类型的对象。
//此时进行第二次去重
     while(nums[right] == nums[right - 1] && right > left) {right--;}
     while(nums[left] == nums[left + 1] && right > left) {left++;}
//找到答案时,双指针同时收缩
    right--;
    left++;
         }
         }
    }
    return result;
}
};

1.首先是第一次的去重(a的去重)。为什么使用//1而不用//2

nums[i] == nums[i - 1]//1
nums[i] == nums[i + 1]//2

我们考虑如下情况,当这个数组是{-1,-1,2}的时候,使用//2我们判断第二个元素和第一个一样就会误把第二个元素删掉,但是我们的目的是删除相同的三元组而不是删除一个三元组中相同的元素。所以我们和前面的元素比较,使用过了,就可以安心的去掉。但是还有一个注意点,我们使用nums【i-1】了以后要注意判断i>0,而且要写在去重判断的前面,if判断&&是有顺序的。

2.在进入主题部分时,为什么是

while(left < right)

而不是

while(left <= right)

因为我们考虑,当left=right,b和c重合了,我们讨论b和c的意义就没有了,b和c公用一个元素,不满足题意了,所以不能加上等于。

3.第二次去重(b和c的去重)为什么不能在前面刚开始判断while就进行去重,也就是这样

while(left < right)//为什么是等于
    {

 while(nums[right] == nums[right - 1] && right > left) {right--;}
 while(nums[left] == nums[left + 1] && right > left) {left++;}
        if(nums[i] + nums[left] + nums[right] > 0) right --;
       else if(nums[i] + nums[left] + nums[right] < 0) left ++;
       else{
           result.push_back(vector<int>{nums[i],nums[left],nums[right]});
    

    right--;
    left++;
         }
         }

因为我们考虑{0,0,0}的这种情况,会把这种情况错误的去掉。同时注意push_back操作,使用了push_back方法向名为result的vector容器中添加一个新的元素,这个新元素是一个vector<int>类型的对象。

 (3)18.四数之和

18. 四数之和icon-default.png?t=N7T8https://leetcode.cn/problems/4sum/这个题目和上一个题目相比只变了两个地方,一个是三数变成了四数,另外一个是上一题让三数之和的目标值等于0,而本题让手动输入一个target,让四个数之和等于target,所以说使用双指针的思路是没有问题的和上一个题目大同小异,两个for循环遍历a和b,c和d用left和right双指针去移动,如果使用哈希法,将更为困难。值得注意的点有以下几个。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
     vector<vector<int>>  result;
     sort(nums.begin(),nums.end());
     for(int k = 0; k < nums.size(); k++)
     {
         if(nums[k] > target && nums[k] >= 0) {break;}//剪枝操作
         if(k > 0 && nums[k] == nums[k-1]) {continue;}//对a去重

         for(int i = k+1; i < nums.size(); i++)
         {
             if(nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {break;}
             if(i > k+1 && nums[i] == nums[i-1]) {continue;}

             int left = i + 1;
             int right = nums.size() -1;
             while(left < right)
             {
                 if((long)nums[i] + nums[k] + nums[left] + nums[right] < target) left ++;
                 else if((long)nums[i] + nums[k] + nums[left] + nums[right] > target) right --;
                 else{
                     result.push_back(vector<int>{nums[k],nums[i],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;
    }
};

1.第一个就是剪枝操作有区别,我们日常思维考虑的是数字相加,越加越大,其实不然,只有在都是正数的时候,正数和正数相加才会越加越大,而负数会越加越小。

2.去重的操作和上一题大同小异基本是一样的。

3.在进行四数相加的时候有可能会超界,所以要声明long,调试跑样例的时候能查出来。

(4)哈希表使用选择问题

三种:1数组 2 set(集合)3map(映射)

  • 对于set

1.当使用set解决哈希问题的时候,优先使用unordered_set,因为查询和增删效率是最优的(需要去重的时候)

2.如果需要集合有序,使用set

3.如果要求不仅有序还要有重复数据的话,那么就用multiset。(需要保留重复数据)

  • 对于map

1.map 是一个key value 的数据结构,map中,对key是有限制,对value没有限制

2.其他有序和重复细节和set是一样的

  • 41
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
好的,关于力扣刷C++常用操作,我可以给你一些常见的操作和技巧: 1. 使用 STL 容器和算法库:STL(Standard Template Library)是 C++ 标准库中的一个重要组成部分,包含了许多常用的容器和算法。在力扣刷中,使用 STL 可以大大提高代码的效率和可读性。例如,vector 可以用来存储动态数组,sort 可以用来排序等等。 2. 使用 auto 关键字:auto 关键字可以自动推导变量类型,可以减少代码量和提高可读性。例如,auto x = 1; 可以自动推导出 x 的类型为 int。 3. 使用 lambda 达式:lambda 达式是 C++11 中引入的一种匿名函数,可以方便地定义一些简单的函数对象。在力扣刷中,使用 lambda 达式可以简化代码,例如在 sort 函数中自定义比较函数。 4. 使用位运算:位运算是一种高效的运算方式,在力扣刷中经常会用到。例如,左移运算符 << 可以用来计算 2 的幂次方,右移运算符 >> 可以用来除以 2 等等。 5. 使用递归:递归是一种常见的算法思想,在力扣刷中也经常会用到。例如,二叉树的遍历、链的反转等等。 6. 使用 STL 中的 priority_queue:priority_queue 是 STL 中的一个容器,可以用来实现堆。在力扣刷中,使用 priority_queue 可以方便地实现一些需要维护最大值或最小值的算法。 7. 使用 STL 中的 unordered_map:unordered_map 是 STL 中的一个容器,可以用来实现哈。在力扣刷中,使用 unordered_map 可以方便地实现一些需要快速查找和插入的算法。 8. 使用 STL 中的 string:string 是 STL 中的一个容器,可以用来存储字符串。在力扣刷中,使用 string 可以方便地处理字符串相关的问。 9. 注意边界条件:在力扣刷中,边界条件往往是解决问的关键。需要仔细分析目,考虑各种边界情况,避免出现错误。 10. 注意时间复杂度:在力扣刷中,时间复杂度往往是评判代码优劣的重要指标。需要仔细分析算法的时间复杂度,并尽可能优化代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A_SHOWY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值