代码随想录 哈希表 Java

news2024/11/17 1:27:08

文章目录

  • (简单)242.有效的字母异位词
  • (简单)383. 赎金信
  • (中等)49. 字母异位词分组
  • (*中等)438. 找到字符串中所有字母异位词
  • (简单)349. 两个数组的交集
  • (简单)350. 两个数组的交集||
  • (*简单)202. 快乐数
  • (*简单)1. 两数之和
  • (中等)454. 四数相加 ||
  • (*中等)15. 三数之和
  • (中等)18. 四数之和


(简单)242.有效的字母异位词

在这里插入图片描述

class Solution {
    public boolean isAnagram(String s, String t) {
    	if (s.length()!=t.length()){
            return false;
        }
        int[] s1 = new int[26];
        int[] t1 = new int[26];
        for (char c : s.toCharArray()) {
            s1[c - 'a']++;
        }
        for (char c : t.toCharArray()) {
            t1[c - 'a']++;
        }
        for (int i = 0; i < 26; i++) {
            if (s1[i] != t1[i]) {
                return false;
            }
        }
        return true;
    }
}

在这里插入图片描述

官方其他思路,方法一:排序

t是s的异位词等价于【两个字符串排序后相等】。因此可以对字符串s和t分别排序,看排序后的字符串是否相等即可判断。此外,如果s和t的长度不同,t必然不是s的异位词。
在这里插入图片描述

import java.util.Arrays;

class Solution {
    public boolean isAnagram(String s, String t) {
        if (s.length() != t.length()) {
            return false;
        }
        char[] arr1 = s.toCharArray();
        char[] arr2 = t.toCharArray();
        Arrays.sort(arr1);
        Arrays.sort(arr2);
        return Arrays.equals(arr1, arr2);
    }
}

复杂度分析:

  • 时间复杂度:O(nlogn),其中n为s的长度。排序的时间复杂度为O(nlogn),比较两个字符串是否相等的时间复杂度为O(n),因此,总体时间复杂度为O(nlogn+n)=O(nlogn)
  • 空间复杂度:O(logn),排序需要O(logn)的空间复杂度

官方其他思路,方法二:哈希表

从另一个角度考虑,t是s的异位词等价于【两个字符串中出现的种类和次数均相等】。由于字符串只包含26个小写字母,因此,可以维护一个长度为26 的数组,先遍历记录字符串s中字符出现的频次,然后遍历字符串t,减去数组中对应的频次,如果出现某一数组元素小于0的情况,则说明t包含一个不在s中的额外字符,返回false

class Solution {
    public boolean isAnagram(String s, String t) {
        if (s.length() != t.length()) {
            return false;
        }
        int[] table = new int[26];
        for (char c : s.toCharArray()) {
            table[c - 'a']++;
        }
        for (char c : t.toCharArray()) {
            table[c - 'a']--;
            if (table[c - 'a'] < 0) {
                return false;
            }
        }
        return true;
    }
}

在这里插入图片描述
对于进阶问题, 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

这样就不能用数组来维护哈希表,直接使用HashMap即可

import java.util.HashMap;

class Solution {
    public boolean isAnagram(String s, String t) {
        if (s.length() != t.length()) {
            return false;
        }
        HashMap<Character, Integer> map = new HashMap<>();
        for (char c : s.toCharArray()) {
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        for (char c : t.toCharArray()) {
            map.put(c, map.getOrDefault(c, 0) - 1);
            if (map.get(c) < 0) {
                return false;
            }
        }
        return true;
    }
}

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(n),其中n为s的长度
  • 空间复杂度:O(S),其中S为字符集大小,本题S为26

(简单)383. 赎金信

在这里插入图片描述

在这里插入图片描述
我的思路:因为题目中说明了randomNote和magazine都是由小写字母组成,所以维护一个长度为26的数组,先统计magazine中各个字母出现的次数,在去计算randomNote中各个字母出现的次数是否小于等于magazine中各个字母出现的次数

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        if (ransomNote.length() > magazine.length()) {
            return false;
        }
        int[] arr = new int[26];
        for (char c : magazine.toCharArray()) {
            arr[c - 'a']++;
        }
        for (char c : ransomNote.toCharArray()) {
            arr[c - 'a']--;
            if (arr[c - 'a'] < 0) {
                return false;
            }
        }
        return true;
    }
}

在这里插入图片描述

(中等)49. 字母异位词分组

在这里插入图片描述
在这里插入图片描述
两个字符串互为字母异位词,当且仅当两个字符串包含的字母相同。同一组字母异位词中的字符串具备相同点,可以使用相同点作为一组字母异位词的标志,使用哈希表存储每一组字母异位词,哈希表的键作为一组字母异位词的标志,哈希表的值为一组字母异位词列表。

思路一:排序

