LeetCode Go · 数组与矩阵(5) ​
来源与许可
题目版权归 LeetCode;题解来自 Doocs/leetcode,按 CC BY-SA 4.0 共享。补充 Go 代码来自灵茶山艾府、halfrost,保留 MIT 许可及逐题署名。本站提取 Go、调整分类及折叠排版;标注“本站整理”的 Go 解法依据题意与参考思路补写,按 CC BY-SA 4.0 共享。收录日期:2026-10-31;主题源版本:91ef8c241a5e。
本卷 30 题 · 点击题目展开,先独立思考,再查看解法。
3131. 找出与数组相加的整数 I ​
题目、示例与约束
给你两个长度相等的数组 nums1 和 nums2。
数组 nums1 中的每个元素都与变量 x 所表示的整数相加。如果 x 为负数,则表现为元素值的减少。
在与 x 相加后,nums1 和 nums2 相等 。当两个数组中包含相同的整数,并且这些整数出现的频次相同时,两个数组 相等 。
返回整数 x 。
示例 1:
输入:nums1 = [2,6,4], nums2 = [9,7,5]
输出:3
解释:
与 3 相加后,nums1 和 nums2 相等。
示例 2:
输入:nums1 = [10], nums2 = [5]
输出:-5
解释:
与 -5 相加后,nums1 和 nums2 相等。
示例 3:
输入:nums1 = [1,1,1,1], nums2 = [1,1,1,1]
输出:0
解释:
与 0 相加后,nums1 和 nums2 相等。
提示:
1 <= nums1.length == nums2.length <= 1000 <= nums1[i], nums2[i] <= 1000- 测试用例以这样的方式生成:存在一个整数
x,使得nums1中的每个元素都与x相加后,nums1与nums2相等。
方法一:求最小值差 · Go 实现
思考
由 每个元素加同一整数得到。若匹配全部排列再求差,需要排序或哈希。
加法不改变相对次序,两个数组的最小值之差就是这个整数。
因此返回 。一次求最小即可。
我们可以分别求出两个数组的最小值,然后返回两个最小值的差值即可。
时间复杂度 ,其中 为数组的长度。空间复杂度 。
func addedInteger(nums1 []int, nums2 []int) int {
return slices.Min(nums2) - slices.Min(nums1)
}
3142. 判断矩阵是否满足条件 ​
题目、示例与约束
给你一个大小为 m x n 的二维矩阵 grid 。你需要判断每一个格子 grid[i][j] 是否满足:
- 如果它下面的格子存在,那么它需要等于它下面的格子,也就是
grid[i][j] == grid[i + 1][j]。 - 如果它右边的格子存在,那么它需要不等于它右边的格子,也就是
grid[i][j] != grid[i][j + 1]。
如果 所有 格子都满足以上条件,那么返回 true ,否则返回 false 。
示例 1:
输入:grid = [[1,0,2],[1,0,2]]
输出:true
解释:

网格图中所有格子都符合条件。
示例 2:
输入:grid = [[1,1,1],[0,0,0]]
输出:false
解释:

同一行中的格子值都相等。
示例 3:
输入:grid = [[1],[2],[3]]
输出:false
解释:

同一列中的格子值不相等。
提示:
1 <= n, m <= 100 <= grid[i][j] <= 9
方法一:模拟 · Go 实现
思考
条件是同列相邻相等、同行相邻不等。定义已给出逐格判定,无需搜索。
任一格子违约即可否定整张表,扫描中途返回。
双重循环检查下方与右方邻居,全部通过则返回真。时间为 。
我们可以遍历每一个格子,判断其是否满足题目条件,如果有一个格子不满足条件,我们就返回 false,否则返回 true。
时间复杂度 ,其中 和 分别是矩阵 grid 的行数和列数。空间复杂度 。
func satisfiesConditions(grid [][]int) bool {
m, n := len(grid), len(grid[0])
for i, row := range grid {
for j, x := range row {
if i+1 < m && x != grid[i+1][j] {
return false
}
if j+1 < n && x == grid[i][j+1] {
return false
}
}
}
return true
}
3151. 特殊数组 I ​
题目、示例与约束
如果数组的每一对相邻元素都是两个奇偶性不同的数字,则该数组被认为是一个 特殊数组。换句话说,每一对中的元素 必须 有一个是偶数,另一个是奇数。
你有一个整数数组 nums。如果 nums 是一个 特殊数组 ,返回 true,否则返回 false。
示例 1:
输入:nums = [1]
输出:true
解释:
只有一个元素,所以答案为 true。
示例 2:
输入:nums = [2,1,4]
输出:true
解释:
只有两对相邻元素: (2,1) 和 (1,4),它们都包含了奇偶性不同的数字,因此答案为 true。
示例 3:
输入:nums = [4,3,1,6]
输出:false
解释:
nums[1] 和 nums[2] 都是奇数。因此答案为 false。
提示:
1 <= nums.length <= 1001 <= nums[i] <= 100
方法一:一次遍历 · Go 实现
思考
特殊数组要求相邻元素奇偶性不同。任一相等奇偶即失败,不必看更远的元素。
很小,一次相邻比较足够。
对
pairwise(nums)检查模 是否都不同,全部不同则返回真。
我们从左到右遍历数组,对于每一对相邻元素,如果它们的奇偶性相同,那么数组就不是特殊数组,返回 false;否则,数组是特殊数组,返回 true。
时间复杂度 ,其中 是数组的长度。空间复杂度 。
func isArraySpecial(nums []int) bool {
for i, x := range nums[1:] {
if x%2 == nums[i]%2 {
return false
}
}
return true
}
3175. 找到连续赢 K 场比赛的第一位玩家 ​
题目、示例与约束
有 n 位玩家在进行比赛,玩家编号依次为 0 到 n - 1 。
给你一个长度为 n 的整数数组 skills 和一个 正 整数 k ,其中 skills[i] 是第 i 位玩家的技能等级。skills 中所有整数 互不相同 。
所有玩家从编号 0 到 n - 1 排成一列。
比赛进行方式如下:
- 队列中最前面两名玩家进行一场比赛,技能等级 更高 的玩家胜出。
- 比赛后,获胜者保持在队列的开头,而失败者排到队列的末尾。
这个比赛的赢家是 第一位连续 赢下 k 场比赛的玩家。
请你返回这个比赛的赢家编号。
示例 1:
输入:skills = [4,2,6,3,9], k = 2
输出:2
解释:
一开始,队列里的玩家为 [0,1,2,3,4] 。比赛过程如下:
- 玩家 0 和 1 进行一场比赛,玩家 0 的技能等级高于玩家 1 ,玩家 0 胜出,队列变为
[0,2,3,4,1]。 - 玩家 0 和 2 进行一场比赛,玩家 2 的技能等级高于玩家 0 ,玩家 2 胜出,队列变为
[2,3,4,1,0]。 - 玩家 2 和 3 进行一场比赛,玩家 2 的技能等级高于玩家 3 ,玩家 2 胜出,队列变为
[2,4,1,0,3]。
玩家 2 连续赢了 k = 2 场比赛,所以赢家是玩家 2 。
示例 2:
输入:skills = [2,5,4], k = 3
输出:1
解释:
一开始,队列里的玩家为 [0,1,2] 。比赛过程如下:
- 玩家 0 和 1 进行一场比赛,玩家 1 的技能等级高于玩家 0 ,玩家 1 胜出,队列变为
[1,2,0]。 - 玩家 1 和 2 进行一场比赛,玩家 1 的技能等级高于玩家 2 ,玩家 1 胜出,队列变为
[1,0,2]。 - 玩家 1 和 0 进行一场比赛,玩家 1 的技能等级高于玩家 0 ,玩家 1 胜出,队列变为
[1,2,0]。
玩家 1 连续赢了 k = 3 场比赛,所以赢家是玩家 1 。
提示:
n == skills.length2 <= n <= 1051 <= k <= 1091 <= skills[i] <= 106skills中的整数互不相同。
方法一:脑筋急转弯 · Go 实现
思考
队列头部两人比技能,败者到队尾,胜者留下。若真模拟队列, 很大时轮数可达 。
败者永远排到未出现的更强者之后,连续胜场一旦达到 即当前人已是全局最大,不会再输。故 可先与 取小。
维护当前胜者下标 与连胜 ,遇更强者更换并重置。 到达 或扫完数组即停。
我们注意到,每次会比较数组的前两个元素,不管结果怎么样,下一次的比较,一定是轮到了数组中的下一个元素和当前的胜者进行比较。因此,如果循环了 次,那么最后的胜者一定是数组中的最大元素。否则,如果某个元素连续胜出了 次,那么这个元素就是最后的胜者。
时间复杂度 ,其中 是数组的长度。空间复杂度 。
相似题目:
func findWinningPlayer(skills []int, k int) int {
n := len(skills)
k = min(k, n-1)
i, cnt := 0, 0
for j := 1; j < n; j++ {
if skills[i] < skills[j] {
i = j
cnt = 1
} else {
cnt++
}
if cnt == k {
break
}
}
return i
}
3195. 包含所有 1 的最小矩形面积 I ​
题目、示例与约束
给你一个二维 二进制 数组 grid。请你找出一个边在水平方向和竖直方向上、面积 最小 的矩形,并且满足 grid 中所有的 1 都在矩形的内部。
返回这个矩形可能的 最小 面积。
示例 1:
输入: grid = [[0,1,0],[1,0,1]]
输出: 6
解释:

