跳到主要内容

131 篇博文 含有标签「数组」

查看所有标签

· 阅读需 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

· 阅读需 7 分钟

1、题干

你正在经营一座摩天轮,该摩天轮共有 4 个座舱 ,每个座舱 最多可以容纳 4 位游客 。你可以 逆时针 轮转座舱,但每次轮转都需要支付一定的运行成本 runningCost 。摩天轮每次轮转都恰好转动 1 / 4 周。

给你一个长度为 n 的数组 customerscustomers[i] 是在第 i 次轮转(下标从 0 开始)之前到达的新游客的数量。这也意味着你必须在新游客到来前轮转 i 次。每位游客在登上离地面最近的座舱前都会支付登舱成本 boardingCost ,一旦该座舱再次抵达地面,他们就会离开座舱结束游玩。

你可以随时停下摩天轮,即便是 在服务所有游客之前 。如果你决定停止运营摩天轮,为了保证所有游客安全着陆,将免费进行所有后续轮转 。注意,如果有超过 4 位游客在等摩天轮,那么只有 4 位游客可以登上摩天轮,其余的需要等待 下一次轮转

返回最大化利润所需执行的 最小轮转次数 。 如果不存在利润为正的方案,则返回 -1

 

示例 1:

输入:customers = [8,3], boardingCost = 5, runningCost = 6
输出:3
解释:座舱上标注的数字是该座舱的当前游客数。
1. 8 位游客抵达,4 位登舱,4 位等待下一舱,摩天轮轮转。当前利润为 4 * $5 - 1 * $6 = $14 。
2. 3 位游客抵达,4 位在等待的游客登舱,其他 3 位等待,摩天轮轮转。当前利润为 8 * $5 - 2 * $6 = $28 。
3. 最后 3 位游客登舱,摩天轮轮转。当前利润为 11 * $5 - 3 * $6 = $37 。
轮转 3 次得到最大利润,最大利润为 $37 。

示例 2:

输入:customers = [10,9,6], boardingCost = 6, runningCost = 4
输出:7
解释:
1. 10 位游客抵达,4 位登舱,6 位等待下一舱,摩天轮轮转。当前利润为 4 * $6 - 1 * $4 = $20 。
2. 9 位游客抵达,4 位登舱,11 位等待(2 位是先前就在等待的,9 位新加入等待的),摩天轮轮转。当前利润为 8 * $6 - 2 * $4 = $40 。
3. 最后 6 位游客抵达,4 位登舱,13 位等待,摩天轮轮转。当前利润为 12 * $6 - 3 * $4 = $60 。
4. 4 位登舱,9 位等待,摩天轮轮转。当前利润为 * $6 - 4 * $4 = $80 。
5. 4 位登舱,5 位等待,摩天轮轮转。当前利润为 20 * $6 - 5 * $4 = $100 。
6. 4 位登舱,1 位等待,摩天轮轮转。当前利润为 24 * $6 - 6 * $4 = $120 。
7. 1 位登舱,摩天轮轮转。当前利润为 25 * $6 - 7 * $4 = $122 。
轮转 7 次得到最大利润,最大利润为$122 。

示例 3:

输入:customers = [3,4,0,5,1], boardingCost = 1, runningCost = 92
输出:-1
解释:
1. 3 位游客抵达,3 位登舱,0 位等待,摩天轮轮转。当前利润为 3 * $1 - 1 * $92 = -$89 。
2. 4 位游客抵达,4 位登舱,0 位等待,摩天轮轮转。当前利润为 7 * $1 - 2 * $92 = -$177 。
3. 0 位游客抵达,0 位登舱,0 位等待,摩天轮轮转。当前利润为 7 * $1 - 3 * $92 = -$269 。
4. 5 位游客抵达,4 位登舱,1 位等待,摩天轮轮转。当前利润为 11 * $1 - 4 * $92 = -$357 。
5. 1 位游客抵达,2 位登舱,0 位等待,摩天轮轮转。当前利润为 13 * $1 - 5 * $92 = -$447 。
利润永不为正,所以返回 -1 。

 

提示:

  • n == customers.length
  • 1 <= n <= 105
  • 0 <= customers[i] <= 50
  • 1 <= boardingCost, runningCost <= 100

2、思路

可以用队列思路模拟,只要 customers 没有遍历完或者队列 queue 中仍有游客,就转动摩天轮并计算当前利润 p,如果当前利润比之前最大利润 mp 更大,则更新最大利润 mp 和最小次数 ans

3、代码

