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