LeetCode 热题 100 —— 哈希
1. 两数之和
这道题的暴力解法相信看到这题的所有人都能想到,使用双重 for 循环进行枚举,算出所有的结果。
// 暴力解法
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
int[] result = {i, j};
return result;
}
}
}
return null;
}
提交发现耗时还是有些长,并且当前的在热题100的分类中,这道题被分在了哈希,那么很明显这一题可以使用哈希表解决。
在这一题中,对于每一个元素 n ,都存在一个另一个数,使他们求和可以得到目标值,那个数就是差 diff。而这个差也需要在数组里找,所以将数组里全部的数存到 HashMap 中。遍历 nums 数组,判断当前元素和目标值的差是否在哈希表中,由于一个数不能重复使用,在判断的时候需要判断下标,这样就只需要通过一次 for 循环算出题目的结果。
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> map = new HashMap();
int[] result = new int[2];
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
}
for (int i = 0; i < nums.length; i++) {
int diff = target - nums[i];
if (map.containsKey(diff) && (Integer)map.get(diff) != i) {
result[0] = i;
result[1] = map.get(diff);
return result;
}
}
return result;
}
49. 字母异位词分组
如果没有做过 242. 有效的字母异位词 建议先做242这一题。
看到这一题第一反应也是相同的思路,把每个单词的字符出现次数记录到数组,相同的数组就是异位词。然后将这个数组作为 哈希表 的 key,值就是一个 List<String>
里面存放单词。
public List<List<String>> groupAnagrams(String[] strs) {
Map<int[], List<String>> map = new HashMap<>();
for(String str : strs) {
int[] letter = new int[26];
for (char c : str.toCharArray()) {
letter[c - 'a'] ++;
}
List<String> list;
if (!map.containsKey(letter)) {
list = new ArrayList();
} else {
list = map.get(letter);
}
list.add(str);
map.put(letter, list);
}
List<List<String>> result = new ArrayList<>();
map.forEach((k, v) -> result.add(v));
return result;
}
运行发现,每个单词都分成了一组。想了一下,应该是key保存的并不是letter的值,而是他的内存地址。然后就思考怎么才能把数组保存成key,想到了 Arrays.toString() 方法,把数组转成字符串,这样就可以把值作为哈希的key了
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for(String str : strs) {
int[] letter = new int[26];
for (char c : str.toCharArray()) {
letter[c - 'a'] ++;
}
String key = Arrays.toString(letter);
List<String> list;
if (!map.containsKey(key)) {
list = new ArrayList();
} else {
list = map.get(key);
}
list.add(str);
map.put(key, list);
}
List<List<String>> result = new ArrayList<>();
map.forEach((k, v) -> result.add(v));
return result;
}
运行发现大概在 15ms 左右,速度并不是很快。再回到 242 这道题,有一种解法是将两个单词的字母进行排序然后比对。这里也同样可以使用,思路和用数组保存类似。这也是leetcode官方提供的一种解法。
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<String, List<String>>();
for (String str : strs) {
char[] array = str.toCharArray();
Arrays.sort(array);
String key = new String(array);
List<String> list = map.getOrDefault(key, new ArrayList<String>());
list.add(str);
map.put(key, list);
}
return new ArrayList<List<String>>(map.values());
}
128. 最长连续序列
一个很容易想到的解法是 O(nlogn) 的,对数组进行排序,然后遍历一遍就可以得到答案。但是这个解法很显然不符合题目要求。
public int longestConsecutive(int[] nums) {
if (nums.length < 2) {
return nums.length;
}
Arrays.sort(nums);
int result = 0;
int temp = 1;
for (int i = 1; i < nums.length; i++) {
int count = Math.abs(nums[i] - nums[i - 1]);
if (count == 0) {
continue;
}
if (count > 1) {
result = Math.max(result, temp);
temp = 1;
continue;
}
temp++;
}
result = Math.max(result, temp);
return result;
}
那么如果想符合题目要求,就不能够排序,只能通过一次遍历得到结果。尽然题目归类在哈希,那么就要想要使用哈希表。将所有元素都作为key,存到哈希表里。根据题目给的示例2,发现也是需要去重的,所以使用哈希表也很合适。
那么保存进去之后怎么计算连续序列长度呢?
连续序列就代表两个值之间相差1,也就是一直判断某一个元素加一后的值在不在这个哈希表中。但是万一遍历到的数并不是起点怎么办呢?所以我们需要判断某个元素减一的值在不在哈希表,如果不在证明他就是起点,我们就从这个起点开始遍历,直到终点。这样的话只需要遍历一遍数组就可以得到最大的连续子序列长度。
public int longestConsecutive(int[] nums) {
Set<Integer> num_set = new HashSet<Integer>();
for (int num : nums) {
num_set.add(num);
}
int longestStreak = 0;
for (int num : num_set) {
if (!num_set.contains(num - 1)) {
int currentNum = num;
int currentStreak = 1;
while (num_set.contains(currentNum + 1)) {
currentNum += 1;
currentStreak += 1;
}
longestStreak = Math.max(longestStreak, currentStreak);
}
}
return longestStreak;
}