当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法。
什么时候用set 什么时候用数组?
使用数组来做哈希的题目,是因为题目都限制了数值的大小。
而这道题目没有限制数值的大小,就无法使用数组来做哈希表了。(后来限制了)
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
但直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。不要小瞧 这个耗时,在数据量大的情况,差距是很明显的。
class Solution {
public boolean isAnagram(String s, String t) {
// 用数组做哈希表
int[] record = new int[26];
// 用s.toCharArray()把String转为char数组,遍历
for (char c : s.toCharArray()) {
// Unicode -- Java中允许字符算术(‘b’-'a' = 98-97)
// char之间进行算数操作返回的是int
record[c - 'a']++;
}
for (char c : t.toCharArray()) {
record[c - 'a']--;
}
for (int r : record) {
if (r != 0) {
return false;
}
}
return true;
}
}
注意toCharArray()的转换方法,注意for (char c : s.toCharArray())的遍历方法
Unicode -- Java中允许字符算术(‘b’-'a' = 98-97)
char之间进行算数操作返回的是int
import java.util.HashSet;
import java.util.Set;
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
// 检查数组是否为null或空
if (nums1 == null || nums1.length == 0 || nums2 == null || nums2.length == 0) {
return new int[0];
}
Set<Integer> set1 = new HashSet<>();
Set<Integer> set2 = new HashSet<>();
for (int i : nums1) {
set1.add(i);
}
for (int i : nums2) {
if (set1.contains(i)) {
set2.add(i);
}
}
int[] arr = new int[set2.size()];
int index = 0;
for (int s : set2) {
arr[index++] = s;
}
return arr;
}
}
检查 nums1 == null
和 nums1.length == 0
两个条件看似有重叠,但它们实际上是检查两种不同的情况:
-
nums1 == null
: 这个条件检查nums1
是否为null
。在 Java 中,尝试访问一个null
引用的任何属性或方法(例如length
)会导致NullPointerException
。因此,这个检查是必要的,以确保在你的方法中进一步访问nums1.length
时不会抛出异常。 -
nums1.length == 0
: 这个条件检查数组是否为空,即不包含任何元素。即使nums1
不是null
(即它是一个有效的数组引用),它也可能是一个空数组,这意味着没有元素可以处理或进行交集操作。如果不检查这个条件,即使方法不会因为null
引用而失败,它仍然会执行后续的集合操作,但实际上没有任何数据需要处理。
总结来说,这两个条件共同确保了:
- 安全性:通过检查
null
来防止NullPointerException
。 - 效率:通过确认数组不为空(长度大于0),避免对空数据执行无意义的操作。
class Solution {
public boolean isHappy(int n) {
Set<Integer> record = new HashSet<>();
while (n != 1 && !record.contains(n)) {
record.add(n);
n = getNextNumber(n);
}
return n == 1;
}
private int getNextNumber(int n) {
int res = 0;
while (n > 0) {
int temp = n % 10;
res += temp * temp;
n = n / 10;
}
return res;
}
}
当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。
所以这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。
判断sum是否重复出现就可以使用unordered_set。
非快乐数的特性: 快乐数定义为不断迭代其位数字的平方和最终能够得到1的数字。然而,如果一个数字不是快乐数,这种迭代过程不会以1结束,而是最终会形成一个循环。这个循环包含的数值不包括1,而是一组固定的数字不断重复出现。
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if (nums == null || nums.length == 0){
return res;
}
// 注意map的各种api
Map<Integer, Integer> map = new HashMap<>();
// 在map中寻找是否有匹配的key
for (int i = 0; i < nums.length; i++) {
int diff = target - nums[i];
if (map.containsKey(diff)) {
res[0] = map.get(diff);
res[1] = i;
break;
}
// 如果没找到匹配对,就把访问过的元素和下标加入到map中
map.put(nums[i], i);
}
return res;
}
}
本题其实有四个重点:
1. 为什么会想到用哈希表
使用哈希表主要是因为哈希表提供了快速的查找能力,平均时间复杂度为 O(1)。在这个问题中,需要快速判断数组中是否存在某个特定的数,这个数是当前数与目标值之差。哈希表的高效查找特性使得我们可以在遍历数组的同时,快速查找每个元素的配对元素是否已经出现在之前的遍历中。
2. 哈希表为什么用map
Map
是一种键值对的数据结构,能够让我们将一个值与一个键关联起来,这样我们就可以插入、修改、访问和删除键值对。在 twoSum
函数中使用 Map
(在 Java 中通常使用 HashMap
)是因为我们需要保存每个数字及其对应的索引位置,以便快速访问和查找。
3. 本题map是用来存什么的
在这个问题中,Map
用来存储数组中的数及其对应的索引。这样做的目的是为了在遍历数组时,能够快速检查目标值与当前数的差是否已经在之前遍历的数中出现过。
4. map中的key和value用来存什么的
在 twoSum
方法中:
- key 存储的是
nums
数组中的元素,即数字本身。 - value 存储的是该元素在数组
nums
中的索引位置。
这种键值对的配置允许我们在遍历数组的过程中,通过简单地计算 target - nums[i]
并查找这个差值是否已经作为一个键存储在 Map
中,从而快速判断是否有两个数的和等于 target
。如果差值存在,就意味着我们找到了这样一对数:当前的数 nums[i]
和之前某个数 nums[j]
,它们的索引分别是 i
和 j
,且它们的和正好是 target
。
通过这种方法,我们不仅优化了问题的时间复杂度(从 O(n^2) 降到 O(n)),也简化了实现,使其更加高效。