跳到主要内容

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

查看所有标签

· 阅读需 2 分钟

1、题干

给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。

由于答案可能很大,因此 返回答案模 10^9 + 7

 

示例 1:

输入:arr = [3,1,2,4]
输出:17
解释:
子数组为 [3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4]。
最小值为 3,1,2,4,1,1,2,1,1,1,和为 17。

示例 2:

输入:arr = [11,81,94,43,3]
输出:444

 

提示:

  • 1 <= arr.length <= 3 * 104
  • 1 <= arr[i] <= 3 * 104

 

解题思路

单调栈,写得有点费力

代码

function sumSubarrayMins(arr: number[]): number {
let res = 0;

function dfs(c: number) {
const n = arr[c];
let [mins, preSum] = c < arr.length - 1 ? dfs(c + 1) : [[], 0];

let count = 1;
while (mins.at(-1)?.at(0) >= n) {
const pair = mins.pop();
count += pair[1];
preSum -= pair[1] * pair[0];
}

mins.push([n, count]);
preSum = (preSum + n * count) % (1e9 + 7);
res = (res + preSum) % (1e9 + 7);

return [mins, preSum];
}

dfs(0);

return res;
}

· 阅读需 7 分钟

1、题干

给你一个 n * n 矩阵 grid ,矩阵由若干 01 组成。请你用四叉树表示该矩阵 grid

你需要返回能表示矩阵 grid 的 四叉树 的根结点。

四叉树数据结构中,每个内部节点只有四个子节点。此外,每个节点都有两个属性:

  • val:储存叶子结点所代表的区域的值。1 对应 True,0 对应 False。注意,当 isLeafFalse 时,你可以把 True 或者 False 赋值给节点,两种值都会被判题机制 接受
  • isLeaf: 当这个节点是一个叶子结点时为 True,如果它有 4 个子节点则为 False
class Node {
public boolean val;
    public boolean isLeaf;
    public Node topLeft;
    public Node topRight;
    public Node bottomLeft;
    public Node bottomRight;
}

我们可以按以下步骤为二维区域构建四叉树:

  1. 如果当前网格的值相同(即,全为 0 或者全为 1),将 isLeaf 设为 True ,将 val 设为网格相应的值,并将四个子节点都设为 Null 然后停止。
  2. 如果当前网格的值不同,将 isLeaf 设为 False, 将 val 设为任意值,然后如下图所示,将当前网格划分为四个子网格。
  3. 使用适当的子网格递归每个子节点。

如果你想了解更多关于四叉树的内容,可以参考 wiki

四叉树格式:

你不需要阅读本节来解决这个问题。只有当你想了解输出格式时才会这样做。输出为使用层序遍历后四叉树的序列化形式,其中 null 表示路径终止符,其下面不存在节点。

它与二叉树的序列化非常相似。唯一的区别是节点以列表形式表示 [isLeaf, val]

如果 isLeaf 或者 val 的值为 True ,则表示它在列表 [isLeaf, val] 中的值为 1 ;如果 isLeaf 或者 val 的值为 False ,则表示值为 0

 

示例 1:

输入:grid = [[0,1],[1,0]]
输出:[[0,1],[1,0],[1,1],[1,1],[1,0]]
解释:此示例的解释如下:
请注意,在下面四叉树的图示中,0 表示 false,1 表示 True 。

示例 2:

输入:grid = [[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,1,1,1,1],[1,1,1,1,1,1,1,1],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0],[1,1,1,1,0,0,0,0]]
输出:[[0,1],[1,1],[0,1],[1,1],[1,0],null,null,null,null,[1,0],[1,0],[1,1],[1,1]]
解释:网格中的所有值都不相同。我们将网格划分为四个子网格。
topLeft,bottomLeft 和 bottomRight 均具有相同的值。
topRight 具有不同的值,因此我们将其再分为 4 个子网格,这样每个子网格都具有相同的值。
解释如下图所示:

 