在排序方法中,一组字母异位词的标志是排序以后的字符串

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {

        HashMap<String, ArrayList<String>> map = new HashMap<>();
        for (String str : strs) {
            char[] chars = str.toCharArray();
            Arrays.sort(chars);
            String s = new String(chars);
            ArrayList<String> list = map.getOrDefault(s, new ArrayList<>());
            list.add(str);
            map.put(s, list);
        }
        return new ArrayList<>(map.values());
    }
}

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(nklogk),其中n是strs中字符串的数量,k是strs中的字符串的最大长度。需要遍历n个字符串,对于每个字符串,需要O(klogk)的时间进行排序以及O(1)的时间更新哈希表,因此总时间复杂度是O(nklogk)
  • 空间复杂度:O(nk),其中n是strs中字符串的数量,k是strs中的字符串的最大长度。需要用哈希表存储全部字符串

思路二:计数

由于互为字母异位词的两个字符串包含的字母相同,因此两个字符串中的相同字母出现的次数一定是相同的,故可以将每个字母出现的次数使用字符串表示,作为哈希表的键

由于字符串只包含小写字母,因此对于每个字符除按,可以使用长度为26的数组记录每个字母出现的次数,需要注意的是,在使用数组作为哈希表的键时,不同语言的支持程度不同,因此不同语言的实现方式也不同

在这里插入图片描述

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        HashMap<String, List<String>> map = new HashMap<>();
        for (String str : strs) {
            int[] counts = new int[26];
            for (char c : str.toCharArray()) {
                counts[c - 'a']++;
            }
            //将每个出现次数大于0的字母和出现次数按顺序拼接成字符串,作为哈希表的键
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < 26; i++) {
                if (counts[i] != 0) {
                    stringBuilder.append((char) ('a' + i));
                    stringBuilder.append(counts[i]);
                }
            }
            String key = stringBuilder.toString();
            List<String> value = map.getOrDefault(key, new ArrayList<>());
            value.add(str);
            map.put(key, value);
        }
        return new ArrayList<>(map.values());
    }
}

(*中等)438. 找到字符串中所有字母异位词

在这里插入图片描述

我的思路:找到包含在p中的字符,截取和p长度一样的子串,比较两个子串是否是字母异位词

在这里插入图片描述

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List<Integer> findAnagrams(String s, String p) {

        ArrayList<Integer> res = new ArrayList<>();

        if (p.length() > s.length()) {
            return res;
        }

        if (p.length() == s.length()) {
            char[] s1 = s.toCharArray();
            char[] p1 = p.toCharArray();
            Arrays.sort(s1);
            Arrays.sort(p1);
            if (Arrays.equals(p1, s1)) {
                res.add(0);
            }
            return res;
        }

        int[] cnt = new int[26];
        for (char c : p.toCharArray()) {
            cnt[c - 'a']++;
        }
        int sLength = s.length();
        int pLength = p.length();
        char[] sCharArray = s.toCharArray();
        char[] pCharArray = p.toCharArray();
        Arrays.sort(pCharArray);
        int i = 0;
        while (i + pLength <= sLength) {
            if (cnt[sCharArray[i] - 'a'] > 0) {
                String substring = s.substring(i, i + pLength);
                char[] subCharArray = substring.toCharArray();
                Arrays.sort(subCharArray);
                if (Arrays.equals(pCharArray, subCharArray)) {
                    res.add(i);
                }
            }
            i++;
        }
        return res;
    }
}

官方思路,方法一:滑动窗口

根据题目要求,我们需要在字符串s寻找字符串p的异位词。因为字符串p的异位词的长度一定与字符串p相同,所以可以在字符串s中构造一个长度为与字符串p的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;当窗口中每种字母的数量与字符串p中每种字母数量相同时,则说明当前窗口为字符串p的异位词

使用数组来存储字符串p和滑动窗口中每种字母的数量

当字符串s的长度小于字符串p的长度时,字符串s中一定不存在字符串p的异位词

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List<Integer> findAnagrams(String s, String p) {

        ArrayList<Integer> res = new ArrayList<>();

        if (p.length() > s.length()) {
            return res;
        }

        int pLen = p.length();
        int sLen = s.length();
        int[] sCount = new int[26];
        int[] pCount = new int[26];
        for (int i = 0; i < pLen; i++) {
            sCount[s.charAt(i) - 'a']++;
            pCount[p.charAt(i) - 'a']++;
        }
        if (Arrays.equals(sCount, pCount)) {
            res.add(0);
        }
        for (int i = 0; i < sLen - pLen; i++) {
            sCount[s.charAt(i) - 'a']--;
            sCount[s.charAt(i + pLen) - 'a']++;
            if (Arrays.equals(sCount, pCount)) {
                res.add(i + 1);
            }
        }
        return res;
    }
}

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(m+(n-m)x ∑ \sum ),其中n为字符串s的长度,m为字符串p的长度, ∑ \sum 为所有可能的字符数。O(m)来统计字符串p中每种字母的数量;需要O(m)来初始化滑动窗口;需要判断n-m+1个滑动窗口中每种字母的数量是否与字符串p中每种字母的数量相同,每次判断需要O( ∑ \sum )。因为s和p仅包含小写字母,所以 ∑ \sum =26
  • 空间复杂度:O( ∑ \sum )。用于存储字符串p和滑动窗口中每种字符的数量