function minOperationsMaxProfit(customers: number[], boardingCost: number, runningCost: number): number {
let p = 0, mp = 0, ans = -1;

for (let i = 0, queue = 0; i < customers.length || queue > 0; i++) {
if (i < customers.length) queue += customers[i];

const k = queue < 4 ? queue : 4;
queue -= k;
p += (k * boardingCost - runningCost);

if (p <= mp) continue;

mp = p;
ans = i + 1;
}

return ans;
};

4、复杂度

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

5、执行结果

image.png

· 阅读需 4 分钟

1、题干

给你一个长度为 n 的字符串数组 names 。你将会在文件系统中创建 n 个文件夹:在第 i 分钟,新建名为 names[i] 的文件夹。

由于两个文件 不能 共享相同的文件名,因此如果新建文件夹使用的文件名已经被占用,系统会以 (k) 的形式为新文件夹的文件名添加后缀,其中 k 是能保证文件名唯一的 最小正整数

返回长度为 n 的字符串数组,其中 ans[i] 是创建第 i 个文件夹时系统分配给该文件夹的实际名称。

 

示例 1:

输入:names = ["pes","fifa","gta","pes(2019)"]
输出:["pes","fifa","gta","pes(2019)"]
解释:文件系统将会这样创建文件名:
"pes" --> 之前未分配,仍为 "pes"
"fifa" --> 之前未分配,仍为 "fifa"
"gta" --> 之前未分配,仍为 "gta"
"pes(2019)" --> 之前未分配,仍为 "pes(2019)"

示例 2:

输入:names = ["gta","gta(1)","gta","avalon"]
输出:["gta","gta(1)","gta(2)","avalon"]
解释:文件系统将会这样创建文件名:
"gta" --> 之前未分配,仍为 "gta"
"gta(1)" --> 之前未分配,仍为 "gta(1)"
"gta" --> 文件名被占用,系统为该名称添加后缀 (k),由于 "gta(1)" 也被占用,所以 k = 2 。实际创建的文件名为 "gta(2)" 。
"avalon" --> 之前未分配,仍为 "avalon"

示例 3:

输入:names = ["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece"]
输出:["onepiece","onepiece(1)","onepiece(2)","onepiece(3)","onepiece(4)"]
解释:当创建最后一个文件夹时,最小的正有效 k 为 4 ,文件名变为 "onepiece(4)"。

示例 4:

输入:names = ["wano","wano","wano","wano"]
输出:["wano","wano(1)","wano(2)","wano(3)"]
解释:每次创建文件夹 "wano" 时,只需增加后缀中 k 的值即可。

示例 5:

输入:names = ["kaido","kaido(1)","kaido","kaido(1)"]
输出:["kaido","kaido(1)","kaido(2)","kaido(1)(1)"]
解释:注意,如果含后缀文件名被占用,那么系统也会按规则在名称后添加新的后缀 (k) 。

 

提示:

  • 1 <= names.length <= 5 * 10^4
  • 1 <= names[i].length <= 20
  • names[i] 由小写英文字母、数字和/或圆括号组成。

2、思路1-暴力哈希

遍历文件名数组 names,遇到不存在的文件名就将它存入哈希表与结果数组;遇到已存在的文件名就从 1 开始尝试给它加后缀,直到找到不存在的文件名,新的文件名同样需要存入哈希表与结果数组

3、代码

function getFolderNames(names: string[]): string[] {
const set = new Set();

return names.map(name => {
if (!set.has(name)) return set.add(name), name;

for (let k = 1; k; k++) {
const newName = name + `(${k})`;
if (!set.has(newName)) {
return set.add(newName), newName;
}
}
});
};

4、执行结果

image.png

5、思路2-优化哈希

上面的解法可以对内层循环进行优化,记录文件名是否存在的同时也记录它出现的次数,这样可以使得内层循环不用每次都从 1 开始

6、代码

function getFolderNames(names: string[]): string[] {
const map = new Map<string, number>();

return names.map(name => {
if (!map.has(name)) return map.set(name, 1), name;

for (let k = map.get(name); k; k++) {
const newName = name + `(${k})`;
if (map.has(newName)) continue;

map.set(name, k + 1);
map.set(newName, 1);

return newName;
}
});
};

7、复杂度

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

8、执行结果

image.png

· 阅读需 3 分钟

1、题干

给你一个整数数组 nums ,返回其中 按位与三元组 的数目。

