C++刷题 -- 哈希表

C++刷题 – 哈希表


当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法;

1.两数之和

https://leetcode.cn/problems/two-sum/
一种方法是直接两个for循环暴力求解,时间复杂度为O(N^2);

另一种解法:使用map记录遍历过的元素

  • 每次遍历一个元素,计算其与target的差值,从map中寻找差值,若存在,则返回下标,若不存在,则将遍历完的元素插入map;
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> nums_map; //记录遍历过的元素
        vector<int> res;

        for(int i = 0; i < nums.size(); i++)
        {
            int diff = target - nums[i];
            if(nums_map.find(diff) != nums_map.end()) // 差值>0且已经遍历
            {
                res.push_back(i);
                res.push_back(nums_map[diff]);
                break;
            }
            else
            {
                // diff不在map中,将遍历过的元素插入map
                nums_map.insert(make_pair(nums[i], i));
            }
        }
        return res;
    }
};

2.四数相加II

https://leetcode.cn/problems/4sum-ii/

  • 将四个数组分为两组,计算出所有nums1和nums2中每个元素的和,使用map保存结果和个数;
  • 然后再遍历nums3和nums4中的元素,计算0 - nums3[i] - nums4[j]的值,结果在map中寻找,如果找到,就在结果中增加相应的个数;
class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> sum_map;
        int count = 0;
        for(const auto& n1 : nums1) // 保存nums1和nums2中所有对应元素的和与个数
        {
            for(const auto& n2 : nums2)
            {
                sum_map[n1 + n2]++;
            }
        }

        for(const auto& n3 : nums3) // 遍历nums3和nums4,差值在map中寻找
        {
            for(const auto& n4 : nums4)
            {
                int diff = 0 - n3 - n4;
                if(sum_map.find(diff) != sum_map.end())
                {
                    count += sum_map[diff];
                }
            }
        }
        return count;
    }
};

3.三数之和(重点)

https://leetcode.cn/problems/3sum/description/

  • 这道题不适合用哈希法,哈希法可以通过O(N^2)的for循环得到两个数的和,并存放在哈希表中,再通过差值寻找第三个数,但这样会造成重复下标,去重的过程很麻烦;
  • 可以使用双指针法
    请添加图片描述
  • 拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
  • 依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。
  • 接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
  • 如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
    时间复杂度:O(n^2)。
  • 最重要的部分其实是去重
  • a的去重
    都是和 nums[i]进行比较,是比较它的前一个,还是比较它的后一个。
    如果仅比较后一个:
    在这里插入图片描述
    那我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。
    不能有重复的三元组,但三元组内的元素是可以重复的!因此需要和前一个已经遍历过的数据对比;
    在这里插入图片描述
  • b与c的去重
    对b和c的去重如果放在找到三元组之前,就会漏掉0,0,0这种情况
    因此left和right的去重应放在找到三元组之后
    如果找到一个三元组后,left右边或者right左边是重复的,那么可以去掉,因为此时三元组有两个数已经确定了,这个三元组就是唯一的,因此重复的left或者right就需要去掉
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end()); // 先排序数组

        //begin从左向右遍历
        //left和right在begin的右边,计算三者的和,如果小于0,left++,如果大于0,right--
        //直到找到目标数字或者left和right越界或者相遇
        for(int begin = 0; begin < nums.size() - 2; begin++)
        {
            //如果begin > 0,就不需要往后寻找了
            if(nums[begin] > 0)
            {
                break;
            }

            //begin去重:不能简单地依据nums[begin] == nums[begin + 1]来去重,这样会漏掉-1,-1,2这种情况
            if(begin > 0 && nums[begin] == nums[begin - 1])
            {
                continue;
            }

            int left = begin + 1, right = nums.size() - 1;
            while(left < right)
            {
                //对left和right的去重如果放在找到三元组之前,就会漏掉0,0,0这种情况
                int sum = nums[begin] + nums[left] + nums[right];
                if(sum < 0)
                {
                    left++;
                }
                else if(sum > 0)
                {
                    right--;
                }
                else
                {
                    //因此left和right的去重应放在找到三元组之后
                    while(left < right && nums[left] == nums[left + 1])
                    {
                        left++;
                    }

                    while(left < right && nums[right] == nums[right - 1])
                    {
                        right--;
                    }
                    res.push_back(vector<int>{nums[begin], nums[left], nums[right]});
                    //找到之后左右指针同时移动
                    left++;
                    right--;
                }
            }

        }
        return res;
    }
};

4.四数之和

https://leetcode.cn/problems/4sum/description/

大体思路与三数之和相同,四数之和就需要使用两个for循环嵌套来遍历a和b,然后使用双指针法确定剩下的c和d

  • 但是有一些细节需要注意,例如: 不要判断nums[k] > target 就返回了,三数之和 可以通过 nums[i] > 0 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1],target是-10,不能因为-4 > -10而跳过。但是我们依旧可以去做剪枝,逻辑变成nums[i] > target && (nums[i] >=0 || target >= 0)就可以了
    在这里插入图片描述
  • 去重的思路与三数之和也相同
  • 时间复杂度O(N^3)
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end()); // 排序

        for(int i = 0; i < nums.size(); i++)
        {
            if(nums[i] > target && nums[i] >= 0)
            {
                break;
            }
            //a去重
            if(i > 0 && nums[i] == nums[i - 1])
            {
                continue;
            }
            for(int j = i + 1; j < nums.size(); j++)
            {
                long sum12 = nums[i] + nums[j];
                if(sum12 > target && sum12 >= 0)
                {
                    break;
                }
                //b去重
                if(j > i + 1 && nums[j] == nums[j - 1])
                {
                    continue;
                }

                int left = j + 1, right = nums.size() - 1;
                while(left < right)
                {
                    long sums = sum12 + nums[left] + nums[right];
                    if(sums > target)
                    {
                        right--;
                    }
                    else if(sums < target)
                    {
                        left++;
                    }
                    else
                    {
                        res.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
                        //cd去重
                        while(left <right && nums[left] == nums[left + 1])
                        {
                            left++;
                        }
                        while(left <right && nums[right] == nums[right - 1])
                        {
                            right--;
                        }

                        left++;
                        right--;
                    }
                }
            }
        }
        return res;
    }
};
  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]中提到了哈希表的正向迭代器的实现,其中包括存储哈希表地址的成员变量。引用\[2\]中提到了哈希表的析构函数的实现,其中释放了哈希表中的结点。引用\[3\]中提到了哈希表的拷贝构造函数的实现,其中进行了深拷贝操作。根据这些信息,我们可以得出哈希表的一些特点和功能。 首先,哈希表是一种数据结构,用于存储键值对。它通过哈希函数将键映射到桶中,并将值存储在对应的桶中。哈希表的实现通常使用数组来表示桶,每个桶中存储一个链表或红黑树来解决哈希冲突。 哈希表的正向迭代器是对哈希结点指针的封装,其中存储了哈希表的地址。通过迭代器,我们可以遍历哈希表中的键值对。 哈希表的析构函数用于释放哈希表中的结点。它遍历哈希表的每个桶,释放桶中的结点,并将桶置为空。 哈希表的拷贝构造函数实现了深拷贝操作。它将源哈希表的大小调整为目标哈希表的大小,然后将源哈希表中的每个桶中的结点一个个拷贝到目标哈希表中,并更新目标哈希表的有效数据个数。 综上所述,哈希表是一种用于存储键值对的数据结构,它提供了正向迭代器、析构函数和拷贝构造函数等功能。 #### 引用[.reference_title] - *1* *2* *3* [C++ STL(九) -------- 哈希表封装unordered_map和unordered_set](https://blog.csdn.net/m0_52169086/article/details/126709111)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值