方法二:优化的滑动窗口

在方法一的基础上,不在分别统计滑动窗口和字符串p中每种字母的数量,而是统计滑动窗口和字符串p中每种字母数量的差;并引入变量differ来记录当前窗口与字符串p中数量不同的字母的个数,并在滑动窗口的过程中维护它。

在判断滑动窗口中每种字母的数量与字符串p中每种字母的数量是否相同时,只需要判断differ是否为0即可。

import java.util.ArrayList;
import java.util.List;

class Solution {
    public List<Integer> findAnagrams(String s, String p) {

        ArrayList<Integer> list = new ArrayList<>();

        int sLen = s.length();
        int pLen = p.length();
        if (sLen < pLen) {
            return list;
        }

        //count数组用来记录 滑动窗口中的字符串s的各个字母的出现次数和字符串p各个字母出现次数的差值
        int[] counts = new int[26];

        //首先看最开始长度为pLen的字符串s的子串
        for (int i = 0; i < pLen; i++) {
            counts[s.charAt(i) - 'a']++;
            counts[p.charAt(i) - 'a']--;
        }

        //differ用来记录,滑动窗口中的字符串的字母与字符串p中的字母相差的个数
        int differ = 0;
        for (int i = 0; i < 26; i++) {
            if (counts[i] != 0) {
                differ++;
            }
        }
        if (differ == 0) {
            list.add(0);
        }

        //准备移动滑动窗口
        for (int i = 0; i < sLen - pLen; i++) {
            char c = s.charAt(i);
            //马上会从滑动窗口中移出一个字符c
            //如果原来滑动窗口中字符c的数量比字符串p中字符c的数量多1,那么移动以后就会刚好不差
            if (counts[c - 'a'] == 1) {
                differ--;
            } else if (counts[c - 'a'] == 0) {
                //如果原来刚好不差,那么移动以后就会少一个
                differ++;
            }
            //滑动窗口向右移动一个字符,原来位置i上的字母被移出,所以
            counts[c - 'a']--;
            
            //马上会进来一个字符c
            c = s.charAt(i + pLen);
            //如果原来字符串p比它多了一个字符c,那么移动以后就会刚好不差
            if (counts[c - 'a'] == -1) {
                differ--;
            } else if (counts[c - 'a'] == 0) {
                differ++;
            }
            counts[c - 'a']++;

            if (differ == 0) {
                list.add(i + 1);
            }
        }
        return list;
    }
}

(简单)349. 两个数组的交集

在这里插入图片描述

我的思路:首先将两个数组nums1和nums2中的数分别放到set1和set2中去重,然后再遍历size较小的那个set,判断两个set是否有交集。因为不确定它们是否有交集,如果有的话,交集有多大都不能确定,则先使用ArrayList来存放交集中的元素,最后再用数组保存。

import java.util.ArrayList;
import java.util.HashSet;

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {

        HashSet<Integer> set1 = new HashSet<>();
        HashSet<Integer> set2 = new HashSet<>();
        ArrayList<Integer> list = new ArrayList<>();

        for (int num : nums1) {
            set1.add(num);
        }
        for (int num : nums2) {
            set2.add(num);
        }

        if (set1.size() < set2.size()) {
            for (Integer num : set1) {
                if (set2.contains(num)) {
                    list.add(num);
                }
            }
        } else {
            for (Integer num : set2) {
                if (set1.contains(num)) {
                    list.add(num);
                }
            }
        }


        int[] ans = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            ans[i] = list.get(i);
        }
        return ans;
    }
}

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(m+n),其中m和n分别是两个数组的长度。使用两个集合分别存储两个数组中的元素需要O(m+n)的时间,遍历较小的集合并判断元素是否在另一个集合中需要O(min(m,n))的时间,因此总时间复杂度是O(m+n)
  • 空间复杂度:O(m+n),其中m和n分别是两个数组的长度。空间复杂度取决于两个集合。

官方提供的其他思路,排序+双指针

如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。

首先对两个数组进行排序,然后使用两个指针遍历两个数组。可以预见的是加入答案的数组的元素一定是递增的,为了保证加入元素的唯一性,需要额外记录变量pre表示上一次加入答案数组的元素。

初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数组的指针右移一位,如果两个数字相等,且该数字不等于pre,将该数字添加到答案并更新pre变量,同时将两个指针右移一位。当至少有一个指针超出数组范围时,遍历结束。

import java.util.ArrayList;
import java.util.Arrays;

