LeetCode题解哈希表篇(TS版)

242.有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

**注意:**若 st 中每个字符出现的次数都相同,则称 st 互为字母异位词。

示例 1: 输入: s = “anagram”, t = “nagaram” 输出: true

示例 2: 输入: s = “rat”, t = “car” 输出: false

说明: 你可以假设字符串只包含小写字母。

哈希表

时间复杂度:O(n),其中n为s的长度

空间复杂度:O(S),其中S为字符集大小

iShot_2023-05-11_16.14.42

function isAnagram(s: string, t: string): boolean {
    if (s.length !== t.length) return false;
    const map = new Map<string, number>();
    for (let i = 0; i < s.length; i++) {
        map.set(s[i], (map.get(s[i]) || 0) + 1);
    }
    for (let i = 0; i < t.length; i++) {
        map.set(t[i], (map.get(t[i]) || 0) - 1);
        if (map.get(t[i]) < 0) return false;
    }
    return true
};

1002.查找共用字符

给你一个字符串数组 words ,请你找出所有在 words 的每个字符串中都出现的共用字符( 包括重复字符),并以数组形式返回。你可以按 任意顺序 返回答案。

示例 1:

输入:words = [“bella”,“label”,“roller”] 输出:[“e”,“l”,“l”] 示例 2:

输入:words = [“cool”,“lock”,“cook”] 输出:[“c”,“o”]

提示:

1 <= words.length <= 100 1 <= words[i].length <= 100 words[i] 由小写英文字母组成

