跳到主要内容

55 篇博文 含有标签「哈希表」

查看所有标签

· 阅读需 2 分钟

1、题干

给你一个下标从 0 开始的整数数组 arr 和一个 m x n 的整数 矩阵 matarrmat 都包含范围 [1,m * n] 内的 所有 整数。

从下标 0 开始遍历 arr 中的每个下标 i ,并将包含整数 arr[i]mat 单元格涂色。

请你找出 arr 中第一个使得 mat 的某一行或某一列都被涂色的元素,并返回其下标 i

 

示例 1:

image explanation for example 1
输入:arr = [1,3,4,2], mat = [[1,4],[2,3]]
输出:2
解释:遍历如上图所示,arr[2] 在矩阵中的第一行或第二列上都被涂色。

示例 2:

image explanation for example 2
输入:arr = [2,8,7,4,1,3,5,6,9], mat = [[3,2,5],[1,4,6],[8,7,9]]
输出:3
解释:遍历如上图所示,arr[3] 在矩阵中的第二列上都被涂色。

 

提示:

  • m == mat.length
  • n = mat[i].length
  • arr.length == m * n
  • 1 <= m, n <= 105
  • 1 <= m * n <= 105
  • 1 <= arr[i], mat[r][c] <= m * n
  • arr 中的所有整数 互不相同
  • mat 中的所有整数 互不相同

2、思路

模拟题,对行列涂色计数即可

3、代码

function firstCompleteIndex(arr: number[], mat: number[][]): number {
const map = new Array(arr.length + 1);
for (let i = 0; i < mat.length; i++) {
for (let j = 0; j < mat[i].length; j++) {
map[mat[i][j]] = [i, j];
}
}

const rows = new Array(mat.length).fill(0), cols = new Array(mat[0].length).fill(0);
for (let i = 0; i < arr.length; i++) {
const [r, c] = map[arr[i]];
rows[r]++, cols[c]++;
if (rows[r] === mat[0].length || cols[c] === mat.length) return i;
}

return -1;
};

· 阅读需 3 分钟

1、题干