提示:

  1. n == grid.length == grid[i].length
  2. n == 2x 其中 0 <= x <= 6

2、解题思路

日常吐槽翻译,题目看了老半天


题目意思是要把 nnn * n 矩阵分割成 44 等份(左上、右上、左下、右下);分割之后返回一个 NodeNode 初始化要确定 33 个值:valisLeafchildren44 个子节点);如果 Node 不是叶子节点就接着递归分割它的 44 个子节点对应的子矩阵。

  • children:可以先递归求出 44 个子节点,当前节点是否有子节点由 isLeaf 决定
  • val:所有子节点的 val 之和除以 44 并向上取整(也可以向下取整)
  • isLeaf:所有子节点的 val 之和是 4400,且所有子节点都是叶子节点

矩阵大小是nn(n=2x,0<=x<=6)n * n(n = 2 ^ x, 0 <= x <= 6),矩阵最小是 111*1 最大是 646464*64,因此除最小矩阵外所有矩阵都可以被 44 等分,没有其他特殊情况需要处理


3、代码

var construct = function (grid) {
const n = grid.length;

function dfs(i0, i1, j0, j1) {
if (i0 === i1 && j0 === j1) return new Node(grid[i0][j0], true);

// 递归求出4个子节点
const mi = Math.floor(i1 / 2 + i0 / 2), mj = Math.floor(j1 / 2 + j0 / 2);
const tl = dfs(i0, mi, j0, mj);
const tr = dfs(i0, mi, mj + 1, j1);
const bl = dfs(mi + 1, i1, j0, mj);
const br = dfs(mi + 1, i1, mj + 1, j1);

const children = [tl, tr, bl, br];
// 求出所有子节点的 val 之和
const val = children.reduce((a, c) => a + c.val, 0);
// isLeaf:所有子节点的 val 之和是 4 或 0,且所有子节点都是叶子节点
const isLeaf = (val % 4 === 0) && children.every((n) => n.isLeaf);

// node.val:所有子节点的 val 之和除以 4 并向上取整
// node.isLeaf:所有子节点的 val 之和是 4 或 0,且所有子节点都是叶子节点
// 4个子节点:当前节点是否有子节点由 isLeaf 决定
return new Node(Math.ceil(val / 4), isLeaf, ...(isLeaf ? [] : children));
}

return dfs(0, n - 1, 0, n - 1);
};

4、执行结果

image.png

· 阅读需 3 分钟

1、题干

现有一个记作二维矩阵 frame 的珠宝架,其中 frame[i][j] 为该位置珠宝的价值。拿取珠宝的规则为:

  • 只能从架子的左上角开始拿珠宝
  • 每次可以移动到右侧或下侧的相邻位置
  • 到达珠宝架子的右下角时,停止拿取

注意:珠宝的价值都是大于 0 的。除非这个架子上没有任何珠宝,比如 frame = [[0]]

 

示例 1:

输入: frame = [[1,3,1],[1,5,1],[4,2,1]]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最高价值的珠宝

 

提示:

  • 0 < frame.length <= 200
  • 0 < frame[0].length <= 200

 

2、解法1-动态规划

循环遍历矩阵,累加礼物价值

状态转移方程:dp[i][j]+=max(dp[i1][j],dp[i][j1])dp[i][j] += max(dp[i-1][j], dp[i][j-1])


3、代码

function maxValue(grid: number[][]): number {
const m = grid.length, n = grid[0].length;
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
grid[i][j] += Math.max(i > 0 ? grid[i - 1][j] : 0, j > 0 ? grid[i][j - 1] : 0);
}
}
return grid[m - 1][n - 1];
};

4、执行结果

image.png


5、解法2-记忆化DFS

DFS遍历矩阵,累加礼物价值

记录每个节点的最大价值作为缓存避免重复计算


6、代码

