leetcode 查找算法(一)
查找表:考虑的基本数据结构
第一类: 查找有无--set
元素'a'是否存在,通常用set:集合
set只存储键,而不需要对应其相应的值。
set中的键不允许重复
第二类: 查找对应关系(键值对应)--map
元素'a'出现了几次:dict-->字典
dict中的键不允许重复
第三类: 改变映射关系--map
通过将原有序列的关系映射统一表示为其他
题目描述:
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
说明:
输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。
解题思路:
用两个HashSet分别统统计两个数组,然后找出两个set中公共元素
代码:
public int[] intersection(int[] nums1, int[] nums2) {
HashSet<Integer> set1 = new HashSet<>();
for (Integer num : nums1) {
set1.add(num);
}
HashSet<Integer> set2 = new HashSet<>();
for (Integer num : nums2) {
set2.add(num);
}
return set1.size() < set2.size() ? help(set1, set2) : help(set2, set1);
}
private int[] help(HashSet<Integer> set1, HashSet<Integer> set2) {
int[] output = new int[set1.size()];
int index = 0;
for (Integer s : set1){
if (set2.contains(s))
output[index++] = s;
}
return Arrays.copyOf(output, index);
}
复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(n),因为开辟了新的数组output 长度与输入的数据大小有关。
题目描述:
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
我们可以不考虑输出结果的顺序。
进阶:
如果给定的数组已经排好序呢?你将如何优化你的算法?
如果 nums1 的大小比 nums2 小很多,哪种方法更优?
如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
解题思路:
跟上一道题目不同的是,这个可以有重复元素,所以可以考虑采用hashmap
方法一:采用上述题目的套路,效率低
方法二:使用hash表存储,效率高
方法一:
public int[] intersect(int[] nums1, int[] nums2) {
HashMap<Integer, Integer> map1 = new HashMap<>();
HashMap<Integer, Integer> map2 = new HashMap<>();
for (int i = 0; i < nums1.length; i++) {
map1.put(nums1[i], map1.getOrDefault(nums1[i], 0) + 1);
}
for (int i = 0; i < nums2.length; i++) {
map2.put(nums2[i], map2.getOrDefault(nums2[i], 0) + 1);
}
int n = nums1.length < nums2.length ? nums1.length : nums2.length;
int[] output = new int[n];
int index = 0;
for (Map.Entry<Integer, Integer> map : map1.entrySet()) {
if (map2.containsKey(map.getKey())) {
int temp = map2.get(map.getKey()) < map.getValue() ? map2.get(map.getKey()) : map.getValue();
for (int i = 0; i < temp; i++) {
output[index++] = map.getKey();
}
}
}
return Arrays.copyOf(output, index);
}
复杂度分析:
- 时间复杂度:O(n*n)
- 空间复杂度:O(min(m,n))
方法二:采用hash表
思路:
由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。
首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。
为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。
public int[] intersect2(int[] nums1, int[] nums2) {
// 保证 nums1的长度始终最小
if (nums1.length > nums2.length)
return intersect2(nums2, nums1);
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums2.length; i++) {
map.put(nums2[i], map.getOrDefault(nums2[i], 0) + 1);
}
int n = nums1.length;
int[] output = new int[n];
int index = 0;
for (Integer num : nums1) {
int count = map.getOrDefault(num, 0);
if (count > 0) {
output[index++] = num;
count--;
if (count > 0) {
map.put(num, count);
} else {
map.remove(num);
}
}
}
return Arrays.copyOf(output, index);
}
复杂度分析:
- 时间复杂度:O(n + m)
- 空间复杂度:O(min(m,n))
两者性能对比:
题目描述:
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
示例1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。
解题思路:
方法一:用两个hashmap分别统计两个字符串各元素出现次数,最后比较key-value是否相等即可 (效率较低)
方法二:由于题目说明,都是小写字母,所以可以用count[26]来统计s,t字符串中各字母出现的次数。对于s字符串,每出现一次就在count[i]自增一次,对于t字符串,每出现一次就在count[i]自减一次。如果s与t满足要求,最终的count又回到原始状态,否则返回false
方法一:
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
int n = s.length();
HashMap<Character, Integer> map1 = new HashMap<>(n);
HashMap<Character, Integer> map2 = new HashMap<>(n);
for (int i = 0; i < n; i++) {
map1.put(s.charAt(i), map1.getOrDefault(s.charAt(i), 0) + 1);
map2.put(t.charAt(i), map2.getOrDefault(t.charAt(i), 0) + 1);
}
for (Character c : s.toCharArray()) {
if (!map1.get(c).equals(map2.getOrDefault(c,0)))
return false;
}
return true;
}
复杂度分析:
- 时间复杂度:O(n),虽然时间复杂度度O(n)但是在最后遍历时
map.get().equals()
函数是比较费时间的 - 空间复杂度:O(n),因为要申请和输入字符串同样大小的空间
运行截图:
方法二**:
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] counter = new int[26]; // 题目假定全为小写字母
for (int i = 0; i < s.length(); i++) {
counter[s.charAt(i) - 'a']++; // 统计s中各元素出现的次数
counter[t.charAt(i) - 'a']--; // 若t中对应元素在的话 就自减,最终回到初始状态
}
for (int count : counter) {
if (count != 0) { // 若有任何不满足的都返回false
return false;
}
}
return true;
}
复杂度分析
- 时间复杂度:O(n),这个遍历效率很高,不需要hash寻址
- 空间复杂度:O(1),因为counter是固定长度的,这里就当作常数级对待啦。
运行结果:
题目描述:
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为 1,那么这个数就是快乐数。
如果 n 是快乐数就返回 True ;不是,则返回 False 。
示例:
输入:19
输出:true
解释:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1
解题思路
本题难点在于何时跳出循环,在算无限循环小数时有一个特征,就是当除的数中,和之前历史的得到的数有重合时,这时就是无限循环小数。
因此,只需要判断有或无,不需要记录次数,故用set的数据结构。每次对求和的数进行append,当新一次求和的值存在于set中时,就return false.
代码:
public boolean isHappy(int n) {
HashSet<Integer> set = new HashSet<>();
int sum;
int temp;
while (n != 1) {
sum = 0;
// 求n各位数的和
while (n > 0) {
temp = n % 10;
sum += temp * temp;
n /= 10;
}
// 如果求的和在过程中出现过
if (set.contains(sum)) {
return false;
} else {
set.add(sum);
}
// 循环下一轮
n = sum;
}
return true;
}
复杂度分析
- 时间复杂度:O(logn)
- 空间复杂度:O(logn)
相关分析见:leetcode讨论版
运行结果
题目描述
给定一种规律 pattern 和一个字符串 str ,判断 str 是否遵循相同的规律。
这里的 遵循 指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应规律。
示例1:
输入: pattern = "abba", str = "dog cat cat dog"
输出: true
示例 2:
输入:pattern = "abba", str = "dog cat cat fish"
输出: false
示例 3:
输入: pattern = "aaaa", str = "dog cat cat dog"
输出: false
示例 4:
输入: pattern = "abba", str = "dog dog dog dog"
输出: false
说明:
你可以假设 pattern 只包含小写字母, str 包含了由单个空格分隔的小写字母。
解题思路:
方法一:将 pattern中的每个字母作为hashmap中的key,str中的每个字母作为value,第一次遇到的 key 就加入到 HashMap 中,第二次遇到同一个 key,那就判断它的value 和当前单词是否一致。用set存放value的值
方法二:两次映射 确定 一一对应 pattern -- > str / str ----> pattern
方法一:
public boolean isWordPattern(String pattern, String str) {
String[] s = str.split(" ");
if (s.length != pattern.length())
return false;
HashMap<Character, String> map = new HashMap<>();
HashSet<String> set = new HashSet<>();
for (int i = 0; i < pattern.length(); i++) {
char key = pattern.charAt(i);
String value = s[i];
if (map.containsKey(key)) {
if (!map.get(key).equals(value)) {
return false;
}
} else {
// 判断value是否存在
if (set.contains(value)) {
return false;
}
map.put(key, value);
set.add(value);
}
}
return true;
}
复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(n)
方法二
public boolean wordPattern(String pattern, String str) {
String[] strs = str.split(" ");
if (pattern.length() != strs.length)
return false;
String[] p = pattern.split("");
// 两个方向映射
return wordPatternMap(p,strs) && wordPatternMap(strs,p);
}
// array1到array2的映射
private boolean wordPatternMap(String[] array1, String[] array2) {
HashMap<String, String> map = new HashMap<>();
for (int i = 0; i < array1.length; i++) {
String key = array1[i];
if (map.containsKey(key)) {
if (!map.get(key).equals(array2[i])) {
return false;
}
} else {
map.put(key, array2[i]);
}
}
return true;
}
复杂度分析:
- 时间复杂度:O(n)
- 空间复杂度:O(n)
运行结果比较:
第二种的运行效率明显低于第一种方法的效率,但是第二种方法更通用,套路
第二种方法运行结果如下:
第一种方法运行结果如下: