LeetCode部分习题解答记录-查找
349.两个数组的交集
- 方法1:使用HashSet
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
HashSet<Integer> hs = new HashSet<>();
int[] ans = new int[Math.min(nums1.length, nums2.length)];
int index = 0;
for(int i = 0; i < nums1.length;i++){
hs.add(nums1[i]);
}
for(int i = 0 ; i < nums2.length ; i++){
if(hs.contains(nums2[i])){
hs.remove(nums2[i]); //只判断出现一次,因此出现一次后就删除,无需再次统计
ans[index++] = nums2[i];
}
}
return Arrays.copyOfRange(ans, 0, index);
}
}
350. 两个数组的交集 II
- 方法1:使用HashMap,元素出现的次数有意义
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
int[] ans = new int[Math.max(nums1.length,nums2.length)];
HashMap<Integer,Integer> hm = new HashMap<>();
int index = 0;
for(int i = 0 ; i < nums1.length ;i++){
int count = hm.containsKey(nums1[i]) ? hm.get(nums1[i]) : 0;
hm.put(nums1[i], count + 1);
}
for(int i = 0 ; i < nums2.length;i++){
//用containKey方法会报NullPointerException错误.具体原因疑问
// int count = hm.containsKey(nums2[i]) ? hm.get(nums1[i]) : 0;
int count = hm.getOrDefault(nums2[i], 0);
if(count > 0){
ans[index++] = nums2[i];
count--;
//更新count值或者删除
if (count > 0) {
hm.put(nums2[i], count);
} else {
hm.remove(nums2[i]);
}
}
}
return Arrays.copyOfRange(ans, 0, index);
}
}
242.有效的字母异位词
- 方法1:使用HashMap统计各字符出现的次数
class Solution {
public boolean isAnagram(String s, String t) {
HashMap<Character,Integer> hm = new HashMap<>();
//若两字符串长度不相等,必输出false
if(s.length() != t.length()){
return false;
}
for(int i = 0 ; i < s.length();i ++ ){
int count = hm.getOrDefault(s.charAt(i),0);
hm.put(s.charAt(i),count+1);
}
for(int i = 0 ; i < t.length(); i++){
int count = hm.getOrDefault(t.charAt(i),0);
//若count为0,则说明字符串s中无此字符,必为false
if(count == 0 ){
return false;
}
if(count > 0){
count--;
//及时更新HashMap值
if(count > 0){
hm.put(t.charAt(i),count);
}else{
hm.remove(t.charAt(i));
}
}
}
return true;
}
}
- 方法2:利用数组
class Solution {
public boolean isAnagram(String s , String t){
if(s.length() != t.length()){
return false;
}
int[] ans = new int[26]; //26个英文字母
//字符串s出现字符加1,字符串t出现字符减1
for(int i = 0 ;i < s.length(); i++){
ans[s.charAt(i) - 'a'] ++;
ans[t.charAt(i) - 'a'] --;
}
//若ans[]中有不为0的数,则说明s与t中的字符出现的次数至少有一个不相等
for(int i = 0 ;i < ans.length;i++){
if(ans[i] != 0){
return false;
}
}
return true;
}
}
202.快乐数
- 方法1:
class Solution {
public boolean isHappy(int n) {
HashSet<Integer> hs = new HashSet<>();
//判断条件表明,若还未计算到1时Set中都包括了contains,则说明数值开始了循环
while(n != 1 && !hs.contains(n)){
hs.add(n);
n = add(n);
}
return n == 1;
}
public int add(int n){
int sum = 0;
while(n > 0){
int number = n % 10;
n = n / 10;
sum += Math.pow(number,2);
}
return sum;
}
}
290.单词规律
- 方法1:注意HashMap的put返回值
class Solution {
public boolean wordPattern(String pattern, String s) {
String[] str = s.split(" ");
if(pattern.length() != str.length){
return false;
}
HashMap<Object,Integer> hm = new HashMap<>();
for(int i = 0 ; i < str.length ; i ++){
//HashMap,put返回值问题:若put时HashMap中不存在此键,则返回空,否则更新值,并返回旧值
Integer v1 = hm.put(pattern.charAt(i),i);
Integer v2 = hm.put(str[i],i);
//Integer对象的比较
if(!Objects.equals(v1,v2)){
return false;
}
}
return true;
}
}
205.同构字符串
-
方法1:
egg 和 add 同构,就意味着下边的映射成立 e -> a g -> d 也就是将 egg 的 e 换成 a, g 换成 d, 就变成了 add 同时倒过来也成立 a -> e d -> g 也就是将 add 的 a 换成 e, d 换成 g, 就变成了 egg foo 和 bar 不同构,原因就是映射不唯一 o -> a o -> r 其中 o 映射到了两个字母
class Solution {
public boolean isIsomorphic(String s, String t) {
Map<Character,Character> map = new HashMap<>();
int len = s.length();
for(int i = 0; i < len; i++){
char ch1 = s.charAt(i);
char ch2 = t.charAt(i);
//建立两个字符的映射关系,如果出现了某个字符判断映射关系是否成立
if(!map.containsKey(ch1)){
if(map.containsValue(ch2))
return false;
map.put(ch1, ch2);
} else if(map.get(ch1) != ch2)
return false;
}
return true;
}
}
- 方法2:判断字符首次出现的位置是否相等,实际上还是建立字符之间的映射关系
class Solution {
public boolean isIsomorphic(String s, String t) {
int len = s.length();
char[] ch1 = s.toCharArray();
char[] ch2 = t.toCharArray();
//通过判断字母首次出现的位置
for (int i = 0; i < len; i++) {
if(s.indexOf(ch1[i]) != t.indexOf(ch2[i])){
return false;
}
}
return true;
}
}
451. 根据字符出现的频率排序
- 方法1:
class Solution {
public String frequencySort(String s) {
StringBuffer sb = new StringBuffer();
HashMap<Character,Integer> hm = new HashMap<>();
//先统计各字符出现的次数
for(int i = 0 ; i < s.length();i++){
int count = hm.getOrDefault(s.charAt(i),0);
hm.put(s.charAt(i),count+1);
}
//用List来存储Map集合
List<Map.Entry<Character, Integer>> list = new ArrayList<Map.Entry<Character, Integer>>(hm.entrySet());
//List排序,重写排序器,根据从大到小排序
list.sort(new Comparator<Map.Entry<Character, Integer>>() {
@Override
public int compare(Map.Entry<Character, Integer> o1, Map.Entry<Character, Integer> o2) {
return o2.getValue().compareTo(o1.getValue());
}
});
//输出每个字符出现的次数,并添加到sb中,最终转换为字符串输出
for(int i = 0 ; i < list.size() ; i++){
int count = list.get(i).getValue();
while(count > 0){
sb.append(list.get(i).getKey());
count--;
}
}
return sb.toString();
}
}
15.3SUM
- 分析:若使用暴力法,则是三重循环,时间复杂度为o(n^2)。可先固定一个数,然后在剩余的数中运用三重指针进行寻找另外两个数。同时,先将数据进行排序操作,方便查找。
- 方法1:排序+双重指针
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list = new ArrayList<>();
if(nums.length <= 2) return list;
Arrays.sort(nums);
for(int i = 0 ;i < nums.length -2 ;i++){
if(nums[i] > 0) break; //可以不加,这句不是很理解
//例如 -4 ,-1,-1,0,1 在i=1时已经找到,i=2时若不跳出则输出重复
if(i > 0 && nums[i] == nums[i-1]) continue;
int left = i + 1;
int right = nums.length-1;
while(left < right){
int sum = nums[i] + nums[left] + nums[right];
if(sum == 0){
list.add(Arrays.asList(nums[i],nums[left],nums[right]));
//现在要增加 left,减小 right,但是不能重复,
//比如: [-2, -1, -1, -1, 3, 3, 3], i = 0, left = 1, right = 6, [-2, -1, 3] 的答案加入后,需要排除重复的 -1 和 3
left++;
right--;
//两个while去除重复情况
while(left < right && nums[left] == nums[left-1]) left++;
while(left < right && nums[right] == nums[right+1]) right--;
} else if (sum < 0){
left++;
}else{
right--;
}
}
}
return list;
}
}
18.四数之和
- 分析:三数之和的升级版,可先固定两个数,在剩余的数中运用双重指针碰撞来进行寻找。
- 方法1:简略版本,未进行剪纸与重复判断操作,相对来说运行时间较长,进行了一些重复操作。因此碰上相同的解时,压入List时进行了判断。
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list = new ArrayList<>();
Arrays.sort(nums);
if(nums.length < 4){
return list;
}
for(int i = 0 ; i < nums.length;i++){
for(int j = i+1; j < nums.length; j++){
// if(nums[j] == nums[j-1]) continue;
int left = j+1;
int right = nums.length -1;
while(left < right){
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if(sum == target){
//去重操作,判断是否有重复元素已被压入
if(list.contains(Arrays.asList(nums[i],nums[j],nums[left],nums[right])) == false){
list.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
}
left++;
right--;
while(left < right && nums[left] == nums[left-1]) left++;
while(left < right && nums[right] == nums[right+1]) right--;
}else if(sum < target){
left++;
}else{
right--;
}
}
}
}
return list;
}
}
- 方法2:加入了去重与剪纸操作,运行时间缩短。四个剪枝操作都去除了一些不可能的情况,例如剪枝操作1:nums[i]+nums[i+1]+nums[i+2]+nums[i+3] > target ,在当前循环中,如这连续的四个数相加大于目标值,因此变化任何数为后面的数都会使和变大,不会出现相等的状况。
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> list = new ArrayList<>();
int length = nums.length;
Arrays.sort(nums);
if(nums.length < 4){
return list;
}
for(int i = 0 ; i < nums.length - 3;i++){
//去重1 例如[-5,-5, -3,-1, 0, 2, 4, 5] -7
//假设在i=0,-5时已找到相应的集合,如果不进行判断去重操作,在i=1,-5时必定重复压入
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
//剪枝操作
if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
break;
}
if (nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {
continue;
}
for(int j = i+1; j < nums.length-2; j++){
//去重2 例如[-2, -1, -1 , 1, 1, 2, 2] 0
//去重操作1与2必须同时存在,才能保证无重复元素
if(nums[j] == nums[j-1] && j > i+1) continue;
//剪枝
if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
break;
}
if (nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {
continue;
}
int left = j+1;
int right = nums.length -1;
while(left < right){
int sum = nums[i] + nums[j] + nums[left] + nums[right];
if(sum == target){
list.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
left++;
right--;
while(left < right && nums[left] == nums[left-1]) left++;
while(left < right && nums[right] == nums[right+1]) right--;
}else if(sum < target){
left++;
}else{
right--;
}
}
}
}
return list;
}
}
16.最接近的三数之和
- 方法1:
class Solution {
public int threeSumClosest(int[] nums, int target) {
//不能使用Integer.MAX_VALUE,测试用例通过130/131,原因未明白
// int best = Integer.MAX_VALUE;
int best = nums[0] + nums[1] + nums[2];
Arrays.sort(nums);
int length = nums.length;
for(int i = 0 ; i < length; i++){
if(i > 0 && nums[i] == nums[i-1]) continue;
int left = i + 1;
int right = length - 1;
while(left < right){
int sum = nums[i] + nums[left] + nums[right];
// 根据差值的绝对值来更新答案
if (Math.abs(sum - target) < Math.abs(best - target)) {
best = sum;
}
if (sum == target) {
return target;
}else if(sum < target){
left++;
while(left < right && nums[left] == nums[left-1]) left++;
}else{
right--;
while(left < right && nums[right] == nums[right+1]) right--;
}
}
}
return best;
}
}
454.四数之和2
- 分析:若运用暴力法四重循环,则超时。因此先将两个数组的和sum压入Map中,再依次便利另两个数组和,判断-sum是否在Map中
- 方法1:
class Solution {
public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
HashMap<Integer,Integer> hm = new HashMap<>();
int length = A.length;
int sum = 0;
int result = 0;
for(int i = 0 ; i < length ;i++){
for(int j = 0 ; j < length ; j++){
sum = C[i] + D[j];
int count = hm.getOrDefault(sum,0);
hm.put(sum,count+1);
}
}
for(int i = 0 ; i < length ;i++){
for(int j = 0 ; j < length ; j++){
sum = A[i] + B[j];
if(hm.containsKey(-sum)){
result += hm.get(-sum);
}
}
}
return result;
}
}
49.字母异位词分组
- 分析:字母异位词排序后是相同的,因此使用哈希表根据排序后的字符串为key存储各个字母异位词的list
- 方法1:
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
HashMap<String,List<String>> hm = new HashMap<>();
for(String value:strs){
char[] ch = value.toCharArray();
Arrays.sort(ch); //将字符数组排序
String s = String.valueOf(ch);//将字符数组转换为字符串
if(!hm.containsKey(s)){ //判断是否存在,不存在则建立映射ArrayList
hm.put(s,new ArrayList<>());
}
//每次都压入,将所有str分组
hm.get(s).add(value);
}
return new ArrayList<>(hm.values());
}
}
447.回旋镖的数量
-
分析:运用暴力法三重循环,但是题目中n最大为500,因此时间复杂度为O(N^3)的算法会超出时间范围。我们考虑双重循环,因此对某一固定i,遍历数组求出j、k和i的距离,从而构建距离的键值对。如图所示:
-
方法1:
class Solution {
public int numberOfBoomerangs(int[][] points) {
int length = points.length;
int result = 0;
HashMap<Integer,Integer> hm = new HashMap<>();
for(int i = 0 ; i < length ; i++){
for(int j = 0; j < length ; j++){
if(i == j) continue;
int distance = (int)(Math.pow(points[i][0]-points[j][0],2) + Math.pow(points[i][1]-points[j][1],2));
int count = hm.getOrDefault(distance,0);
hm.put(distance,count + 1);
}
for(Map.Entry<Integer, Integer> entry: hm.entrySet()){
int count = entry.getValue();
if(count > 1){
// while(count > 1){
// i固定,只需要考虑i,k的取值,因此是count * count -1 ,不需要while循环递减
result += count * (count - 1);
// count--;
// }
}
}
hm.clear();
}
return result;
}
}
219.重复元素Ⅱ
- 分析:采用HashSet模拟重复窗口,在集合中,一直都只有k个元素。遍历数组,遇到相同的数则返回true。
- 方法1:
class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
HashSet<Integer> hs = new HashSet<>();
for(int i = 0 ; i < nums.length ; i++){
if(hs.contains(nums[i])){
return true;
}
hs.add(nums[i]);
if(hs.size() > k){
hs.remove(nums[i-k]);
}
}
return false;
}
}
220.存在重复元素Ⅲ
- 分析:与219题类似,都是采用set来模拟滑动窗口,只不过条件变化。在本题中,需要寻找在某一个范围的值,因此需要使用有序集合TreeSet。其中有floor与ceiling方法可以找到<=或者>=某个数的最大或者最小值。由于本题中测试样例可能出现int能表示的最大值,则num[i]+t会出现超出int表示范围的值。因此,需要转换为long类型进行逻辑判断。
- 方法1:
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
TreeSet<Long> ts = new TreeSet<>();
for(int i = 0 ; i < nums.length ;i++){
Long number = ts.ceiling((long)nums[i] - (long)t);
if(number != null && number <= (long)nums[i] + (long)t){
return true;
}
ts.add((long)nums[i]);
if(ts.size() > k){
ts.remove((long)nums[i - k]);
}
}
return false;
}
}