哈希表

  • 时间复杂度: O ( n ( m + ∣ ∑ ∣ ) O(n(m+|∑|) O(n(m+),其中 n n n 是数组 A 的长度(即字符串的数目), m m m 是字符串的平均长度, ∑ ∑ 为字符集,在本题中字符集为所有小写字母, ∑ ∑ =26。
  • 空间复杂度: O ( ∣ ∑ ∣ ) O(|∑|) O()
1002.查找常用字符.png
class Solution {
    public List<String> commonChars(String[] words) {
        List<String> result = new ArrayList<String>();
        if (words.length == 0) return result;
        int[] hash = new int[26];
        for (int i = 0; i < words[0].length(); i++) {
            hash[words[0].charAt(i) - 'a']++;
        }
        for (int i = 1; i < words.length; i++) {
            int[] hashOtherStr = new int[26];
            for (int j = 0; j < words[i].length(); j++) {
                hashOtherStr[words[i].charAt(j) - 'a']++;
            }
            for (int k = 0; k < 26; k++) {
                hash[k] = Math.min(hash[k], hashOtherStr[k]);
            }
        }

        for (int i = 0; i < 26; i++) {
            while (hash[i] != 0) {
                char c = (char) (i + 'a');
                result.add(String.valueOf(c));
                hash[i]--;
            }
        }
        return result;
    }
}

349.两个数组的交集

给定两个数组 nums1nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

两个集合

使用两个集合分别存储两个数组中的元素,然后遍历较小的集合,判断其中的每个元素是否在另一个集合中,如果元素也在另一个集合中,则将该元素添加到返回值。

  • 时间复杂度: O ( m + n ) O(m+n) O(m+n),其中 m m m n n n 分别是两个数组的长度。使用两个集合分别存储两个数组中的元素需要 O ( m + n ) O(m+n) O(m+n) 的时间,遍历较小的集合并判断元素是否在另一个集合中需要 O ( m i n ( m , n ) ) O(min(m,n)) O(min(m,n)) 的时间,因此总时间复杂度是 O ( m + n ) O(m+n) O(m+n)
  • 空间复杂度: O ( m + n ) O(m+n) O(m+n),其中 m m m n n n 分别是两个数组的长度。空间复杂度主要取决于两个集合。
function intersection(nums1: number[], nums2: number[]): number[] {
    let set1 = new Set(nums1);
    let set2 = new Set(nums2);
    const res = new Set<number>();

    if (set1.size > set2.size) {
        [set1, set2] = [set2, set1];
    }

    for (let num of set1) {
        if (set2.has(num)) {
            res.add(num);
        }
    }

    return [...res];;
};

排序+双指针

思路:数组排序,然后用两个指针分别遍历数组,如果两个指针指向的元素相等就是其中一个交集,否则比较两个指针指向的元素的大小,较小的向前移动。

  • 时间复杂度: O ( m l o g m + n l o g n ) O(mlogm+n logn) O(mlogm+nlogn),其中m和n分别是两个数组的长度。对两个数组排序的时间复杂度分别是 O ( m l o g m ) O(mlog m) O(mlogm) O ( n l o g n ) O(nlogn) O(nlogn),双指针寻找交集元素的时间复杂度是 O ( m + n ) O(m+n) O(m+n),因此总时间复杂度是 O ( m l o g m + n l o g n ) O(mlog m + nlog n) O(mlogm+nlogn)
  • 空间复杂度: O ( l o g m + l o g n ) O(logm + logn) O(logm+logn),其中 m m m n n n 分别是两个数组的长度。空间复杂度主要取决于排序使用的额外空间。
iShot_2023-05-17_14.51.41
function intersection(nums1: number[], nums2: number[]): number[] {
    nums1.sort((x, y) => x - y);
    nums2.sort((x, y) => x - y);
    let index1 = 0;
    let index2 = 0;
    const intersection = new Set<number>();
    while (index1 < nums1.length && index2 < nums2.length) {
        if (nums1[index1] === nums2[index2]) {
            intersection.add(nums1[index1]);
            index1++;
            index2++;
        } else if (nums1[index1] > nums2[index2]) {
            index2++;
        } else {
            index1++;
        }
    }
    return [...intersection];
};

202.快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

image-20230821171343351

提示:

  • 1 <= n <= 231 - 1

哈希表

思路:通过持续计算数字的平方和并将结果添加到集合中,如果最终得到1(快乐数),则结束;如果产生的数字已经在集合中(表示陷入循环且循环中没有1),则可以确定该数不是快乐数。

  • 时间复杂度: O ( l o g n ) O(log n) O(logn)
  • 空间复杂度: O ( l o g n ) O(log n) O(logn)
function getNext(n: number) {
    let totalSum = 0;
    while (n > 0) {
        let d = n % 10;
        n = Math.floor(n / 10);
        totalSum += d * d;
    }
    return totalSum;
}

function isHappy(n: number): boolean {
    const set = new Set<Number>();
    let sum = n;
    while (sum !== 1) {
        let newSum = getNext(sum);
        if (set.has(newSum)) return false;
        set.add(newSum);
        sum = newSum;
    }
    return true;
};

【推荐】快慢指针

思路:通过快慢两个指针(一个一次计算一步,另一个一次计算两步)遍历数字的平方和序列,如果这个序列能够到达1(快乐数),则快指针将首先到达1;如果序列陷入循环且循环中没有1,那么快慢指针最终将相遇,此时可以确定该数不是快乐数。

  • 时间复杂度: O ( l o g n ) O(log n) O(logn)
  • 空间复杂度: O ( 1 ) O(1) O(1)
iShot_2023-05-17_17.23.16
function getNext(n: number) {
    let totalSum = 0;
    while (n > 0) {
        let d = n % 10;
        n = Math.floor(n / 10);
        totalSum += d * d;
    }
    return totalSum;
}

function isHappy(n: number): boolean {
    let slow = n;
    let fast = getNext(n);
    while (fast !== 1) {
        if (slow === fast) return false;
        slow = getNext(slow);
        fast = getNext(getNext(fast));
    }
    return true;
};

1.两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

哈希表

  • 时间复杂度: O ( N ) O(N) O(N),其中 N N N 是数组中的元素数量。对于每一个元素 x,我们可以 O ( 1 ) O(1) O(1) 地寻找 target - x
  • 空间复杂度: O ( N ) O(N) O(N),其中 N N N 是数组中的元素数量。主要为哈希表的开销。
iShot_2023-05-18_09.43.57
function twoSum(nums: number[], target: number): number[] {
    const map = new Map<number, number>();
    for (let i = 0; i < nums.length; i++) {
        const index = map.get(target - nums[i])
        if (index !== undefined) {
            return [index, i];
        }
        map.set(nums[i], i);
    }
    return [];
};

454.四数相加Ⅱ

给你四个整数数组 nums1nums2nums3nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例 2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

提示:

  • n == nums1.length
  • n == nums2.length
  • n == nums3.length
  • n == nums4.length
  • 1 <= n <= 200
  • -228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228

哈希表

  1. 先计算列表A和B中所有可能的两数之和,并在一个哈希表中记录每种两数之和出现的次数。对于每一对(A[i], B[j]),将A[i] + B[j]加入到哈希表,并更新哈希表中对应的计数。
  2. 然后,对于列表C和D中的每一对(C[k], D[l]),计算-(C[k] + D[l]),看它是否在哈希表中。如果在,那么就找到了一种组合,使得A[i] + B[j] + C[k] + D[l]等于0。
  3. 将哈希表中与-(C[k] + D[l])相对应的值加到结果中,因为这个值表示存在多少种可能的(A[i], B[j])组合,使得A[i] + B[j]的和等于-(C[k] + D[l]),也就是说,存在多少种可能的四元组使得他们的和等于0。

这个方法将原问题从四个列表的四重循环简化为了两个两重循环,大大减少了计算的复杂性。使用哈希表记录两数之和的频率,也提高了查找的效率。这种方法的时间复杂度是 O ( n 2 ) O(n^2) O(n2),空间复杂度也是 O ( n 2 ) O(n^2) O(n2),其中n是列表A, B, C, D的长度。

function fourSumCount(nums1: number[], nums2: number[], nums3: number[], nums4: number[]): number {
    const map = new Map<number, number>();

    for (let num1 of nums1) {
        for (let num2 of nums2) {
            const sum = num1 + num2
            map.set(sum, map.has(sum) ? map.get(sum) + 1 : 1);
        }
    }

    let ans = 0
    for (let num3 of nums3) {
        for (let num4 of nums4) {
            if (map.get(-num3 - num4) !== undefined) {
                ans += map.get(-num3 - num4);
            }
        }
    }

    return ans
};

383.赎金信

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

输入:ransomNote = "aa", magazine = "ab"
输出:false

示例 3:

输入:ransomNote = "aa", magazine = "aab"
输出:true

提示:

  • 1 <= ransomNote.length, magazine.length <= 105
  • ransomNotemagazine 由小写英文字母组成

哈希表

  • 时间复杂度: O ( m + n ) O(m+n) O(m+n),只需遍历两个字符一次即可。
  • 空间复杂度: O ( S ) O(S) O(S) S S S 是字符集,这道题中 S S S 为全部小写英语字母,因此 S = 26 S=26 S=26
function canConstruct(ransomNote: string, magazine: string): boolean {
    if (ransomNote.length > magazine.length) return false;
    const arr = Array.from(new Array(26), () => 0);
    for (let s of magazine) {
        const index = s.charCodeAt(0) - 'a'.charCodeAt(0);
        arr[index]++;
    }
    for (let s of ransomNote) {
        const index = s.charCodeAt(0) - 'a'.charCodeAt(0);
        if (arr[index] - 1 < 0) {
            return false
        }
        arr[index]--;
    }
    return true;
};

15.三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]

示例1

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例2

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例3

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

排序+双指针

  • 时间复杂度: O ( N 2 ) O(N^2) O(N2),其中 N N N 是数组的长度。
  • 空间复杂度: O ( l o g N ) O(log N) O(logN)
iShot_2023-05-21_16.44.47
function threeSum(nums: number[]): number[][] {
    if (nums.length < 3) return [];
    nums.sort((a, b) => a - b);
    const ans: number[][] = [];
    for (let i = 0; i < nums.length; i++) {
        if (nums[i] > 0) break;
        if (i > 0 && nums[i] === nums[i - 1]) continue;
        let L = i + 1;
        let R = nums.length - 1;
        while (L < R) {
            const sum = nums[i] + nums[L] + nums[R];
            if (sum < 0) {
                L++;
            } else if (sum > 0) {
                R--;
            } else {
                while (nums[L] === nums[L + 1]) L++; // 去重
                while (nums[R] === nums[R - 1]) R--; // 去重
                ans.push([nums[i], nums[L], nums[R]]);
                L++;
                R--;
            }
        }
    }
    return ans;
};

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] ]

