哈希
什么时候可以用hash?当需要重复找数据时,用空间换时间。
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(n2)
的算法吗?
方法一:暴力枚举
class Solution {
public int[] twoSum(int[] nums, int target) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return new int[0];
}
}
方法二:哈希表
//哈希表
class Solution {
public int[] twoSum(int[] nums, int target) {
//key:数组元素,value:该元素对应的下标
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
//遍历数组 nums,i 为当前下标,每个值都判断map中是否存在 target-nums[i] 的 key 值
for (int i = 0; i < nums.length; ++i) {
//如果存在则找到了两个值
if (hashtable.containsKey(target - nums[i])) {
return new int[]{hashtable.get(target - nums[i]), i};
}
//如果不存在则将当前的 (nums[i],i) 存入 map 中,继续遍历直到找到为止
hashtable.put(nums[i], i);
}
return new int[0];
}
}
49. 字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
示例 2:
输入: strs = [""]
输出: [[""]]
示例 3:
输入: strs = ["a"]
输出: [["a"]]
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i]
仅包含小写字母
方法一:排序
由于互为字母异位词的两个字符串包含的字母相同,因此对两个字符串分别进行排序之后得到的字符串一定相同
,故可以将排序之后的字符串作为哈希表的键
。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
//key:排序之后的字符串,value:互为字母异位词的所有字符串
Map<String, List<String>> map = new HashMap<String, List<String>>();
//遍历字符串数组strs
for (String str : strs) {
char[] array = str.toCharArray();//将字符串转换为char数组
Arrays.sort(array);//对char数字进行排序
String key = new String(array);//将排序后的char数组转回为字符串key
//将key作为键,如果map中还不存在该key,则new一个ArrayList作为key的value
List<String> list = map.getOrDefault(key, new ArrayList<String>());
//并将原始的str字符串加入该ArrayList
list.add(str);
//将(排序后的str--即key,[原始str])put进map中
map.put(key, list);
}
//将map的所有value(即不同的字母异位词list)组合成一个ArrayList
return new ArrayList<List<String>>(map.values());
}
}
方法二:计数
由于互为字母异位词的两个字符串包含的字母相同,因此两个字符串中的相同字母出现的次数一定是相同
的,故可以将每个字母出现的次数使用字符串表示,作为哈希表的键。
由于字符串只包含小写字母,因此对于每个字符串,可以使用长度为 26的数组记录每个字母出现的次数
。需要注意的是,在使用数组作为哈希表的键时,不同语言的支持程度不同,因此不同语言的实现方式也不同。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<String, List<String>>();
//遍历字符串数组strs
for (String str : strs) {
int[] counts = new int[26];//准备记录字符串str的每个字母出现的次数的数组counts
int length = str.length();//字符串str的长度
//遍历字符串str的每个字母
for (int i = 0; i < length; i++) {
counts[str.charAt(i) - 'a']++;
}
//将每个出现次数大于 0 的字母和出现次数按顺序拼接成字符串,作为哈希表的键
//比如将 [b,a,a,a,b,c] 编码成 a3b2c1
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 26; i++) {
if (counts[i] != 0) {
sb.append((char) ('a' + i));
sb.append(counts[i]);
}
}
String key = sb.toString();
//如:将""a3b2c1""作为键key,得到该键对应的value(是一个list数组)
List<String> list = map.getOrDefault(key, new ArrayList<String>());
list.add(str);//将原始字符串add进list
map.put(key, list);//("a3b2c1",[str1,str6,str8...])
}
return new ArrayList<List<String>>(map.values());
}
}
128. 最长连续序列
给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
方法一:哈希表
核心思想:找到可能序列的第一个数currentNum,初始化currentStreak = 1,然后从currentNum逐次+1,判断currentNum+1是否在set中,如果在,则currentStreak+1,结束序列长度就则为currentStreak,与上一次的longestStreak进行比较,取max为最新的longestStreak。
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> num_set = new HashSet<Integer>();//用set对nums去重
for (int num : nums) {
num_set.add(num);
}
int longestStreak = 0;//初始化最长序列的长度为0
for (int num : num_set) {//遍历每个元素
//只有序列的第一个数(即:最小的数)才能进入该if
if (!num_set.contains(num - 1)) {//如果set中不存在比num小1的数,则num就是序列的第一个数
int currentNum = num;
int currentStreak = 1;//记录当前的循环中的序列长度
while (num_set.contains(currentNum + 1)) {//从最小的数+1开始往后遍历,看set中是否包含
currentNum += 1;
currentStreak += 1;
}
//更新当前最长的序列长度
longestStreak = Math.max(longestStreak, currentStreak);
}
}
return longestStreak;
//如果序列是:52 76 5 7 321 6 4 77
//第1轮:52,不存在51,52进,但是不存在53,出,longestStreak=1
//第2轮:76,不存在75,76进,存在77,进,longestStreak=2
//第3轮:5,存在4,不进
//第4轮:7,存在6,不进
//第5轮:321,不存在320,进,但是不存在322,出,longestStreak=2
//第6轮:6,存在5,不进
//第7轮:4,不存在3,进,存在4、5、6、7,循环之后longestStreak=4
//第8轮:77,不存在76,进,不存在77,出,longestStreak=4
}
}