按位与三元组 是由下标 (i, j, k) 组成的三元组,并满足下述全部条件:

  • 0 <= i < nums.length
  • 0 <= j < nums.length
  • 0 <= k < nums.length
  • nums[i] & nums[j] & nums[k] == 0 ,其中 & 表示按位与运算符。
 

示例 1:

输入:nums = [2,1,3]
输出:12
解释:可以选出如下 i, j, k 三元组:
(i=0, j=0, k=1) : 2 & 2 & 1
(i=0, j=1, k=0) : 2 & 1 & 2
(i=0, j=1, k=1) : 2 & 1 & 1
(i=0, j=1, k=2) : 2 & 1 & 3
(i=0, j=2, k=1) : 2 & 3 & 1
(i=1, j=0, k=0) : 1 & 2 & 2
(i=1, j=0, k=1) : 1 & 2 & 1
(i=1, j=0, k=2) : 1 & 2 & 3
(i=1, j=1, k=0) : 1 & 1 & 2
(i=1, j=2, k=0) : 1 & 3 & 2
(i=2, j=0, k=1) : 3 & 2 & 1
(i=2, j=1, k=0) : 3 & 1 & 2

示例 2:

输入:nums = [0,0,0]
输出:27

 

提示:

  • 1 <= nums.length <= 1000
  • 0 <= nums[i] < 216

先暴力打个卡睡觉

2、思路

本题可以用三数之和的思路暴力解题。先用哈希表 map 存储所有二元组按位与的结果与出现次数,再枚举数组 numsmap 所有的键,如果二者位与结果为 0 就累计其次数

这种暴力思路会超时吗?可以稍微分析下时间复杂度:

  • 第一个嵌套循环的时间复杂度为 O(n2)O(n^2),其中 nn 为数组 nums 长度,由于 n<=1000n <= 1000,整体量级最多为 10610^6,基本不会超时;
  • 第二个嵌套循环的时间复杂度为 O(nm)O(nm),其中 mm 为哈希表键的个数,由于 nums[i] 小于 2162^{16},所以二元组按位与的结果个数不会超过 2162^{16},即哈希表的键数 m<=65536m<=65536,整体量级最多为 61076*10^7,大概率能过

3、代码

function countTriplets(nums: number[]): number {
const map = new Map();
for (const x of nums) {
for (const y of nums) {
const z = x & y;
map.set(z, (map.get(z) || 0) + 1);
}
}

let ans = 0;
for (const x of nums) {
for (const [y, c] of map) {
if (!(x & y)) ans += c;
}
}

return ans;
};

4、复杂度

  • 时间复杂度:O(nm)O(nm)
  • 空间复杂度:O(m)O(m)

5、执行结果

image.png

· 阅读需 3 分钟

1、题干

给你一个大小为 n x n 的整数矩阵 grid

生成一个大小为 (n - 2) x (n - 2) 的整数矩阵  maxLocal ,并满足:

  • maxLocal[i][j] 等于 grid 中以 i + 1 行和 j + 1 列为中心的 3 x 3 矩阵中的 最大值

换句话说,我们希望找出 grid 中每个 3 x 3 矩阵中的最大值。

返回生成的矩阵。

 

示例 1:

输入:grid = [[9,9,8,1],[5,6,2,6],[8,2,6,4],[6,2,2,2]]
输出:[[9,9],[8,6]]
解释:原矩阵和生成的矩阵如上图所示。
注意,生成的矩阵中,每个值都对应 grid 中一个相接的 3 x 3 矩阵的最大值。

示例 2:

输入:grid = [[1,1,1,1,1],[1,1,1,1,1],[1,1,2,1,1],[1,1,1,1,1],[1,1,1,1,1]]
输出:[[2,2,2],[2,2,2],[2,2,2]]
解释:注意,2 包含在 grid 中每个 3 x 3 的矩阵中。

 

提示:

  • n == grid.length == grid[i].length
  • 3 <= n <= 100
  • 1 <= grid[i][j] <= 100

2、思路

遍历矩阵 [1,n-2] 行列范围内的元素,该元素所在九宫格的最大整数作为最大值存入结果矩阵

3、代码

function largestLocal(grid: number[][]): number[][] {
const n = grid.length;
const dirs = [[1, 0], [-1, 0], [0, 1], [0, -1], [1, -1], [-1, -1], [-1, 1], [1, 1]];
const ans = Array.from({ length: n - 2 }, () => Array.from({ length: n - 2 }, () => 0));

for (let i = 1; i < n - 1; i++) {
for (let j = 1; j < n - 1; j++) {
ans[i - 1][j - 1] = grid[i][j];

for (let k = 0; k < dirs.length; k++) {
ans[i - 1][j - 1] = Math.max(ans[i - 1][j - 1], grid[i + dirs[k][0]][j + dirs[k][1]]);
}
}
}

return ans;
};