如果可以使用以下操作从一个字符串得到另一个字符串,则认为两个字符串 接近

  • 操作 1:交换任意两个 现有 字符。
    • 例如,abcde -> aecdb
  • 操作 2:将一个 现有 字符的每次出现转换为另一个 现有 字符,并对另一个字符执行相同的操作。
    • 例如,aacabb -> bbcbaa(所有 a 转化为 b ,而所有的 b 转换为 a

你可以根据需要对任意一个字符串多次使用这两种操作。

给你两个字符串,word1word2 。如果 word1 word2 接近 ,就返回 true ;否则,返回 false

 

示例 1:

输入:word1 = "abc", word2 = "bca"
输出:true
解释:2 次操作从 word1 获得 word2 。
执行操作 1:"abc" -> "acb"
执行操作 1:"acb" -> "bca"

示例 2:

输入:word1 = "a", word2 = "aa"
输出:false
解释:不管执行多少次操作,都无法从 word1 得到 word2 ,反之亦然。

示例 3:

输入:word1 = "cabbba", word2 = "abbccc"
输出:true
解释:3 次操作从 word1 获得 word2 。
执行操作 1:"cabbba" -> "caabbb"
执行操作 2:"caabbb" -> "baaccc"
执行操作 2:"baaccc" -> "abbccc"

示例 4:

输入:word1 = "cabbba", word2 = "aabbss"
输出:false
解释:不管执行多少次操作,都无法从 word1 得到 word2 ,反之亦然。

 

提示:

  • 1 <= word1.length, word2.length <= 105
  • word1word2 仅包含小写英文字母

2、思路

需要校验两个关键点:

  • 两个字符串包含的字母相同
  • 两个字符串分别按字母计数,计数结果排序后相同

3、代码

function closeStrings(word1: string, word2: string): boolean {
if (word1.length !== word2.length) return false;

const K = 26, AC = 'a'.charCodeAt(0);
const c1 = new Array(K).fill(0), c2 = new Array(K).fill(0);

for (let i = 0; i < word1.length; i++) {
c1[word1.charCodeAt(i) - AC] += 1;
c2[word2.charCodeAt(i) - AC] += 1;
}

return c1.every((c, i) => +!c === +!c2[i]) && c1.sort().toString() === c2.sort().toString();
};

· 阅读需 3 分钟

1、题干

现有一个包含所有正整数的集合 [1, 2, 3, 4, 5, ...]

实现 SmallestInfiniteSet 类:

  • SmallestInfiniteSet() 初始化 SmallestInfiniteSet 对象以包含 所有 正整数。
  • int popSmallest() 移除 并返回该无限集中的最小整数。
  • void addBack(int num) 如果正整数 num 存在于无限集中,则将一个 num 添加 到该无限集最后。

 

示例:

输入
["SmallestInfiniteSet", "addBack", "popSmallest", "popSmallest", "popSmallest", "addBack", "popSmallest", "popSmallest", "popSmallest"]
[[], [2], [], [], [], [1], [], [], []]
输出
[null, null, 1, 2, 3, null, 1, 4, 5]

解释
SmallestInfiniteSet smallestInfiniteSet = new SmallestInfiniteSet();
smallestInfiniteSet.addBack(2); // 2 已经在集合中,所以不做任何变更。
smallestInfiniteSet.popSmallest(); // 返回 1 ,因为 1 是最小的整数,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 2 ,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 3 ,并将其从集合中移除。
smallestInfiniteSet.addBack(1); // 将 1 添加到该集合中。
smallestInfiniteSet.popSmallest(); // 返回 1 ,因为 1 在上一步中被添加到集合中,
// 且 1 是最小的整数,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 4 ,并将其从集合中移除。
smallestInfiniteSet.popSmallest(); // 返回 5 ,并将其从集合中移除。

 

提示:

  • 1 <= num <= 1000
  • 最多调用 popSmallestaddBack 方法 共计 1000

2、思路

两个思路:

  • 优先队列:直接用优先队列存取数据即可,但要注意数字不能重复,总体最差时间复杂度 O(nlogn)O(n\log{n})
  • 暴力:由于数据范围比较小,可以使用计数排序的思路存取数据进行暴力求解,总体最差时间复杂度 O(n2)O(n^2)

3、代码

优先队列

const N = 1001;
class SmallestInfiniteSet {
nums = new Array(N).fill(1);
minQueue = new MinPriorityQueue();
constructor() {
for (let i = 1; i < N; i++) {
this.minQueue.enqueue(i);
}
}

popSmallest(): number {
const n = this.minQueue.dequeue().element;
this.nums[n] = 0;
return n;
}

addBack(num: number): void {
if (this.nums[num]) return;
this.nums[num] = 1;
this.minQueue.enqueue(num);
}
}

暴力

class SmallestInfiniteSet {
nums: number[] = [];
constructor() {
this.nums = new Array(1001).fill(1);
}

popSmallest(): number {
const k = this.nums.findIndex((n, i) => i > 0 && n > 0);
this.nums[k] = 0;
return k;
}

addBack(num: number): void {
this.nums[num] = 1;
}
}

· 阅读需 2 分钟

1、题干

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

 

示例 1:

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例 2:

输入:inorder = [-1], postorder = [-1]
输出:[-1]

 

提示:

  • 1 <= inorder.length <= 3000
  • postorder.length == inorder.length
  • -3000 <= inorder[i], postorder[i] <= 3000
  • inorder 和 postorder 都由 不同 的值组成
  • postorder 中每一个值都在 inorder 中
  • inorder 保证是树的中序遍历
  • postorder 保证是树的后序遍历

2、思路

后序遍历数组最后一个元素是根节点,中序遍历数组中根节点前、后两边分别是左子树与右子树,根据这两个性质递归即可,时间复杂度最优 O(nlogn)O(n\log{n}),最差 O(n2)O(n^2)

3、代码

function buildTree(inorder: number[], postorder: number[]): TreeNode | null {
if (!inorder.length) return null;

const rv = postorder.at(-1);
const root = new TreeNode(rv);

const c = inorder.indexOf(rv);

const inLeft = inorder.slice(0, c);
const inRight = inorder.slice(c + 1);

const postLeft = postorder.slice(0, c);
const postRight = postorder.slice(c, postorder.length - 1);

root.left = buildTree(inLeft, postLeft);
root.right = buildTree(inRight, postRight);

return root;
}

· 阅读需 3 分钟

1、题干

给你一个下标从 0 开始的正整数数组 nums 。请你找出并统计满足下述条件的三元组 (i, j, k) 的数目:

  • 0 <= i < j < k < nums.length
  • nums[i]nums[j]nums[k] 两两不同
    • 换句话说:nums[i] != nums[j]nums[i] != nums[k]nums[j] != nums[k]

返回满足上述条件三元组的数目

 

示例 1:

输入:nums = [4,4,2,4,3]
输出:3
解释:下面列出的三元组均满足题目条件:
- (0, 2, 4) 因为 4 != 2 != 3
- (1, 2, 4) 因为 4 != 2 != 3
- (2, 3, 4) 因为 2 != 4 != 3
共计 3 个三元组,返回 3 。
注意 (2, 0, 4) 不是有效的三元组,因为 2 > 0 。

示例 2:

输入:nums = [1,1,1,1,1]
输出:0
解释:不存在满足条件的三元组,所以返回 0 。

 

提示:

  • 3 <= nums.length <= 100
  • 1 <= nums[i] <= 1000

2、思路

  • 枚举:3重循环枚举所有不等三元组
  • 哈希表:对数组所有元素计数,枚举累计所有不等三元组

3、代码

use std::collections::HashMap;

impl Solution {
pub fn unequal_triplets(nums: Vec<i32>) -> i32 {
let len = nums.len();
let mut map = HashMap::new();
for n in nums {
let c = map.entry(n).or_insert(0);
*c += 1;
}

let mut ans = 0;
let mut pc = 0;
for (k, c) in map {
ans += pc * c * (len - pc - c);
pc += c;
}

return ans as i32;
}
}
function unequalTriplets(nums: number[]): number {
const map = new Map<number, number>();
for (let i = 0; i < nums.length; i++) {
map.set(nums[i], (map.get(nums[i]) || 0) + 1);
}

let ans = 0, pc = 0;

for (const [_, c] of map) {
ans += pc * c * (nums.length - pc - c);
pc += c;
}

return ans;
};
function unequalTriplets(nums: number[]): number {
let ans = 0;

for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
for (let k = j + 1; k < nums.length; k++) {
if (nums[i] !== nums[j] && nums[i] !== nums[k] && nums[j] !== nums[k]) ans += 1;
}
}
}