这个最小矩形的高度为 2,宽度为 3,因此面积为 2 * 3 = 6。
示例 2:
输入: grid = [[0,0],[1,0]]
输出: 1
解释:

这个最小矩形的高度和宽度都是 1,因此面积为 1 * 1 = 1。
提示:
1 <= grid.length, grid[i].length <= 1000grid[i][j]是 0 或 1。- 输入保证
grid中至少有一个 1 。
方法一:求最小边界和最大边界 · Go 实现
思考
一个边平行矩形盖住全部 。枚举所有矩形再检查覆盖是 。
最优矩形就是 的最小外接矩形,由最小/最大行列决定。
一遍扫描维护 ,面积为 。
我们可以遍历 grid,找到所有 1 的最小边界,记为 ,最大边界,记为 ,那么最小矩形的面积就是 。
时间复杂度 ,其中 和 分别是 grid 的行数和列数。空间复杂度 。
func minimumArea(grid [][]int) int {
x1, y1 := len(grid), len(grid[0])
x2, y2 := 0, 0
for i, row := range grid {
for j, x := range row {
if x == 1 {
x1, y1 = min(x1, i), min(y1, j)
x2, y2 = max(x2, i), max(y2, j)
}
}
}
return (x2 - x1 + 1) * (y2 - y1 + 1)
}
3197. 包含所有 1 的最小矩形面积 II ​
题目、示例与约束
给你一个二维 二进制 数组 grid。你需要找到 3 个 不重叠、面积 非零 、边在水平方向和竖直方向上的矩形,并且满足 grid 中所有的 1 都在这些矩形的内部。
返回这些矩形面积之和的 最小 可能值。
注意,这些矩形可以相接。
示例 1:
输入: grid = [[1,0,1],[1,1,1]]
输出: 5
解释:

- 位于
(0, 0)和(1, 0)的 1 被一个面积为 2 的矩形覆盖。 - 位于
(0, 2)和(1, 2)的 1 被一个面积为 2 的矩形覆盖。 - 位于
(1, 1)的 1 被一个面积为 1 的矩形覆盖。
示例 2:
输入: grid = [[1,0,1,0],[0,1,0,1]]
输出: 5
解释:

- 位于
(0, 0)和(0, 2)的 1 被一个面积为 3 的矩形覆盖。 - 位于
(1, 1)的 1 被一个面积为 1 的矩形覆盖。 - 位于
(1, 3)的 1 被一个面积为 1 的矩形覆盖。
提示:
1 <= grid.length, grid[i].length <= 30grid[i][j]是 0 或 1。- 输入保证
grid中至少有三个 1 。
方法一:枚举 · Go 实现
思考
三个边平行矩形盖住全部 ,矩形不可重叠地分割平面。枚举三个矩形的位置高达坐标四次方以上。
三个矩形对网格的剖分只有六种拓扑:两刀全横、两刀全竖,以及一横一竖的四种「先切再切一半」。
函数 计算一块子矩形内 的外接面积。枚举分割线,把三块面积相加取最小。网格不大,四重循环可过。
根据题目描述,我们可以用两条分割线,将矩形分成三个部分,我们分别计算每一部分包含所有 的最小矩形面积,然后取三个部分面积之和的最小值。
我们可以枚举两条分割线的位置,共有 种情况:
- 两次横向分割
- 两次纵向分割
- 先进行一次横向分割,再对上部分进行一次纵向分割
- 先进行一次横向分割,再对下部分进行一次纵向分割
- 先进行一次纵向分割,再对左部分进行一次横向分割
- 先进行一次纵向分割,再对右部分进行一次横向分割
我们可以用一个函数 来计算矩形 到 包含所有 的最小矩形面积。
时间复杂度 ,其中 和 分别是矩形的行数和列数。空间复杂度 。
func minimumSum(grid [][]int) int {
m := len(grid)
n := len(grid[0])
ans := m * n
inf := math.MaxInt32
f := func(i1, j1, i2, j2 int) int {
x1, y1 := inf, inf
x2, y2 := -inf, -inf
for i := i1; i <= i2; i++ {
for j := j1; j <= j2; j++ {
if grid[i][j] == 1 {
x1 = min(x1, i)
y1 = min(y1, j)
x2 = max(x2, i)
y2 = max(y2, j)
}
}
}
if x1 == inf {
return 0
}
return (x2 - x1 + 1) * (y2 - y1 + 1)
}
for i1 := 0; i1 < m-1; i1++ {
for i2 := i1 + 1; i2 < m-1; i2++ {
ans = min(ans, f(0, 0, i1, n-1)+f(i1+1, 0, i2, n-1)+f(i2+1, 0, m-1, n-1))
}
}
for j1 := 0; j1 < n-1; j1++ {
for j2 := j1 + 1; j2 < n-1; j2++ {
ans = min(ans, f(0, 0, m-1, j1)+f(0, j1+1, m-1, j2)+f(0, j2+1, m-1, n-1))
}
}
for i := 0; i < m-1; i++ {
for j := 0; j < n-1; j++ {
ans = min(ans, f(0, 0, i, j)+f(0, j+1, i, n-1)+f(i+1, 0, m-1, n-1))
ans = min(ans, f(0, 0, i, n-1)+f(i+1, 0, m-1, j)+f(i+1, j+1, m-1, n-1))
ans = min(ans, f(0, 0, i, j)+f(i+1, 0, m-1, j)+f(0, j+1, m-1, n-1))
ans = min(ans, f(0, 0, m-1, j)+f(0, j+1, i, n-1)+f(i+1, j+1, m-1, n-1))
}
}
return ans
}
3200. 三角形的最大高度 ​
题目、示例与约束
给你两个整数 red 和 blue,分别表示红色球和蓝色球的数量。你需要使用这些球来组成一个三角形,满足第 1 行有 1 个球,第 2 行有 2 个球,第 3 行有 3 个球,依此类推。
每一行的球必须是 相同 颜色,且相邻行的颜色必须 不同。
返回可以实现的三角形的 最大 高度。
示例 1:
输入: red = 2, blue = 4
输出: 3
解释:

上图显示了唯一可能的排列方式。
示例 2:
输入: red = 2, blue = 1
输出: 2
解释:

上图显示了唯一可能的排列方式。
示例 3:
输入: red = 1, blue = 1
输出: 1
示例 4:
输入: red = 10, blue = 1
输出: 2
解释:

