LeetCode Go · 贪心(14) ​
来源与许可
题目版权归 LeetCode;题解来自 Doocs/leetcode,按 CC BY-SA 4.0 共享。补充 Go 代码来自灵茶山艾府、halfrost,保留 MIT 许可及逐题署名。本站提取 Go、调整分类及折叠排版;标注“本站整理”的 Go 解法依据题意与参考思路补写,按 CC BY-SA 4.0 共享。收录日期:2026-10-31;主题源版本:91ef8c241a5e。
本卷 3 题 · 点击题目展开,先独立思考,再查看解法。
4026. 工位的最大间隔 ​
题目、示例与约束
给你两个长度分别为 n 和 m 的字符串 skill 和 station。
skill[i] 表示工人 i 的技能,station[j] 表示工位 j 所支持的技能。
你必须将每一名工人分配到一个互不相同的工位。令 ji 表示分配给工人 i 的工位下标。有效的分配方案必须满足:
- 对于每个
0 <= i < n,都有station[ji] == skill[i]。 - 按照工人的顺序,分配的工位下标必须严格递增,即
j0 < j1 < ... < jn - 1。
分配方案的间隔是分配给两名相邻工人的工位下标之间的最大差值。换句话说,它等于所有 1 <= i < n 中 ji - ji - 1 的最大值。
如果只有一名工人,则间隔为 0。
返回所有有效分配方案中可能得到的最大间隔。题目保证至少存在一种有效的分配方案。
示例 1:
输入: skill = "aa", station = "aaaa"
输出: 3
解释:
- 必须将两名工人分配到两个不同的
'a'工位。 - 将他们分配到工位
[0, 3],得到的间隔为 3。
示例 2:
输入: skill = "xyz", station = "xyzz"
输出: 2
解释:
- 将工人 0 分配到工位
j = 0,将工人 1 分配到工位j = 1。 - 为了最大化间隔,将工人 2 分配到工位
j = 3。 - 由此得到分配方案
[0, 1, 3],相邻工位下标的差值为[1, 2],因此间隔为 2。
示例 3:
输入: skill = "cbc", station = "cbcdbc"
输出: 4
解释:
- 将工人 0 分配到工位
j = 0,将工人 1 分配到工位j = 1。 - 为了最大化间隔,将工人 2 分配到工位
j = 5。 - 由此得到分配方案
[0, 1, 5],相邻工位下标的差值为[1, 4],因此间隔为 4。
提示:
skill.length == nstation.length == m1 <= n <= m <= 105skill和station仅由小写英文字母组成。- 题目保证所有工人都存在一种有效的分配方案。
方法一:贪心 · Go 实现
思考
最大间隔只可能出现在某对相邻工人之间。要拉大 ,左侧工人应尽量靠左、右侧尽量靠右,且技能必须与工位匹配。
从右向左预处理每人在「更右侧工人已占更右工位」时能分到的最右匹配位置,再从左向右把当前工人放到最左匹配工位,用二者之差更新答案。
只有一名工人时不存在间隔,答案为 。
最大间隔一定出现在某对相邻工人 之间。要最大化这一对的间隔,应让工人 尽量靠左分配,工人 尽量靠右分配。
因此,我们从右往左贪心,预处理 :在工人 占据更靠右工位的前提下,工人 能分配到的最右工位。然后从左往右贪心,将工人 分配到当前最左的匹配工位 ,用 更新答案。
对所有相邻对取最大值即可。若只有一名工人,答案为 。
时间复杂度 ,空间复杂度 。其中 和 分别是字符串 和 的长度。
func maximumGap(skill string, station string) int {
n, m := len(skill), len(station)
suf := make([]int, n)
j := m - 1
for i := n - 1; i > 0; i-- {
for station[j] != skill[i] {
j--
}
suf[i] = j
j--
}
ans := 0
pre := 0
for i := 0; i < n-1; i++ {
for station[pre] != skill[i] {
pre++
}
ans = max(ans, suf[i+1]-pre)
pre++
}
return ans
}
4036. 字符对转换后字典序最大的字符串 ​
题目、示例与约束
给你一个整数数组 nums。
对于 nums 中的每个整数 x,首先生成一个由 x 个小写字母 'a' 组成的字符串。
你可以执行以下操作任意次(包括零次):
- 选择两个 相邻且相同 的字母,并将它们替换为字母表中的下一个字母。
例如,"aa" 可以替换为 "b","bb" 可以替换为 "c"。对 "zz" 则无法进行替换。
对于每个 x,请你确定可以获得的 字典序最大 的字符串。
返回一个字符串数组,其中第 i 个字符串是 nums[i] 的答案。
在两个字符串不同处的第一个位置,如果字符串 a 包含的字母在字母表中的顺序晚于 b 中的相应字母,则字符串 a 字典序大于 字符串 b。如果前 min(a.length, b.length) 个字符相同,则较长的字符串字典序更大。
示例 1:
输入: nums = [2,5,7]
输出: ["b","ca","cba"]
解释:
nums[0] = 2:"aa"→"b"。nums[1] = 5:"aaaaa"→"baaa"→"bba"→"ca"。nums[2] = 7:"aaaaaaa"→"baaaaa"→"bbaaa"→"bbba"→"cba"。- 因此,
ans = ["b", "ca", "cba"]。
示例 2:
输入: nums = [3,9,1]
输出: ["ba","da","a"]
解释:
nums[0] = 3:"aaa"→"ba"。nums[1] = 9:"aaaaaaaaa"→"baaaaaaa"→"bbaaaaa"→"bbbaaa"→"bbbba"→"cbba"→"cca"→"da"。nums[2] = 1:无法进行任何转换,因此结果为"a"。- 因此,
ans = ["ba", "da", "a"]。
提示:
1 <= nums.length <= 1051 <= nums[i] <= 108
方法一:贪心 + 二进制拆分 · Go 实现
思考
两个相邻且相同的字母可以合成字母表中的下一个,故 等价于 个 。由 个 能得到的串,恰为权值和等于 的串。
字典序最大应优先使用权值大的字母。从 倒序取出 个对应字母,并令 。
当 时必有 ,答案里只有 可能相邻,而 无法再合并,因此得到的串合法且字典序最大。
由于两个相邻且相同的字母可以合并成字母表中的下一个字母,因此字母 等价于 个 。也就是说,由 个 出发能够得到的字符串,恰好是那些字母权值之和等于 的字符串。
要使字典序最大,我们贪心地优先使用权值大的字母。字母表中最大的字母是 ,权值为 ,因此从 开始倒序枚举,每次取出 个字母 追加到答案末尾,并令 。
注意到当 时必有 ,所以答案中只有 可能连续出现,而 无法继续合并,因此得到的字符串是合法且字典序最大的。
时间复杂度 ,空间复杂度 。其中 是数组 的长度,而 是数组 中的最大值。这里不计入答案数组的空间消耗。
func largestString(nums []int) []string {
ans := make([]string, 0, len(nums))
for _, x := range nums {
s := []byte{}
for j := 25; j >= 0; j-- {
for t := x >> j; t > 0; t-- {
s = append(s, byte('a'+j))
}
x &= (1 << j) - 1
}
ans = append(ans, string(s))
}
return ans
}
4059. 字典序最大的答案数组 ​
题目、示例与约束
给你一个长度为 n 的整数数组 nums。你可以重新排列其中的元素以形成任意 排列 perm。
定义一个长度为 15 的数组 power。对于每个 0 <= i < 15,考查 perm 的前 j 个元素的第 (14 - i) 位,power[i] 是满足这些位全为 1 的最大整数 j(其中 0 <= j <= n)。
二进制位的位置从右向左编号,从第 0 位开始。
返回可能得到的 字典序最大 的 power 数组。
排列 是数组中所有元素的一种重新排列。
置位 指的是数字在二进制表示中对应位的值为 1。
对于两个长度相同的数组,如果在它们不同的第一个下标处,数组 a 包含的元素大于数组 b 中的元素,则称数组 a 的 字典序大于 数组 b。
示例 1:
输入:nums = [7,5]
输出:[0,0,0,0,0,0,0,0,0,0,0,0,2,1,2]
解释:
选择 perm = [7, 5]。
- 两个元素的第 2 位都置位了,因此
power[12] = 2。 - 第一个元素的第 1 位置位了,但第二个元素没有,因此
power[13] = 1。 - 两个元素的第 0 位都置位了,因此
power[14] = 2。
第一个元素的所有更高位都未置位,因此其余项都为 0。
示例 2:
输入:nums = [3,1,7]
输出:[0,0,0,0,0,0,0,0,0,0,0,0,1,2,3]
解释:
选择 perm = [7, 3, 1]。
- 第一个元素的第 2 位置位了,但第二个元素没有,因此
power[12] = 1。 - 前两个元素的第 1 位都置位了,但第三个元素没有,因此
power[13] = 2。 - 所有三个元素的第 0 位都置位了,因此
power[14] = 3。
第一个元素的所有更高位都未置位,因此其余项都为 0。
提示:
1 <= nums.length <= 5 * 1040 <= nums[i] < 215
方法一:Go 实现(灵茶山艾府) · Go 实现
灵茶山艾府 · 源码 · MIT 许可 · 原题解
题意:重排 ,首先,只看最高位,让只含 的前缀尽可能长;在这个前提下,让次高位的只含 的前缀尽可能长;依此类推。
从最高位开始思考。既然要把 尽量排前面,那么直接把 从大到小排序,然后统计最高位的只含 的最长前缀长度。
例如 这四个数的二进制如下,最高位的最长前缀 (竖着看)的长度为 。
在最高位的前缀 尽可能长的前提下,次高位的最长前缀 (竖着看)的长度只能是 。其他元素的次高位是 还是 不重要,可以一视同仁。为方便排序,其他元素的次高位可以都置为 。
置 后,重新从大到小排序,得到
这样既可以让低位的 尽量往前排,又保证了高位的 的位置不变,高位的最长前缀 的长度不变。
重复上述过程,直到二进制的最低位。
本题视频讲解,欢迎点赞关注~
优化前
复杂度分析
- 时间复杂度:,其中 是 的长度,。瓶颈在排序上。
- 空间复杂度:。返回值不计入。忽略排序的栈开销。
优化排序
复杂度分析
- 时间复杂度:,其中 是 的长度,。 展开后恰好有 个元素。
- 空间复杂度:。返回值不计入。
专题训练
见下面位运算题单的「八、思维题」。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府
import (
"math/bits"
"slices"
)
// https://space.bilibili.com/206214
func largestPower1(nums []int) []int {
ans := [15]int{}
slices.SortFunc(nums, func(a, b int) int { return b - a })
maxWidth := bits.Len(uint(nums[0]))
for i := maxWidth - 1; i >= 0; i-- {
// 找最长前缀连续 1
j := 0
for j < len(nums) && nums[j]>>i&1 > 0 {
j++
}
ans[14-i] = j
// [0, j-1] 这一位都是 1,后面无关紧要,为方便排序,全置为 0
for ; j < len(nums); j++ {
nums[j] &^= 1 << i
}
slices.SortFunc(nums, func(a, b int) int { return b - a })
}
return ans[:]
}
func largestPower(nums []int) []int {
ans := [15]int{}
groups := [][]int{nums}
maxWidth := bits.Len(uint(slices.Max(nums)))
for i := maxWidth - 1; i >= 0; i-- {
nxt := [][]int{}
cnt := 0
for j, a := range groups {
// 手动排 groups[j]
var ones, zeros []int
for _, x := range a {
if x>>i&1 > 0 {
ones = append(ones, x)
} else {
zeros = append(zeros, x)
}
}
cnt += len(ones)
// 1 排前面,0 排后面
if len(ones) > 0 {
nxt = append(nxt, ones)
}
if len(zeros) > 0 {
nxt = append(nxt, zeros)
// 遇到 0,剩下的 groups[j] 不再排序
nxt = append(nxt, groups[j+1:]...)
break
}
}
ans[14-i] = cnt
groups = nxt
}
return ans[:]
}
继续阅读
- 实践案例
- 实践案例
- 实践案例