查找问题的思路
查找问题一般分为两类:
- 查找有无,这样的查找问题多用set解决
- 查找对应关系(键值对应),这样的问题多用map解决
在查找问题中,难点往往是分析出需要查找的是什么,然后将待查找的内容放入查找表中(set / map)
注意:在java中, set / map 的底层实现有两类,一类是基于红黑树实现的 TreeSet / TreeMap ,另一类是基于哈希表实现的 HashSet / HashMap, 在增删查的过程中基于哈希表实现的set/map的时间复杂度为O(1)性能上更优(TreeSet/TreeMap增删查的时间复杂度为O(lofn)), 但相比其在提高时间性能的过程中失去了数据的顺序性,在查找过程中若需要利用数据之间的顺序性时(如:数据集中的最大值最小值;某个元素的前驱和后继;某个元素的ceil和floor;某个元素的排位rank;选择某个排位的元素select 等),TreeSet/TreeMap才是首选。
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]
说明:输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。
思路:使用set去重并进行查找
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
ArrayList<Integer> list = new ArrayList<>();
HashSet<Integer> set = new HashSet<>();
for(int i=0; i<nums1.length; i++){
set.add(nums1[i]);
}
for(int i=0; i<nums2.length; i++){
if(set.contains(nums2[i])){
list.add(nums2[i]);
set.remove(nums2[i]);
}
}
int[] res = new int[list.size()];
for(int i=0; i<list.size(); i++){
res[i] = list.get(i);
}
return res;
}
}
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]
说明:输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。
我们可以不考虑输出结果的顺序。
思路:使用Map查找
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
ArrayList<Integer> list = new ArrayList<>();
//将nums1写入查找表
HashMap<Integer, Integer> map = new HashMap<>();
for(int e : nums1){
if(!map.containsKey(e)){
map.put(e, 1);
}else{
map.put(e, map.get(e)+1);
}
}
for(int i=0; i<nums2.length; i++){
if(map.containsKey(nums2[i])){
list.add(nums2[i]);
map.put(nums2[i], map.get(nums2[i])-1);
if(map.get(nums2[i])<1){
map.remove(nums2[i]);
}
}
}
int[] res = new int[list.size()];
for(int i=0; i<list.size(); i++){
res[i] = list.get(i);
}
return res;
}
}
242. 有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的一个字母异位词。
示例 1:
输入: s = "anagram", t = "nagaram"
输出: true示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。
思路:使用map进行记录
class Solution {
public boolean isAnagram(String s, String t) {
if(s.length() != t.length()){
return false;
}
HashMap<Character, Integer> map = new HashMap<>();
//将s放入查找表中
for(char e : s.toCharArray()){
if(!map.containsKey(e)){
map.put(e, 1);
}else{
map.put(e, map.get(e)+1);
}
}
for(int i=0; i<t.length(); i++){
if(map.containsKey(t.charAt(i))){
map.put(t.charAt(i), map.get(t.charAt(i))-1);
if(map.get(t.charAt(i))<1){
map.remove(t.charAt(i));
}
}
}
return map.isEmpty();
}
}
202. 快乐数
编写一个算法来判断一个数是不是“快乐数”。
一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。
示例:
输入: 19
输出: true解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
思路:通过set记录查找中间数,判断中间是否出现重复数字,若出现了重复数字则必然会进入循环,不可能是“快乐数”
class Solution {
public boolean isHappy(int n) {
if(n==1){
return true;
}
HashSet<Integer> set = new HashSet<>();
while(true){
int next = getSum(n);
if(next == 1){
return true;
}
if(set.contains(next)){
return false;
}
set.add(next);
n=next;
}
}
private int getSum(int n){
int res = 0;
while(n!=0){
res+=Math.pow(n%10, 2);
n/=10;
}
return res;
}
}
模式匹配问题:可以通过map的 K-V 解决
290. 单词模式(模式匹配问题)
给定一种 pattern(模式) 和一个字符串 str ,判断 str 是否遵循相同的模式。
这里的遵循指完全匹配,例如, pattern 里的每个字母和字符串 str 中的每个非空单词之间存在着双向连接的对应模式。
示例1:
输入: pattern = "abba", str = "dog cat cat dog"
输出: true示例 2:
输入:pattern = "abba", str = "dog cat cat fish"
输出: false示例 3:
输入: pattern = "aaaa", str = "dog cat cat dog"
输出: false示例 4:
输入: pattern = "abba", str = "dog dog dog dog"
输出: false说明:
你可以假设 pattern 只包含小写字母, str 包含了由单个空格分隔的小写字母。
思路:通过map的 K-V 进行模式匹配
class Solution {
public boolean wordPattern(String pattern, String str) {
//String的split方法支持正则表达式;
//正则表达式\s表示匹配任何空白字符,+表示匹配一次或多次。
String[] s = str.split("\\s+");
if(pattern.length() != s.length){
return false;
}
HashMap<Character, String> map = new HashMap<>();
for(int i=0; i<pattern.length(); i++){
if(!map.containsKey(pattern.charAt(i))){
if(map.containsValue(s[i])){
return false;
}
map.put(pattern.charAt(i), s[i]);
}else{
if(!map.get(pattern.charAt(i)).equals(s[i])){
return false;
}
}
}
return true;
}
}
205. 同构字符串 (模式匹配问题 思路同290)
给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以被替换得到 t ,那么这两个字符串是同构的。
所有出现的字符都必须用另一个字符替换,同时保留字符的顺序。两个字符不能映射到同一个字符上,但字符可以映射自己本身。
示例 1:
输入: s = "egg", t = "add"
输出: true示例 2:
输入: s = "foo", t = "bar"
输出: false示例 3:
输入: s = "paper", t = "title"
输出: true
class Solution {
public boolean isIsomorphic(String s, String t) {
if(s.length() != t.length()){
return false;
}
HashMap<Character, Character> map = new HashMap<>();
for(int i=0; i<s.length(); i++){
if(!map.containsKey(s.charAt(i))){
if(map.containsValue(t.charAt(i))){
return false;
}
map.put(s.charAt(i), t.charAt(i));
}else{
if(map.get(s.charAt(i))!=t.charAt(i)){
return false;
}
}
}
return true;
}
}
451. 根据字符出现频率排序
给定一个字符串,请将字符串里的字符按照出现的频率降序排列。
示例 1:
输入:
"tree"
输出:
"eert"
解释:
'e'出现两次,'r'和't'都只出现一次。
因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。示例 2:
输入:
"cccaaa"
输出:
"cccaaa"
解释:
'c'和'a'都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。示例 3:
输入:
"Aabb"
输出:
"bbAa"
解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意'A'和'a'被认为是两种不同的字符。
思路:通过Map记录频次, 将map转为set,根据value值排序,重新构建字符串
class Solution {
public String frequencySort(String s) {
HashMap<Character, Integer> map = new HashMap<>();
//tag
// for(char e : s.toCharArray()){
// if(map.containsKey(e)){
// map.put(e, map.get(e)+1);
// }else{
// map.put(e, 1);
// }
// }
//tag的更优雅的写法
for(char e : s.toCharArray()){
map.put(e, map.getOrDefault(e, 0)+1);
}
//将map的Map.Entry对象(k-v pair对)提出成set结构,然后将set转成list,最后使用Collections的sort方法对list进行排序。
ArrayList<Map.Entry<Character, Integer>> list = new ArrayList<>(map.entrySet());
Collections.sort(list, new Comparator<Map.Entry<Character, Integer>>(){
public int compare(Map.Entry<Character, Integer> o1, Map.Entry<Character, Integer> o2){
return o2.getValue()-o1.getValue();
}
});
//更优雅的写法 使用lambda表达式实现比较器的比较定义。
// Collections.sort(list, (o1, o2) -> (o2.getValue() - o1.getValue()));
StringBuilder sb = new StringBuilder();
for(Map.Entry<Character, Integer> e : list){
for(int i=0; i<e.getValue(); i++){
sb.append(e.getKey());
}
}
return sb.toString();
}
}
1. 两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
思路:不同于167号问题,这里给定的数组是无序的,无法直接使用对撞指针的思路解决。要想使用对撞指针的方法首先需要对数组进行一次排序。大的思路是确定一个数nums[i],然后从剩下的数中寻找target-nums[i], 关键是这个寻找的过程,可以使用for循环遍历(暴力法),可以使用二分搜索(需要先排序),也可以使用查找表
暴力解法
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] result=new int[2];
for(int i=0;i<nums.length;i++){
for(int j=i+1;j<nums.length;j++){
if(target-nums[i]==nums[j]){
result[0]=i;
result[1]=j;
return result;
}
}
}
return result;
}
}
使用map作为查找表,时间性能大大提升
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++){
if(map.containsKey(target-nums[i])){
res[0] = map.get(target-nums[i]);
res[1] = i;
break;
}else{
map.put(nums[i], i);
}
}
return res;
}
}
15. 三数之和
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
思路:首先对数组进行排序,然后把三个数求和的解就转化为两个数相加等于某个数。求解两个数相加等于某个数,用双指针法即可。
关键在于处理重复的问题,由于排完序了,所以重复问题的处理只需要判断相邻两个是不是相等,如果相等移动指针即可。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
for(int i=0; i<nums.length-2; i++){
//由于nums已经按升序排好序,若第一个数大于0,则三数之和不可能等于0
if(nums[i]>0){
break;
}
//去重
if(i>0 && nums[i]==nums[i-1]){
continue;
}
//对撞指针
int l=i+1, r=nums.length-1;
int sum = 0-nums[i];
while(l<r){
if(nums[l]+nums[r] == sum){
res.add(Arrays.asList(nums[i], nums[l], nums[r]));
//去重
while(l<r && nums[l] == nums[l+1]){
l++;
}
while(l<r && nums[r] == nums[r-1]){
r--;
}
l++;
r--;
}else if(nums[l]+nums[r]>sum){
r--;
}else{
l++;
}
}
}
return res;
}
}
18. 四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。示例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
思路:类似于三数之和,只不过多加了一重循环
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> res = new ArrayList<>();
//Arrays.sort() 可以用于对基本类型的数组排序
//Collections.sort() 用于对List进行排序
//Arrays.asList() 可以将包裹类型的array转换为list, 但不可将基本类型的array转换为list
Arrays.sort(nums);
for(int i=0; i<nums.length-3; i++){
//去重
if(i>0 && nums[i] == nums[i-1]){
continue;
}
for(int j=i+1; j<nums.length-2; j++){
//去重
if(j>i+1 && nums[j] == nums[j-1]){
continue;
}
//对撞指针
int l=j+1, r=nums.length-1;
int temp = target-nums[i]-nums[j];
while(l<r){
if(nums[l]+nums[r] == temp){
res.add(Arrays.asList(nums[i], nums[j], nums[l], nums[r]));
//去重
while(l<r && nums[l] == nums[l+1]){
l++;
}
while(l<r && nums[r] == nums[r-1]){
r--;
}
l++;
r--;
}else if(nums[l]+nums[r] > temp){
r--;
}else{
l++;
}
}
}
}
return res;
}
}
16. 最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
思路:先排序, 然后 fix 一个数进行遍历, 然后内部使用双指针
class Solution {
public int threeSumClosest(int[] nums, int target) {
int res=0;
int dis=Integer.MAX_VALUE; //记录三数字和与目标值的距离
Arrays.sort(nums);
for(int i=0; i<nums.length-2; i++){
//对撞指针
int l=i+1, r=nums.length-1;
while(l<r){
int sum = nums[i]+nums[l]+nums[r];
if(Math.abs(sum-target)<dis){
dis = Math.abs(sum-target);
res = sum;
}
if(l<r && sum<target){
l++;
}else if(l<r && sum>target){
r--;
}else{
return target;
}
}
}
return res;
}
}
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解释:
两个元组如下:
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
思路:使用查找表,通过一个map记录A,B所有组合及其频次,时间复杂度为O(n^2)
class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
int res = 0;
//通过一个map记录数组A, B组合的结果及其频次
HashMap<Integer, Integer> map = new HashMap<>();
for(int i=0; i<A.length; i++){
for(int j=0; j<B.length; j++){
int sum = A[i]+B[j];
map.put(sum, map.getOrDefault(sum, 0)+1);
}
}
//遍历C, D, 查找map求解
for(int k=0; k<C.length; k++){
for(int l=0; l<D.length; l++){
if(map.containsKey(0-C[k]-D[l])){
res+=map.get(0-C[k]-D[l]);
}
}
}
return res;
}
}
49. 字母异位词分组
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
说明:
所有输入均为小写字母。
不考虑答案输出的顺序。
思路:查找表法,通过一个map记录, 将每个字符串对应字符数组经排序后得到的字符串作为该字符串的key
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
//通过一个map记录, 将每个字符串对应字符数组经排序后得到的字符串作为该字符串的key
HashMap<String, List<String>> map = new HashMap<>();
for(String s : strs){
//找出字符串的键, 所有的异位词共用同一个键
char[] c = s.toCharArray();
Arrays.sort(c);
String key = new String(c);
if(!map.containsKey(key)){
List<String> list = new ArrayList<>();
list.add(s);
map.put(key, list);
}else{
List<String> list = map.get(key);
list.add(s);
map.put(key, list);
}
}
//Map.values()方法只是返回了一个Collection集合, 这里是Collection<List<String>>
//在ArrayList中,有一个构造函数,可以接受一个集合类型的参数,然后返回一个list
return new ArrayList<List<String>>(map.values());
}
}
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]]
思路:由于每一个点都可能是回旋镖的顶点 (三个点中的第一个点),需要对每个点都分别进行考虑。对每一个点,构造一个查询表,K为其它点到该点的距离(避免浮点误差,使用距离的平方),V为该距离出现的频次
class Solution {
public int numberOfBoomerangs(int[][] points) {
int res = 0;
//由于每一个点都可能是回旋镖的顶点 (三个点中的第一个点),需要对每个点都分别进行考虑
//对每一个点,构造一个查询表,K为其它点到该点的距离(避免浮点误差,使用距离的平方),V为该距离出现的频次
for(int i=0; i<points.length; i++){
HashMap<Integer, Integer> map = new HashMap<>();
for(int j=0; j<points.length; j++){
if(i!=j){
int dist = (int)Math.pow((points[i][0]-points[j][0]), 2)+(int)Math.pow((points[i][1]-points[j][1]), 2);
map.put(dist, map.getOrDefault(dist, 0)+1);
}
}
//计算以i为顶点的回旋镖的个数
for(int d : map.keySet()){
int nums = map.get(d);
if(nums>=2){
res+=nums*(nums-1); //有nums个点到点i的距离相等
}
}
}
return res;
}
}
149
滑动窗口+查找表
219
217
220