function maxValue(grid: number[][]): number {
const m = grid.length, n = grid[0].length;
const visited = grid.map(row => row.map(() => 0));
function dfs(i: number, j: number) {
if (i < 0 || j < 0 || i >= m || j >= n) return 0;
if (!visited[i][j]) visited[i][j] = grid[i][j] + Math.max(dfs(i - 1, j), dfs(i, j - 1));
return visited[i][j];
}
return dfs(m - 1, n - 1);
};

7、执行结果

  • 执行用时: 56 ms
  • 内存消耗: 44.1 MB

· 阅读需 3 分钟

1、题干

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?

 

示例 1:

输入: 
[
  [1,3,1],
  [1,5,1],
  [4,2,1]
]

输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物

 

提示:

  • 0 < grid.length <= 200
  • 0 < grid[0].length <= 200

2、解法1-动态规划

循环遍历矩阵,累加礼物价值

状态转移方程:dp[i][j]+=max(dp[i1][j],dp[i][j1])dp[i][j] += max(dp[i-1][j], dp[i][j-1])


3、代码

function maxValue(grid: number[][]): number {
const m = grid.length, n = grid[0].length;
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
grid[i][j] += Math.max(i > 0 ? grid[i - 1][j] : 0, j > 0 ? grid[i][j - 1] : 0);
}
}
return grid[m - 1][n - 1];
};

4、执行结果

image.png


5、解法2-记忆化DFS

DFS遍历矩阵,累加礼物价值

记录每个节点的最大价值作为缓存避免重复计算


6、代码

function maxValue(grid: number[][]): number {
const m = grid.length, n = grid[0].length;
const visited = grid.map(row => row.map(() => 0));
function dfs(i: number, j: number) {
if (i < 0 || j < 0 || i >= m || j >= n) return 0;
if (!visited[i][j]) visited[i][j] = grid[i][j] + Math.max(dfs(i - 1, j), dfs(i, j - 1));
return visited[i][j];
}
return dfs(m - 1, n - 1);
};

7、执行结果

  • 执行用时: 56 ms
  • 内存消耗: 44.1 MB

· 阅读需 5 分钟

1、题干

给你一个长桌子,桌子上盘子和蜡烛排成一列。给你一个下标从 0 开始的字符串 s ,它只包含字符 '*' 和 '|' ,其中 '*' 表示一个 盘子 ,'|' 表示一支 蜡烛 。

同时给你一个下标从 0 开始的二维整数数组 queries ,其中 queries[i] = [lefti, righti] 表示 子字符串 s[lefti...righti] (包含左右端点的字符)。对于每个查询,你需要找到 子字符串中 在 两支蜡烛之间 的盘子的 数目 。如果一个盘子在 子字符串中 左边和右边  至少有一支蜡烛,那么这个盘子满足在 两支蜡烛之间 。

  • 比方说,s = "||**||**|*" ,查询 [3, 8] ,表示的是子字符串 "*||**|" 。子字符串中在两支蜡烛之间的盘子数目为 2 ,子字符串中右边两个盘子在它们左边和右边 至少有一支蜡烛。

请你返回一个整数数组 answer ,其中 answer[i] 是第 i 个查询的答案。

 

示例 1:

ex-1

输入:s = "**|**|***|", queries = [[2,5],[5,9]]
输出:[2,3]
解释:
- queries[0] 有两个盘子在蜡烛之间。
- queries[1] 有三个盘子在蜡烛之间。

示例 2:

ex-2

输入:s = "***|**|*****|**||**|*", queries = [[1,17],[4,5],[14,17],[5,11],[15,16]]
输出:[9,0,0,0,0]
解释:
- queries[0] 有 9 个盘子在蜡烛之间。
- 另一个查询没有盘子在蜡烛之间。

 

提示:

  • 3 <= s.length <= 105
  • s 只包含字符 '*' 和 '|' 。
  • 1 <= queries.length <= 105
  • queries[i].length == 2
  • 0 <= lefti <= righti < s.length

2、解题思路

