目录
例题7: 按照字符串出现的顺序重组字符串 leetcode 451
例题7: 一个使用查找表的经典问题 leetcode 1 两数之和
1.两类查找
例题1 两个数组的交集:leetcode 349
解题思路:这是典型的第一类查找,查找有无,所以我们会想到使用Set,将第一个数组的元素放到数组中,再判断第二个数组的元素是否存在于第一个元素的数组中,如果存在则加入到一个新的Set,最后返回这个新的Set
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> temp = new HashSet<>();
Set<Integer> re = new HashSet<>();
for (int i = 0; i < nums1.length; i++) {
temp.add(nums1[i]);
}
for (int i = 0; i < nums2.length; i++) {
if (temp.contains(nums2[i])) {
re.add(nums2[i]);
}
}
int[] reArr = new int[re.size()];
Object[] objects = re.toArray();
for (int i = 0; i < objects.length; i++) {
reArr[i] = (int) objects[i];
}
return reArr;
}
例题2 两个数组的交集 II:leetcode 350
解题思路:这次跟上一题相比我们需要考虑元素重复的情况,所以将Set换成List,判断是否存在的条件依然不变,只是需要判断完成后要删除元素。
但是这属于第二种查找,键值对的类型,需要用键值对来记录元素出现的频次
public int[] intersect(int[] nums1, int[] nums2) {
List<Integer> temp = new ArrayList<>();
List<Integer> re = new ArrayList<>();
for (int i = 0; i < nums1.length; i++) {
temp.add(nums1[i]);
}
for (int i = 0; i < nums2.length; i++) {
if (temp.contains(nums2[i])) {
re.add(nums2[i]);
temp.remove(Integer.valueOf(nums2[i]));
}
}
int[] reArr = new int[re.size()];
Object[] objects = re.toArray();
for (int i = 0; i < objects.length; i++) {
reArr[i] = (int) objects[i];
}
return reArr;
}
思考,在数组有序的情况下,我们就可以不借用辅助的数据结构,而是可以很容易的实现一个O(logn)级别的算法来实现查找,比如二分查找。
还有一类的查找是非常快的,就是哈希表,但是他也失去了数据的顺序性
例题3 有效的字母异位词:leetcode 242
s
和t
仅包含小写字母
解题思路:
解法1:t 是 ss 的异位词等价于「两个字符串排序后相等」,因此我们可以对字符串 ss 和 tt 分别排序,看排序后的字符串是否相等即可判断。此外,如果 ss 和 tt 的长度不同,tt 必然不是 ss 的异位词。
时间复杂度是O(nlogn),空间复杂度O(logn)
解法2:使用哈希表来记录字符出现的频次
tt 是 ss 的异位词等价于「两个字符串中字符出现的种类和次数均相等」。由于字符串只包含 2626 个小写字母,因此我们可以维护一个长度为 2626 的频次数组 \textit{table}table,先遍历记录字符串 ss 中字符出现的频次,然后遍历字符串 tt,减去 \textit{table}table 中对应的频次,如果出现 \textit{table}[i]<0table[i]<0,则说明 tt 包含一个不在 ss 中的额外字符,返回 \text{false}false 即可。
时间复杂度O(n) 空间复杂度O(s)
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
int[] table = new int[26];
for (int i = 0; i < s.length(); i++) {
table[s.charAt(i) - 'a']++;
}
for (int i = 0; i < t.length(); i++) {
table[t.charAt(i) - 'a']--;
if (table[t.charAt(i) - 'a'] < 0) {
return false;
}
}
return true;
}
如果t跟s不仅仅包含的是小写字母吗,而是所有的字符,比如包含了大写的字母,这个时候可以扩大数组的长度为256,但是如果我还包含了中文,这个时候长度会很大,所以这个时候需要真正的hash表来存贮字符出现的频次。
public static boolean isBigAnagram(String s, String t) {
if (s.length() != t.length()) {
return false;
}
Map<Character, Integer> table = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
table.put(s.charAt(i), table.getOrDefault(s.charAt(i), 0) + 1);
}
for (int i = 0; i < t.length(); i++) {
table.put(t.charAt(i), table.getOrDefault(t.charAt(i), 0) - 1);
if (table.get(t.charAt(i)) < 0) {
return false;
}
}
return true;
}
例题4 : 快乐数 leetcode202
解题思路:
解法一 快慢指针法
解法二 用哈希集合检测循环,他有两种的可能一种是循环到结果为1
一种是进入一个死循环
有没有第三种可能 值会愈来愈大 不会进入死循环呢?答案是没有的,对于一个一位数来说,他分解后最大的值不会超过81,二位数分解后最大的值不会超过162,三位数分解后最大的值不会超过243,以此类推,最后都会在一个可控的范围内循环,所以用一个哈希表存储每次循环出现过的数字,当出现相同的数字的 时候,就说明已经可以返回false了。
public static boolean isHappy(int n) {
Set<Integer> stringList = new HashSet<>();
while (n != 1) {
n = getNext(n);
if (stringList.contains(n)) {
return false;
}else {
stringList.add(n);
}
}
return true;
}
private static int getNext(int n) {
int re = 0;
while (n > 0) {
int i = n % 10;
re = re + i * i;
n = n / 10;
}
return re;
}
时间复杂度:O(logn)
空间复杂度:O(logn)
例题5: 单词找规律 leetcode 290
解题思路:
我们需要判断字符与字符串之间是否恰好一一对应。即任意一个字符都对应着唯一的字符串,任意一个字符串也只被唯一的一个字符对应。在集合论中,这种关系被称为「双射」。
想要解决本题,我们可以利用哈希表记录每一个字符对应的字符串,以及每一个字符串对应的字符。然后我们枚举每一对字符与字符串的配对过程,不断更新哈希表,如果发生了冲突,则说明给定的输入不满足双射关系。
需要注意的是插入的是同一个对象返回来的才是同一个对象
代码:
public static boolean wordPattern(String pattern, String s) {
Map<Object, Object> strMap = new HashMap<>();
Map<Object, Object> cha = new HashMap<>();
String[] s1 = s.split(" ");
if (s1.length != pattern.length()) {
return false;
}
//果key重复了,返回的是map.get(key),
// 注意这里是Integer 把同一个Integer作为value存进去 当hash冲突的时候返回来的才是同一个对象,如果存的是int 则返回来的是两个不同的Integer
for (Integer i = 0; i < s1.length; i++) {
if (strMap.put(s1[i], String.valueOf(i)) != cha.put(pattern.charAt(i), String.valueOf(i))) {
return false;
}
}
return true;
}
例题6: 同构字符串 leetcode 205
解题思路:这个题目和上个题目的解题思路是一样的,不再多说
代码:
public boolean isIsomorphic(String s, String t) {
if (s.length() != t.length()) {
return false;
}
Map<Character, Integer> sMap = new HashMap<>();
Map<Character, Integer> tMap = new HashMap<>();
for (Integer i = 0; i < s.length(); i++) {
if (sMap.put(s.charAt(i), i) != tMap.put(t.charAt(i), i)) {
return false;
}
}
return true;
}
例题7: 按照字符串出现的顺序重组字符串 leetcode 451
解题思路:
当出现频次的时候,肯定会想到使键值的方式来存储字符出现的频率,但是还有一个频率次数的排序。这是一个对map的value进行排序,所以需要将map转化为一个List,然后对value进行排序就可以达到目的。有序的Map我们会想到的是TreeMap,但是TreeMap是对Key进行自然排序
代码:
public String frequencySort(String s) {
Map<Character, Integer> sMap = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
sMap.put(s.charAt(i), sMap.getOrDefault(s.charAt(i), 0) + 1);
}
List<Map.Entry<Character, Integer>> list = new ArrayList<>(sMap.entrySet());
Collections.sort(list, new Comparator<Map.Entry<Character, Integer>>() {
@Override
public int compare(Map.Entry<Character, Integer> o1, Map.Entry<Character, Integer> o2) {
return -(o1.getValue().compareTo(o2.getValue()));
}
});
StringBuilder stringBuilder = new StringBuilder();
for (Map.Entry<Character, Integer> characterIntegerEntry : list) {
for (int i = 0; i < characterIntegerEntry.getValue(); i++) {
stringBuilder.append(characterIntegerEntry.getKey());
}
}
return stringBuilder.toString();
}
复杂度分析
时间复杂度:O(n + k \log k)O(n+klogk),其中 nn 是字符串 ss 的长度,kk 是字符串 ss 包含的不同字符的个数,这道题中 ss 只包含大写字母、小写字母和数字,因此 k=26 + 26 + 10 = 62k=26+26+10=62。
遍历字符串统计每个字符出现的频率需要 O(n)O(n) 的时间。
将字符按照出现频率排序需要 O(k \log k)O(klogk) 的时间。
生成排序后的字符串,需要遍历 kk 个不同字符,需要 O(k)O(k) 的时间,拼接字符串需要 O(n)O(n) 的时间。
因此总时间复杂度是 O(n + k \log k + k + n)=O(n + k \log k)O(n+klogk+k+n)=O(n+klogk)。
空间复杂度:O(n + k)O(n+k),其中 nn 是字符串 ss 的长度,kk 是字符串 ss 包含的不同字符的个数。空间复杂度主要取决于哈希表、列表和生成的排序后的字符串。
例题7: 一个使用查找表的经典问题 leetcode 1 两数之和
解题思路:
思路1: 暴力解法,遍历两次 找到和等于目标值target的坐标返回,这个算法的时间复杂度为O(n2)
思路2:采用双索引指正碰撞,但是数组是无序的,所以要先拍个序,时间复杂度O(nlogn),然后双指针碰撞,O(n),所以总的时间复杂度是O(nlogn)+O(n),但是要返回的是排序前的索引
思路3:将数组的元素放到一个哈希表里面,遍历哈希表,找到target-v存在的即可。
代码:
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
int a = 0, b = 0;
for (int i = 0; i < nums.length; i++) {
int temp = target - nums[i];
if (map.keySet().contains(temp)) {
a = map.get(temp);
b = i;
return new int[]{a, b};
}
map.put(nums[i], i);
}
return null;
}
时间复杂度为O(n)
空间复杂度O(n)
例题8: 三数之和 leetcode 15