《全网最易懂 | 图文一步步带你搞懂哈希表(下)》

【哈希表导读】哈希表的结构和原理,笔者在上一篇文章中介绍过了,本文是上篇的延续,本文主要以算法题为核心,逐步剖析面试中经常遇到的高频面试点,以及如何使用哈希表解决面试遇到的算法题。

1. 两数之和:

在这里插入图片描述
1.1、剖析:

看题干,需要求数组中两个元素的和等于指定值的下标,那这也是个范围的问题,也许有同学想到双指针的思想,但这里还略微有些不同,因为题目并没有要求两个元素必须是连续的;因为两个元素之间可能还间隔了几个元素,因此我们需要有个容器去记录当前我们已经遍历了哪些元素,而且当前遍历的元素num[cur]和容器中存储的元素之和是否等于指定值target,也即是容器中是否存在这样的值等于target - nums[cur]。

解锁更多互联网大厂面试宝典,请关注VX公众号:边城梦溪

说到这里,笔者为大家绘制一个简单的流程图,看完流程图,我相信大家就能很容易理解这道题目的解题思路了。
在这里插入图片描述

继续往下遍历,遍历到元素7的位置,按照上面的思路,在unordered_map中寻找(9-7)的元素,如果没有就把7塞到unordered_map中去。否则就返回元素7的下标以及unordered_map中key为(9-7)的元素对应的value值,也即是下标值(因为unordered_map中存储的便是数组中的元素值,以及该元素值对应的下标)。

在这里插入图片描述
好,我们将上述流程用C++代码实现一遍:


std::vector<int> twoSum(std::vector<int>& nums, int target)
{
    std::unordered_map<int, int> subMap;
    std::vector<int> resIndex;
    int iSize = nums.size();
    for (int i = 0; i < iSize; ++i)
    {
        auto indexIter = subMap.find(target - nums[i]);
        if (indexIter != subMap.end())
	{
            return { indexIter->second, i };
	}
	subMap.insert(std::pair<int, int>(nums[i], i));
    }
    return resIndex;
}

1.2、LeetCode运行结果:

在这里插入图片描述
2、四数相加:

在这里插入图片描述

2.1、剖析:

上一道题目,是在一个数组中寻找两个元素和等于指定值的下标。本题可以算是对上一道题目的延伸和扩展,因为本题也是寻找和等于指定值的数组元素下标,只是元素的个数由2个变成了4个,数组由一个变成了4个数组。但思路还是一样的,我可以借助分而治之的思想:将4个元素和的问题变成两组元素和,一组含有两个元素;将4个数组变成两组数组,一组含有两个数组。 比如:我们先遍历数组A和数组B,统计数组A中的元素a和数组B中元素b的和,并记录a+b和对应的次数,用代码可以这样来实现。


std::unordered_map<int, int> sumMap;
for (auto& a : A)
{
    for (auto& b : B)
        ++sumMap[a+b];
}

经过这两轮遍历,sumMap中便存储了数组A和数组B所有元素之间的两两和,以及两两和对应的次数。

那处理完数组A和数组B的元素,接下来就是数组C和数组D了,因为要求四个数组中元素的和为0的元素对应的下标,如果sumMap中存在这样的两数和,该两数和等于数组C和数组D中两两元素之和(也即是:c+d)的相反数。也可理解成sumMap中存在这样的值等于-(c+d)。那么我们就可以在数组A、B、C、D中找到这样的组合,使得它们的和等于0。

int iCount = 0;
for (auto& c : C)
{
    for (auto& d: D)
    {
        if (sumMap.find(0 -(c + d)) != sumMap.end())
           iCount += sumMap[0 -(c + d)];
    }
}

那上述流程,用C++实现一遍,完整代码如下所示:

int fourNumSum(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D)
{
	std::unordered_map<int, int> sumMap;
	//key表示A、B中的a与b的和,value为a+b出现的次数
	for (auto& a : A)
	{
	   for (auto& b : B)
	      ++sumMap[a + b];
	}
	int count = 0;
	for (auto& c : C)
	{
	    for (auto& d : D)
		if (sumMap.find(0 - (c + d)) != sumMap.end())
		    count += sumMap[0 - (c + d)];
	}
	return count;
}