按题目意思要查找任意区间 [i,j] 内两支蜡烛之间的盘子数量,要解决两个核心问题:

  • 1、查找区间 [i,j] 内最左边和最右边蜡烛的下标 [l,r]
  • 2、求出区间 [l,r] 内的蜡烛数量

问题1采用区间映射解决,使用二维数组 ranges 存储离任意下标 i 最近的左右蜡烛下标,形如 ranges[i] = [li,ri]。对于任意区间 [i,j] 内最左边和最右边蜡烛的下标为 l = ranges[i][1], r = ranges[j][0]

特殊情况 :

  • 左边没有蜡烛时有 ranges[i] = [-1,ri]
  • 右边没有蜡烛时有 ranges[i] = [li,s.length]
  • 该位置是蜡烛时有 ranges[i] = [i,i]

问题2采用前缀和解决,使用数组 sumList 存储蜡烛数量前缀和,对于任意下标 i 而言 sumList[i] 表示 s[0]s[i] 之间的蜡烛数量。对于任意区间 [l,r] 内的蜡烛数量为 sumList[r] - sumList[l]


至此两个核心问题都得以解决,且都能在 O(1)O(1) 时间复杂度内求出正解


3、代码

var platesBetweenCandles = function (s, queries) {
const n = s.length, sumList = new Array(n), ranges = new Array(n);
let ps = 0, range = [-1, n];

for (let i = 0; i < n; i++) {
if (s[i] === '*') {
ps += 1;
ranges[i] = range;
} else {
range[1] = i;
ranges[i] = [i, i];
range = [i, n];
}

sumList[i] = ps;
}

return queries.map(([i, j]) => {
const l = ranges[i][1], r = ranges[j][0];
return l > -1 && r < n && l < r ? sumList[r] - sumList[l] : 0;
});
};

4、复杂度

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

5、执行结果

  • 执行用时: 308 ms
  • 内存消耗: 86.1 MB

· 阅读需 5 分钟

1、题干

我们有 n 栋楼,编号从 0 到 n - 1 。每栋楼有若干员工。由于现在是换楼的季节,部分员工想要换一栋楼居住。

给你一个数组 requests ,其中 requests[i] = [fromi, toi] ,表示一个员工请求从编号为 fromi 的楼搬到编号为 toi 的楼。

一开始 所有楼都是满的,所以从请求列表中选出的若干个请求是可行的需要满足 每栋楼员工净变化为 0 。意思是每栋楼 离开 的员工数目 等于 该楼 搬入 的员工数数目。比方说 n = 3 且两个员工要离开楼 0 ,一个员工要离开楼 1 ,一个员工要离开楼 2 ,如果该请求列表可行,应该要有两个员工搬入楼 0 ,一个员工搬入楼 1 ,一个员工搬入楼 2 。

请你从原请求列表中选出若干个请求,使得它们是一个可行的请求列表,并返回所有可行列表中最大请求数目。

 

示例 1:

输入:n = 5, requests = [[0,1],[1,0],[0,1],[1,2],[2,0],[3,4]]
输出:5
解释:请求列表如下:
从楼 0 离开的员工为 x 和 y ,且他们都想要搬到楼 1 。
从楼 1 离开的员工为 a 和 b ,且他们分别想要搬到楼 2 和 0 。
从楼 2 离开的员工为 z ,且他想要搬到楼 0 。
从楼 3 离开的员工为 c ,且他想要搬到楼 4 。
没有员工从楼 4 离开。
我们可以让 x 和 b 交换他们的楼,以满足他们的请求。
我们可以让 y,a 和 z 三人在三栋楼间交换位置,满足他们的要求。
所以最多可以满足 5 个请求。

示例 2:

输入:n = 3, requests = [[0,0],[1,2],[2,1]]
输出:3
解释:请求列表如下:
从楼 0 离开的员工为 x ,且他想要回到原来的楼 0 。
从楼 1 离开的员工为 y ,且他想要搬到楼 2 。
从楼 2 离开的员工为 z ,且他想要搬到楼 1 。
我们可以满足所有的请求。