class Solution {
    public int[] intersection(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int p1 = 0;
        int p2 = 0;
        int len1 = nums1.length;
        int len2 = nums2.length;
        int pre = -1;
        ArrayList<Integer> list = new ArrayList<>();
        while (p1 < len1 && p2 < len2) {
            if (nums1[p1] != nums2[p2]) {
                if (nums1[p1] < nums2[p2]) {
                    p1++;
                } else {
                    p2++;
                }
            } else {
                if (nums1[p1] != pre) {
                    list.add(nums1[p1]);
                    pre = nums1[p1];
                    
                }
                p1++;
                p2++;
            }
        }
        int[] ans = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            ans[i] = list.get(i);
        }
        return ans;
    }
}

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(mlogm+nlogn),其中m和n分别是两个数组的长度。对数组排序的时间复杂度是O(mlogm)和O(nlogn),双指针寻找交集元素的时间复杂度是O(m+n),因此,总的时间复杂度是O(mlogm+nlogn)
  • 空间复杂度:O(logm+logn),其中m和n分别是两个数组的长度。空间复杂度主要取决于排序使用的额外空间。

(简单)350. 两个数组的交集||

在这里插入图片描述
我的思路:

首先,让nums1数组是长度较短的那个数组,nums2是长度较长的那个数组。

然后使用HashMap来记录nums2中的元素和元素出现的次数

再循环遍历nums1,当HashMap中存在nums1中的某元素,且次数大于0,则说明该元素是nums1和nums2的交集,将该元素加入ArrayList,HashMap中该元素的次数减一

最后将ArrayList中的值赋值到一个数组中,返回

import java.util.ArrayList;
import java.util.HashMap;

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        if (nums1.length > nums2.length) {
            return intersect(nums2, nums1);
        }

        ArrayList<Integer> list = new ArrayList<>();

        HashMap<Integer, Integer> map = new HashMap<>();
        for (int num : nums1) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }

        for (int num : nums2) {
            if (map.containsKey(num) && map.get(num) > 0) {
                list.add(num);
                map.put(num, map.get(num) - 1);
            }
        }

        int[] ans = new int[list.size()];
        for (int i = 0; i < ans.length; i++) {
            ans[i] = list.get(i);
        }
        return ans;
    }
}

在这里插入图片描述

复杂度分析:

  • 时间复杂度:O(m+n),其中m和n分别是两个数组的长度。需要遍历两个数组对哈希表进行操作,哈希表操作的时间复杂度是O(1),因此,总时间复杂度与两个数组的长度和呈线性关系
  • 空间复杂度:O(min(n,m)),其中m和n分别是两个数组得到长度。对于较短的数组进行哈希表的操作,哈希表的大小不会超过较短的哈希表的长度。

我的另一种思路:是在看了349的官方题解以后,觉得这一题也可以用排序+双指针的做法来解题,和349题不同的地方在于这一题中不需要变量来记录上一次加入集合的是什么数字,因为这一题的答案需要包含重复的元素

import java.util.ArrayList;
import java.util.Arrays;

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int p1 = 0;
        int p2 = 0;
        int len1 = nums1.length;
        int len2 = nums2.length;
        ArrayList<Integer> list = new ArrayList<>();
        while (p1 < len1 && p2 < len2) {
            if (nums1[p1] < nums2[p2]) {
                p1++;
            } else if (nums1[p1] > nums2[p2]) {
                p2++;
            } else {
                list.add(nums1[p1]);
                p1++;
                p2++;
            }
        }
        int[] ans = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            ans[i] = list.get(i);
        }
        return ans;
    }
}

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(mlogm+nlogn),其中m和n分别是两个数组的长度。对两个数组进行排序的时间复杂度是O(mlogm+nlogn),遍历两个数组的时间复杂度是O(m+n),因此总的时间复杂度是O(mlogm+nlogn)
  • 空间复杂度:O(min(m,n)),其中m和n分别是两个数组的长度。其中将符合的元素放在ArrayList中,ArrayList的长度不会超过较短的数组的长度。

如果nums2的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中。那么就无法对nums2进行排序,因此推荐使用哈希表的思路。在思路一中,nums2只关系到查询操作,因此每次读取nums2中的一部分数据,并进行处理。

(*简单)202. 快乐数

哈希表方法的复杂度分析
快慢指针的复杂度分析

在这里插入图片描述

在这里插入图片描述

我的思路:solve(n)方法就是计算数字每一位上的平方的和,将该结果赋值给n,一直到n<10为止,在判断n是否等于1,等于1则表示n是快乐数,在第一次提交的时候倒数第二个测试用例1111111没对,手算以后发现,如果多次迭代后,n=7,那么原始的n也是快乐数

1+1+1+1+1+1+1=7
7^2 = 49
4^2+9^2=97
9^2+7^2=130
1^2+3^2+0^2 = 10
class Solution {
    public boolean isHappy(int n) {
        do {
            n = solve(n);
        } while (n >= 10);
        return n == 1 || n == 7;
    }

    public int solve(int n) {
        int sum = 0;
        while (n != 0) {
            sum += ((n % 10) * (n % 10));
            n /= 10;
        }
        return sum;
    }
}

其他思路,用哈希集合检测循环

49这个数是一个快乐数