2.1、LeetCode执行结果:
在这里插入图片描述
3、三数之和

在这里插入图片描述

3.1、剖析:

两层for循环可确定两个数值,可以使用哈希法来确定第三个数 0-(a+b) 是否在数组里出现过,但有一个难搞的问题,就是题目中说的不可以包含重复的三元组。我们要在循环中去除重复的起始点,比如:如果前后两次循环中,三元组起始点对应的值相同,那这两次循环得到的三元组后两位元素值肯定也是相同的。再比如:三元组中第一位元素a的值已经确定了,但是呢?后面两位元素b、c相同,那么也可以得出同样的结论,以b、c为起点的三元组,它们包含的后两个元素肯定也相同。因此要去掉!

那我们用两重for来解决上述问题:

vector<vector<int>> threeSum(vector<int>& nums) 
 {
        int iSize = nums.size();
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < iSize; ++i)
        {
            //因为nums是升序排列的,如果起始点的值都大于0,
            //那无论后面b、c是啥值都可能出现三数之和等于0
            if (nums[i] > 0)
                return result;
            
            //第一轮循环起始点的值和第二轮循环中起始点的值相同
            //那两轮循环得到的三元组肯定是一样的
            if (i > 0 && nums[i] == nums[i - 1])
                continue;
            
            std::unordered_set<int> numSet;
            for (int k = i + 1; k < iSize; k++)
            {
               //内层循环寻找b、c的值,
               //如果nums数组中出现三个元素都相同,
               //那么以这三个元素作为起始点的三元组肯定也相同
               if (k > i + 2 && nums[k] == nums[k - 1] 
                      && nums[k - 1] == nums[k - 2])
                     continue;
                
                int iTarget = 0 - (nums[i] + nums[k]);
                auto iter = numSet.find(iTarget);
                if (iter != numSet.end())
                {
                    result.push_back({nums[i], iTarget, nums[k]});
                    numSet.erase(iTarget);
                }
                else
                {
                     //如果没有扎到起始点和终止点两个元素和的相反数
                     //就先先把对应的终止点元素nums[k]塞到哈希表中去
                     numSet.insert(nums[k]);
                }
            }
        }
        return result;
    }

3.2、LeetCode执行结果:

在这里插入图片描述

这道题目用哈希表来解决,显得有点抽象,其实双指针也能很好处理这类问题,因为我们可以通过双指针不断地去缩小查找的范围,最终把三元组给定位到,我们可以这样来实现。

3.3、双指针思想:
在这里插入图片描述
以上面的nums数组为例,首先将数组做升序排列,做一层for循环,i从下标0的地方开始,定义一个下标left 定义在i+1的位置上,定义一个下标right 在数组结尾的位置上。依然在数组中找到 a、b、c 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。

如果nums[i] + nums[left] + nums[right] > 0 就说明三数之和大了,此时right下标往左移动,减小right的值,使得三数之和向0靠拢。

如果 nums[i] + nums[left] + nums[right] < 0, 说明三数之和小了,left 就向右移动,使得三数之和向0靠拢。

重复上述过程,直到left和right相遇为止,用C++代码实现该双指针思想,代码如下:

vector<vector<int>> threeSumDoublePointer(vector<int>& nums)
{
     sort(nums.begin(), nums.end());
     vector<vector<int>> result;
     for (int i = 0; i < nums.size(); i++)
     {
	  if (nums[i] > 0)
	     return result;

	  if (i > 0 && nums[i] == nums[i - 1])
		continue;

	   int left = i + 1;
	   int right = nums.size() - 1;

	   while (right > 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] }));
		    while (right > left && nums[right] == nums[right - 1]) right--;
	            while (right > left && nums[left] == nums[left + 1]) left++;

	            right--;
		    left++;
		}
	   }
       }
      return result;
}

3.3、LeetCode执行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值