示例 3:

输入:n = 4, requests = [[0,3],[3,1],[1,2],[2,0]]
输出:4

 

提示:

  • 1 <= n <= 20
  • 1 <= requests.length <= 16
  • requests[i].length == 2
  • 0 <= fromi, toi < n

2、解法1-回溯

回溯枚举 requests 所有子集,如果所有节点的出度和入度都为0则属于可行的请求列表,取所有可行请求列表的最大长度作为返回结果


3、代码

var maximumRequests = function (n, requests) {
let max = 0;
function backtrace(i, path) {
const sums = new Array(n).fill(0);
for (const [f, t] of path) sums[f]--, sums[t]++;
if (sums.every(s => !s)) max = Math.max(max, path.length);

for (let j = i + 1; j < requests.length; j++) {
path.push(requests[j]);
backtrace(j, path);
path.pop();
}
}

for (let i = 0; i < requests.length; i++) backtrace(i, [requests[i]]);

return max;
};

4、执行结果

image.png


5、解法2-BFS

BFS 枚举 requests 所有子集,如果所有节点的出度和入度都为0则属于可行的请求列表,取所有可行请求列表的最大长度作为返回结果


6、代码

var maximumRequests = function (n, requests) {
let max = 0, queue = requests.map((r) => [r]);
while (queue.length) {
const nextQueue = [];
for (let i = 0; i < queue.length; i++) {
const reqs = queue[i], sums = new Array(n).fill(0);

for (const [f, t] of reqs) sums[f]--, sums[t]++;
if (sums.every((s) => !s)) max = Math.max(max, reqs.length);

const idx = requests.indexOf(reqs[reqs.length - 1]);
for (let j = idx + 1; j < requests.length; j++) nextQueue.push([...reqs, requests[j]]);
}
queue = nextQueue;
}
return max;
};

7、执行结果

  • 执行用时: 924 ms
  • 内存消耗: 68 MB

· 阅读需 2 分钟

1、题干

给定一正整数数组 numsnums 中的相邻整数将进行浮点除法。例如, [2,3,4] -> 2 / 3 / 4 。

  • 例如,nums = [2,3,4],我们将求表达式的值 "2/3/4"

但是,你可以在任意位置添加任意数目的括号,来改变算数的优先级。你需要找出怎么添加括号,以便计算后的表达式的值为最大值。

以字符串格式返回具有最大值的对应表达式。

注意:你的表达式不应该包含多余的括号。

 

示例 1:

输入: [1000,100,10,2]
输出: "1000/(100/10/2)"
解释: 1000/(100/10/2) = 1000/((100/10)/2) = 200
但是,以下加粗的括号 "1000/((100/10)/2)" 是冗余的,
因为他们并不影响操作的优先级,所以你需要返回 "1000/(100/10/2)"。

其他用例:
1000/(100/10)/2 = 50
1000/(100/(10/2)) = 50
1000/100/10/2 = 0.5
1000/100/(10/2) = 2

 

示例 2:

输入: nums = [2,3,4]
输出: "2/(3/4)"
解释: (2/(3/4)) = 8/3 = 2.667
可以看出,在尝试了所有的可能性之后,我们无法得到一个结果大于 2.667 的表达式。

 

说明:

  • 1 <= nums.length <= 10
  • 2 <= nums[i] <= 1000
  • 对于给定的输入只有一种最优除法。

2、解题思路

贪心,加括号使得被除数最大、除数最小即可


3、代码

var optimalDivision = function (nums) {
return nums.length < 3 ? nums.join('/') : `${nums.shift()}/(${nums.join('/')})`;
};

4、执行结果

image.png

· 阅读需 1 分钟

1、题干

给定一个不含重复数字的整数数组 nums ,返回其 所有可能的全排列 。可以 按任意顺序 返回答案。

 

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

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