在这里插入图片描述

116这个数不是快乐数
在这里插入图片描述
那是否会存在值越来越大,最后接近无穷大的情况?

数字位数最大值下一个
1981
299162
3999243
49999342
1399999999999991053

对于3位数的数组,他不可能大于243。这意味着它要么被困在243以下的循环内,要么跌到1。4位或以上的数字在每一步都会丢失一位,直到降到3位为止。所以,最坏情况可能在243以下的所有数字上循环,然后回到它已经到过的一个循环或者回到1。但它不会无限期地进行下去,所以,不存在最后接近无穷大的情况。

所以,需要解决两个问题:

  1. 给一个数字n,计算它的下一个数字是什么
  2. 判断该数字是否已经进入循环,也就是该数字之前是否出现过,用哈希集合完成
    • 如果它不在哈希集合中,则添加该数字到哈希集合中
    • 如果它在哈希集合中,这就意味着我们处于一个循环中,此时返回false

使用哈希集合而不是向量、列表或数组的原因是因为需要反复检查其中是否存在某数组。检查数字是否在哈希集合中需要O(1)的时间,而对于其他数据结构,则需要O(n)的时间。选择正确的数据结构是解决这些问题的关键部分。

import java.util.HashSet;

class Solution {
    public boolean isHappy(int n) {
        HashSet<Integer> set = new HashSet<>();
        while (n != 1 && !set.contains(n)) {
            set.add(n);
            n = getSum(n);
        }
        return n == 1;
    }

    public int getSum(int n) {
        int sum = 0;
        while (n != 0) {
            sum += ((n % 10) * (n % 10));
            n /= 10;
        }
        return sum;
    }
}

在这里插入图片描述
在这里插入图片描述
其他思路:快慢指针法

通过反复调用getNext(n)得到的链是一个隐式的链表。隐式意味着我们没有实际的链表节点和指针,但是数据仍然形成链表结构。起始数字是链表的头“节点”,链中的所有其他数字都是节点。next指针是通过调用getNext(n)函数获得。

意识到实际有个链表,那么问题就可以转换为检测一个链表是否有环。因此,在这里可以使用弗洛伊德循环查找算法。这个算法是两个奔跑选手,一个跑得快,一个跑得慢。

不管乌龟还是兔子在循环中从哪里开始,它们最终都会相遇。因为兔子每走一步,就向乌龟靠近一个节点。

算法:

不止跟踪链表中的一个值,而是跟踪两个值,一个由fast指针指向,另一个由slow指针指向,如果n是一个快乐数,即没有循环,那么fast指针会先到达数字1。如果n不是一个快乐数,那么fast指针和slow指针会在同一数字上相遇。

class Solution {
    public boolean isHappy(int n) {
        int slow = n;
        int fast = getSum(n);
        while (fast != 1 && slow != fast) {
            fast = getSum(getSum(fast));
            slow = getSum(slow);
        }
        return fast == 1;
    }

    public int getSum(int n) {
        int sum = 0;
        while (n != 0) {
            sum += ((n % 10) * (n % 10));
            n /= 10;
        }
        return sum;
    }
}

在这里插入图片描述
在这里插入图片描述

(*简单)1. 两数之和

在这里插入图片描述
在这里插入图片描述
暴力法,二重循环

class Solution {
    public int[] twoSum(int[] nums, int target) {
        //暴力法
        for (int i = 0; i < nums.length - 1; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[i] + nums[j] == target) {
                    return new int[]{i, j};
                }
            }
        }
        return new int[]{-1, -1};
    }
}

在这里插入图片描述
另一种思路,哈希表法

上一种思路的时间复杂度较高,原因是寻找target-x的时间复杂度过高。因此,需要一种更优的方法能够快速寻找数组中是否存在目标元素,如果存在,则找出它的索引。

使用哈希表,可以将寻找target-x的时间复杂度从降低O(N)到O(1)。

创建一个哈希表,对于每一个x,首先查询哈希表中是否存在target-x,将x插入到哈希表中,即可保证不会让x和自己匹配。

import java.util.HashMap;

class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            if (!map.containsKey(target - nums[i])) {
                map.put(nums[i], i);
            } else {
                return new int[]{i, map.get(target - nums[i])};
            }
        }
        return new int[]{-1, -1};
    }
}

在这里插入图片描述

(中等)454. 四数相加 ||

在这里插入图片描述

在这里插入图片描述
将四个数组分成两部分,A和B为一组,C和D为另外一组。

对于A和B,使用二重循环对它们进行遍历,得到所有A[i]+B[j]的值并存入哈希映射中。对于哈希映射中的每个键值对,每个键表示一种A[i]+B[j],对应的值为A[i]+B[j]出现的次数。

对于C和D,我们同样使用二重循环对它们进行遍历。当遍历到C[k]+D[l]时,如果 -(C[k]+D[l]) 出现在哈希映射中,那么将 -(C[k]+D[l]) 对应的值累加近答案中。