上图显示了唯一可能的排列方式。
提示:
1 <= red, blue <= 100
方法一:模拟 · Go 实现
思考
高度为 时需消耗 个球,故 至多为 。在 下即便枚举高度与每行着色,规模也可接受,但相邻行必须异色,整座三角形的着色由第一行唯一决定。
因此只需枚举第一行取红或取蓝,再按行长 交替扣减对应颜色的剩余数量。若当前行所需个数已超过该色存量则停止,并在两种起点中取最大可行行数。异或翻转下标即可在常数空间内完成这一过程。
我们可以枚举第一行的颜色,然后模拟构造三角形,计算最大高度。
时间复杂度 ,其中 为红色球和蓝色球的数量。空间复杂度 。
func maxHeightOfTriangle(red int, blue int) (ans int) {
for k := 0; k < 2; k++ {
c := [2]int{red, blue}
for i, j := 1, k; i <= c[j]; i, j = i+1, j^1 {
c[j] -= i
ans = max(ans, i)
}
}
return
}
3285. 找到稳定山的下标 ​
题目、示例与约束
有 n 座山排成一列,每座山都有一个高度。给你一个整数数组 height ,其中 height[i] 表示第 i 座山的高度,再给你一个整数 threshold 。
对于下标不为 0 的一座山,如果它左侧相邻的山的高度 严格大于 threshold ,那么我们称它是 稳定 的。我们定义下标为 0 的山 不是 稳定的。
请你返回一个数组,包含所有 稳定 山的下标,你可以以 任意 顺序返回下标数组。
示例 1:
输入:height = [1,2,3,4,5], threshold = 2
输出:[3,4]
解释:
- 下标为 3 的山是稳定的,因为
height[2] == 3大于threshold == 2。 - 下标为 4 的山是稳定的,因为
height[3] == 4大于threshold == 2.
示例 2:
输入:height = [10,1,10,1,10], threshold = 3
输出:[1,3]
示例 3:
输入:height = [10,1,10,1,10], threshold = 10
输出:[]
提示:
2 <= n == height.length <= 1001 <= height[i] <= 1001 <= threshold <= 100
方法一:遍历 · Go 实现
思考
下标 当且仅当左侧山高大于阈值时稳定。 ,按定义过滤即可。
从 扫到末尾,收集 的 。无需前缀结构。
我们直接从下标为 的山开始遍历,如果它左侧相邻的山的高度大于 ,那么我们就将它的下标加入到结果数组中。
遍历结束后,返回结果数组即可。
时间复杂度 ,其中 是数组 的长度。忽略答案数组的空间消耗,空间复杂度 。
func stableMountains(height []int, threshold int) (ans []int) {
for i := 1; i < len(height); i++ {
if height[i-1] > threshold {
ans = append(ans, i)
}
}
return
}
3349. 检测相邻递增子数组 I ​
题目、示例与约束
给你一个由 n 个整数组成的数组 nums 和一个整数 k,请你确定是否存在 两个 相邻 且长度为 k 的 严格递增 子数组。具体来说,需要检查是否存在从下标 a 和 b (a < b) 开始的 两个 子数组,并满足下述全部条件:
- 这两个子数组
nums[a..a + k - 1]和nums[b..b + k - 1]都是 严格递增 的。 - 这两个子数组必须是 相邻的,即
b = a + k。
如果可以找到这样的 两个 子数组,请返回 true;否则返回 false。
子数组 是数组中的一个连续 非空 的元素序列。
示例 1:
输入:nums = [2,5,7,8,9,2,3,4,3,1], k = 3
输出:true
解释:
- 从下标
2开始的子数组为[7, 8, 9],它是严格递增的。 - 从下标
5开始的子数组为[2, 3, 4],它也是严格递增的。 - 两个子数组是相邻的,因此结果为
true。
示例 2:
输入:nums = [1,2,3,4,4,4,4,5,6,7], k = 5
输出:false
提示:
2 <= nums.length <= 1001 <= 2 * k <= nums.length-1000 <= nums[i] <= 1000
方法一:一次遍历 · Go 实现
思考
要判断是否存在两个相邻、长度均为 的严格递增段。 ,枚举起点可行,一次扫描也能直接得到最大可行 。
相邻两段要么来自同一段递增被从中切开(长度为 ),要么来自两段紧邻递增的较短者 。
在递增断裂处更新这两种候选,最后看最大值是否至少为 。
根据题目描述,我们只需要找到最大的相邻递增子数组长度 ,如果 ,则说明存在两个相邻且长度为 的严格递增子数组。
我们可以使用一次遍历来计算 。具体来说,我们维护三个变量,其中 和 分别表示当前递增子数组的长度和前一个递增子数组的长度,而 表示最大的相邻递增子数组长度。
每当遇到一个非递增的位置时,我们就更新 ,并将 赋值给 ,然后将 重置为 ,其中 的更新方式为 ,即相邻递增子数组来自于当前递增子数组的一半长度,或者前一个递增子数组和当前递增子数组的较小值。
最后,我们只需要判断 是否大于等于 即可。
时间复杂度 ,其中 是数组的长度。空间复杂度 。
func hasIncreasingSubarrays(nums []int, k int) bool {
mx, pre, cur := 0, 0, 0
for i, x := range nums {
cur++
if i == len(nums)-1 || x >= nums[i+1] {
mx = max(mx, max(cur/2, min(pre, cur)))
pre, cur = cur, 0
}
}
return mx >= k
}
3353. 最小总操作数 🔒 ​
题目、示例与约束
给定一个整数数组 nums,你可以在这个数组上进行 任意 次操作。
在每次 操作 中,你可以:
- 选择这个数组的一个 前缀。
- 选择一个整数
k(可以为负)并且对选中前缀的每一个元素加k。
数组的 前缀 是一个子数组,从数组的开头开始并延伸到数组内的任何位置。
返回使 arr 中的所有元素都相等的 最小 操作数。
示例 1:
输入:nums = [1,4,2]
输出:2
解释:
- 操作 1:选择长度为 2 的前缀
[1, 4]并且对其中的所有元素加 -2。数组变为[-1, 2, 2]。 - 操作 2:选择长度为 1 的前缀
[-1]并且对其中的所有元素加 3。数组变为[2, 2, 2]。 - 因此,所需的最小操作数为 2。
示例 2:
输入:nums = [10,10,10]
输出:0
解释:
- 所有元素已经相等,所以不需要操作。
提示:
1 <= nums.length <= 105-109 <= nums[i] <= 109
方法一:一次遍历 · Go 实现
思考
一次操作把某个前缀全部改为同一值。目标是把整个数组变成相等,次数尽量少。
从右往左看,最终值必是 ;每当相邻两项不同,就必须多一次「把左侧前缀改掉」。
因此答案等于相邻不相等的对数,一次扫描即可。
我们可以遍历数组,对于每个元素,如果它与前一个元素不相等,那么就需要进行一次操作,最后返回操作的次数即可。
时间复杂度 ,其中 为数组的长度。空间复杂度 。
func minOperations(nums []int) (ans int) {
for i, x := range nums[1:] {
if x != nums[i] {
ans++
}
}
return
}
3379. 转换数组 ​
题目、示例与约束
给你一个整数数组 nums,它表示一个循环数组。请你遵循以下规则创建一个大小 相同 的新数组 result :
i(其中 0 <= i < nums.length),独立执行以下操作:
- 如果
nums[i] > 0:从下标i开始,向 右 移动nums[i]步,在循环数组中落脚的下标对应的值赋给result[i]。 - 如果
nums[i] < 0:从下标i开始,向 左 移动abs(nums[i])步,在循环数组中落脚的下标对应的值赋给result[i]。 - 如果
nums[i] == 0:将nums[i]的值赋给result[i]。
返回新数组 result。
注意:由于 nums 是循环数组,向右移动超过最后一个元素时将回到开头,向左移动超过第一个元素时将回到末尾。
示例 1:
输入: nums = [3,-2,1,1]
输出: [1,1,1,3]
解释:
- 对于
nums[0]等于 3,向右移动 3 步到nums[3],因此result[0]为 1。 - 对于
nums[1]等于 -2,向左移动 2 步到nums[3],因此result[1]为 1。 - 对于
nums[2]等于 1,向右移动 1 步到nums[3],因此result[2]为 1。 - 对于
nums[3]等于 1,向右移动 1 步到nums[0],因此result[3]为 3。
示例 2:
输入: nums = [-1,4,-1]
输出: [-1,-1,4]
解释:
- 对于
nums[0]等于 -1,向左移动 1 步到nums[2],因此result[0]为 -1。 - 对于
nums[1]等于 4,向右移动 4 步到nums[2],因此result[1]为 -1。 - 对于
nums[2]等于 -1,向左移动 1 步到nums[1],因此result[2]为 4。
提示:
1 <= nums.length <= 100-100 <= nums[i] <= 100
方法一:模拟 · Go 实现
思考
从 出发按 的符号移动 步(环形),写入落点的值。 ,直接算下标。
步数对 取模时注意负数: 即可。
结果数组与原数组分离,避免覆盖尚未使用的 。
我们创建一个结果数组 ,对于每个下标,根据 的正负向右或向左移动 步,计算出落脚的下标,并将该下标对应的值赋给 。
时间复杂度 ,空间复杂度 。其中 是数组 的长度。
func constructTransformedArray(nums []int) []int {
n := len(nums)
ans := make([]int, n)
for i, x := range nums {
ans[i] = nums[(i+x%n+n)%n]
}
return ans
}
3386. 按下时间最长的按钮 ​
题目、示例与约束
给你一个二维数组 events,表示孩子在键盘上按下一系列按钮触发的按钮事件。
每个 events[i] = [indexi, timei] 表示在时间 timei 时,按下了下标为 indexi 的按钮。
- 数组按照
time的递增顺序排序。 - 按下一个按钮所需的时间是连续两次按钮按下的时间差。按下第一个按钮所需的时间就是其时间戳。
返回按下时间 最长 的按钮的 index。如果有多个按钮的按下时间相同,则返回 index 最小的按钮。
示例 1:
输入: events = [[1,2],[2,5],[3,9],[1,15]]
输出: 1
解释:
- 下标为 1 的按钮在时间 2 被按下。
- 下标为 2 的按钮在时间 5 被按下,因此按下时间为
5 - 2 = 3。 - 下标为 3 的按钮在时间 9 被按下,因此按下时间为
9 - 5 = 4。 - 下标为 1 的按钮再次在时间 15 被按下,因此按下时间为
15 - 9 = 6。
最终,下标为 1 的按钮按下时间最长,为 6。
示例 2:
输入: events = [[10,5],[1,7]]
输出: 10
解释:
- 下标为 10 的按钮在时间 5 被按下。
- 下标为 1 的按钮在时间 7 被按下,因此按下时间为
7 - 5 = 2。
最终,下标为 10 的按钮按下时间最长,为 5。
提示:
1 <= events.length <= 1000events[i] == [indexi, timei]1 <= indexi, timei <= 105- 输入保证数组
events按照timei的递增顺序排序。
方法一:一次遍历 · Go 实现
思考
按下时长为相邻事件的时间差,第一个按钮的时长即其时间戳。 ,扫一遍即可。
维护当前最长时长与对应下标;时长更大,或时长相同但下标更小,则更新。
事件已按时间排序,不必再排序。
我们定义两个变量 和 ,分别表示按下时间最长的按钮的索引和按下时间。
接下来,我们从下标 开始遍历数组 ,对于每个 ,我们计算当前按钮的按下时间 ,其中 是当前按钮的按下时间,而 是前一个按钮的按下时间。如果 或者 且当前按钮的索引 小于 ,我们更新 和 。
最后,我们返回 。
时间复杂度 ,其中 是数组 的长度。空间复杂度 。
func buttonWithLongestTime(events [][]int) int {
ans, t := events[0][0], events[0][1]
for k, e := range events[1:] {
i, t2, t1 := e[0], e[1], events[k][1]
d := t2 - t1
if d > t || (d == t && i < ans) {
ans, t = i, d
}
}
return ans
}
3392. 统计符合条件长度为 3 的子数组数目 ​
题目、示例与约束
给你一个整数数组 nums ,请你返回长度为 3 的 子数组 的数量,满足第一个数和第三个数的和恰好为第二个数的一半。
子数组 指的是一个数组中连续 非空 的元素序列。
示例 1:
输入:nums = [1,2,1,4,1]
输出:1
解释:
只有子数组 [1,4,1] 包含 3 个元素且第一个和第三个数字之和是中间数字的一半。number.
示例 2:
输入:nums = [1,1,1]
输出:0
解释:
[1,1,1] 是唯一长度为 3 的子数组,但第一个数和第三个数的和不是第二个数的一半。
提示:
3 <= nums.length <= 100-100 <= nums[i] <= 100
方法一:一次遍历 · Go 实现
思考
长度为 的子数组满足两翼之和的两倍等于中间。 ,枚举中间下标即可。
对每个 判断 ,计数为真的个数。
我们遍历数组 每个长度为 的子数组,判断第一个数和第三个数的和乘以 是否等于第二个数,若是,答案加 。
遍历结束后,返回答案即可。
时间复杂度 ,其中 为数组 的长度。空间复杂度 。
func countSubarrays(nums []int) (ans int) {
for i := 1; i+1 < len(nums); i++ {
if (nums[i-1]+nums[i+1])*2 == nums[i] {
ans++
}
}
return
}
3417. 跳过交替单元格的之字形遍历 ​
题目、示例与约束
给你一个 m x n 的二维数组 grid,数组由 正整数 组成。
你的任务是以 之字形 遍历 grid,同时跳过每个 交替 的单元格。
之字形遍历的定义如下:
- 从左上角的单元格
(0, 0)开始。 - 在当前行中向 右 移动,直到到达该行的末尾。
- 下移到下一行,然后在该行中向 左 移动,直到到达该行的开头。
- 继续在行间交替向右和向左移动,直到所有行都被遍历完。
注意:在遍历过程中,必须跳过每个 交替 的单元格。
返回一个整数数组 result,其中包含按 顺序 记录的、且跳过交替单元格后的之字形遍历中访问到的单元格值。
示例 1:
输入: grid = [[1,2],[3,4]]
输出: [1,4]
解释:

