前言
哈希篇,继续下一题。
记录 十六【1. 两数之和】
一、题目阅读
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
进阶:
你可以想出一个时间复杂度小于 O(n ^2) 的算法吗?
二、尝试解答
思路和实现
我们先不考虑这个题目是出现在哪个篇章的,出现在固定篇章会先知解答方法,只从题目本身来想。
(1)这是一个数组,处理数组:学过的方法有双指针法、滑动窗口法、直接遍历(暴力解法)。
-
双指针/滑动窗口试一下:题目让返回两个数之和=target,这两个数的下标。如果用快慢指针,滑动窗口这代表的是一个区间,记录求和得到区间内的元素之和,不是两个元素相加。
-
直接遍历(暴力解法),肯定可以。
class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { vector<int> result; for(int i=0;i < nums.size();i++){ for(int j = i+1;j < nums.size();j++){ //为什么是j=i+1,因为同一个元素不让重复出现 if(nums[i] + nums[j] == target){ result.push_back(i); result.push_back(j); break; } } } return result; } };
(2)ok,已经用一种方法实现,可以考虑另外办法?上面的遍历时间复杂度O(n^2)。进阶:如何减小时间复杂度?
答:肯定要遍历数组,只遍历一次数组就会减少时间复杂度,用i得到某个数组的值,接下来要找(target-nums[i])有没有在数组中存在过?“找一个元素有没有在集合中存在过”——哈希法。
-
第一步:确定用什么哈希结构:
- 数组?:本来就是数组,用下标作为key,可是目标是得到下标key,所以数组不行。
- set?:输入数组元素可能重复(示例3),所以multiset,key是单个,如果放数组下标,那么nums[i]没有存;如果放nums[i],那么对应下标是什么?不知道。所以multiset也不行。
- map?:元素可重复,肯定是multimap;map有key还有mapped-value,可以存key对应的value。下标和nums[i]都能访问到,好像可以,确定用multimap。
-
第二步:确定什么作为key,什么是mapped-value?
- 如果数组下标作为key,但是multimap的find(k),是从key来得到mapped-value。题目要求的是下标。所以不行。
- 那么nums[i]元素值所以key,关联key的mapped-value是对应下标i。
-
代码实现
class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { multimap<int,int> map;//哈希结构,先定义容器,可重复。 vector<int> result;//返回值先摆上 for(int i = 0;i < nums.size();i++){ //遍历数组,把键值对放进去 map.insert({nums[i],i}); //用元素值作为key,用下标i作为value } for(auto it = map.begin();it != map.end();++it){ //在容器map中找,用begin()和end()可以遍历所有对象。 auto another = map.find(target - it->first); //找另一个加数 //multimap<int,int>::iterator another = map.find(target - it->first);//auto可以换成:multimap<int,int>::iterator,用auto,让自动识别类型。 if(another != map.end() && it != another){ //找到并且不是it本身,把下标value读出来放到result中 result.push_back(it->second); result.push_back (another->second); break;//找到之后可以退出 } } return result; } };
代码随想录学习
学习内容
(1)思路区别:虽然都是哈希法,但是处理方法不同。
上面用哈希结构multimap,先把数组所有内容都放到multimap中,在multimap里面遍历看能不能找到两个加数?
-
学习到的思路,边遍历边找。把遍历过的元素加入到容器中,遍历到某个元素,看另一个加数有没有存在过。
-
新思路的代码实现:
- 确定结构:还是map,但是这里可以用unordered_map。解释:无序——只是看之前遍历到的nums[i]是不是当前元素的另一半加数,顺序不是那么重要。不重复——虽然nums可能有重复元素,但是相同元素key有一个代表在容器unordered-map中存在就可以了,反正key一样,返回哪个value下标值都可以。
- 用unordered-map,效率还能更高,相比于multimap。
class Solution { public: vector<int> twoSum(vector<int>& nums, int target) { unordered_map<int,int> map;//放遍历过的元素及下标 vector<int> result; for(int i = 0;i < nums.size();i++){ unordered_map<int,int>::iterator it = map.find(target-nums[i]); if(it != map.end()){//找到另一个加数 result.push_back(i); result.push_back(it->second); break; //找到就可以退出循环,返回结果。 } map.insert({nums[i],i}); //没找到 } return result; } };
-
对比参考代码
在插入的时候:都可以通过,但是使用的函数原型不一样而已。 参考给的:map.insert(pair<int, int>(nums[i], i)); //先创建pair对象,再把对象作为参数给insert。那么函数原型是:template <class P> pair<iterator,bool> insert ( P&& val ); 个人实现:map.insert({nums[i],i});//使用初始化列表的方式,函数原型:void insert ( initializer_list<value_type> il );
总结:参考给的思路:效率可以更高。
总结
- 文章给了三种实现,都是对的。区别效率问题。可见即使都是哈希法,但是存放东西的含义不一样,结构不一样,思路也就不一样。可以体会。
- unordered_map 使用方法链接:https://cplusplus.com/reference/unordered_map/unordered_map/
(欢迎指正,转载表明出处)