最终即可得到满足A[i]+B[j]+C[k]+D[l]=0的四元素数目

一开始在写代码的时候,把cnt += map.get(-nums3[i] - nums4[j]) 写成了cnt++,如果有多个nums1[i]和nums2[j]的值相加是相等的,就代表了不同的组合,不能直接cnt++

import java.util.HashMap;

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int n = nums1.length;
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                map.put(nums1[i] + nums2[j], map.getOrDefault(nums1[i] + nums2[j], 0) + 1);
            }
        }
        int cnt = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (map.containsKey(-nums3[i] - nums4[j])) {
                    cnt += map.get(-nums3[i] - nums4[j]);
                }
            }
        }
        return cnt;
    }
}

(*中等)15. 三数之和

在这里插入图片描述
在这里插入图片描述
题目要求找到所有【不重复】且【和为0】的三元组

需要三重循环,依次遍历,再使用哈希表去重,得到不包含重复三元组的最终答案,这种做法时间和空间的复杂度都很高。

可以做一点改变:

  • 第二重循环枚举到的元素不小于当前第一重循环枚举到的元素
  • 第三重循环枚举到的元素不小于当前第二重循环枚举到的元素

那么枚举三元组a<=b<=c,保证了只有(a,b,c)这个顺序能够被枚举到。为了实现这一点,将数组中的元素从小到大排序。

如果固定了前两重循环枚举到的元素a和b,那么只有唯一的c满足a+b+c=0,当二重循环往后继续枚举b以后,那么满足等式的c会相应的减小,也就是说,我们可以从小到大枚举b,同时从大到小枚举c,即第二重循环和第三重循环实际上是并列的关系

有了这样的发现,可以保持第二重循环不变,而将第三重循环变成一个从数组最右边开始向左移动的指针

这个方法就是双指针方法,当需要枚举数组的两个元素时,如果发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从O(N^2)减少至O(N)。因为在枚举的过程每一步中,【左指针】会向右移动一个为止(也就是题目中的b),而【右指针】会想做移动若干位置,这个与数组的元素有关,但知道它一共会移动的位置数为O(N),均摊下来,也就是每次也向左移动一个位置,因此时间复杂度是O(N)。

import java.util.*;

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {

        List<List<Integer>> res = new ArrayList<>();

        Arrays.sort(nums);
        int n = nums.length;
        for (int i = 0; i < n; i++) {

            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
			
			//注意,这个变量k要写在第二层for循环的外面
			//如果写在里面的话,那么j每加1,k都要从数组的最后一个元素重新遍历,导致执行时间能达到1000ms以上
            int k = n - 1;
            int target = -nums[i];

            for (int j = i + 1; j < n; j++) {

                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }

                while (j < k && nums[j] + nums[k] > target) {
                    k--;
                }

                if (j == k) {
                    break;
                }

                if (nums[j] + nums[k] == target) {
                    ArrayList<Integer> list = new ArrayList<>();
                    list.add(nums[i]);
                    list.add(nums[j]);
                    list.add(nums[k]);
                    res.add(list);
                }

            }
        }

        return res;

    }
}

在这里插入图片描述
复杂度分析:

  • 时间复杂度:O(N^2),其中N是数组nums的长度
  • 空间复杂度:O(logN),忽略了存储答案的空间,额外的排序的空间复杂度为O(logN)。然而我们修改了输入的数组nums,在实际情况下不一定允许,因此也可以看成使用了一个额外的数组存储了nums的副本并进行排序,空间复杂度为O(N)。

代码随想录提供的思路,使用双指针

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //题目要求不重复,那么就让三个数按从小到大的顺序输出,就不会重复
        //首先对数组进行排序
        Arrays.sort(nums);

        int n = nums.length;

        ArrayList<List<Integer>> ans = new ArrayList<>();

        //假设a+b+c=0
        for (int i = 0; i < n - 2; i++) {

            //如果排序后的第一个元素已经大于0,那么后面的数字无论怎么组合,都不可能凑成一个合格的三元组
            //直接返回答案即可
            if (nums[i] > 0) {
                return ans;
            }

            //去除重复的数字a
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            int left = i + 1;
            int right = n - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {
                    right--;
                } else if (sum < 0) {
                    left++;
                } else {
                    ans.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    //去重逻辑应该放在找到一个三元组之后,对b和c去重
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    left++;
                    right--;
                }
            }

        }
        return ans;
    }
}

在这里插入图片描述

(中等)18. 四数之和

在这里插入图片描述
可以借鉴三数之和中,代码随想录提供的思路,首先对数据进行排序,然后固定前两个数字,后两个数字的确定使用双指针的方法,但是每一个nums[i]的最大值是10^9,所以四个数相加会超过int的范围,所以需要使用long型进行接收

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        int n = nums.length;
        ArrayList<List<Integer>> result = new ArrayList<>();
        for (int i = 0; i < n; i++) {

            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            for (int j = i + 1; j < n; j++) {

                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }

                int left = j + 1;
                int right = n - 1;
                while (left < right) {
                    long sum = (long)nums[i] + (long)nums[j] + (long)nums[left] + (long)nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        while (left < right && nums[left] == nums[left + 1]) {
                            left++;
                        }
                        while (left < right && nums[right] == nums[right - 1]) {
                            right--;
                        }
                        left++;
                        right--;
                    }
                }
            }
        }
        return result;
    }
}

