文章目录
242.有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
**注意:**若
s
和t
中每个字符出现的次数都相同,则称s
和t
互为字母异位词。示例 1: 输入: s = “anagram”, t = “nagaram” 输出: true
示例 2: 输入: s = “rat”, t = “car” 输出: false
说明: 你可以假设字符串只包含小写字母。
哈希表
时间复杂度:O(n),其中n为s的长度
空间复杂度:O(S),其中S为字符集大小
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(∣∑∣)。
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.两个数组的交集
给定两个数组
nums1
和nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。示例 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 分别是两个数组的长度。空间复杂度主要取决于排序使用的额外空间。
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
。提示:
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)。
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 是数组中的元素数量。主要为哈希表的开销。
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.四数相加Ⅱ
给你四个整数数组
nums1
、nums2
、nums3
和nums4
,数组长度都是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
哈希表
- 先计算列表A和B中所有可能的两数之和,并在一个哈希表中记录每种两数之和出现的次数。对于每一对(A[i], B[j]),将A[i] + B[j]加入到哈希表,并更新哈希表中对应的计数。
- 然后,对于列表C和D中的每一对(C[k], D[l]),计算-(C[k] + D[l]),看它是否在哈希表中。如果在,那么就找到了一种组合,使得A[i] + B[j] + C[k] + D[l]等于0。
- 将哈希表中与-(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.赎金信
给你两个字符串:
ransomNote
和magazine
,判断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
ransomNote
和magazine
由小写英文字母组成
哈希表
- 时间复杂度: 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)
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;
};