return ans;
};

· 阅读需 4 分钟

1、题干

总计有 n 个环,环的颜色可以是红、绿、蓝中的一种。这些环分别穿在 10 根编号为 09 的杆上。

给你一个长度为 2n 的字符串 rings ,表示这 n 个环在杆上的分布。rings 中每两个字符形成一个 颜色位置对 ,用于描述每个环:

  • i 对中的 第一个 字符表示第 i 个环的 颜色'R''G''B')。
  • i 对中的 第二个 字符表示第 i 个环的 位置,也就是位于哪根杆上('0''9')。

例如,"R3G2B1" 表示:共有 n == 3 个环,红色的环在编号为 3 的杆上,绿色的环在编号为 2 的杆上,蓝色的环在编号为 1 的杆上。

找出所有集齐 全部三种颜色 环的杆,并返回这种杆的数量。

 

示例 1:

输入:rings = "B0B6G0R6R0R6G9"
输出:1
解释:
- 编号 0 的杆上有 3 个环,集齐全部颜色:红、绿、蓝。
- 编号 6 的杆上有 3 个环,但只有红、蓝两种颜色。
- 编号 9 的杆上只有 1 个绿色环。
因此,集齐全部三种颜色环的杆的数目为 1 。

示例 2:

输入:rings = "B0R0G0R9R0B0G0"
输出:1
解释:
- 编号 0 的杆上有 6 个环,集齐全部颜色:红、绿、蓝。
- 编号 9 的杆上只有 1 个红色环。
因此,集齐全部三种颜色环的杆的数目为 1 。

示例 3:

输入:rings = "G4"
输出:0
解释:
只给了一个环,因此,不存在集齐全部三种颜色环的杆。

 

提示:

  • rings.length == 2 * n
  • 1 <= n <= 100
  • i偶数 ,则 rings[i] 的值可以取 'R''G''B'(下标从 0 开始计数)
  • i奇数 ,则 rings[i] 的值可以取 '0''9' 中的一个数字(下标从 0 开始计数)

2、思路

模拟+位运算,遍历 rings 检查所有杆和环,位运算记录是否3环状态,返回3环状态总数。

Rust 真是又难写又难调试

3、代码

impl Solution {
pub fn count_points(rings: String) -> i32 {
let mut bits:[i32; 10] = [0; 10];
let mut ans:i32 = 0;

for i in 0..rings.len() {
if i % 2 == 1 {
continue;
}

let color:char = (&rings[i..=i]).parse().unwrap();
let j:usize = (&rings[i + 1..=i + 1]).parse().unwrap();

if bits[j] == 7 {
continue;
}

let b = if color == 'R' {
1
} else if color == 'G' {
2
} else {
4
};

bits[j] = bits[j] | b;
if bits[j] == 7 {
ans += 1;
}
}

return ans;
}
}
function countPoints(rings: string): number {
const map = { R: 1, G: 2, B: 4 };
const bits = new Array(10).fill(0);

let ans = 0;
for (let i = 0; i < rings.length; i += 2) {
const j = rings[i + 1];
if (bits[j] === 7) continue;

bits[j] |= map[rings[i]];
if (bits[j] === 7) ans++;
}

return ans;
};