示例 2:
输入: grid = [[2,1],[2,1],[2,1]]
输出: [2,1,2]
解释:

示例 3:
输入: grid = [[1,2,3],[4,5,6],[7,8,9]]
输出: [1,3,5,7,9]
解释:

提示:
2 <= n == grid.length <= 502 <= m == grid[i].length <= 501 <= grid[i][j] <= 2500
方法一:模拟 · Go 实现
思考
网格至多 ,按题面模拟走法即可。偶数行走从左到右,奇数行从右到左,并且沿途隔一个格子取一个。
若先生成完整之字形序列再下标取偶数位,需要额外数组;把「是否收取」做成沿途翻转的布尔量,可以在遍历时直接写入答案。
因此按行扫描,奇数行先就地反转,再用 交替决定是否加入。行间连续性由 跨行保持,无需记录全局下标。
我们遍历每一行,如果当前行的索引是奇数,我们就将这一行的元素逆序,然后遍历这一行的元素,按照题目要求的规则将元素加入答案数组中。
时间复杂度 ,其中 和 分别是二维数组 的行数和列数。忽略答案数组的空间消耗,空间复杂度 。
func zigzagTraversal(grid [][]int) (ans []int) {
ok := true
for i, row := range grid {
if i%2 != 0 {
slices.Reverse(row)
}
for _, x := range row {
if ok {
ans = append(ans, x)
}
ok = !ok
}
}
return
}
3423. 循环数组中相邻元素的最大差值 ​
题目、示例与约束
给你一个 循环 数组 nums ,请你找出相邻元素之间的 最大 绝对差值。
注意:一个循环数组中,第一个元素和最后一个元素是相邻的。
示例 1:
输入:nums = [1,2,4]
输出:3
解释:
由于 nums 是循环的,nums[0] 和 nums[2] 是相邻的,它们之间的绝对差值是最大值 |4 - 1| = 3 。
示例 2:
输入:nums = [-5,-10,-5]
输出:5
解释:
相邻元素 nums[0] 和 nums[1] 之间的绝对差值为最大值 |-5 - (-10)| = 5 。
提示:
2 <= nums.length <= 100-100 <= nums[i] <= 100
方法一:模拟 · Go 实现
思考
环形数组的相邻差还包含首尾一对。 ,扫一遍即可。
把 接到末尾后,所有相邻对都落在线性序列里,无需特判最后一对。
因此对 做 ,取绝对差的最大值。
我们遍历数组 ,计算相邻元素之间的绝对差值,并维护最大的绝对差值,最后与首尾元素之间的绝对差值比较,取最大值即可。
时间复杂度 ,其中 为数组 的长度。空间复杂度 。
func maxAdjacentDistance(nums []int) int {
ans := abs(nums[0] - nums[len(nums)-1])
for i := 1; i < len(nums); i++ {
ans = max(ans, abs(nums[i]-nums[i-1]))
}
return ans
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
3452. 好数字之和 ​
题目、示例与约束
给定一个整数数组 nums 和一个整数 k,如果元素 nums[i] 严格 大于下标 i - k 和 i + k 处的元素(如果这些元素存在),则该元素 nums[i] 被认为是 好 的。如果这两个下标都不存在,那么 nums[i] 仍然被认为是 好 的。
返回数组中所有 好 元素的 和。
示例 1:
输入: nums = [1,3,2,1,5,4], k = 2
输出: 12
解释:
好的数字包括 nums[1] = 3,nums[4] = 5 和 nums[5] = 4,因为它们严格大于下标 i - k 和 i + k 处的数字。
示例 2:
输入: nums = [2,1], k = 1
输出: 2
解释:
唯一的好数字是 nums[0] = 2,因为它严格大于 nums[1]。
提示:
2 <= nums.length <= 1001 <= nums[i] <= 10001 <= k <= floor(nums.length / 2)
方法一:遍历 · Go 实现
思考
好数须严格大于两侧距离为 的邻居(若存在)。 ,逐个检查即可。
越界的一侧视为没有约束,不能把它当成 去比较。
对每个 ,若左侧存在且 ,或右侧存在且 ,则跳过;否则累加 。
我们可以遍历数组 ,对于每个元素 ,检查是否满足条件:
- 如果 且 ,则 不是好数字;
- 如果 且 ,则 不是好数字。
- 否则, 是好数字,我们将其累加到答案中。
遍历结束后,返回答案即可。
时间复杂度 ,其中 是数组 的长度。空间复杂度 。
func sumOfGoodNumbers(nums []int, k int) (ans int) {
for i, x := range nums {
if i >= k && x <= nums[i-k] {
continue
}
if i+k < len(nums) && x <= nums[i+k] {
continue
}
ans += x
}
return
}
3502. 到达每个位置的最小费用 ​
题目、示例与约束
给你一个长度为 n 的整数数组 cost 。当前你位于位置 n(队伍的末尾),队伍中共有 n + 1 人,编号从 0 到 n 。
你希望在队伍中向前移动,但队伍中每个人都会收取一定的费用才能与你 交换位置。与编号 i 的人交换位置的费用为 cost[i] 。
你可以按照以下规则与他人交换位置:
- 如果对方在你前面,你 必须 支付
cost[i]费用与他们交换位置。 - 如果对方在你后面,他们可以免费与你交换位置。
返回一个大小为 n 的数组 answer,其中 answer[i] 表示到达队伍中每个位置 i 所需的 最小 总费用。
示例 1:
输入: cost = [5,3,4,1,3,2]
输出: [5,3,3,1,1,1]
解释:
我们可以通过以下方式到达每个位置:
i = 0。可以花费 5 费用与编号 0 的人交换位置。i = 1。可以花费 3 费用与编号 1 的人交换位置。i = 2。可以花费 3 费用与编号 1 的人交换位置,然后免费与编号 2 的人交换位置。i = 3。可以花费 1 费用与编号 3 的人交换位置。i = 4。可以花费 1 费用与编号 3 的人交换位置,然后免费与编号 4 的人交换位置。i = 5。可以花费 1 费用与编号 3 的人交换位置,然后免费与编号 5 的人交换位置。
示例 2:
输入: cost = [1,2,4,6,7]
输出: [1,1,1,1,1]
解释:
可以花费 1 费用与编号 0 的人交换位置,然后可以免费到达队伍中的任何位置 i。
提示
1 <= n == cost.length <= 1001 <= cost[i] <= 100
方法一:脑筋急转弯 · Go 实现
思考
到达位置 既可以支付 直接登上,也可以先登上某个 再无额外费用走到 。因此 等于 。
从左向右维护前缀最小值即可一次扫出全部答案,无需建图求最短路。
根据题目描述,每个位置 的最小费用,就是从 到 的最小费用。我们可以用一个变量 来记录从 到 的最小费用。
我们从 开始遍历每个位置 ,每次更新 为 ,然后将 赋值给答案数组的第 个位置。
最后返回答案数组即可。
时间复杂度 ,其中 为数组 的长度。忽略答案数组的空间消耗,空间复杂度 。
func minCosts(cost []int) []int {
n := len(cost)
ans := make([]int, n)
mi := cost[0]
for i, c := range cost {
mi = min(mi, c)
ans[i] = mi
}
return ans
}
3565. 顺序网格路径覆盖 🔒 ​
题目、示例与约束
给定一个 m x n 大小的 2 维数组 grid,和一个整数 k。grid 中有 k 个单元格包含从 1 到 k 的值,每个值恰好出现一次,其余单元格的值为 0。
你可以从任何单元格开始,并且从一个单元格移动到相邻的单元格(上,下,左,右)。你必须找到一条 grid 中的路径,满足:
- 访问
grid中的每个单元格 恰好一次。 - 按顺序 访问值为 1 到
k的单元格。
返回一个大小为 (m * n) 的二维数组 result,其中 result[i] = [xi, yi] 表示路径中访问的第 i 个单元格。如果存在多条这样的路径,你可以返回 任何 一条。
如果不存在这样的路径,返回一个 空 数组。
示例 1:
输入:grid = [[0,0,0],[0,1,2]], k = 2
输出:[[0,0],[1,0],[1,1],[1,2],[0,2],[0,1]]
解释:

示例 2:
输入:grid = [[1,0,4],[3,0,2]], k = 4
输出:[]
解释:
没有满足条件的路径。
提示:
1 <= m == grid.length <= 51 <= n == grid[i].length <= 51 <= k <= m * n0 <= grid[i][j] <= kgrid包含 1 到k的所有整数 恰好 一次。
方法一:状态压缩 + DFS · Go 实现
思考
网格至多 ,需找一条访问全部格子且按 顺序经过特殊值的路径。状态可用位掩码表示已访问集合。
从值为 或 的格子出发 DFS,下一步必须未访问且值为 或当前期望 ;走到特殊值则 加一。回溯并尝试每个合法起点。
我们注意到,矩阵的大小不超过 ,因此可以使用状态压缩来表示已经访问过的格子。我们可以使用一个整数 来表示已经访问过的格子,其中第 位为 1 表示格子 已经被访问过,0 表示未被访问过。
接下来,我们遍历每一个格子作为起点,如果该格子是 0 或 1,则从该格子开始进行深度优先搜索(DFS)。在 DFS 中,我们将当前格子加入路径中,并将其标记为已访问。然后,我们检查当前格子的值,如果等于 ,则将 加 1。接着,我们尝试向四个方向移动到相邻的格子,如果相邻格子未被访问且其值为 0 或 ,则继续进行 DFS。
如果 DFS 成功找到了一条完整的路径,则返回该路径。如果无法找到完整路径,则回溯,撤销当前格子的访问标记,并尝试其他方向。
时间复杂度 ,空间复杂度 ,其中 和 分别是矩阵的行数和列数。
func findPath(grid [][]int, k int) [][]int {
_ = k
m := len(grid)
n := len(grid[0])
var st uint64
path := [][]int{}
dirs := []int{-1, 0, 1, 0, -1}
f := func(i, j int) int { return i*n + j }
var dfs func(int, int, int) bool
dfs = func(i, j, v int) bool {
path = append(path, []int{i, j})
if len(path) == m*n {
return true
}
idx := f(i, j)
st |= 1 << idx
if grid[i][j] == v {
v++
}
for t := 0; t < 4; t++ {
a, b := dirs[t], dirs[t+1]
x, y := i+a, j+b
if 0 <= x && x < m && 0 <= y && y < n {
idx2 := f(x, y)
if (st>>idx2)&1 == 0 && (grid[x][y] == 0 || grid[x][y] == v) {
if dfs(x, y, v) {
return true
}
}
}
}
path = path[:len(path)-1]
st ^= 1 << idx
return false
}
for i := 0; i < m; i++ {
for j := 0; j < n; j++ {
if grid[i][j] == 0 || grid[i][j] == 1 {
if dfs(i, j, 1) {
return path
}
path = path[:0]
st = 0
}
}
}
return [][]int{}
}
3616. 学生替换人数 🔒 ​
题目、示例与约束
给定一个整数数组 ranks,其中 ranks[i] 表示第 i 个 按顺序 到达的学生排名。数字越低表示排名 越好。
最初,默认 选中 第一个学生。
当一名排名 严格 更好的学生到来时,会发生 替换,并 取代 当前的选择。
返回替换的总数。
示例 1:
输入:ranks = [4,1,2]
输出:1
解释:
- 第一个
ranks[0] = 4的学生最初被选中。 - 第二个学生
ranks[1] = 1比当前选择更好,因此发生替换。 - 第三名学生排名更差,因此没有发生替换。
- 因此,替换的人数为 1。
示例 2:
输入:ranks = [2,2,3]
输出:0
解释:
- 第一个
ranks[0] = 2的学生最初被选中。 - 两个
ranks[1] = 2或ranks[2] = 3都不如当前选择好。 - 因此,替换的人数为 0。
提示:
1 <= ranks.length <= 1051 <= ranks[i] <= 105
方法一:模拟 · Go 实现
思考
替换发生当且仅当出现严格更优(更小)的排名。只需维护当前选中的排名 ,初值为第一位学生。
从左到右扫描,若 则更新 并计数加一。 ,一次遍历即可,不必记录历史列表。
我们用一个变量 来记录当前选中的学生的排名。遍历数组 ,如果遇到一个排名更好的学生(即 ),则更新 并将答案加一。
遍历结束后,返回答案即可。
时间复杂度 ,其中 是学生的数量。空间复杂度 。
func totalReplacements(ranks []int) (ans int) {
cur := ranks[0]
for _, x := range ranks {
if x < cur {
cur = x
ans++
}
}
return
}
3637. 三段式数组 I ​
题目、示例与约束
给你一个长度为 n 的整数数组 nums。
如果存在索引 0 < p < q < n − 1,使得数组满足以下条件,则称其为 三段式数组(trionic):
nums[0...p]严格 递增,nums[p...q]严格 递减,nums[q...n − 1]严格 递增。
如果 nums 是三段式数组,返回 true;否则,返回 false。
示例 1:
输入: nums = [1,3,5,4,2,6]
输出: true
解释:
选择 p = 2, q = 4:
nums[0...2] = [1, 3, 5]严格递增 (1 < 3 < 5)。nums[2...4] = [5, 4, 2]严格递减 (5 > 4 > 2)。nums[4...5] = [2, 6]严格递增 (2 < 6)。
示例 2:
输入: nums = [2,1,3]
输出: false
解释:
无法选出能使数组满足三段式要求的 p 和 q 。
提示:
3 <= n <= 100-1000 <= nums[i] <= 1000
方法一:一次遍历 · Go 实现
思考
三段式要求严格升、严格降、再严格升,三段均非空。分段检查比枚举两个峰谷更直接。
指针 走完第一段上升,若停在起点则失败。 接着走下降,若未移动或已到末尾则缺少中段或末段。
最后继续上升,必须恰好走到 。一次遍历同时验证三段的存在性与严格性。
我们首先定义一个指针 ,初始时 ,表示当前指向数组的第一个元素。我们将 向右移动,直到找到第一个不满足严格递增的元素,即 。如果此时 ,说明数组的前半部分没有严格递增的部分,因此直接返回 。
接下来,我们定义另一个指针 ,初始时 ,表示当前指向数组的第二个部分的第一个元素。我们将 向右移动,直到找到第一个不满足严格递减的元素,即 。如果此时 或者 ,说明数组的第二部分没有严格递减的部分或者没有第三部分,因此直接返回 。
如果以上条件都满足,说明数组是三段式的,返回 。
时间复杂度 ,其中 是数组的长度。空间复杂度 ,只使用了常数级别的额外空间。
func isTrionic(nums []int) bool {
n := len(nums)
p := 0
for p < n-2 && nums[p] < nums[p+1] {
p++
}
if p == 0 {
return false
}
q := p
for q < n-1 && nums[q] > nums[q+1] {
q++
}
if q == p || q == n-1 {
return false
}
for q < n-1 && nums[q] < nums[q+1] {
q++
}
return q == n-1
}
3687. 图书馆逾期罚款计算器 🔒 ​
题目、示例与约束
给定一个整数数组 daysLate,其中 daysLate[i] 表示第 i 书的归还日期晚了几天。
罚款计算如下:
- 如果
daysLate[i] == 1,罚款为 1。 - 如果
2 <= daysLate[i] <= 5,罚款为2 * daysLate[i]。 - 如果
daysLate[i] > 5,罚款为3 * daysLate[i]。
返回所有书的总罚款。
示例 1:
输入:daysLate = [5,1,7]
输入:32
解释:
daysLate[0] = 5:罚款是2 * daysLate[0] = 2 * 5 = 10。daysLate[1] = 1:罚款是1。daysLate[2] = 7:罚款是3 * daysLate[2] = 3 * 7 = 21。- 因此,总罚款为
10 + 1 + 21 = 32。
示例 2:
输入:daysLate = [1,1]
输出:2
解释:
daysLate[0] = 1:罚款为1。daysLate[1] = 1:罚款为1。- 因此,总罚款为
1 + 1 = 2。
提示:
1 <= daysLate.length <= 1001 <= daysLate[i] <= 100
方法一:模拟 · Go 实现
思考
罚款按逾期天数分段: 天为 , 天为 ,更长为 。各项独立,求和即可。
函数 实现三段,对 映射累加。 ,无需额外结构。
我们定义一个函数 来计算每本书的罚款:
然后我们对数组 中的每个元素 计算 并累加得到总罚款。
时间复杂度 ,其中 是数组 的长度。空间复杂度 。
func lateFee(daysLate []int) (ans int) {
f := func(x int) int {
if x == 1 {
return 1
} else if x > 5 {
return 3 * x
}
return 2 * x
}
for _, x := range daysLate {
ans += f(x)
}
return
}
3701. 计算交替和 ​
题目、示例与约束
给你一个整数数组 nums。
交替和 定义为:将 nums 中偶数下标位置的元素 相加 ,减去 奇数下标位置的元素。即:nums[0] - nums[1] + nums[2] - nums[3]...
返回表示 nums 的交替和的整数。
示例 1:
输入: nums = [1,3,5,7]
输出: -4
解释:
- 偶数下标位置的元素是
nums[0] = 1和nums[2] = 5,因为 0 和 2 是偶数。 - 奇数下标位置的元素是
nums[1] = 3和nums[3] = 7,因为 1 和 3 是奇数。 - 交替和为
nums[0] - nums[1] + nums[2] - nums[3] = 1 - 3 + 5 - 7 = -4。
示例 2:
输入: nums = [100]
输出: 100
解释:
- 唯一的偶数下标位置的元素是
nums[0] = 100,因为 0 是偶数。 - 没有奇数下标位置的元素。
- 交替和为
nums[0] = 100。
提示:
1 <= nums.length <= 1001 <= nums[i] <= 100
方法一:模拟 · Go 实现
思考
数组长度不超过 ,直接按定义累加即可。交替和正是偶数下标之和减去奇数下标之和,用切片一次取完两段即可,不必再按奇偶分支书写循环。
我们可以直接遍历数组 ,对于每个下标 ,如果 是偶数,则将 加到答案中,否则将 减去。
最后返回答案即可。
时间复杂度 ,其中 是数组 的长度。空间复杂度 。
func alternatingSum(nums []int) (ans int) {
for i, x := range nums {
if i%2 == 0 {
ans += x
} else {
ans -= x
}
}
return
}
3708. 最长斐波那契子数组 ​
题目、示例与约束
给你一个由 正 整数组成的数组 nums。
斐波那契 数组是一个连续序列,其中第三项及其后的每一项都等于这一项前面两项之和。
返回 nums 中最长 斐波那契 子数组的长度。
注意: 长度为 1 或 2 的子数组总是 斐波那契 的。
子数组 是数组中 非空 的连续元素序列。
示例 1:
输入: nums = [1,1,1,1,2,3,5,1]
输出: 5
解释:
最长的斐波那契子数组是 nums[2..6] = [1, 1, 2, 3, 5]。
[1, 1, 2, 3, 5] 是斐波那契的,因为 1 + 1 = 2, 1 + 2 = 3, 且 2 + 3 = 5。
示例 2:
输入: nums = [5,2,7,9,16]
输出: 5
解释:
最长的斐波那契子数组是 nums[0..4] = [5, 2, 7, 9, 16]。
[5, 2, 7, 9, 16] 是斐波那契的,因为 5 + 2 = 7 ,2 + 7 = 9 且 7 + 9 = 16。
示例 3:
输入: nums = [1000000000,1000000000,1000000000]
输出: 2
解释:
最长的斐波那契子数组是 nums[1..2] = [1000000000, 1000000000]。
[1000000000, 1000000000] 是斐波那契的,因为它的长度为 2。
提示:
3 <= nums.length <= 1051 <= nums[i] <= 109
方法一:递推 · Go 实现
思考
斐波那契关系只约束相邻三项,段在中间断开后必须重开,不必为每个右端点回溯。用变量记录以当前下标结尾的段长,满足递推则延长,否则重置为 (任意两项都合法)。
我们可以使用一个变量 来记录以当前元素结尾的最长斐波那契子数组的长度。初始时, ,表示任意两个元素都可以构成一个斐波那契子数组。
然后我们从索引 开始遍历数组,对于每个元素 ,如果它等于前两个元素之和,即 ,则说明当前元素可以接在前面的斐波那契子数组后面,我们将 增加 。否则,说明当前元素不能接在前面的斐波那契子数组后面,我们将 重置为 。在遍历过程中,我们不断更新答案 。
时间复杂度为 ,其中 是数组的长度。空间复杂度为 。
func longestSubarray(nums []int) int {
f := 2
ans := f
for i := 2; i < len(nums); i++ {
if nums[i] == nums[i-1]+nums[i-2] {
f++
ans = max(ans, f)
} else {
f = 2
}
}
return ans
}
3809. 最好可到达的塔 ​
题目、示例与约束
给你一个二维整数数组 towers,其中 towers[i] = [xi, yi, qi] 表示第 i 座塔的坐标 (xi, yi) 和质量因子 qi。
另外给你一个整数数组 center = [cx, cy] 表示你的位置,以及一个整数 radius。
如果一座塔与 center 之间的 曼哈顿距离小于或等于 radius,则称该塔是 可到达的。
在所有可到达的塔中:
- 返回质量因子 最大 的塔的坐标。
- 如果存在并列的塔,返回坐标 字典序最小 的塔。如果没有塔是可到达的,返回
[-1, -1]。
(xi, yi) 和 (xj, yj) 之间的 曼哈顿距离 为 |xi - xj| + |yi - yj|。
坐标 [xi, yi] 字典序小于 [xj, yj] 是指:xi < xj,或者 xi == xj 且 yi < yj。
|x| 表示 x 的 绝对值。
示例 1:
输入: towers = [[1,2,5], [2,1,7], [3,1,9]], center = [1,1], radius = 2
输出: [3,1]
解释:
- 塔
[1, 2, 5]:曼哈顿距离 =|1 - 1| + |2 - 1| = 1,可到达。 - 塔
[2, 1, 7]:曼哈顿距离 =|2 - 1| + |1 - 1| = 1,可到达。 - 塔
[3, 1, 9]:曼哈顿距离 =|3 - 1| + |1 - 1| = 2,可到达。
所有塔都是可到达的。最大质量因子为 9,对应塔 [3, 1]。
示例 2:
输入: towers = [[1,3,4], [2,2,4], [4,4,7]], center = [0,0], radius = 5
输出: [1,3]
解释:
- 塔
[1, 3, 4]:曼哈顿距离 =|1 - 0| + |3 - 0| = 4,可到达。 - 塔
[2, 2, 4]:曼哈顿距离 =|2 - 0| + |2 - 0| = 4,可到达。 - 塔
[4, 4, 7]:曼哈顿距离 =|4 - 0| + |4 - 0| = 8,不可到达。
在可到达的塔中,最大质量因子为 4。[1, 3] 和 [2, 2] 的质量因子相同,因此返回字典序较小的坐标 [1, 3]。
示例 3:
输入: towers = [[5,6,8], [0,3,5]], center = [1,2], radius = 1
输出: [-1,-1]
解释:
- 塔
[5, 6, 8]:曼哈顿距离 =|5 - 1| + |6 - 2| = 8,不可到达。 - 塔
[0, 3, 5]:曼哈顿距离 =|0 - 1| + |3 - 2| = 2,不可到达。
在给定半径内没有可到达的塔,故返回 [-1, -1]。
提示:
1 <= towers.length <= 105towers[i] = [xi, yi, qi]center = [cx, cy]0 <= xi, yi, qi, cx, cy <= 1050 <= radius <= 105
方法一:一次遍历 · Go 实现
思考
在曼哈顿距离不超过 的塔中选质量最大者,并列取坐标字典序最小。 ,预处理空间索引并无必要。
每座塔与中心的距离独立,且只需与当前最优比较。
一次遍历即可:距离超限则跳过,否则按质量、再按坐标更新最优下标。
若从未选中任何塔则返回 ,否则输出该下标的坐标。
我们定义一个变量 来记录当前最佳塔的下标,初始时 。然后我们遍历每一座塔,计算其与 之间的曼哈顿距离 :
如果 ,则该塔不可到达,跳过该塔。否则,我们比较当前塔与最佳塔的质量因子 :
- 如果 ,说明当前还没有可到达的塔,我们将 更新为当前塔的下标。
- 如果当前塔的质量因子 大于最佳塔的质量因子 ,则将 更新为当前塔的下标。
- 如果当前塔的质量因子 等于最佳塔的质量因子 ,则比较两座塔的坐标,选择字典序较小的塔。
遍历结束后,如果 ,说明没有可到达的塔,返回 。否则,返回最佳塔的坐标。
时间复杂度 ,其中 是塔的数量。空间复杂度 。
func bestTower(towers [][]int, center []int, radius int) []int {
cx, cy := center[0], center[1]
idx := -1
for i, a := range towers {
x, y, q := a[0], a[1], a[2]
dist := abs(x-cx) + abs(y-cy)
if dist > radius {
continue
}
if idx == -1 ||
towers[idx][2] < q ||
(towers[idx][2] == q &&
(x < towers[idx][0] ||
(x == towers[idx][0] && y < towers[idx][1]))) {
idx = i
}
}
if idx == -1 {
return []int{-1, -1}
}
return []int{towers[idx][0], towers[idx][1]}
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
3818. 移除前缀使数组严格递增 ​
题目、示例与约束
给你一个整数数组 nums。
你需要从 nums 中 恰好 移除一个前缀(可以为空)。
返回一个整数,表示被移除的前缀的 最小 长度,使得剩余的数组 严格递增 。
数组的 前缀 是从数组的起始位置开始,一直延伸到任意位置的子数组。
如果一个数组的每个元素都严格大于它的前一个元素(若存在),则称该数组 严格递增 。
示例 1:
输入: nums = [1,-1,2,3,3,4,5]
输出: 4
解释:
移除前缀 prefix = [1, -1, 2, 3] 后,剩余数组为 [3, 4, 5],严格递增。
示例 2:
输入: nums = [4,3,-2,-5]
输出: 3
解释:
移除前缀 prefix = [4, 3, -2] 后,剩余数组为 [-5],严格递增。
示例 3:
输入: nums = [1,2,3,4]
输出: 0
解释:
数组 nums = [1, 2, 3, 4] 已经是严格递增的,因此移除空前缀即可。
提示:
1 <= nums.length <= 105-109 <= nums[i] <= 109
方法一:倒序遍历 · Go 实现
思考
必须删去一个前缀(可空)使剩余严格递增,并要前缀尽量短。 ,不能枚举每个前缀再检查。
剩余段必须是原数组的某个后缀,且该后缀本身严格递增。最短前缀对应最长合法后缀。
从右往左走,遇到第一处 即无法再延长,答案为 。
若一直走到头,则整个数组已递增,答案为 。
我们可以从数组的末尾开始往前遍历,找到第一个不满足严格递增条件的位置 ,即满足 的位置。此时,移除前缀的最小长度即为 。
如果整个数组都是严格递增的,那么我们不需要移除任何前缀,返回 即可。
时间复杂度 ,其中 为数组的长度。空间复杂度 。
func minimumPrefixLength(nums []int) int {
for i := len(nums) - 1; i > 0; i-- {
if nums[i-1] >= nums[i] {
return i
}
}
return 0
}
3833. 统计主导元素下标数 ​
题目、示例与约束
给你一个长度为 n 的整数数组 nums。
当下标 i 满足以下条件时,该下标处的元素被称为 主导元素:nums[i] > average(nums[i + 1], nums[i + 2], ..., nums[n - 1])
你的任务是统计数组中 主导元素 的下标数。
平均值 是指一组数的总和除以该组数的个数得到的值。
注意:数组的 最右边元素 不算作 主导元素 。
示例 1:
输入: nums = [5,4,3]
输出: 2
解释:
- 在下标
i = 0处,值 5 是主导元素,因为5 > average(4, 3) = 3.5。 - 在下标
i = 1处,值 4 是主导元素,相对于子数组[3]。 - 下标
i = 2不是主导元素,因为它右侧没有元素。因此答案是 2。
示例 2:
输入: nums = [4,1,2]
输出: 1
解释:
- 在下标
i = 0处,值 4 是主导元素,相对于子数组[1, 2]。 - 在下标
i = 1处,值 1 不是主导元素。 - 下标
i = 2不是主导元素,因为它右侧没有元素。因此答案是 1。
提示:
1 <= nums.length <= 1001 <= nums[i] <= 100
方法一:逆序遍历 · Go 实现
思考
主导元须严格大于其右侧平均值,最右元素不计。 ,从每个 重算右侧和可通过,但会重复累加。
右侧平均值只依赖后缀和与后缀长度。
自右向左维护后缀和 ,在下标 用 比较 ,再把 并入后缀。
一次逆序遍历完成全部判定。
我们可以从后向前遍历数组,维护一个后缀和 ,表示当前元素右侧所有元素的和。对于每个元素,判断其是否大于右侧元素的平均值 ,如果是,则将答案加一。最后返回答案即可。
时间复杂度 ,其中 是数组 的长度。空间复杂度 。
func dominantIndices(nums []int) int {
n := len(nums)
ans := 0
suf := nums[n-1]
for i := n - 2; i >= 0; i-- {
if nums[i]*(n-i-1) > suf {
ans++
}
suf += nums[i]
}
return ans
}
3847. 计算比赛分数差 ​
题目、示例与约束
给你一个整数数组 nums,其中 nums[i] 表示在第 i 场比赛中获得的分数。
恰好 有两位玩家。初始时,第一位玩家为 主动玩家,第二位玩家为 被动玩家。
按顺序 将下述规则应用于每场比赛 i:
- 如果
nums[i]是奇数,主动玩家和被动玩家互换角色。 - 在每第 6 场比赛(即比赛索引为
5, 11, 17, ...的比赛中),主动玩家和被动玩家互换角色。 - 主动玩家参与第
i场比赛,并获得nums[i]分。
返回 分数差,即第一位玩家的 总分 减去第二位玩家的 总分 。
示例 1:
输入: nums = [1,2,3]
输出: 0
解释:
- 第 0 场比赛:分数为奇数,第二位玩家成为主动玩家,获得
nums[0] = 1分。 - 第 1 场比赛:没有交换角色。第二位玩家获得
nums[1] = 2分。 - 第 2 场比赛:分数为奇数,第一位玩家成为主动玩家,获得
nums[2] = 3分。 - 分数差为
3 - 3 = 0。
示例 2:
输入: nums = [2,4,2,1,2,1]
输出: 4
解释:
- 第 0 到第 2 场比赛:第一位玩家获得
2 + 4 + 2 = 8分。 - 第 3 场比赛:分数为奇数,第二位玩家成为主动玩家,获得
nums[3] = 1分。 - 第 4 场比赛:第二位玩家获得
nums[4] = 2分。 - 第 5 场比赛:分数为奇数,玩家互换角色。由于这是第 6 场比赛,玩家再次互换角色。第二位玩家获得
nums[5] = 1分。 - 分数差为
8 - 4 = 4。
示例 3:
输入: nums = [1]
输出: -1
解释:
- 第 0 场比赛:分数为奇数,第二位玩家成为主动玩家,获得
nums[0] = 1分。 - 分数差为
0 - 1 = -1。
提示:
1 <= nums.length <= 10001 <= nums[i] <= 1000
方法一:模拟 · Go 实现
思考
主动玩家得分,奇数分或每第 场会交换角色。求第一位减第二位的总分差。 ,按规则模拟。
差可用符号 表示当前主动方属于第一位或第二位,把分数乘 累加。
先按奇数翻转 ,再按 翻转,然后计入 。
顺序与题目「先判奇数、再判第 场、最后得分」一致。
我们用一个变量 来表示当前玩家的角色,初始时 ,当 时表示第一位玩家是主动玩家,当 时表示第二位玩家是主动玩家。对于每场比赛,我们根据题目描述更新 的值,并将当前比赛的分数乘以 加到答案中。最后返回答案即可。
时间复杂度 ,其中 是数组 的长度。空间复杂度 。
func scoreDifference(nums []int) int {
ans := 0
k := 1
for i, x := range nums {
if x%2 != 0 {
k = -k
}
if i%6 == 5 {
k = -k
}
ans += k * x
}
return ans
}
3861. 容量最小的箱子 ​
题目、示例与约束
给你一个整数数组 capacity,其中 capacity[i] 表示第 i 个箱子的容量,以及一个整数 itemSize,表示一个物品的大小。
如果第 i 个箱子的容量满足 capacity[i] >= itemSize,那么该箱子可以存放该物品。
要求返回可以存放该物品的容量 最小 的箱子的下标。如果有多个这样的箱子,返回下标 最小 的一个。
如果没有任何箱子可以存放该物品,则返回 -1。
示例 1:
输入: capacity = [1,5,3,7], itemSize = 3
输出: 2
解释:
下标为 2 的箱子容量为 3,是可以存放该物品的容量最小的箱子,因此答案是 2。
示例 2:
输入: capacity = [3,5,4,3], itemSize = 2
输出: 0
解释:
可以存放该物品的最小容量为 3,出现在下标 0 和 3。由于下标 0 更小,因此答案是 0。
示例 3:
输入: capacity = [4], itemSize = 5
输出: -1
解释:
没有任何箱子的容量足够存放该物品,因此答案是 -1。
提示:
1 <= capacity.length <= 1001 <= capacity[i] <= 1001 <= itemSize <= 100
方法一:一次遍历 · Go 实现
思考
在容量 的箱子中选容量最小者,并列取最小下标。长度 ,一次遍历。
无需排序:边走边记录当前最优下标。
当 且(尚未选取或 更小)时更新。因从左到右,容量相等时自然保留更小下标。
若从未更新则 。
我们初始化一个变量 ,表示可以存放该物品的容量最小的箱子的下标,初始值为 。我们遍历数组 ,对于每个箱子,如果它的容量大于等于 ,则说明它可以存放该物品。此时,我们需要判断它是否是目前为止容量最小的箱子,如果是,则更新 的值。最后返回 即可。
时间复杂度 ,其中 是数组 的长度。空间复杂度 。
func minimumIndex(capacity []int, itemSize int) int {
ans := -1
for i, x := range capacity {
if x >= itemSize && (ans == -1 || x < capacity[ans]) {
ans = i
}
}
return ans
}
3872. 替换最多一个元素后的最长等差子数组 ​
题目、示例与约束
给你一个整数数组 nums。
如果子数组中相邻元素的差值是一个常数,那么这个子数组被称为 等差子数组。
你可以将 nums 中的 最多 一个元素替换为任意一个 整数。然后,从 nums 中选择一个等差子数组。
返回一个整数,该整数表示你可以选择的 最长 等差子数组的长度。
子数组 是数组中一段连续的元素序列。
示例 1:
输入: nums = [9,7,5,10,1]
输出: 5
解释:
- 将
nums[3] = 10替换为 3,数组变为[9, 7, 5, 3, 1]。 - 选择子数组
[9, 7, 5, 3, 1],它是等差数组,相邻元素的公差为 -2。
示例 2:
输入: nums = [1,2,6,7]
输出: 3
解释:
- 将
nums[0] = 1替换为 -2,数组变为[-2, 2, 6, 7]。 - 选择子数组
[-2, 2, 6, 7],它是等差数组,相邻元素的公差为 4。
提示:
4 <= nums.length <= 1051 <= nums[i] <= 105
方法一:前后缀分解 + 枚举 · Go 实现
思考
至多改一个元素后,求最长连续等差子数组。 ,枚举修改位置再扩展过慢。
不修改时的最长等差由相邻差相等的游程给出。修改 可延长左侧游程、右侧游程,或把两侧接到同一公差。
预处理以 结尾、以 开头的等差长度 ,再枚举修改点按三种方式更新。
连接两侧时要求 为偶数,且公差与左右游程一致才把额外长度加上。
我们先计算出数组中相邻元素的差值,记为数组 ,其中 。
接下来,我们定义两个数组 和 ,其中 表示以第 个元素结尾的最长等差子数组的长度,而 表示以第 个元素开头的最长等差子数组的长度。初始时 , ,其他元素初始化为 。
我们可以通过一次遍历来计算出 和 的值:
- 对于 ,如果 ,则 。
- 对于 ,如果 ,则 。
然后,我们初始化答案为 ,因为可以通过替换一个元素来形成一个长度为 的等差子数组。接下来,我们枚举每个元素,尝试将其替换为一个合适的值来形成更长的等差子数组:
- 对于每个元素 ,我们可以直接使用 或 来更新答案。
- 如果 ,我们可以将 替换为 来延长以 结尾的等差子数组,更新答案为 。
- 如果 ,我们可以将 替换为 来延长以 开头的等差子数组,更新答案为 。
- 如果 ,我们可以将 替换为 来尝试连接 和 ,如果这个值是整数且与 和 匹配,则更新答案为 。
最后返回答案即可。
时间复杂度 ,空间复杂度 。其中 是数组 的长度。
func longestArithmetic(nums []int) int {
n := len(nums)
d := make([]int, n)
for i := 1; i < n; i++ {
d[i] = nums[i] - nums[i-1]
}
f := make([]int, n)
g := make([]int, n)
for i := range f {
f[i], g[i] = 2, 2
}
f[0], g[n-1] = 1, 1
for i := 2; i < n; i++ {
if d[i] == d[i-1] {
f[i] = f[i-1] + 1
}
}
for i := n - 3; i >= 0; i-- {
if d[i+1] == d[i+2] {
g[i] = g[i+1] + 1
}
}
ans := 3
for i := 0; i < n; i++ {
ans = max(ans, f[i], g[i])
if i > 0 {
ans = max(ans, f[i-1]+1)
}
if i+1 < n {
ans = max(ans, g[i+1]+1)
}
if i > 0 && i < n-1 {
diff := nums[i+1] - nums[i-1]
if diff%2 == 0 {
diff /= 2
k := 3
if i > 1 && diff == d[i-1] {
k += f[i-1] - 1
}
if i < n-2 && diff == d[i+2] {
k += g[i+1] - 1
}
ans = max(ans, k)
}
}
}
return ans
}
3880. 两个值之间的最小绝对差值 ​
题目、示例与约束
给你一个只包含 0、1 和 2 的整数数组 nums。
如果 nums[i] == 1 且 nums[j] == 2,则称下标对 (i, j) 为 有效 的。
请返回所有有效下标对中 i 和 j 之间的 最小 绝对差。如果不存在有效下标对,则返回 -1。
下标 i 和 j 之间的绝对差定义为 abs(i - j)。
示例 1:
输入: nums = [1,0,0,2,0,1]
输出: 2
解释:
有效下标对有:
- (0, 3),其绝对差为
abs(0 - 3) = 3。 - (5, 3),其绝对差为
abs(5 - 3) = 2。
因此,结果是 2。
示例 2:
输入: nums = [1,0,1,0]
输出: -1
解释:
数组中不存在有效下标对,因此结果是 -1。
提示:
1 <= nums.length <= 1000 <= nums[i] <= 2
方法一:一次遍历 · Go 实现
思考
数组仅含 ,求某个 与某个 下标差的最小值。长度 ,一次扫描足够。
最近的异类下标只可能是上一次出现的 。
记录 的最近下标,遇到非零则与对方最近下标作差。
若从未配对成功则 。
我们用一个长度为 的数组 来记录数字 , 和 最后一次出现的下标。初始时 。我们遍历数组 ,对于当前遍历到的数字 ,如果 不等于 ,则更新答案 ,其中 是当前遍历到的数字 的下标。然后更新 。
遍历结束后,如果 大于数组 的长度,则说明不存在有效下标对,返回 -1;否则返回 。
时间复杂度 ,其中 是数组 的长度。空间复杂度 。
func minAbsoluteDifference(nums []int) int {
n := len(nums)
ans := n + 1
last := []int{-ans, -ans, -ans}
for i, x := range nums {
if x != 0 {
ans = min(ans, i-last[3-x])
last[x] = i
}
}
if ans > n {
return -1
}
return ans
}
继续阅读
- 实践案例
- 实践案例
- 实践案例