排序+双指针

  • 时间复杂度: O ( n 3 ) O(n^3) O(n3),其中 n n n 是数组的长度。排序的时间复杂度是 O ( n l o g n ) O(nlog n) O(nlogn),枚举四元组的时间复杂度是 O ( n 3 ) O(n^3) O(n3),因此总时间复杂度为 O ( n 3 + n l o g n ) = O ( n 3 ) O(n^3 + nlog n) = O(n^3) O(n3+nlogn)=O(n3)
  • 空间复杂度: O ( l o g n ) O(logn) O(logn),其中 n n n 是数组的长度。空间复杂度主要取决于排序额外使用的空间。此外排序修改了输入数组 n u m s nums nums,实际情况中不一定允许,因此也可以看成使用了一个额外的数组存储了数组 n u m s nums nums 的副本并排序,空间复杂度为 O ( n ) O(n) O(n)

和三数之和解法一样。

function fourSum(nums: number[], target: number): number[][] {
    const quadruplets: number[][] = [];
    if (nums.length < 4) {
        return quadruplets;
    }
    nums.sort((x, y) => x - y);
    const length = nums.length;
    for (let i = 0; i < length - 3; i++) {
        if (i > 0 && nums[i] === nums[i - 1]) {
            continue;
        }
      	// 最小值大于target,退出循环
        if (nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
            break;
        }
        // 最大值小于target,跳过当前循环
        if (nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {
            continue;
        }
        for (let j = i + 1; j < length - 2; j++) {
            if (j > i + 1 && nums[j] === nums[j - 1]) {
                continue;
            }
          	// 最小值大于target,退出循环
            if (nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
                break;
            }
	          // 最大值小于target,跳过当前循环
            if (nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {
                continue;
            }
            let left = j + 1, right = length - 1;
            while (left < right) {
                const sum = nums[i] + nums[j] + nums[left] + nums[right];
                if (sum === target) {
                    quadruplets.push([nums[i], nums[j], nums[left], nums[right]]);
                    while (left < right && nums[left] === nums[left + 1]) {
                        left++;
                    }
                    left++;
                    while (left < right && nums[right] === nums[right - 1]) {
                        right--;
                    }
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    right--;
                }
            }
        }
    }
    return quadruplets;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值