· 阅读需 3 分钟

1、题干

给你一个下标从 0 开始的整数数组 nums 和一个整数 value

在一步操作中,你可以对 nums 中的任一元素加上或减去 value

  • 例如,如果 nums = [1,2,3]value = 2 ,你可以选择 nums[0] 减去 value ,得到 nums = [-1,2,3]

数组的 MEX (minimum excluded) 是指其中数组中缺失的最小非负整数。

  • 例如,[-1,2,3] 的 MEX 是 0 ,而 [1,0,3] 的 MEX 是 2

返回在执行上述操作 任意次 后,nums 的最大 MEX

 

示例 1:

输入:nums = [1,-10,7,13,6,8], value = 5
输出:4
解释:执行下述操作可以得到这一结果:
- nums[1] 加上 value 两次,nums = [1,0,7,13,6,8]
- nums[2] 减去 value 一次,nums = [1,0,2,13,6,8]
- nums[3] 减去 value 两次,nums = [1,0,2,3,6,8]
nums 的 MEX 是 4 。可以证明 4 是可以取到的最大 MEX 。

示例 2:

输入:nums = [1,-10,7,13,6,8], value = 7
输出:2
解释:执行下述操作可以得到这一结果:
- nums[2] 减去 value 一次,nums = [1,-10,0,13,6,8]
nums 的 MEX 是 2 。可以证明 2 是可以取到的最大 MEX 。

 

提示:

  • 1 <= nums.length, value <= 105
  • -109 <= nums[i] <= 109

2、思路

  • nums 所有元素与 value 取模计数,得到数组 ms
  • 遍历区间 [0,nums.length),消费 ms,直到遍历结束 或 ms 中某个余数被消耗完(ms[i % value] < 1) ,此时的下标 i 即所求结果

3、代码

function findSmallestInteger(nums: number[], value: number): number {
const ms = new Array(value).fill(0);
for (const n of nums) {
const mod = n % value + (n % value < 0 ? value : 0);
ms[mod]++;
}

let ans = 0;
for (let i = 0; i < nums.length; i++, ans++) {
const mod = i % value;
if (ms[mod] < 1) break;
ms[mod]--;
}

return ans;
};

4、复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)

5、执行结果

image.png

· 阅读需 4 分钟

1、题干

给你一个长度为 n 的数组 nums ,该数组由从 1n不同 整数组成。另给你一个正整数 k

统计并返回 nums 中的 中位数 等于 k 的非空子数组的数目。

注意:

  • 数组的中位数是按 递增 顺序排列后位于 中间 的那个元素,如果数组长度为偶数,则中位数是位于中间靠 的那个元素。
    • 例如,[2,3,1,4] 的中位数是 2[8,4,3,5,1] 的中位数是 4
  • 子数组是数组中的一个连续部分。

 

示例 1:

输入:nums = [3,2,1,4,5], k = 4
输出:3
解释:中位数等于 4 的子数组有:[4]、[4,5] 和 [1,4,5] 。

示例 2:

输入:nums = [2,3,1], k = 3
输出:1
解释:[3] 是唯一一个中位数等于 3 的子数组。

 

提示:

  • n == nums.length
  • 1 <= n <= 105
  • 1 <= nums[i], k <= n
  • nums 中的整数互不相同

整得有点复杂,错了几次,裂开

2、思路

对于任意包含 k 的子数组符合要求的条件是:假设小于 k 的数有 cl个,大于 k 的数有 cr个,只要满足 cl === cr 或者 cl + 1 === cr 即可

k 的下标 ki 为界限,考虑三类子数组情况:

  • 左半边子数组符合要求
  • 右半边子数组符合要求
  • 左右两边子数组组合后符合要求

前两类情况在 O(n)O(n) 时间复杂度内可以出结果,第三类情况如果暴力求解,时间复杂度会达到O(n2)O(n^2),用哈希表优化可以做到 O(n)O(n)

3、代码

function countSubarrays(nums: number[], k: number): number {
const ki = nums.indexOf(k), om = new Map(), em = new Map();
let ans = 0, sum = 0;
for (let i = ki - 1; i > -1; i--) {
sum += nums[i] < k ? -1 : 1;
if (!sum || sum === 1) ans++;

const map = i % 2 ? om : em;
map.set(sum, (map.get(sum) || 0) + 1);
}

sum = 0;
for (let i = ki + 1; i < nums.length; i++) {
sum += nums[i] < k ? -1 : 1;
if (!sum || sum === 1) ans++;

let map = i % 2 ? om : em;
if (map.get(-sum)) ans += map.get(-sum);

map = i % 2 ? em : om;
if (map.get(1 - sum)) ans += map.get(1 - sum);
}

return ans + 1;
};

