哈希
哈希
哈希是一种非常重要的算法思想,通过利用某些数据相同的特征对其进行分组,从而大大提高查询时的效率
关键:发现特征,构造函数,避免/减少冲突
例题
两数之和
题目
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9 |
示例 2:
输入:nums = [3,2,4], target = 6 |
示例 3:
输入:nums = [3,3], target = 6 |
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
- 只会存在一个有效答案
分析
由于题目限定了只存在一个有效答案,即只存在一种组合(x, target - x),所以考虑使用数+补数的思路进行搜索,即如果一个数x的补数target - x不在数组中,那它一定不在答案中。
考虑到原有的数组中可能存在重复元素,且有可能出现3 + 3 = 6这种组合,所以不能简单粗暴地将原数组转为Java中的HashSet
。因此我们选择先新建一个空set,然后遍历原数组,每次拿到一个数时,先查看一下它的补数在不在set
中,如果在,算法结束;否则将此数加入set
。如果没有数重复,此算法一定可行, 即使存在重复数,如果不是恰好x + x = target
的形式,这个数最多有一个就够用了(可能成为其他数的补数),直接加入set
去重即可; 如果恰好为x + x = target
形式,那么这个数第一次被访问到时会加入set
, 再次访问到时它的补数(即自身)已经在set
里了,所以找到结果算法结束,因此算法仍然可行。
代码如下
class Solution { |
字母异位词分组
题目
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"] |
示例 2:
输入: strs = [""] |
示例 3:
输入: strs = ["a"] |
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i]
仅包含小写字母
分析
字母异位词的相同特征是拥有完全相同的字符,因此他们按字典序排列自己的字符结果一定一样。所以可以建立一个哈希表,key是字符按字典序排列的字符串,value是key的字母异位词。
代码如下
class Solution { |
最长连续序列
题目
给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2] |
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1] |
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
分析
一个连续序列如果是最长的,那么它的开头必然是这个序列组中最小的,即它在数组中不应该有直接前驱。换言之,如果一个数组中的数,它的直接前驱在数组中,那么它一定不是一个最长连续序列的开头,就不需要考虑以它为始的情况了。
那么该如何寻找一个序列的伙伴呢?只需要不断查询当前数的后继是否在数组中即可,若在,则长度加1,继续循环;反之退出,当前长度就是这个序列的极限长度,找出这些极限长度中的最大值即可。
注:本题不能排序,至少我不知道有什么复杂度为O(n)的排序算法,查询采用 Java的 HashSet(数组没有查询的轮子),因为重复数字对于连续序列是无意义的
代码如下
class Solution { |
复杂度分析
直观上这里存在双重循环,看起来复杂度是O(n)。然而我们从n个数的角度来看,如果一个数有直接前驱,那么它在一开始就被跳过了,只有在作为直接前驱的直接后继时才会被访问一次;**换言之,开头的数只会作为开头被访问一次(不是任何数的直接后继),其他数都只会作为直接后继仅被访问一次。**因此n个数每个都只访问一次,复杂度是O(n)
缺失的第一个正数
题目
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n)
并且只使用常数级别额外空间的解决方案。
示例 1:
输入:nums = [1,2,0] |
示例 2:
输入:nums = [3,4,-1,1] |
示例 3:
输入:nums = [7,8,9,11,12] |
提示:
1 <= nums.length <= 105
-231 <= nums[i] <= 231 - 1
分析
要满足题目的复杂度需求,本题使用原地哈希。因为一个数组可以被看作是一张哈希表,所以把输入的nums看作哈希表,对它进行更新。
因为本题找的是最小的缺失正数,根据鸽巢原理,这个数一定位于**[1, n + 1]**中,问题的关键在于我们怎么记录这个区间中的一个数是否出现过,一个想法是将位于[1, n + 1]中的数放到对应下标的位置,结束后如果某个位置的数和下标(或减1)不一致则说明这个数没有出现,最小的这种数就是答案。因此我们考虑遍历整个数组,将读到的正数放到对应下标位置(如果可以),遍历完后再遍历一遍找答案。
代码如下
class Solution { |
考场就座
题目
在考场里,有 n
个座位排成一行,编号为 0
到 n - 1
。
当学生进入考场后,他必须坐在离最近的人最远的座位上。如果有多个这样的座位,他会坐在编号最小的座位上。(另外,如果考场里没有人,那么学生就坐在 0
号座位上。)
设计一个模拟所述考场的类。
实现 ExamRoom
类:
ExamRoom(int n)
用座位的数量n
初始化考场对象。int seat()
返回下一个学生将会入座的座位编号。void leave(int p)
指定坐在座位p
的学生将离开教室。保证座位p
上会有一位学生。
示例 1:
输入: |
提示:
1 <= n <= 109
- 保证有学生正坐在座位
p
上。 seat
和leave
最多被调用104
次。
分析
这题的关键是降低复杂度,超过复杂度的应该都会超时。那怎么降低复杂度?其实还是寻找题目特征,建模,转化问题。这道题的关键在于怎么快速找到距离最近的人最远的座位。距离最近的人可以看成是到两边有人的位置的距离中较短的那一个,换位思考一下就是相邻两个坐了人的位置中间夹着的区间的中点,则区间越长,区间中点的座位距离最近的人越远,相同距离则选择靠前的区间(座位编号小的)。因此我们只需要用一个哈希表维护座位上有人的区间端点即可,seat()
就是找到合适的位置坐下,并把该位置作为一个坐了人的端点加入哈希表中,leave(p)
就是将区间端点从哈希表中移除。
代码如下:
|
(•‿•)