示例 3:

输入:nums = [1]
输出:[[1]]

 

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

 

注意:本题与主站 46 题相同:https://leetcode-cn.com/problems/permutations/ 

2、解题思路

BFS每次往组合中添加1个数

3、代码

var permute = function (nums) {
const queue = nums.map(n => [n]);
while (queue[0] && queue[0].length !== nums.length) {
const arr = queue.shift();
for (const n of nums) if (!arr.includes(n)) queue.push([...arr, n]);
}
return queue;
};

· 阅读需 1 分钟

1、题干

给定一个不含重复数字的整数数组 nums ,返回其 所有可能的全排列 。可以 按任意顺序 返回答案。

 

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

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

示例 3:

输入:nums = [1]
输出:[[1]]

 

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

 

注意:本题与主站 46 题相同:https://leetcode-cn.com/problems/permutations/ 

2、解题思路

BFS每次往组合中添加1个数

3、代码

var permute = function (nums) {
const queue = nums.map(n => [n]);
while (queue[0] && queue[0].length !== nums.length) {
const arr = queue.shift();
for (const n of nums) if (!arr.includes(n)) queue.push([...arr, n]);
}
return queue;
};

· 阅读需 3 分钟

1、题干

用一个大小为 m x n 的二维网格 grid 表示一个箱子。你有 n 颗球。箱子的顶部和底部都是开着的。

箱子中的每个单元格都有一个对角线挡板,跨过单元格的两个角,可以将球导向左侧或者右侧。

  • 将球导向右侧的挡板跨过左上角和右下角,在网格中用 1 表示。
  • 将球导向左侧的挡板跨过右上角和左下角,在网格中用 -1 表示。

在箱子每一列的顶端各放一颗球。每颗球都可能卡在箱子里或从底部掉出来。如果球恰好卡在两块挡板之间的 "V" 形图案,或者被一块挡导向到箱子的任意一侧边上,就会卡住。

返回一个大小为 n 的数组 answer ,其中 answer[i] 是球放在顶部的第 i 列后从底部掉出来的那一列对应的下标,如果球卡在盒子里,则返回 -1

 

示例 1:

输入:grid = [[1,1,1,-1,-1],[1,1,1,-1,-1],[-1,-1,-1,1,1],[1,1,1,1,-1],[-1,-1,-1,-1,-1]]
输出:[1,-1,-1,-1,-1]
解释:示例如图:
b0 球开始放在第 0 列上,最终从箱子底部第 1 列掉出。
b1 球开始放在第 1 列上,会卡在第 2、3 列和第 1 行之间的 "V" 形里。
b2 球开始放在第 2 列上,会卡在第 2、3 列和第 0 行之间的 "V" 形里。
b3 球开始放在第 3 列上,会卡在第 2、3 列和第 0 行之间的 "V" 形里。
b4 球开始放在第 4 列上,会卡在第 2、3 列和第 1 行之间的 "V" 形里。

示例 2:

输入:grid = [[-1]]
输出:[-1]
解释:球被卡在箱子左侧边上。

示例 3:

输入:grid = [[1,1,1,1,1,1],[-1,-1,-1,-1,-1,-1],[1,1,1,1,1,1],[-1,-1,-1,-1,-1,-1]]
输出:[0,1,2,3,4,-1]

 

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 100
  • grid[i][j]1-1

2、解题思路

按题意模拟

3、代码

var findBall = function (grid) {
const res = [], m = grid.length, n = grid[0].length;

for (let j = 0; j < n; j++) {
let k = j;
for (let i = 0; i < m; i++) {
if (grid[i][k] === 1 && grid[i][k] === grid[i][k + 1]) k++;
else if (grid[i][k] === -1 && grid[i][k] === grid[i][k - 1]) k--;
else res[j] = -1;
}
if (res[j] === undefined) res[j] = k;
}

return res;
};

4、执行结果

image.png