哈希系列(空间换时间)

哈希表,又称为散列表,是一种可以根据关键字(码值)快速实现查找、插入和删除的存储结构。结合了数组和链表的优点。

哈希函数:是hash表的映射函数:关键确定映射关系!

哈希算法:哈希算法是一类算法的统称,简单说,一段信息经过哈希算法可以映射为固定长度的数字串(对数组区间取模)。

哈希碰撞:不同的输入数据产生了相同的哈希值。解决方式:拉链法和线性探测法。

1.两数之和(1-easy)

题目描述:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。假设每种输入只会对应一个答案。

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

输入:nums = [3,3], target = 6
输出:[0,1]

思路:本题可以使用暴力法和hash映射解决。

法1.遍历两遍数组,返回找到和为目标值的两个整数,实现简单。

2.hashmap:使用hashmap存储数组元素与索引之间的映射,遍历数组将映射加入map,当出现另一个元素,返回他们的标。

代码实现:

class Solution {
    // 暴力法O(n^2)
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            int num = target - nums[i];
            for (int j = i + 1; j < n; j++) {
                if (nums[j] == num) {
                    return new int[] {i, j};
                }
            }
        }
        return null;
    }

    // hashmap<元素:索引>
    public int[] twoSum(int[] nums, int target) {
        HashMap map = new HashMap<>();
        int n = nums.length;

        for (int i = 0; i < n; i++) {
            if (map.containsKey(target - nums[i])) {
                return new int[] {map.get(target - nums[i]), i};
            }
            map.put(nums[i], i);
        }
        return null;
    }
}

2.存在重复元素(217-easy)

题目描述:给定一个整数数组,判断是否存在重复元素。如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false

示例

输入: [1,2,3,1]
输出: true

输入: [1,2,3,4]
输出: false

思路:本题只判断数组中有无重复元素,可以使用hashmap映射和hashset集合元素唯一性进行判断。

  • 法1.hashmap:常规是遍历数组,建立hashmap数组元素与出现次数的映射,判断是否某个元素出现次数大于1;

  • 法2.hashset:利用set集合存储元素的特性(严格无重复元素),判断集合和原数组大小判断是否含有重复元素。

  • 法3:排序

代码实现:

class Solution {
    // hashset
    public boolean containsDuplicate(int[] nums) {
        HashSet set = new HashSet<>();
        for (int num : nums) {
            set.add(num);
        }
        return set.size() < nums.length;
    }

    // hashmap
    public boolean containsDuplicate(int[] nums) {
        HashMap map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
            if (map.get(num) > 1) {
                return true;
            }
        }
        return false;
    }

    // 排序
    public boolean containsDuplicate(int[] nums) {
        Arrays.sort(nums);
        for (int i = 1; i < nums.length; i++) {
            if (nums[i - 1] == nums[i]) {
                return true;
            }
        }
        return false;
    }
}

3.最长和谐序列(594-easy)

题目描述:和谐序列,元素的最大值和最小值之间的差别正好是1在所有可能的子序列中找到【最长的和谐子序列的长度】。

示例

输入: [1,3,2,2,5,2,3,7]
输出: 5
原因: 最长的和谐数组是:[3,2,2,2,3],区分子串(子串是连续的一段)!

思路:本题关键是如何获取所有可能子序列,并记录最长的子序列。使用hashmap:

  • 可以遍历数组,使用hashmap映射元素与出现的次数
  • 遍历map所有键(map.keySet()),根据和谐子序列的定义,更新最长子序列长度。

优化:当加入num映射,判断map是否出现num + 1和num - 1(tips),num与前后位置都可能构成结果,更新最大值。

代码实现:

class Solution {
    // hashmap<元素,出现次数>
    public int findLHS(int[] nums) {
        HashMap map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }

        int max = 0;
        for (int key : map.keySet()) {
            if (map.containsKey(key + 1)) {
                max = Math.max(max, map.get(key) + map.get(key + 1));
            }
        }
        return max;
    }

    // 优化(效率不高,引入过多判断)
    public int findLHS(int[] nums) {
        HashMap map = new HashMap<>();
        int max = 0;
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
            if (map.containsKey(num - 1)) {
                max = Math.max(max, map.get(num) + map.get(num - 1));
            }
            if (map.containsKey(num + 1)) {
                max = Math.max(max, map.get(num) + map.get(num + 1));
            }
        }
        return max;
    }
}

4.最长连续序列(128-hard)

题目描述:给定一个未排序的整数数组 nums ,找出数字连续(即步长为1)的最长序列(不要求序列元素在原数组中连续)的长度。要求:时间复杂度为 O(n)

示例

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

思路:要求:时间复杂度要求O(n),注意点:1.结果元素唯一性(去重) 2.原始数组未排序 3.保证结果为连续子序列(步长1)。

法1.hashset:

  • 首先使用hashset元素去重。
  • 遍历集合,寻找被切割的连续子序列片段。关键:找到每个片段的起始元素!更新最大长度和当前元素。

法2.hashmap:使用map建立当前值与是否被标记之间的映射,递归的去寻找连续子序列片段的长度!

代码实现:

class Solution {
    // hashset去重+连续子序区间起始元素
    public int longestConsecutive(int[] nums) {
        HashSet set = new HashSet<>();
        for (int num : nums) {
            set.add(num);
        }

        int ans = 0;
        for (int num : set) {
            if (!set.contains(num - 1)) {
                // 找到当前子序列的开始元素
                int curNum = num;
                int curLen = 1;
                while (set.contains(curNum + 1)) {
                    curNum++;
                    curLen++;
                }
                ans = Math.max(ans, curLen);
            }
        } 
        return ans;
    }

