查找,是使用计算机处理问题时的一个最基本的任务,因此也是面试中非常常见的一类问题。很多算法问题的本质,就是要能够高效查找。学会使用系统库中的map和set,就已经成功了一半。
目录
-
4-1 set的使用 Intersection of Two Arrays
-
4-2 map的使用 Intersection of Two Arrays II
-
4-3 set和map不同底层实现的区别
-
4-4 使用查找表的经典问题 Two Sum
-
4-5 灵活选择键值 4Sum II
-
4-6 灵活选择键值 Number of Boomerangs
-
4-7 查找表和滑动窗口 Contain Duplicate II
-
4-8 二分搜索树底层实现的顺序性 Contain Duplicate III
两类查找问题
- 查找有无:元素"a"是否存在? 使用set;集合
- 查找对应关系(键值对应):元素"a"出现了几次? map;字典
set和map
- 通常语言的标准库中都有内置set和map
- 容器类
- 屏蔽实现细节
- 了解语言中标准库里常见的容器类的使用
set和map常见操作
- insert
- find
- erase
- chang(map)
4-1 set的使用 Intersection of Two Arrays
题目: LeetCode 349. 两个数组的交集
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
说明:
输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。
import java.util.TreeSet;
// 349\. Intersection of Two Arrays
// https://leetcode.com/problems/intersection-of-two-arrays/description/
// 时间复杂度: O(nlogn)
// 空间复杂度: O(n)
public class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
TreeSet record = new TreeSet();
for(int num: nums1)
record.add(num);
TreeSet resultSet = new TreeSet();
for(int num: nums2)
if(record.contains(num))
resultSet.add(num);
int[] res = new int[resultSet.size()];
int index = 0;
for(Integer num: resultSet)
res[index++] = num;
return res;
}
private static void printArr(int[] arr){
for(int e: arr)
System.out.print(e + " ");
System.out.println();
}
public static void main(String[] args) {
int[] nums1 = {1, 2, 2, 1};
int[] nums2 = {2, 2};
int[] res = (new Solution()).intersection(nums1, nums2);
printArr(res);
}
}
4-2 map的使用 Intersection of Two Arrays II
题目: LeetCode 350. 两个数组的交集 II
给定两个数组,编写一个函数来计算它们的交集。
示例 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 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
import java.util.TreeMap;
import java.util.ArrayList;
// 350\. Intersection of Two Arrays II
// https://leetcode.com/problems/intersection-of-two-arrays-ii/description/
// 时间复杂度: O(nlogn)
// 空间复杂度: O(n)
public class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
TreeMap record = new TreeMap();
for(int num: nums1)
if(!record.containsKey(num))
record.put(num, 1);
else
record.put(num, record.get(num) + 1);
ArrayList result = new ArrayList();
for(int num: nums2)
if(record.containsKey(num) && record.get(num) > 0){
result.add(num);
record.put(num, record.get(num) - 1);
}
int[] ret = new int[result.size()];
int index = 0;
for(Integer num: result)
ret[index++] = num;
return ret;
}
private static void printArr(int[] arr){
for(int e: arr)
System.out.print(e + " ");
System.out.println();
}
public static void main(String[] args) {
int[] nums1 = {1, 2, 2, 1};
int[] nums2 = {2, 2};
int[] res = (new Solution()).intersect(nums1, nums2);
printArr(res);
}
}
/// 让我们来测试使用Java中的TreeMap:)
import java.util.TreeMap;
public class Main {
public static void main(String[] args) {
TreeMap myMap = new TreeMap();
if(myMap.containsKey(42))
System.out.println("Element 42 is in the map");
else
System.out.println("Can not find element 42");
System.out.println(myMap.get(42)); // 输出 null
// Java不存在C++中默认的访问key即添加默认(key, value)的行为
// 以下代码仍然无法找到42
if(myMap.containsKey(42))
System.out.println("Element 42 is in the map");
else
System.out.println("Can not find element 42");
myMap.put(42, 0);
myMap.put(42, myMap.get(42) + 1);
System.out.println(myMap.get(42)); // 输出 1
if(myMap.containsKey(42))
System.out.println("Element 42 is in the map");
else
System.out.println("Can not find element 42");
myMap.put(42, myMap.get(42) - 1);
System.out.println(myMap.get(42)); // 输出 0
// 注意: key对应的值为0, 不代表key不存在
if(myMap.containsKey(42))
System.out.println("Element 42 is in the map");
else
System.out.println("Can not find element 42");
// 注意: 也不可以为key对应的值设置null来删除一个key
myMap.put(42, null);
if(myMap.containsKey(42))
System.out.println("Element 42 is in the map");
else
System.out.println("Can not find element 42");
// 使用remove删除一个key
myMap.remove(42);
if(myMap.containsKey(42))
System.out.println("Element 42 is in the map");
else
System.out.println("Can not find element 42");
}
}
4-3 set和map不同底层实现的区别
哈希表失去了数据的顺序性
- 数据集中的最大值和最小值
- 某个元素的前驱和后继
- 某个元素的floor和ceil
- 某个元素的排位rank
- 选择某个排位的元素select
注意:C++语言中map和set的底层实现为平衡二叉树;unordered_map和unorder_set底层为哈希表。
以下分别是对4-1和4-2中的题目使用不同的数据结构进行解答。
import java.util.HashSet;
// 349\. Intersection of Two Arrays
// https://leetcode.com/problems/intersection-of-two-arrays/description/
// 时间复杂度: O(len(nums1)+len(nums2))
// 空间复杂度: O(len(nums1))
public class Solution349 {
public int[] intersection(int[] nums1, int[] nums2) {
HashSet record = new HashSet();
for(int num: nums1)
record.add(num);
HashSet resultSet = new HashSet();
for(int num: nums2)
if(record.contains(num))
resultSet.add(num);
int[] res = new int[resultSet.size()];
int index = 0;
for(Integer num: resultSet)
res[index++] = num;
return res;
}
private static void printArr(int[] arr){
for(int e: arr)
System.out.print(e + " ");
System.out.println();
}
public static void main(String[] args) {
int[] nums1 = {1, 2, 2, 1};
int[] nums2 = {2, 2};
int[] res = (new Solution349()).intersection(nums1, nums2);
printArr(res);
}
}
import java.util.HashMap;
import java.util.ArrayList;
// 350\. Intersection of Two Arrays II
// https://leetcode.com/problems/intersection-of-two-arrays-ii/description/
// 时间复杂度: O(len(nums1)+len(nums2))
// 空间复杂度: O(len(nums1))
public class Solution350 {
public int[] intersect(int[] nums1, int[] nums2) {
HashMap record = new HashMap();
for(int num: nums1)
if(!record.containsKey(num))
record.put(num, 1);
else
record.put(num, record.get(num) + 1);
ArrayList result = new ArrayList();
for(int num: nums2)
if(record.containsKey(num) && record.get(num) > 0){
result.add(num);
record.put(num, record.get(num) - 1);
}
int[] ret = new int[result.size()];
int index = 0;
for(Integer num: result)
ret[index++] = num;
return ret;
}
private static void printArr(int[] arr){
for(int e: arr)
System.out.print(e + " ");
System.out.println();
}
public static void main(String[] args) {
int[] nums1 = {1, 2, 2, 1};
int[] nums2 = {2, 2};
int[] res = (new Solution350()).intersect(nums1, nums2);
printArr(res);
}
}
课后作业: LeetCode 242、202、290、205、451
4-4 使用查找表的经典问题 Two Sum
题目: LeetCode 1. 两数之和
给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
import java.util.HashMap;
// 1\. Two Sum
// https://leetcode.com/problems/two-sum/description/
// 时间复杂度:O(n)
// 空间复杂度:O(n)
public class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap record = new HashMap();
for(int i = 0 ; i < nums.length; i ++){
int complement = target - nums[i];
if(record.containsKey(complement)){
int[] res = {i, record.get(complement)};
return res;
}
record.put(nums[i], i);
}
throw new IllegalStateException("the input has no solution");
}
private static void printArr(int[] nums){
for(int num: nums)
System.out.print(num + " ");
System.out.println();
}
public static void main(String[] args) {
int[] nums = {0,4,3,0};
int target = 0;
printArr((new Solution()).twoSum(nums, target));
}
}
import java.util.HashMap;
// 1\. Two Sum
// https://leetcode.com/problems/two-sum/description/
//
// 感谢课程中的 @Charles_Zhang 提出:
// 由于题目中只要求求出唯一的一个解。因此可以在最初的时候遍历整个数组, 将数组中的每个数字的索引放在map中。
// 此时, record中记录的永远是每一个数字最后出现的位置。
// 而对于 target = 2*a的情况, 如果nums中有两个或两个以上a,
// 我们在扫描时会先看到第一个a, 而从record中拿到的是最后一个a :)
//
// 时间复杂度:O(n)
// 空间复杂度:O(n)
public class Solution2 {
public int[] twoSum(int[] nums, int target) {
HashMap record = new HashMap();
for(int i = 0 ; i < nums.length ; i ++)
record.put(nums[i], i);
for(int i = 0 ; i < nums.length; i ++){
if(record.containsKey(target - nums[i]))
if(record.get(target - nums[i]) != i){
int[] res = {i, record.get(target - nums[i])};
return res;
}
record.put(nums[i], i);
}
throw new IllegalStateException("the input has no solution");
}
private static void printArr(int[] nums){
for(int num: nums)
System.out.print(num + " ");
System.out.println();
}
public static void main(String[] args) {
int[] nums = {0,4,3,0};
int target = 0;
printArr((new Solution()).twoSum(nums, target));
}
}
课后作业: LeetCode 15、18、16
4-5 灵活选择键值 4Sum II
题目: LeetCode 454. 四数相加 II
给定四个包含整数的数组列表 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
解释:
两个元组如下:
- (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
- (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
import java.util.HashMap;
// 454\. 4Sum II
// https://leetcode.com/problems/4sum-ii/description/
// 时间复杂度: O(n^2)
// 空间复杂度: O(n^2)
public class Solution1 {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
if(A == null || B == null || C == null || D == null)
throw new IllegalArgumentException("Illegal argument");
HashMap map = new HashMap();
for(int i = 0 ; i < C.length ; i ++)
for(int j = 0 ; j < D.length ; j ++){
int sum = C[i] + D[j];
if(map.containsKey(sum))
map.put(sum, map.get(sum) + 1);
else
map.put(sum, 1);
}
int res = 0;
for(int i = 0 ; i < A.length ; i ++)
for(int j = 0 ; j < B.length ; j ++)
if(map.containsKey(-A[i]-B[j]))
res += map.get(-A[i]-B[j]);
return res;
}
public static void main(String[] args) {
int[] a = {1, 2};
int[] b = {-2, -1};
int[] c = {-1, 2};
int[] d = {0, 2};
System.out.println((new Solution1()).fourSumCount(a, b, c, d));
}
}
import java.util.HashMap;
// 454\. 4Sum II
// https://leetcode.com/problems/4sum-ii/description/
// 时间复杂度: O(n^2)
// 空间复杂度: O(n^2)
public class Solution2 {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
if(A == null || B == null || C == null || D == null)
throw new IllegalArgumentException("Illegal argument");
HashMap mapAB = new HashMap();
for(int i = 0 ; i < A.length ; i ++)
for(int j = 0 ; j < B.length ; j ++){
int sum = A[i] + B[j];
if(mapAB.containsKey(sum))
mapAB.put(sum, mapAB.get(sum) + 1);
else
mapAB.put(sum, 1);
}
HashMap mapCD = new HashMap();
for(int i = 0 ; i < C.length ; i ++)
for(int j = 0 ; j < D.length ; j ++){
int sum = C[i] + D[j];
if(mapCD.containsKey(sum))
mapCD.put(sum, mapCD.get(sum) + 1);
else
mapCD.put(sum, 1);
}
int res = 0;
for(Integer sumab: mapAB.keySet()){
if(mapCD.containsKey(-sumab))
res += mapAB.get(sumab) * mapCD.get(-sumab);
}
return res;
}
public static void main(String[] args) {
int[] a = {1, 2};
int[] b = {-2, -1};
int[] c = {-1, 2};
int[] d = {0, 2};
System.out.println((new Solution2()).fourSumCount(a, b, c, d));
}
}
课后作业: LeetCode 49
4-6 灵活选择键值 Number of Boomerangs
题目: LeetCode 447. 回旋镖的数量
给定平面上 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]]
import java.util.HashMap;
// 447\. Number of Boomerangs
// https://leetcode.com/problems/number-of-boomerangs/description/
// 时间复杂度: O(n^2)
// 空间复杂度: O(n)
public class Solution {
public int numberOfBoomerangs(int[][] points) {
int res = 0;
for( int i = 0 ; i < points.length ; i ++ ){
// record中存储 点i 到所有其他点的距离出现的频次
HashMap record = new HashMap();
for(int j = 0 ; j < points.length ; j ++)
if(j != i){
// 计算距离时不进行开根运算, 以保证精度
int dis = dis(points[i], points[j]);
if(record.containsKey(dis))
record.put(dis, record.get(dis) + 1);
else
record.put(dis, 1);
}
for(Integer dis: record.keySet())
res += record.get(dis) * (record.get(dis) - 1);
}
return res;
}
private int dis(int[] pa, int pb[]){
return (pa[0] - pb[0]) * (pa[0] - pb[0]) +
(pa[1] - pb[1]) * (pa[1] - pb[1]);
}
public static void main(String[] args) {
int[][] points = {{0, 0}, {1, 0}, {2, 0}};
System.out.println((new Solution()).numberOfBoomerangs(points));
}
}
课后作业: LeetCode 149
4-7 查找表和滑动窗口 Contain Duplicate II
题目: LeetCode 219. 存在重复元素 II
给定一个整数数组和一个整数 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;
// 219\. Contains Duplicate II
// https://leetcode.com/problems/contains-duplicate-ii/description/
// 时间复杂度: O(n)
// 空间复杂度: O(k)
public class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
if(nums == null || nums.length <= 1)
return false;
if(k <= 0)
return false;
HashSet record = new HashSet();
for(int i = 0 ; i < nums.length; i ++){
if(record.contains(nums[i]))
return true;
record.add(nums[i]);
if(record.size() == k + 1)
record.remove(nums[i-k]);
}
return false;
}
private static void printBool(boolean b){
System.out.println(b ? "True" : "False");
}
public static void main(String[] args) {
int[] nums = {1, 2, 1};
int k = 1;
printBool((new Solution()).containsNearbyDuplicate(nums, k));
}
}
课后作业: LeetCode 217
4-8 二分搜索树底层实现的顺序性 Contain Duplicate III
题目: LeetCode 220. 存在重复元素 III
给定一个整数数组,判断数组中是否有两个不同的索引 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;
// 220\. Contains Duplicate III
// https://leetcode.com/problems/contains-duplicate-iii/description/
// 时间复杂度: O(nlogk)
// 空间复杂度: O(k)
public class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
// 这个问题的测试数据在使用int进行加减运算时会溢出
// 所以使用long long
TreeSet record = new TreeSet();
for(int i = 0 ; i < nums.length ; i ++){
if(record.ceiling((long)nums[i] - (long)t) != null &&
record.ceiling((long)nums[i] - (long)t) <= (long)nums[i] + (long)t)
return true;
record.add((long)nums[i]);
if(record.size() == k + 1)
record.remove((long)nums[i-k]);
}
return false;
}
private static void printBool(boolean b){
System.out.println(b ? "True" : "False");
}
public static void main(String[] args) {
int[] nums = {-2147483648, -2147483647};
int k = 3;
int t = 3;
printBool((new Solution()).containsNearbyAlmostDuplicate(nums, k, t));
}
}
非本人原创,转载请注明来源!https://blog.csdn.net/KAIKAI_ING/article/details/82943159