在这里插入图片描述
对于上面的代码,在看了官方提供的代码之后,对上面代码进行了一些修改,多加了一些判断条件

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {

        ArrayList<List<Integer>> result = new ArrayList<>();
        if (nums.length < 4) {
            return result;
        }

        Arrays.sort(nums);
        int n = nums.length;

        for (int i = 0; i < n - 3; i++) {

			//下面这两个if,确定范围别边界,看看从i开始的四个数字的和 和 target的大小关系
            if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
                break;
            }
            //判断当前nums[i]和最右边三个数的和 和 target的大小关系
            if ((long) nums[i] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) {
                continue;
            }

            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            for (int j = i + 1; j < n - 2; j++) {

                if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
                    break;
                }

                if ((long) nums[i] + nums[j] + nums[n - 2] + nums[n - 1] < target) {
                    continue;
                }

                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }

                int left = j + 1;
                int right = n - 1;
                while (left < right) {
                    long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        while (left < right && nums[left] == nums[left + 1]) {
                            left++;
                        }
                        while (left < right && nums[right] == nums[right - 1]) {
                            right--;
                        }
                        left++;
                        right--;
                    }
                }
            }
        }
        return result;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/546918.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CyberLink的专业视频编辑软件ActionDirector Ultra 3.0版本在win10系统的下载与安装配置教程

目录 前言一、ActionDirector Ultra安装二、使用配置总结 前言 ActionDirector Ultra是CyberLink公司开发的专业视频编辑软件&#xff0c;旨在帮助用户创作高质量的运动和冒险视频。该工具提供了一些先进的特效和编辑工具&#xff0c;让用户能够轻松地剪辑、修剪、调整颜色和添…

队列实现栈(你看我讲的是不是最细的就完了)

最伟大的成就往往起源于最强烈的热情。 -- 诺曼文森特皮尔目录 &#x1f5fc;一.队列实现栈 &#x1f345;二.使用两个队列来模拟实现栈 &#x1f34b;1.栈结构体包含两个队列 &#x1f352;2.创建一个结构体的指针 &#x1f342;3.myStackPush入栈操…

【小沐学Python】Python实现Web图表功能(ECharts.js,Flask)

&#x1f388;&#x1f388;&#x1f388;Python实现Web图表功能系列&#xff1a;&#x1f388;&#x1f388;&#x1f388;1&#x1f388;【Web开发】Python实现Web图表功能&#xff08;D-Tale入门&#xff09;&#x1f388;2&#x1f388;【Web开发】Python实现Web图表功能&a…

编程不头秃,Google「AI程序员」来了,聊天就能敲代码

上周 Google 在 I/O 大会宣布了一个能够辅助编程的聊天机器人 Codey&#xff0c;现在它终于上线 Google Colab 啦&#xff01; &#x1f31f; Codey 是基于 Google 目前最新的大语言模型 PaLM 2 运行&#xff0c;有着强大的语言理解和编程能力。 Codey 有这些功能&#xff1…

【k8s】【ELK】【三】Sidecar容器运行日志Agent

1、日志收集场景分析与说明 对于那些能够将日志输出到本地文件的Pod&#xff0c;我们可以使用Sidecar模式方式运行一个日志采集Agent&#xff0c;对其进行单独收集日志1、首先需要将Pod中的业务容器日志输出至本地文件&#xff0c;而后运行一个Filebeat边车容器&#xff0c;采…

chatgpt赋能Python-python3_绝对值

Python3中的绝对值 在本文中&#xff0c;我们将深入了解Python3中的绝对值&#xff08;Absolute Value&#xff09;以及如何在Python3中使用它。 我将介绍Python3的abs函数&#xff0c;它是一个内置函数&#xff0c;用于计算数字的绝对值。 什么是绝对值&#xff1f; 在数学…

详解MySQL主从复制

目录 1.概述 2.配置使用 2.1.master配置 2.2.slave配置 2.3.认主 2.4.确认认主结果 3.请求分发 3.1.概述 3.2.手动分发 3.2.1.原生JDBC 3.2.2.数据源 3.2.3.中间件 1.概述 在实际的数据密集型应用中&#xff0c;数据库层面往往呈现两个特点&#xff1a; 单点数据…

VONR排查指导分享

不能注册或呼叫到SIP服务器端30秒挂断呼叫的黄金法则咬线或摘机状态单通或无语音收到400 bad request收到413&#xff0c;513 Request Entity Too Large或Message Too Large消息收到408&#xff0c; 480或者487 消息483 - Too Many Hops488 – Not Acceptable Here语音质量和思…