    // hashmap:<元素,是否被标记> 递归的去搜索
    public int longestConsecutive(int[] nums) {
        HashMap map = new HashMap<>();
        for (int num : nums) {
            map.put(num, false);
        }

        int ans = 0;
        for (int num : map.keySet()) {
            if (!map.get(num)) {
                // 未标记过更新
                ans = Math.max(ans, dfs(map, num));
            }
        }
        return ans;
    }

    private int dfs(HashMap map, int num) {
        if (map.containsKey(num) && !map.get(num)) {
            map.put(num, true);
            return 1 + dfs(map, num - 1) + dfs(map, num + 1);
        } else {
            return 0;
        }
    }
}

5.有效的字母异位词(242-easy)

题目描述:给定两个字符串 st ,编写一个函数来判断 t 是否是 s 的字母异位词。假设字符串只包含小写字母。要求:如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

示例

输入: s = "anagram", t = "nagaram"
输出: true

思路:本题关键是判断两个字符串是否具有相同字符,可以使用ascii码进行计数比较,也可以使用hashmap进行元素数目统计。

  • ascii码计数:定义一个数组,数组存放小写字母出现的个数。

  • hashmap计数:map中键值对存放每个字母和出现的个数。

代码实现:

class Solution {
    // ascii计数
    public boolean isAnagram(String s, String t) {
        int[] counts = new int[26];
        for (char cs : s.toCharArray()) {
            counts[cs - 'a']++;
        }
        for (char ct : t.toCharArray()) {
            counts[ct - 'a']--;
        }

        for (int i : counts) {
            if (i != 0) {
                return false;
            }
        }
        return true;
    }

    // hashmap
    public boolean isAnagram(String s, String t) {
        HashMap map = new HashMap<>();
        for (char cs : s.toCharArray()) {
            map.put(cs, map.getOrDefault(cs, 0) + 1);
        }
        for (char ct : t.toCharArray()) {
            map.put(ct, map.getOrDefault(ct, 0) - 1);
            if (map.get(ct) == -1) {
                return false;
            }
        }
        return s.length() == t.length();
    }
}

6.两数组的交集(349-easy)

题目描述:给定两个数组,编写一个函数来计算它们的交集。

  • 输出结果中的每个元素一定是唯一的。
  • 我们可以不考虑输出结果的顺序。

示例

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

思路:都要用set进行去重。

  • 双Set集合:先遍历一个数组存入集合作为父集合,遍历另一个数组,将交集作为子集合(即结果)

  • 排序+双指针:先排序,在遍历两个数组,将两数组相同的元素存入集合。

  • 二分查找+Set:对其中一个数组进行排序,然后进行二分查找(数组必须有序),同时,遍历另一个数组,用集合存储结果。

代码实现:

class Solution {

    // 双set
    public int[] intersection(int[] nums1, int[] nums2) {
        HashSet parentSet = new HashSet<>();
        HashSet childSet = new HashSet<>();

        for (int num : nums1) {
            parentSet.add(num);
        }

        for (int num : nums2) {
            if (parentSet.contains(num)) {
                childSet.add(num);
            }
        }
        int[] ans = new int[childSet.size()];
        int i = 0;
        for (int num : childSet) {
            ans[i++] = num;
        }
        return ans;
    }

    // 排序 + 双指针 + set
    public int[] intersection(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        HashSet set = new HashSet<>();
        int i = 0, j = 0;

        while (i < nums1.length && j < nums2.length) {
            if (nums1[i] == nums2[j]) {
                set.add(nums1[i]);
                i++;
                j++;
            } else if (nums1[i] > nums2[j]) {
                j++;
            } else {
                i++;
            }
        }

        int[] ans = new int[set.size()];
        int index = 0;
        for (int num : set) {
            ans[index++] = num;
        }
        return ans;
    }

    // 二分查找 + set
    public int[] intersection(int[] nums1, int[] nums2) {
        HashSet set = new HashSet<>();
        Arrays.sort(nums2);

        for (int target : nums1) {
            if (binarySearch(nums2, target) && !set.contains(target)) {
                set.add(target);
            }
        }

        int[] ans = new int[set.size()];
        int index = 0;
        for (int num : set) {
            ans[index++] = num;
        }
        return ans;
    }

    private boolean binarySearch(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return true;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return false;
    }
}

7.两数组的交集II(350-easy)

题目描述:与上题不同的是,本题结果考虑重复元素,与两数组中出现次数较小值一致。

进阶问题:

  • 如果给定的数组已经排好序呢?你将如何优化你的算法?
  • 如果 nums1 的大小比 nums2 小很多,哪种方法更优?
  • 如果 nums2 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?

示例

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]

思路:输出结果可以含有重复元素,可以使用hashmap<元素,出现次数>,当第二个数组包含这个元素,并且元素出现的次数>0,记录结果。

  • 对于进阶问题1:已经排序的数组,可以使用双指针 + list,如上题一样(自行实现)。

  • 对于进阶问题2:两数组相差比较大,利用hashmap计数,一个hash计数,另一个hash来寻找。

  • 对于进阶问题3:由于内存有限,故hashmap计数不能用(耗内存);涉及外部排序,首先想到归并排序,降低空间复杂度。

代码实现

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        List list = new ArrayList<>();
        HashMap map = new HashMap<>();
        
        for (int num : nums1) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        for (int num : nums2) {
            if (map.containsKey(num) && map.get(num) > 0) {
                list.add(num);
                map.put(num, map.getOrDefault(num, 0) - 1);
            }
        }

        int[] ans = new int[list.size()];
        int index = 0;
        for (int i : list) {
            ans[index++] = i;
        }
        return ans;
    }
}

你可能感兴趣的:(哈希系列(空间换时间))