文章目录
LeetCode精选题之查找表
参考资料:
bobo老师慕课网LeetCode课程
CyC2018的LeetCode题解
LeetCode官方题解
总结:
- 使用查找表的本质思想就是用空间换时间。
- 在解题中对Set和Map的使用,Set只能存储元素,如果要保存位置,次数等信息就需要使用Map。
- 灵活选择键key,应该根据题目来定,比如距离,和,元素等等,进行灵活选择。
1 两个数组的交集–LeetCode349
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
说明:
- 输出结果中的每个元素一定是唯一的。
- 我们可以不考虑输出结果的顺序。
思路:使用容器Set。
代码如下:
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> record = new HashSet<>();
for (int num : nums1) {
record.add(num);
}
Set<Integer> res = new HashSet<>();
for (int num : nums2) {
if (record.contains(num)) {
res.add(num);
}
}
int[] resArr = new int[res.size()];
int index = 0;
for (Integer num : res) {
resArr[index++] = num;
}
return resArr;
}
}
2 两个数组的交集 II–LeetCode350
给定两个数组,编写一个函数来计算它们的交集。
示例 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 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
思路:使用容器Map
代码如下:
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int num : nums1) {
if (!map.containsKey(num)) {
map.put(num, 1);
}else {
map.put(num, 1+map.get(num));
}
}
List<Integer> res = new ArrayList<>();
for (int num : nums2) {
if (map.containsKey(num) && map.get(num) > 0) {
res.add(num);
map.put(num, map.get(num)-1);
}
}
int[] resArr = new int[res.size()];
int index = 0;
for (Integer num : res) {
resArr[index++] = num;
}
return resArr;
}
}
相关练习题:242、202、290、205、451
3 两数之和–LeetCode1
题目:给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
思路:
暴力法的时间复杂度是O(n2)
查找表,用空间来换时间。
import java.util.HashMap;
class Solution {
public int[] twoSum(int[] nums, int target) {
// 边界条件
if (nums == null) {
return new int[]{};// 返回空数组
}
HashMap<Integer,Integer> map = new HashMap<>();
int n = nums.length;
int[] res = new int[2];
for (int i = 0; i < n; i++) {
int diff = target - nums[i];
if (map.containsKey(diff)) {
res[0] = map.get(diff);
res[1] = i;
}
map.put(nums[i], i);
}
return res;
}
}
相关练习题:15、18、16.
4 四数相加II–LeetCode454(Medium)
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组(i, j, k, l)
,使得 A[i] + B[j] + C[k] + D[l] = 0
。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -228 到 228 - 1 之间,最终结果不会超过 231 - 1 。
例如:
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
使用查找表将时间复杂度降到O(n2)
import java.util.HashMap;
class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
int n = A.length;
HashMap<Integer, Integer> map1 = new HashMap<>();
HashMap<Integer, Integer> map2 = new HashMap<>();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int twoSum = A[i] + B[j];
if (!map1.containsKey(twoSum)) {
map1.put(twoSum, 1);
}else {
map1.put(twoSum, 1+map1.get(twoSum));
}
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
int twoSum = C[i] + D[j];
if (!map2.containsKey(twoSum)) {
map2.put(twoSum, 1);
}else {
map2.put(twoSum, 1+map2.get(twoSum));
}
}
}
int cnt = 0;
for (Integer twoSum : map1.keySet()) {
int target = 0-twoSum;
if (map2.containsKey(target)) {
cnt += map1.get(twoSum) * map2.get(target);
}
}
return cnt;
}
}
相关练习题:49.
5 回旋镖的数量–LeetCode447
题目:给定平面上 n 对不同的点,“回旋镖” 是由点表示的元组 (i, j, k) ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。找到所有回旋镖的数量。你可以假设 n 最大为 500,所有点的坐标在闭区间 [-10000, 10000] 中。
示例:
输入:
[[0,0],[1,0],[2,0]]
输出:
2
解释:
两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]
代码如下:
class Solution {
public int numberOfBoomerangs(int[][] points) {
int pointNum = points.length;
int cnt = 0;
for (int i = 0; i < pointNum; i++) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int j = 0; j < pointNum; j++) {
int distance = (int)Math.pow(points[j][0]-points[i][0], 2)
+ (int)Math.pow(points[j][1]-points[i][1], 2);
if (!map.containsKey(distance)) {
map.put(distance, 1);
}else {
map.put(distance, 1+map.get(distance));
}
}
// 求回旋镖,必须要超过两个点才行
for (Integer distance : map.keySet()) {
if (map.get(distance) >= 2) {//考虑个数
// 求可能的排列,因为要考虑顺序
cnt += map.get(distance) * (map.get(distance)-1);
}
}
}
return cnt;
}
}
相关练习题:149.
6 最长连续序列–LeetCode128(Hard)
给定一个未排序的整数数组,找出最长连续序列的长度。要求算法的时间复杂度为 O(n)。
示例:
输入: [100, 4, 200, 1, 3, 2]
输出: 4
解释: 最长连续序列是 [1, 2, 3, 4]。它的长度为 4。
思路:使用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;
}
}
参考题解:LeetCode官方题解
- 时间复杂度: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) 个数字。除此以外,所需空间与暴力解法一致。
7 存在重复元素II–LeetCode219
给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 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;
}
}
相关练习题:217.
8 存在重复元素III–LeetCode220(Medium)
题目:给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ。
示例 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)
。
3、对数字越界的考虑不够,所以有一个特殊用例没通过,也就是t=2147483647的时候发生了错误,所以要采用long型。