4、复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)

5、执行结果

image.png

· 阅读需 2 分钟

1、题干

给定一个放有字母和数字的数组,找到最长的子数组,且包含的字母和数字的个数相同。

返回该子数组,若存在多个最长子数组,返回左端点下标值最小的子数组。若不存在这样的数组,返回一个空数组。

示例 1:

输入: ["A","1","B","C","D","2","3","4","E","5","F","G","6","7","H","I","J","K","L","M"]

输出: ["A","1","B","C","D","2","3","4","E","5","F","G","6","7"]

示例 2:

输入: ["A","A"]

输出: []

提示:

  • array.length <= 100000

2、思路

  • 遍历数组 array 并计算前缀和 sum,遇到字母 sum+1 否则 sum-1
  • 如果当前 sum 的值从未出现过,将 sum 与下标 i 分别作为键值存入哈希表 map
  • 如果当前 sum 的值已出现过,则 [map.get(sum)+1,i+1) 区间的子数组作为备选答案
  • 最后取左下标最小的最长子数组

3、代码

function findLongestSubarray(array: string[]): string[] {
let sum = 0, l = 0, r = 0;
const map = new Map([[0, -1]]);

for (let i = 0; i < array.length; i++) {
sum += (/[a-z]/i.test(array[i]) ? 1 : -1);
const j = map.get(sum);

if (j === undefined) map.set(sum, i);
else if (i - j > r - l) r = i, l = j;
}

return array.slice(l + 1, r + 1);
};

4、复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)

5、执行结果

image.png

· 阅读需 4 分钟

1、题干

给你一个正整数数组 nums,请你移除 最短 子数组(可以为 ),使得剩余元素的  能被 p 整除。 不允许 将整个数组都移除。

请你返回你需要移除的最短子数组的长度,如果无法满足题目要求,返回 -1 。

子数组 定义为原数组中连续的一组元素。

 

示例 1:

输入:nums = [3,1,4,2], p = 6
输出:1
解释:nums 中元素和为 10,不能被 p 整除。我们可以移除子数组 [4] ,剩余元素的和为 6 。

示例 2:

输入:nums = [6,3,5,2], p = 9
输出:2
解释:我们无法移除任何一个元素使得和被 9 整除,最优方案是移除子数组 [5,2] ,剩余元素为 [6,3],和为 9 。

示例 3:

输入:nums = [1,2,3], p = 3
输出:0
解释:和恰好为 6 ,已经能被 3 整除了。所以我们不需要移除任何元素。

示例  4:

输入:nums = [1,2,3], p = 7
输出:-1
解释:没有任何方案使得移除子数组后剩余元素的和被 7 整除。

示例 5:

输入:nums = [1000000000,1000000000,1000000000], p = 3
输出:0

 

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109
  • 1 <= p <= 109

前缀和,我是真的前了
哈希表,我是真的哈了
模运算,我模xxxxxxxxx了

2、思路

先求 nums 前缀和数组 sums,数组总和 sum,总和与 p 的余数 mod,然后讨论几种情况:

  • 无需剔除:如果 mod0,返回 0
  • 剔除1个:如果剔除 nums 中某个数能整除,返回 1
  • 剔除尾部:如果 sums[i] 能整除 pnums.length - i - 1 作为备选答案
  • 剔除头部:如果 sums[i] % p === modi+1 作为备选答案
  • 剔除中间:如果存在下标 ijsums[j] % p ===(sums[i] % p + p - mod) % pi-j 作为备选答案

真是不知道写了个什么妖魔鬼怪

3、代码

function minSubarray(nums: number[], p: number): number {
const sums = [...nums];
for (let i = 1; i < sums.length; i++) {
sums[i] += sums[i - 1];
}

const sum = sums.at(-1), mod = sum % p;
if (!mod) return 0;

let ans = nums.length;
const map = new Map();
for (let i = 0; i < sums.length; i++) {
if ((sum - nums[i]) % p === 0) return 1;

const m = sums[i] % p;
if (m === 0) ans = Math.min(nums.length - i - 1, ans);
else if (m === mod) ans = Math.min(i + 1, ans);
else {
const sm = (m + p - mod) % p;
if (map.has(sm)) ans = Math.min(i - map.get(sm), ans);
}

map.set(m, i);
}

return ans === nums.length ? -1 : ans;
}

4、复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)

5、执行结果

image.png