4、复杂度

  • 时间复杂度:O(n2)O(n^2)
  • 空间复杂度:O(1)O(1)

5、执行结果

image.png

· 阅读需 6 分钟

1、题干

给你两个二维整数数组 items1 和 items2 ,表示两个物品集合。每个数组 items 有以下特质:

  • items[i] = [valuei, weighti] 其中 valuei 表示第 i 件物品的 价值 ,weighti 表示第 i 件物品的 重量 。
  • items 中每件物品的价值都是 唯一的 。

请你返回一个二维数组 ret,其中 ret[i] = [valuei, weighti], weighti 是所有价值为 valuei 物品的 重量之和 。

注意:ret 应该按价值 升序 排序后返回。

 

示例 1:

输入:items1 = [[1,1],[4,5],[3,8]], items2 = [[3,1],[1,5]]
输出:[[1,6],[3,9],[4,5]]
解释:
value = 1 的物品在 items1 中 weight = 1 ,在 items2 中 weight = 5 ,总重量为 1 + 5 = 6 。
value = 3 的物品再 items1 中 weight = 8 ,在 items2 中 weight = 1 ,总重量为 8 + 1 = 9 。
value = 4 的物品在 items1 中 weight = 5 ,总重量为 5 。
所以,我们返回 [[1,6],[3,9],[4,5]] 。

示例 2:

输入:items1 = [[1,1],[3,2],[2,3]], items2 = [[2,1],[3,2],[1,3]]
输出:[[1,4],[2,4],[3,4]]
解释:
value = 1 的物品在 items1 中 weight = 1 ,在 items2 中 weight = 3 ,总重量为 1 + 3 = 4 。
value = 2 的物品在 items1 中 weight = 3 ,在 items2 中 weight = 1 ,总重量为 3 + 1 = 4 。
value = 3 的物品在 items1 中 weight = 2 ,在 items2 中 weight = 2 ,总重量为 2 + 2 = 4 。
所以,我们返回 [[1,4],[2,4],[3,4]] 。

示例 3:

输入:items1 = [[1,3],[2,2]], items2 = [[7,1],[2,2],[1,4]]
输出:[[1,7],[2,4],[7,1]]
解释:
value = 1 的物品在 items1 中 weight = 3 ,在 items2 中 weight = 4 ,总重量为 3 + 4 = 7 。
value = 2 的物品在 items1 中 weight = 2 ,在 items2 中 weight = 2 ,总重量为 2 + 2 = 4 。
value = 7 的物品在 items2 中 weight = 1 ,总重量为 1 。
所以,我们返回 [[1,7],[2,4],[7,1]] 。

 

提示:

  • 1 <= items1.length, items2.length <= 1000
  • items1[i].length == items2[i].length == 2
  • 1 <= valuei, weighti <= 1000
  • items1 中每个 valuei 都是 唯一的 。
  • items2 中每个 valuei 都是 唯一的 。

2、思路1-整合+排序

整合两个数组再统一升序排序,接着遍历所有物品得到结果

3、代码

function mergeSimilarItems(items1: number[][], items2: number[][]): number[][] {
items1.push(...items2);
items1.sort((a, b) => a[0] - b[0]);

const ans = [];
for (let i = 0; i < items1.length; i++) {
if (items1[i][0] === items1[i + 1]?.at(0)) items1[i + 1][1] += items1[i][1];
else ans.push(items1[i]);
}

return ans;
};

4、复杂度

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

5、执行结果

image.png


6、思路2-桶排序

以价值作为桶序号,累加物品重量,最后返回重量大于0的桶序号及其重量

桶内不需要再排序,而是累加数值,跟常见的桶排序有点不同

7、代码

function mergeSimilarItems(items1: number[][], items2: number[][]): number[][] {
const bucket = new Array(1001).fill(0);
for (let i = 0; i < items1.length || i < items2.length; i++) {
if (items1[i]) bucket[items1[i][0]] += items1[i][1];
if (items2[i]) bucket[items2[i][0]] += items2[i][1];
}

return bucket.map((w, i) => [i, w]).filter(b => b[1]);
};

8、复杂度

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

9、执行结果

image.png


10、思路3-哈希+排序