chatgpt赋能Python-python3_9怎么安装

Python 3.9&#xff1a;安装指南 如果你正在学习编程或者已经是一名程序员&#xff0c;那么一定会了解到Python这个编程语言。Python是一种高级编程语言&#xff0c;其强大的设计特点和易于操作的特性使其成为了开发人员的首选。Python 3.9已经发布了&#xff0c;它虽然不是Py…

CSDN官方创作助手InsCode AI 教你分分钟搞定一篇好文章

CSDN官方推出创作助手InsCode AI很多天了&#xff0c;有心人都能发现&#xff0c;在写作界面的右上角多了一个创作助手的浮动按钮&#xff0c;点击后出现如下界面&#xff1a; 现阶段是“限免”状态&#xff0c;不好好利用它来创作&#xff0c;就有点辜负CSDN官方大佬们的良苦用…

【王道·计算机网络】第五章 传输层【未完】

一、传输层概述 传输层为应用层提供通信服务&#xff0c;使用网络层服务传输层的功能&#xff1a; 提供进程和进程之间的逻辑通信&#xff08;网络层提供主机之间的逻辑通信&#xff09;复用&#xff08;发送发不同的应用进程&#xff09;和分用&#xff08;接收方正确的数据传…

Primer C++(第三章)

补码、原码和反码 正数的原码、反码、补码都相同 负数的补码&#xff1a;1、正数的原码符号位由0变1 &#xff08;负数的原码&#xff09; 2、对负数的原码除符号位外每位取反 &#xff08;负数的反码&#xff09; 3、对负数的反码末尾1 &#xff08;负数的补码&#xff09; …

众位力量汇集《永恒之塔私服》新版龙战前传

盛大游戏《永恒之塔》从万众翘首企盼中登陆国服到现在&#xff0c;已经过去了一年有余。在前不久前更新的周年庆版本“云上的召唤”中&#xff0c;精灵星的宝宝终于可以和精灵星一起翱翔在天际了…… “云上的召唤”我们还没有体验够&#xff0c;全新版本“龙战前传”已然于7月…

【观察】从业界首款“空间穿越屏”,看华为全屋智能的进化与重构

这个时代&#xff0c;“家”的构成不再是简单的一家三口&#xff0c;客厅、厨房、卧室也不再只是承担某个单一功能或场景的空间。 无数身在异乡打拼的青年&#xff0c;开始向往一个专属的独立空间&#xff1b;那些奔波劳碌的中年夫妻&#xff0c;在为家人创造更好生活环境的同时…

RabbitMQ_面试题01

文章目录 1.RabbitMQ如何防止消息堆积2.RabbitMQ如何保证消息顺序消费3.RabbitMQ如何防止消息重复消费4.RabbitMQ如何保证消息可靠性4.1 消息持久化4.2 生产者确认2.2.1 application.yml2.2.2 Config2.2.3 Test 4.3 消费者确认4.3.1 application.yml4.3.2 Test 1.RabbitMQ如何防…

OPT (奥普特)锂电池视觉检测技术精彩亮相CIBF

5月16&#xff5e;18日&#xff0c;第十五届中国国际电池技术展览会在深圳举办&#xff0c;全球2500多家优秀电池企业参展。 OPT&#xff08;奥普特&#xff09;作为锂电行业机器视觉核心供应商&#xff0c;携3D、深度学习、分频技术等视觉检测技术亮相&#xff0c;并展示了上…

chatgpt赋能Python-python3免费吗

Python3免费吗&#xff1f; Python3到底免费还是收费呢&#xff1f;这是一个被许多人关注和疑惑的问题。本文将从不同方面解答这个问题&#xff0c;希望能给你提供一个清晰的认识。 什么是Python3&#xff1f; Python3是一种通用、高级、解释型的编程语言。它是由Guido van …

【Linux初阶】fork进程创建 进程终止 进程等待

&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f; &#x1f36d;&#x1f36d;系列专栏&#xff1a;【Linux初阶】 ✒️✒️本篇内容&#xff1a;fork进程创建&#xff0c;理解fork返回值和常规用法&#xff0c;进程终止&#xff08;退出码、退出场景、退出…

第08章_聚合函数

第08章_聚合函数 我们上一章讲到了 SQL 单行函数。实际上 SQL 函数还有一类&#xff0c;叫做聚合&#xff08;或聚集、分组&#xff09;函数&#xff0c;它是对一组数据进行汇总的函数&#xff0c;输入的是一组数据的集合&#xff0c;输出的是单个值。 1. 聚合函数介绍 什么是…

【sentinel】Sentinel工作主流程以流控规则源码分析

Sentinel工作主流程 在Sentinel里面&#xff0c;所有的资源都对应一个资源名称&#xff08;resourceName&#xff09;&#xff0c;每次资源调用都会创建一个Entry对象。Entry可以通过对主流框架的适配自动创建&#xff0c;也可以通过注解的方式或调用SphU API显式创建。Entry创…