LeetCode精选题之哈希表

LeetCode精选题之哈希表

总结:
1、在解题中对Set和Map的使用,Set只能存储元素,如果要保存位置,次数等信息就需要使用Map。
2、灵活选择键key,应该根据题目来定,比如距离,和,元素等等,进行灵活选择。

1 两数之和–LeetCode1

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
import java.util.HashMap;
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] res = new int[2];
        HashMap<Integer, Integer> map = new HashMap<>();
        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;
                return res;
            }else {
                map.put(nums[i], i);
            }
        }
        return res;
    }
}

2 存在重复元素–LeetCode217

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

class Solution {
    public boolean containsDuplicate(int[] nums) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        HashSet<Integer> set = new HashSet<>();
        for (int num : nums) {
            if (set.contains(num)) {
                return true;
            }
            set.add(num);
        }
        return false;
    }
}

3 存在重复元素II–LeetCode219

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引ij,使得 nums [i] = nums [j],并且 ij的差的 绝对值 至多为 k

示例 1:

输入: nums = [1,2,3,1], k = 3
输出: true

示例 2:

输入: nums = [1,0,1,1], k = 1
输出: true

示例 3:

输入: nums = [1,2,3,1,2,3], k = 2
输出: false

思路:滑动窗口+查找表
注意:这里的滑动窗口是固定长度的滑动窗口

import java.util.HashSet;
class Solution {
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            if (set.contains(nums[i])) {
                return true;
            }
            set.add(nums[i]);

            if (set.size() == k+1) {
                set.remove(nums[i-k]);
            }
        }
        return false;
    }
}

4 存在重复元素III–LeetCode220(Medium)

题目:给定一个整数数组,判断数组中是否有两个不同的索引 ij,使得 nums [i]nums [j]的差的绝对值最大为 t,并且 ij之间的差的绝对值最大为 ķ

示例 1:

输入: nums = [1,2,3,1], k = 3, t = 0
输出: true

示例 2:

输入: nums = [1,0,1,1], k = 1, t = 2
输出: true

示例 3:

输入: nums = [1,5,9,1,5,9], k = 2, t = 3
输出: false

代码如下:

import java.util.TreeSet;
class Solution {
    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        TreeSet<Long> set = new TreeSet<>();
        for (int i = 0; i < nums.length; i++) {
            if (set.ceiling((long)nums[i]-(long)t)!=null 
            && set.ceiling((long)nums[i]-(long)t)<=(long)nums[i]+(long)t) {
                return true;
            }
            set.add((long)nums[i]);

            if (set.size() >= k+1) {
                set.remove((long)nums[i-k]);
            }
        }
        return false;
    }
}

总结:
1、这道题目涉及到元素的差值,就要求Set中的元素是有序的,所以使用TreeSet,对TreeSet的使用不熟悉。这里用到了ceiling()方法,对应还有floor()方法。
2、自己考虑的时候考虑的条件是set中最小值(first()方法)是否小于nums[i]+t,以及最大值(last()方法)是否大于nums[i]-t,这样考虑是不行的。一定要理解题目的意思,只要有一个数在[nums[i]-t, nums[i]+t]范围内就可以了,所以考虑的是set.ceiling(nums[i]-t),TreeSet的ceiling(E e)方法是返回大于或者等于e的最小元素。
3、对数字越界的考虑不够,所以有一个特殊用例没通过,也就是t=2147483647的时候发生了错误,所以要采用long型。

5 最长和谐子序列–LeetCode594

和谐数组是指一个数组里元素的最大值和最小值之间的差别正好是1。现在,给定一个整数数组,你需要在所有可能的子序列中找到最长的和谐子序列的长度。

示例 1:

输入: [1,3,2,2,5,2,3,7]
输出: 5
原因: 最长的和谐数组是:[3,2,2,2,3].

说明: 输入的数组长度最大不超过20,000.

思路:先用哈希表存储每个数字出现的次数,然后对于哈希表里面的每个键,查看哈希表里面是否存在该键加一的键值,如果存在,计算并比较该和谐子序列是否为最长的序列。

class Solution {
    public int findLHS(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            if (map.containsKey(num)) {
                map.put(num, 1+map.get(num));
            }else {
                map.put(num, 1);
            }
        }
        int res = 0;
        for (int key : map.keySet()) {
            if (map.containsKey(key+1)) {
                res = Math.max(res, map.get(key)+map.get(key+1));
            }
        }
        return res;
    }
}

6 最长连续序列–LeetCode128(Hard)

给定一个未排序的整数数组,找出最长连续序列的长度。要求算法的时间复杂度为 O(n)。

示例:

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

思路一:用哈希表存储每个元素所在的连续区间的长度,即键为数组元素,值为元素所在的连续区间的长度。若元素已在哈希表中,表示是重复元素,直接跳过;若是新的元素:

  • 取出其左右相邻数已有的连续区间长度 leftright
  • 计算当前数的区间长度:currLen = left + right + 1
  • 根据 currLen更新最大长度 longestLen的值
  • 更新区间两端点的连续序列长度值

参考思路:jalan的题解

代码如下:

class Solution {
    public int longestConsecutive(int[] nums) {
        HashMap<Integer, Integer> map = new HashMap<>();
        int longestLen = 0;
        for (int num : nums) {
            if (!map.containsKey(num)) {
                int left = map.get(num-1)==null ? 0 : map.get(num-1);
                int right = map.get(num+1)==null ? 0 : map.get(num+1);

                int currLen = 1+left+right;
                longestLen = Math.max(longestLen, currLen);

                map.put(num, currLen);
                map.put(num-left, currLen);//更新区间两端点的连续序列长度值
                map.put(num+right, currLen);
            }
        }
        return longestLen;
    }
}

其中map.put(num, currLen)是用于表示这个元素已经被访问过,因为区间长度用两端的元素来确定就可以了,故map.put(num,1)也是没问题的。

思路二:使用HashSet来保存数组元素,实现O(1)时间复杂度的查找。其次若当前元素为curr,只有当curr-1不在Set中的时候,才将curr作为连续序列的第一个数字去查找。解释:如果curr-1在Set中,那么curr肯定不是连续序列的第一个数字。

代码如下:

class Solution {
    public int longestConsecutive(int[] nums) {
        HashSet<Integer> set = new HashSet<>();
        for (int num : nums) {
            set.add(num);
        }

        int longest = 0;
        for (int num : nums) {
            if (!set.contains(num-1)) {
                int currNum = num;
                int currCnt = 1;
                while (set.contains(currNum+1)) {
                    currNum++;
                    currCnt++;
                }
                longest = Math.max(longest, currCnt);
            }
        }
        return longest;
    }
}

复杂度分析:

  • 时间复杂度:O(n)。尽管在 for 循环中嵌套了一个 while 循环,时间复杂度看起来像是二次方级别的。但其实它是线性的算法。因为只有当 currNum遇到了一个序列的开始, while 循环才会被执行(也就是 currNum-1不在数组 nums里), while 循环在整个运行过程中只会被迭代 n次。这意味着尽管看起来时间复杂度为 O(n*n),实际这个嵌套循环只会运行 O(n+n)=O(n)次。所有的计算都是线性时间的,所以总的时间复杂度是 O(n)的。
  • 空间复杂度:O(n)。为了实现 O(1)的查询,我们对哈希表分配线性空间,以保存 nums 数组中的 O(n)个数字。除此以外,所需空间与暴力解法一致。

参考题解:LeetCode官方题解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值