以价值作为哈希表的键,累加物品重量,最后返回按键升序排序的键值对

11、代码

function mergeSimilarItems(items1: number[][], items2: number[][]): number[][] {
const map = new Map();
for (let i = 0; i < items1.length || i < items2.length; i++) {
if (items1[i]) map.set(items1[i][0], (map.get(items1[i][0]) || 0) + items1[i][1]);
if (items2[i]) map.set(items2[i][0], (map.get(items2[i][0]) || 0) + items2[i][1]);
}

return [...map].sort((a, b) => a[0] - b[0]);
};

12、复杂度

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

13、执行结果

image.png


14、思路4-双指针+排序

先对两个数组分别排序,再使用双指针按价值从小到大遍历所有物品

15、代码

function mergeSimilarItems(items1: number[][], items2: number[][]): number[][] {
items1.sort((a, b) => a[0] - b[0]);
items2.sort((a, b) => a[0] - b[0]);

const ans = [];
for (let i = 0, j = 0; i < items1.length || j < items2.length;) {
while (i < items1.length && (!items2[j] || items1[i][0] < items2[j][0])) {
ans.push(items1[i]);
i++;
}

while (j < items2.length && (!items1[i] || items1[i][0] > items2[j][0])) {
ans.push(items2[j]);
j++;
}

if (i < items1.length && j < items2.length && items1[i][0] === items2[j][0]) {
items1[i][1] += items2[j][1];
ans.push(items1[i]);
i++, j++;
}
}

return ans;
};

16、复杂度

  • 时间复杂度:O(nlogn)O(n \log n)
  • 空间复杂度:O(logn)O(\log n)

17、执行结果

image.png

· 阅读需 3 分钟

1、题干

给你一个整数数组 nums,每次 操作 会从中选择一个元素并 将该元素的值减少 1

如果符合下列情况之一,则数组 A 就是 锯齿数组

  • 每个偶数索引对应的元素都大于相邻的元素,即 A[0] > A[1] < A[2] > A[3] < A[4] > ...
  • 或者,每个奇数索引对应的元素都大于相邻的元素,即 A[0] < A[1] > A[2] < A[3] > A[4] < ...

返回将数组 nums 转换为锯齿数组所需的最小操作次数。

 

示例 1:

输入:nums = [1,2,3]
输出:2
解释:我们可以把 2 递减到 0,或把 3 递减到 1。

示例 2:

输入:nums = [9,6,1,6,2]
输出:4

 

提示:

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

2、思路

这里操作的方式可以考虑3种:只减小偶数索引的数,只减小奇数索引的数,两种索引的数都减小。显然第三种操作无法达到最小操作次数。

总体思路是:尝试只减小偶数索引的数或奇数索引的数,减小后的数尽可能与相邻的数相差最小,那减小后的数字应为 min(nums[i-1],nums[i+1]);最后取两个结果中较小的那个数字。

3、代码

function movesToMakeZigzag(nums: number[]): number {
let ans1 = 0;
for (let i = 0; i < nums.length; i += 2) {
const min = Math.min(nums[i - 1] ?? nums[i + 1], nums[i + 1] ?? nums[i - 1]);
if (nums[i] >= min) ans1 += nums[i] - min + 1;
}

let ans2 = 0;
for (let i = 1; i < nums.length; i += 2) {
const min = Math.min(nums[i - 1] ?? nums[i + 1], nums[i + 1] ?? nums[i - 1]);
if (nums[i] >= min) ans2 += nums[i] - min + 1;
}

return Math.min(ans1, ans2);
};

4、复杂度

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

5、执行结果

image.png

· 阅读需 4 分钟

1、题干

你将会得到一份单词表 words,一个字母表 letters (可能会有重复字母),以及每个字母对应的得分情况表 score

请你帮忙计算玩家在单词拼写游戏中所能获得的「最高得分」:能够由 letters 里的字母拼写出的 任意 属于 words 单词子集中,分数最高的单词集合的得分。

单词拼写游戏的规则概述如下:

  • 玩家需要用字母表 letters 里的字母来拼写单词表 words 中的单词。
  • 可以只使用字母表 letters 中的部分字母,但是每个字母最多被使用一次。
  • 单词表 words 中每个单词只能计分(使用)一次。
  • 根据字母得分情况表score,字母 'a''b''c', ... , 'z' 对应的得分分别为 score[0], score[1], ..., score[25]
  • 本场游戏的「得分」是指:玩家所拼写出的单词集合里包含的所有字母的得分之和。

 

示例 1:

输入:words = ["dog","cat","dad","good"], letters = ["a","a","c","d","d","d","g","o","o"], score = [1,0,9,5,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0]
输出:23
解释:
字母得分为 a=1, c=9, d=5, g=3, o=2
使用给定的字母表 letters,我们可以拼写单词 "dad" (5+1+5)和 "good" (3+2+2+5),得分为 23 。
而单词 "dad" 和 "dog" 只能得到 21 分。

示例 2:

输入:words = ["xxxz","ax","bx","cx"], letters = ["z","a","b","c","x","x","x"], score = [4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,10]
输出:27
解释:
字母得分为 a=4, b=4, c=4, x=5, z=10
使用给定的字母表 letters,我们可以组成单词 "ax" (4+5), "bx" (4+5) 和 "cx" (4+5) ,总得分为 27 。
单词 "xxxz" 的得分仅为 25 。

示例 3:

输入:words = ["leetcode"], letters = ["l","e","t","c","o","d"], score = [0,0,1,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0]
输出:0
解释:
字母 "e" 在字母表 letters 中只出现了一次,所以无法组成单词表 words 中的单词。

 

提示:

  • 1 <= words.length <= 14
  • 1 <= words[i].length <= 15
  • 1 <= letters.length <= 100
  • letters[i].length == 1
  • score.length == 26
  • 0 <= score[i] <= 10
  • words[i] 和 letters[i] 只包含小写的英文字母。

2、思路

回溯,枚举所有情况计算最高得分。未优化,存在重复计算,效率不高。

3、代码

function maxScoreWords(words: string[], letters: string[], score: number[]): number {
const wordScore = new Array(words.length).fill(0);
for (let i = 0; i < words.length; i++) {
for (let j = 0; j < words[i].length; j++) {
wordScore[i] += score[words[i][j].charCodeAt(0) - 97];
}
}

const letterDict = new Array(26).fill(0);
for (let i = 0; i < letters.length; i++) {
letterDict[letters[i].charCodeAt(0) - 97] += 1;
}

let ans = 0;
function dfs(sc: number, visited: Set<number>) {
for (let i = 0; i < words.length; i++) {
if (visited.has(i)) continue;

let valid = true;
for (let j = 0; j < words[i].length; j++) {
const c = words[i][j].charCodeAt(0) - 97;
letterDict[c]--;
if (letterDict[c] < 0) valid = false;
}

if (valid) {
ans = Math.max(ans, sc + wordScore[i]);

visited.add(i);
dfs(sc + wordScore[i], visited);
visited.delete(i);
}

for (let j = 0; j < words[i].length; j++) {
letterDict[words[i][j].charCodeAt(0) - 97]++;
}
}
}

return dfs(0, new Set()), ans;
};

4、执行结果

image.png

· 阅读需 3 分钟

1、题干

给你一个非负整数数组 nums 。在一步操作中,你必须:

  • 选出一个正整数 xx 需要小于或等于 nums最小非零 元素。
  • nums 中的每个正整数都减去 x

返回使 nums 中所有元素都等于 0 需要的 最少 操作数。

 

示例 1:

输入:nums = [1,5,0,3,5]
输出:3
解释:
第一步操作:选出 x = 1 ,之后 nums = [0,4,0,2,4] 。
第二步操作:选出 x = 2 ,之后 nums = [0,2,0,0,2] 。
第三步操作:选出 x = 2 ,之后 nums = [0,0,0,0,0] 。

示例 2:

输入:nums = [0]
输出:0
解释:nums 中的每个元素都已经是 0 ,所以不需要执行任何操作。

 

提示:

  • 1 <= nums.length <= 100
  • 0 <= nums[i] <= 100

2、思路1

根据题目描述,每一步都消除了所有最小的正整数,因此最少步数就是数组 nums 去重后正整数的个数。

第一个思路是:排序后统计去重正整数个数

3、代码

function minimumOperations(nums: number[]): number {
nums.sort((a, b) => a - b);

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

return step;
};

4、复杂度

  • 时间复杂度:O(nlogn)O(n \log n)
  • 空间复杂度:O(logn)O(\log n)

5、执行结果

image.png

6、思路2

哈希去重统计正整数的个数

7、代码

function minimumOperations(nums: number[]): number {
const set = new Set(nums);
return set.size - (set.has(0) ? 1 : 0);
};

或者这样

function minimumOperations(nums: number[]): number {
return new Set(nums.filter(Boolean)).size;
};

8、复杂度

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

9、执行结果

image.png