LeetCode Go · 数学与几何(10) ​
来源与许可
题目版权归 LeetCode;题解来自 Doocs/leetcode,按 CC BY-SA 4.0 共享。补充 Go 代码来自灵茶山艾府、halfrost,保留 MIT 许可及逐题署名。本站提取 Go、调整分类及折叠排版;标注“本站整理”的 Go 解法依据题意与参考思路补写,按 CC BY-SA 4.0 共享。收录日期:2026-10-31;主题源版本:91ef8c241a5e。
本卷 30 题 · 点击题目展开,先独立思考,再查看解法。
3516. 找到最近的人 ​
题目、示例与约束
给你三个整数 x、y 和 z,表示数轴上三个人的位置:
x是第 1 个人的位置。y是第 2 个人的位置。z是第 3 个人的位置,第 3 个人 不会移动 。
第 1 个人和第 2 个人以 相同 的速度向第 3 个人移动。
判断谁会 先 到达第 3 个人的位置:
- 如果第 1 个人先到达,返回 1 。
- 如果第 2 个人先到达,返回 2 。
- 如果两个人同时到达,返回 0 。
根据上述规则返回结果。
示例 1:
输入: x = 2, y = 7, z = 4
输出: 1
解释:
- 第 1 个人在位置 2,到达第 3 个人(位置 4)需要 2 步。
- 第 2 个人在位置 7,到达第 3 个人需要 3 步。
由于第 1 个人先到达,所以输出为 1。
示例 2:
输入: x = 2, y = 5, z = 6
输出: 2
解释:
- 第 1 个人在位置 2,到达第 3 个人(位置 6)需要 4 步。
- 第 2 个人在位置 5,到达第 3 个人需要 1 步。
由于第 2 个人先到达,所以输出为 2。
示例 3:
输入: x = 1, y = 5, z = 3
输出: 0
解释:
- 第 1 个人在位置 1,到达第 3 个人(位置 3)需要 2 步。
- 第 2 个人在位置 5,到达第 3 个人需要 2 步。
由于两个人同时到达,所以输出为 0。
提示:
1 <= x, y, z <= 100
方法一:数学 · Go 实现
思考
三人在数轴上以相同速度移动,先到达 的人由距离决定。比较 与 即可区分先到、同时或第二人先到。
无需模拟时间轴,常数时间给出 、 或 。
我们计算出第 个人和第 个人的距离 ,第 个人和第 个人的距离 。
- 如果 ,说明两个人同时到达,返回 ;
- 如果 ,说明第 个人会先到达,返回 ;
- 否则,说明第 个人会先到达,返回 。
时间复杂度 ,空间复杂度 。
func findClosest(x int, y int, z int) int {
a, b := abs(x-z), abs(y-z)
if a == b {
return 0
}
if a < b {
return 1
}
return 2
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
3518. 最小回文排列 II ​
题目、示例与约束
给你一个 回文 字符串 s 和一个整数 k。
返回 s 的按字典序排列的 第 k 小 回文排列。如果不存在 k 个不同的回文排列,则返回空字符串。
注意: 产生相同回文字符串的不同重排视为相同,仅计为一次。
如果一个字符串从前往后和从后往前读都相同,那么这个字符串是一个 回文 字符串。
排列 是字符串中所有字符的重排。
如果字符串 a 按字典序小于字符串 b,则表示在第一个不同的位置,a 中的字符比 b 中的对应字符在字母表中更靠前。
如果在前 min(a.length, b.length) 个字符中没有区别,则较短的字符串按字典序更小。
示例 1:
输入: s = "abba", k = 2
输出: "baab"
解释:
"abba"的两个不同的回文排列是"abba"和"baab"。- 按字典序,
"abba"位于"baab"之前。由于k = 2,输出为"baab"。
示例 2:
输入: s = "aa", k = 2
输出: ""
解释:
- 仅有一个回文排列:
"aa"。 - 由于
k = 2超过了可能的排列数,输出为空字符串。
示例 3:
输入: s = "bacab", k = 1
输出: "abcba"
解释:
"bacab"的两个不同的回文排列是"abcba"和"bacab"。- 按字典序,
"abcba"位于"bacab"之前。由于k = 1,输出为"abcba"。
提示:
1 <= s.length <= 104s由小写英文字母组成。- 保证
s是回文字符串。 1 <= k <= 106
方法一 · Go 实现
思考
上一题只求最小回文;此处要第 个不同回文, 且 ,不能列出全部前半排列。
前半多重集的排列数可用组合数计算,超过 时截断以免溢出。从左到右试填字母:若将该字母放在当前位置后后缀排列数不少于剩余名次,则选定;否则累减名次并换下一个字母。最后镜像并插入中心字符。
func smallestPalindrome(s string, k int) string {
const limit = 1_000_001
freq := [26]int{}
for i := 0; i < len(s); i++ {
freq[s[i]-'a']++
}
odd, mid := 0, 26
for i, v := range freq {
if v&1 == 1 {
odd++
mid = i
}
}
if odd > 1 {
return ""
}
half := [26]int{}
halfLen := 0
for i, v := range freq {
half[i] = v / 2
halfLen += half[i]
}
countPermutations := func(counts [26]int) int {
remaining := 0
for _, c := range counts {
remaining += c
}
perms := 1
for _, count := range counts {
if count == 0 {
continue
}
selected := count
if remaining-count < selected {
selected = remaining - count
}
combos := 1
for step := 1; step <= selected; step++ {
combos = combos * (remaining - step + 1) / step
if combos >= limit {
combos = limit
break
}
}
perms *= combos
if perms >= limit {
return limit
}
remaining -= count
}
return perms
}
if k > countPermutations(half) {
return ""
}
n := len(s)
pal := make([]byte, n)
rank, pos := k, 0
for t := 0; t < halfLen; t++ {
for i := 0; i < 26; i++ {
if half[i] == 0 {
continue
}
half[i]--
suffix := countPermutations(half)
if suffix >= rank {
pal[pos] = byte('a' + i)
pos++
break
}
rank -= suffix
half[i]++
}
}
if mid < 26 {
pal[halfLen] = byte('a' + mid)
}
for i := 0; i < halfLen; i++ {
pal[n-1-i] = pal[i]
}
return string(pal)
}
3536. 两个数字的最大乘积 ​
题目、示例与约束
给定一个正整数 n。
返回 任意两位数字 相乘所得的 最大 乘积。
注意:如果某个数字在 n 中出现多次,你可以多次使用该数字。
示例 1:
输入: n = 31
输出: 3
解释:
n的数字是[3, 1]。- 任意两位数字相乘的结果为:
3 * 1 = 3。 - 最大乘积为 3。
示例 2:
输入: n = 22
输出: 4
解释:
n的数字是[2, 2]。- 任意两位数字相乘的结果为:
2 * 2 = 4。 - 最大乘积为 4。
示例 3:
输入: n = 124
输出: 8
解释:
n的数字是[1, 2, 4]。- 任意两位数字相乘的结果为:
1 * 2 = 2,1 * 4 = 4,2 * 4 = 8。 - 最大乘积为 8。
提示:
10 <= n <= 109
方法一:找到最大和次大数字 · Go 实现
思考
两位数字之积在选取最大与次大时最大,与数位顺序无关。拆位维护 两个变量即可,不必把数字存进数组再排序。
遍历 位后返回 。
我们用两个变量 和 来记录当前最大的数字和次大的数字。我们遍历 的每一位数字,如果当前数字大于 ,则将 赋值为 ,然后将 赋值为当前数字;否则,如果当前数字大于 ,则将 赋值为当前数字。最后返回 即可。
时间复杂度 ,其中 是输入数字的大小。空间复杂度 。
func maxProduct(n int) int {
a, b := 0, 0
for ; n > 0; n /= 10 {
x := n % 10
if a < x {
b, a = a, x
} else if b < x {
b = x
}
}
return a * b
}
3549. 两个多项式相乘 🔒 ​
题目、示例与约束
给定两个整数数组 poly1 和 poly2,其中每个数组中下标 i 的元素表示多项式中 xi 的系数。
设 A(x) 和 B(x) 分别是 poly1 和 poly2 表示的多项式。
返回一个长度为 (poly1.length + poly2.length - 1) 的整数数组 result 表示乘积多项式 R(x) = A(x) * B(x) 的系数,其中 result[i] 表示 R(x) 中 xi 的系数。
示例 1:
输入:poly1 = [3,2,5], poly2 = [1,4]
输出:[3,14,13,20]
解释:
A(x) = 3 + 2x + 5x2且B(x) = 1 + 4xR(x) = (3 + 2x + 5x2) * (1 + 4x)R(x) = 3 * 1 + (3 * 4 + 2 * 1)x + (2 * 4 + 5 * 1)x2 + (5 * 4)x3R(x) = 3 + 14x + 13x2 + 20x3- 因此,result =
[3, 14, 13, 20]。
示例 2:
输入:poly1 = [1,0,-2], poly2 = [-1]
输出:[-1,0,2]
解释:
A(x) = 1 + 0x - 2x2且B(x) = -1R(x) = (1 + 0x - 2x2) * (-1)R(x) = -1 + 0x + 2x2- 因此,result =
[-1, 0, 2]。
示例 3:
输入:poly1 = [1,5,-3], poly2 = [-4,2,0]
输出:[-4,-18,22,-6,0]
解释:
A(x) = 1 + 5x - 3x2且B(x) = -4 + 2x + 0x2R(x) = (1 + 5x - 3x2) * (-4 + 2x + 0x2)R(x) = 1 * -4 + (1 * 2 + 5 * -4)x + (5 * 2 + -3 * -4)x2 + (-3 * 2)x3 + 0x4R(x) = -4 -18x + 22x2 -6x3 + 0x4- 因此,result =
[-4, -18, 22, -6, 0]。
提示:
1 <= poly1.length, poly2.length <= 5 * 104-103 <= poly1[i], poly2[i] <= 103poly1与poly2至少包含一个非零系数。
方法一:FFT · Go 实现
思考
朴素双重循环卷积是 ,多项式较长时偏慢。乘积系数是卷积,可变换到点值相乘。
将长度补到不低于 的 的幂,对两序列做 FFT,逐点相乘后再逆变换,实部四舍五入即得整数系数。
我们可以使用快速傅里叶变换(FFT)来高效地计算两个多项式的乘积。FFT 是一种高效的算法,可以在 的时间复杂度内计算多项式的乘积。
具体步骤如下:
- 补足长度 将结果长度 向上取最近的 2 的幂 ,便于分治 FFT。
- FFT 变换 分别对两条系数序列做正变换(
invert=False)。 - 逐点相乘 对应频域元素相乘。
- 逆 FFT 对乘积序列做逆变换(
invert=True),并把实部四舍五入取整得到最终系数。
时间复杂度 ,空间复杂度 。其中 是多项式的长度。
func multiply(poly1 []int, poly2 []int) []int64 {
if len(poly1) == 0 || len(poly2) == 0 {
return []int64{}
}
m := len(poly1) + len(poly2) - 1
n := 1
for n < m {
n <<= 1
}
fa := make([]complex128, n)
fb := make([]complex128, n)
for i := 0; i < len(poly1); i++ {
fa[i] = complex(float64(poly1[i]), 0)
}
for i := 0; i < len(poly2); i++ {
fb[i] = complex(float64(poly2[i]), 0)
}
fft(fa, false)
fft(fb, false)
for i := 0; i < n; i++ {
fa[i] *= fb[i]
}
fft(fa, true)
res := make([]int64, m)
for i := 0; i < m; i++ {
res[i] = int64(math.Round(real(fa[i])))
}
return res
}
func fft(a []complex128, invert bool) {
n := len(a)
for i, j := 1, 0; i < n; i++ {
bit := n >> 1
for ; j&bit != 0; bit >>= 1 {
j ^= bit
}
j ^= bit
if i < j {
a[i], a[j] = a[j], a[i]
}
}
for length := 2; length <= n; length <<= 1 {
angle := 2 * math.Pi / float64(length)
if invert {
angle = -angle
}
wlen := cmplx.Rect(1, angle)
for i := 0; i < n; i += length {
w := complex(1, 0)
half := length >> 1
for j := 0; j < half; j++ {
u := a[i+j]
v := a[i+j+half] * w
a[i+j] = u + v
a[i+j+half] = u - v
w *= wlen
}
}
}
if invert {
for i := range a {
a[i] /= complex(float64(n), 0)
}
}
}
3550. 数位和等于下标的最小下标 ​
题目、示例与约束
给你一个整数数组 nums 。
返回满足 nums[i] 的数位和(每一位数字相加求和)等于 i 的 最小 下标 i 。
如果不存在满足要求的下标,返回 -1 。
示例 1:
输入:nums = [1,3,2]
输出:2
解释:
nums[2] = 2,其数位和等于 2 ,与其下标i = 2相等。因此,输出为 2 。
示例 2:
输入:nums = [1,10,11]
输出:1
解释:
nums[1] = 10,其数位和等于1 + 0 = 1,与其下标i = 1相等。nums[2] = 11,其数位和等于是1 + 1 = 2,与其下标i = 2相等。- 由于下标 1 是满足要求的最小下标,输出为 1 。
示例 3:
输入:nums = [1,2,3]
输出:-1
解释:
- 由于不存在满足要求的下标,输出为 -1 。
提示:
1 <= nums.length <= 1000 <= nums[i] <= 1000
方法一:枚举 + 数位和 · Go 实现
思考
要求最小下标 使得 的数位和等于 。从 起枚举即可,先出现者即最小。
数位和按除 累加,找不到则返回 。线性扫描已足够。
我们可以从下标 开始,遍历数组中的每个元素 ,计算 的数位和 。如果 ,则返回下标 。如果遍历完所有元素都没有找到满足条件的下标,则返回 -1。
时间复杂度 ,其中 是数组的长度。空间复杂度 ,只使用了常数级别的额外空间。
func smallestIndex(nums []int) int {
for i, x := range nums {
s := 0
for ; x > 0; x /= 10 {
s += x % 10
}
if s == i {
return i
}
}
return -1
}
3556. 最大质数子字符串之和 ​
题目、示例与约束
给定一个字符串 s,找出可以由其 子字符串 组成的 3个最大的不同质数 的和。
返回这些质数的 总和 ,如果少于 3 个不同的质数,则返回 所有 不同质数的和。
质数是大于 1 且只有两个因数的自然数:1和它本身。
子字符串 是字符串中的一个连续字符序列。
注意:每个质数即使出现在 多个 子字符串中,也只能计算 一次 。此外,将子字符串转换为整数时,忽略任何前导零。
示例 1:
输入: s = "12234"
输出: 1469
解释:
- 由
"12234"的子字符串形成的不同质数为 2 ,3 ,23 ,223 和 1223。 - 最大的 3 个质数是 1223、223 和 23。它们的和是 1469。
示例 2:
输入: s = "111"
输出: 11
解释:
- 由
"111"的子字符串形成的不同质数是 11。 - 由于只有一个质数,所以结果是 11。
提示:
1 <= s.length <= 10s仅由数字组成。
方法一:枚举 + 哈希表 · Go 实现
思考
子串对应的整数最多 个, 不大,可以全部枚举并判质数。题目要不同质数中最大的三个之和。
用集合去重后排序取末三位求和;不足三个则全加。从每个起点累乘 加下一位,避免反复转换字符串。
我们可以枚举所有的子字符串,然后判断它们是否是质数。由于题目要求我们返回最大的 3 个不同质数的和,因此我们可以使用一个哈希表来存储所有的质数。
在遍历完所有的子字符串后,我们将哈希表中的质数按从小到大的顺序排序,然后取出最大的 3 个质数进行求和。
如果哈希表中质数的数量小于 3,则返回所有质数的和。
时间复杂度 ,空间复杂度 ,其中 为字符串的长度,而 为字符串中最大的子字符串的值。
func sumOfLargestPrimes(s string) (ans int64) {
st := make(map[int64]struct{})
n := len(s)
for i := 0; i < n; i++ {
var x int64 = 0
for j := i; j < n; j++ {
x = x*10 + int64(s[j]-'0')
if isPrime(x) {
st[x] = struct{}{}
}
}
}
nums := make([]int64, 0, len(st))
for num := range st {
nums = append(nums, num)
}
sort.Slice(nums, func(i, j int) bool { return nums[i] < nums[j] })
for i := len(nums) - 1; i >= 0 && len(nums)-i <= 3; i-- {
ans += nums[i]
}
return
}
func isPrime(x int64) bool {
if x < 2 {
return false
}
sqrtX := int64(math.Sqrt(float64(x)))
for i := int64(2); i <= sqrtX; i++ {
if x%i == 0 {
return false
}
}
return true
}
3560. 木材运输的最小成本 ​
题目、示例与约束
给你三个整数 n、m 和 k。
有两根长度分别为 n 和 m 单位的木材,需要通过三辆卡车运输。每辆卡车最多只能装载一根长度 不超过 k 单位的木材。
你可以将木材切成更小的段,其中将长度为 x 的木材切割成长度为 len1 和 len2 的段的成本为 cost = len1 * len2,并且满足 len1 + len2 = x。
返回将木材分配到卡车上的 最小总成本 。如果木材不需要切割,总成本为 0。
示例 1:
输入: n = 6, m = 5, k = 5
输出: 5
解释:
将长度为 6 的木材切割成长度为 1 和 5 的两段,成本为 1 * 5 == 5。现在三段长度分别为 1、5 和 5 的木材可以分别装载到每辆卡车。
示例 2:
输入: n = 4, m = 4, k = 6
输出: 0
解释:
两根木材已经可以直接装载到卡车上,因此不需要切割。
提示:
2 <= k <= 1051 <= n, m <= 2 * k- 输入数据保证木材总存在能被运输的方案。
方法一:数学 · Go 实现
思考
至多一根原木长于 ,且只需切一刀使两段都不超过 。较长者记为 ,若 则无需切割。
切成 与 时代价为 ,这是唯一合法切法的代价。常数时间计算即可。
如果两根木材的长度都不超过卡车的最大载重 ,则不需要切割,直接返回 。
否则,说明只有一个木材的长度超过了 ,我们需要将其切割成两段。设较长的木材长度为 ,则切割成本为 。
时间复杂度 ,空间复杂度 。
func minCuttingCost(n int, m int, k int) int64 {
x := max(n, m)
if x <= k {
return 0
}
return int64(k * (x - k))
}
3574. 最大子数组 GCD 分数 ​
题目、示例与约束
给你一个正整数数组 nums 和一个整数 k。
你最多可以执行 k 次操作。在每次操作中,你可以选择数组中的一个元素并将其值 翻倍 。每个元素 最多 只能翻倍一次。
连续 子数组 的 分数 定义为其所有元素的最大公约数 (GCD) 与子数组长度的 乘积 。
你的任务是返回修改后数组中选择一个连续子数组可以获得的最大 分数 。
注意:
- 子数组 是数组中连续的元素序列。
- 数组的 最大公约数 (GCD) 是能整除数组所有元素的最大整数。
示例 1:
输入: nums = [2,4], k = 1
输出: 8
解释:
- 使用一次操作将
nums[0]翻倍到 4。修改后的数组变为[4, 4]。 - 子数组
[4, 4]的 GCD 是 4,长度是 2。 - 因此,最大可能分数是
2 × 4 = 8。
示例 2:
输入: nums = [3,5,7], k = 2
输出: 14
解释:
- 使用一次操作将
nums[2]翻倍到 14。修改后的数组变为[3, 5, 14]。 - 子数组
[14]的 GCD 是 14,长度是 1。 - 因此,最大可能分数是
1 × 14 = 14。
示例 3:
输入: nums = [5,5,5], k = 1
输出: 15
解释:
- 子数组
[5, 5, 5]的 GCD 是 5,长度是 3。 - 因为翻倍任何元素都不能提高分数,所以最大分数是
3 × 5 = 15。
提示:
1 <= n == nums.length <= 15001 <= nums[i] <= 1091 <= k <= n
方法一:枚举 + 数学 · Go 实现
思考
,可枚举全部子数组。每个数至多乘一次 ,子数组 GCD 至多再乘 ,是否乘得上取决于因子 最少的那些位置个数是否 。
预处理每个数的 的幂次。枚举左端并扩展右端,维护区间 GCD、最小幂次及其出现次数,按 决定分数为 还是 ,再乘长度。
我们注意到,题目中数组的长度 ,因此,我们可以枚举所有的子数组。对于每个子数组,计算其 GCD 分数,找出最大值即为答案。
由于每个数最多只能翻倍一次,那么子数组的 GCD 最多也只能乘以 ,因此,我们需要统计子数组中每个数的因子 的个数的最小值,以及这个最小值的出现次数。如果次数大于 ,则 GCD 分数为 GCD,否则 GCD 分数为 GCD 乘以 。
因此,我们可以预处理每个数的因子 的个数,然后在枚举子数组时,维护当前子数组的 GCD、最小因子 的个数以及其出现次数即可。
时间复杂度 ,空间复杂度 。其中 是数组 的长度。
func maxGCDScore(nums []int, k int) int64 {
n := len(nums)
cnt := make([]int, n)
for i, x := range nums {
for x%2 == 0 {
cnt[i]++
x /= 2
}
}
ans := 0
for l := 0; l < n; l++ {
g := 0
mi := math.MaxInt32
t := 0
for r := l; r < n; r++ {
g = gcd(g, nums[r])
if cnt[r] < mi {
mi = cnt[r]
t = 1
} else if cnt[r] == mi {
t++
}
length := r - l + 1
score := g * length
if t <= k {
score *= 2
}
ans = max(ans, score)
}
}
return int64(ans)
}
func gcd(a, b int) int {
for b != 0 {
a, b = b, a%b
}
return a
}
3577. 统计计算机解锁顺序排列数 ​
题目、示例与约束
给你一个长度为 n 的数组 complexity。
在房间里有 n 台 上锁的 计算机,这些计算机的编号为 0 到 n - 1,每台计算机都有一个 唯一 的密码。编号为 i 的计算机的密码复杂度为 complexity[i]。
编号为 0 的计算机密码已经 解锁 ,并作为根节点。其他所有计算机必须通过它或其他已经解锁的计算机来解锁,具体规则如下:
- 可以使用编号为
j的计算机的密码解锁编号为i的计算机,其中j是任何小于i的整数,且满足complexity[j] < complexity[i](即j < i并且complexity[j] < complexity[i])。 - 要解锁编号为
i的计算机,你需要事先解锁一个编号为j的计算机,满足j < i并且complexity[j] < complexity[i]。
求共有多少种 [0, 1, 2, ..., (n - 1)] 的排列方式,能够表示从编号为 0 的计算机(唯一初始解锁的计算机)开始解锁所有计算机的有效顺序。
由于答案可能很大,返回结果需要对 109 + 7 取余数。
注意:编号为 0 的计算机的密码已解锁,而 不是 排列中第一个位置的计算机密码已解锁。
排列 是一个数组中所有元素的重新排列。
示例 1:
输入: complexity = [1,2,3]
输出: 2
解释:
有效的排列有:
- [0, 1, 2]
- 首先使用根密码解锁计算机 0。
- 使用计算机 0 的密码解锁计算机 1,因为
complexity[0] < complexity[1]。 - 使用计算机 1 的密码解锁计算机 2,因为
complexity[1] < complexity[2]。
- [0, 2, 1]
- 首先使用根密码解锁计算机 0。
- 使用计算机 0 的密码解锁计算机 2,因为
complexity[0] < complexity[2]。 - 使用计算机 0 的密码解锁计算机 1,因为
complexity[0] < complexity[1]。
示例 2:
输入: complexity = [3,3,3,4,4,4]
输出: 0
解释:
没有任何排列能够解锁所有计算机。
提示:
2 <= complexity.length <= 1051 <= complexity[i] <= 109
方法一:脑筋急转弯 · Go 实现
思考
编号 已解锁,其它机器只能被复杂度严格更小且已解锁的机器打开。若存在 使 ,则该机永远无法解锁。
否则 能打开其余所有机器,解锁顺序是 的任意排列,共 种。一边扫描一边累乘即可。
由于编号为 的计算机密码已经被解锁,那么对于其他计算机 ,如果存在 ,则无法解锁计算机 ,因此返回 。否则,排列可以是任意的,一共有 种排列方式。
时间复杂度 ,其中 是数组 的长度。空间复杂度 。
func countPermutations(complexity []int) int {
mod := int64(1e9 + 7)
ans := int64(1)
for i := 1; i < len(complexity); i++ {
if complexity[i] <= complexity[0] {
return 0
}
ans = ans * int64(i) % mod
}
return int(ans)
}
3591. 检查元素频次是否为质数 ​
题目、示例与约束
给你一个整数数组 nums。
如果数组中任一元素的 频次 是 质数,返回 true;否则,返回 false。
元素 x 的 频次 是它在数组中出现的次数。
质数是一个大于 1 的自然数,并且只有两个因数:1 和它本身。
示例 1:
输入: nums = [1,2,3,4,5,4]
输出: true
解释:
数字 4 的频次是 2,而 2 是质数。
示例 2:
输入: nums = [1,2,3,4,5]
输出: false
解释:
所有元素的频次都是 1。
示例 3:
输入: nums = [2,2,2,4,4]
输出: true
解释:
数字 2 和 4 的频次都是质数。
提示:
1 <= nums.length <= 1000 <= nums[i] <= 100
方法一:计数 + 判断质数 · Go 实现
思考
只需判断是否存在某个元素的出现次数为质数。先计数,再对频次做试除判质。
频次不超过 ,试除到平方根即可。有一个质数频次则真,否则假。
我们用一个哈希表 统计每个元素的频次。然后遍历 中的值,判断是否有质数,如果有则返回 true,否则返回 false。
时间复杂度 ,空间复杂度 。其中 是数组 的长度,而 是 中的最大值。
func checkPrimeFrequency(nums []int) bool {
cnt := make(map[int]int)
for _, x := range nums {
cnt[x]++
}
for _, x := range cnt {
if isPrime(x) {
return true
}
}
return false
}
func isPrime(x int) bool {
if x < 2 {
return false
}
for i := 2; i*i <= x; i++ {
if x%i == 0 {
return false
}
}
return true
}
3596. 交替方向的最小路径代价 I 🔒 ​
题目、示例与约束
给定两个整数 m 和 n 分别表示一个网格的行数和列数。
进入单元格 (i, j) 的花费定义为 (i + 1) * (j + 1)。
路径始终从第 1 步进入单元格 (0, 0) 并支付入场花费开始。
在每一步,你移动到 相邻 的单元格,遵循交替的模式:
- 在 奇数次 移动,你必须向 右方 或 下方 移动。
- 在 偶数次 移动,你必须向 左方 或 上方 移动。
返回到达 (m - 1, n - 1) 的最小总花费。如果不可能到达,返回 -1。
示例 1:
输入:m = 1, n = 1
输出:1
解释:
- 你从单元格
(0, 0)开始。 - 进入
(0, 0)的花费是(0 + 1) * (0 + 1) = 1。 - 由于你已经到达了目标,总花费为 1。
示例 2:
输入:m = 2, n = 1
输出:3
解释:
- 你从单元格
(0, 0)开始,花费为(0 + 1) * (0 + 1) = 1。 - 第 1 次移动(奇数次):你可以向下移动到
(1, 0),花费为(1 + 1) * (0 + 1) = 2。 - 因此,总花费是
1 + 2 = 3。
提示:
1 <= m, n <= 106
方法一:脑筋急转弯 · Go 实现
思考
交替方向的花费规则使得大多数网格无法到达终点。枚举可达情形后只剩 (花费 )以及 、 (花费 )。
其余规模返回 。判断三个特解即可,不必做最短路。
由于题目中给定的移动规则,实际上只有以下三种情况可以到达目标单元格:
- 行列数为 的网格,花费为 。
- 行数为 ,列数为 的网格,花费为 。
- 行数为 ,列数为 的网格,花费为 。
对于其他情况,无法到达目标单元格,返回 。
时间复杂度 ,空间复杂度 。
func minCost(m int, n int) int {
if m == 1 && n == 1 {
return 1
}
if m == 1 && n == 2 {
return 3
}
if m == 2 && n == 1 {
return 3
}
return -1
}
3602. 十六进制和三十六进制转化 ​
题目、示例与约束
给你一个整数 n。
返回 n2 的 十六进制表示 和 n3 的 三十六进制表示 拼接成的字符串。
十六进制 数定义为使用数字 0 – 9 和大写字母 A - F 表示 0 到 15 的值。
三十六进制 数定义为使用数字 0 – 9 和大写字母 A - Z 表示 0 到 35 的值。
示例 1:
输入:n = 13
输出: "A91P1"
解释:
n2 = 13 * 13 = 169。在十六进制中,它转换为(10 * 16) + 9 = 169,对应于"A9"。n3 = 13 * 13 * 13 = 2197。在三十六进制中,它转换为(1 * 362) + (25 * 36) + 1 = 2197,对应于"1P1"。- 连接两个结果得到
"A9" + "1P1" = "A91P1"。
示例 2:
输入:n = 36
输出:"5101000"
解释:
n2 = 36 * 36 = 1296。在十六进制中,它转换为(5 * 162) + (1 * 16) + 0 = 1296,对应于"510"。n3 = 36 * 36 * 36 = 46656。在三十六进制中,它转换为(1 * 363) + (0 * 362) + (0 * 36) + 0 = 46656,对应于"1000"。- 连接两个结果得到
"510" + "1000" = "5101000"。
提示:
1 <= n <= 1000
方法一:模拟 · Go 实现
思考
需要同时给出 的十六进制与 的三十六进制。语言内置转换通常只覆盖十六进制,两套逻辑并不统一。
任意进制表示都可由反复取模与整除得到。为此抽象 :余数不大于 时写数字,否则映射为字母,再将低位序列反转。
计算 、 后返回 。 的量级使循环次数为对数级。
我们定义一个函数 ,它将整数 转换为以 进制表示的字符串。该函数通过不断取模和整除来构建结果字符串。
对于给定的整数 ,我们计算 和 ,然后分别将它们转换为十六进制和三十六进制字符串。最后,将这两个字符串连接起来返回。
时间复杂度 ,空间复杂度 。
func concatHex36(n int) string {
x := n * n
y := n * n * n
return f(x, 16) + f(y, 36)
}
func f(x, k int) string {
res := []byte{}
for x > 0 {
v := x % k
if v <= 9 {
res = append(res, byte('0'+v))
} else {
res = append(res, byte('A'+v-10))
}
x /= k
}
for i, j := 0, len(res)-1; i < j; i, j = i+1, j-1 {
res[i], res[j] = res[j], res[i]
}
return string(res)
}
3609. 到达目标点的最小移动次数 ​
题目、示例与约束
给你四个整数 sx、sy、tx 和 ty,表示在一个无限大的二维网格上的两个点 (sx, sy) 和 (tx, ty)。
你的起点是 (sx, sy)。
在任何位置 (x, y),定义 m = max(x, y)。你可以执行以下两种操作之一:
- 移动到
(x + m, y),或者 - 移动到
(x, y + m)。
返回到达 (tx, ty) 所需的 最小 移动次数。如果无法到达目标点,则返回 -1。
示例 1:
输入: sx = 1, sy = 2, tx = 5, ty = 4
输出: 2
解释:
最优路径如下:
- 移动 1:
max(1, 2) = 2。增加 y 坐标 2,从(1, 2)移动到(1, 2 + 2) = (1, 4)。 - 移动 2:
max(1, 4) = 4。增加 x 坐标 4,从(1, 4)移动到(1 + 4, 4) = (5, 4)。
因此,到达 (5, 4) 的最小移动次数是 2。
示例 2:
输入: sx = 0, sy = 1, tx = 2, ty = 3
输出: 3
解释:
最优路径如下:
- 移动 1:
max(0, 1) = 1。增加 x 坐标 1,从(0, 1)移动到(0 + 1, 1) = (1, 1)。 - 移动 2:
max(1, 1) = 1。增加 x 坐标 1,从(1, 1)移动到(1 + 1, 1) = (2, 1)。 - 移动 3:
max(2, 1) = 2。增加 y 坐标 2,从(2, 1)移动到(2, 1 + 2) = (2, 3)。
因此,到达 (2, 3) 的最小移动次数是 3。
示例 3:
输入: sx = 1, sy = 1, tx = 2, ty = 2
输出: -1
解释:
- 无法通过题中允许的移动方式从
(1, 1)到达(2, 2)。因此,答案是 -1。
提示:
0 <= sx <= tx <= 1090 <= sy <= ty <= 109
方法一:Go 实现(灵茶山艾府) · Go 实现
灵茶山艾府 · 源码 · MIT 许可 · 原题解
正着走,不知道该执行哪个操作,但倒着走就知道了。为什么?且听我细说~
从终点 倒着走到起点 。
不失一般性,假设 且 。如果 ,则交换 和 ,以及交换 和 。假设 是因为对于 的情况我们可以特判。
注:为什么可以交换?因为 与 是无序的,计算 是无序的,操作 还是 都是把其中一个数增加 ,也是无序的。所以 与 是等价的,计算出的答案是一样的。如果 ,把每个点 变换到直线 的镜像位置 。
从 倒退一步,上一步在哪?上一步执行了什么操作?
有四种情况:
- 执行了操作一(横坐标加横坐标,或者横坐标加纵坐标):
- 上一步把横坐标乘以 ,换句话说,上一步在 。前提条件: 是偶数且 ,即 。
- 上一步把横坐标加上了 ,换句话说,上一步在 。前提条件:,即 。
- 执行了操作二(纵坐标加纵坐标,或者纵坐标加横坐标),类似地:
- 上一步在 。前提条件: 是偶数且 ,即 ,结合 的假设,得 ,这只在 时才成立(不考虑负数)。代入 ,得 ,即 。这与 的假设矛盾,所以无需考虑这种情况。
- 上一步在 。由于 且本题不需要考虑负数,所以只有当 时,才能这么操作。由于 ,此时 。
根据上述讨论,从 倒退一步的方法如下:
- 如果 :
- 当 的时候,只能把 变成 (把 变成 无法回到起点)。
- 当 的时候,只能把 变成 (把 变成 无法回到起点)。
- 无需判断 的情况, 或 在减半的过程中一定会变成奇数,我们会返回 。
- 否则 ,此时只能执行操作一:
- 如果 ,只能把 减半。
- 如果 ,只能把 减少 。
- 如果 ,二者效果一样,都会把 变成 。
按照上述规则模拟,每倒退一步,把答案加一。注意这不是贪心,是纯模拟。换句话说,如果我们没有返回 ,那么起点与终点之间的走法是唯一的。
边界情况:
- 如果 或 ,走过头了,返回 。
- 如果 且 ,返回答案。
具体请看 视频讲解,欢迎点赞关注~
复杂度分析
- 时间复杂度:。 时把 减半; 时把 减去 ,减去的 。所以 至少减半。
- 空间复杂度:。
相似题目
另见下面贪心与思维题单的「§5.3 逆向思维」。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
// https://space.bilibili.com/206214
func minMoves(sx, sy, x, y int) (ans int) {
for ; x != sx || y != sy; ans++ {
if x < sx || y < sy {
return -1
}
if x == y {
if sy > 0 {
x = 0
} else {
y = 0
}
continue
}
// 保证 x > y
if x < y {
x, y = y, x
sx, sy = sy, sx
}
if x >= y*2 {
if x%2 > 0 {
return -1
}
x /= 2
} else {
x -= y
}
}
return
}
3618. 根据质数下标分割数组 ​
题目、示例与约束
给你一个整数数组 nums。
根据以下规则将 nums 分割成两个数组 A 和 B:
nums中位于 质数 下标的元素必须放入数组A。- 所有其他元素必须放入数组
B。
返回两个数组和的 绝对 差值:|sum(A) - sum(B)|。
质数 是一个大于 1 的自然数,它只有两个因子,1和它本身。
注意:空数组的和为 0。
示例 1:
输入: nums = [2,3,4]
输出: 1
解释:
- 数组中唯一的质数下标是 2,所以
nums[2] = 4被放入数组A。 - 其余元素
nums[0] = 2和nums[1] = 3被放入数组B。 sum(A) = 4,sum(B) = 2 + 3 = 5。- 绝对差值是
|4 - 5| = 1。
示例 2:
输入: nums = [-1,5,7,0]
输出: 3
解释:
- 数组中的质数下标是 2 和 3,所以
nums[2] = 7和nums[3] = 0被放入数组A。 - 其余元素
nums[0] = -1和nums[1] = 5被放入数组B。 sum(A) = 7 + 0 = 7,sum(B) = -1 + 5 = 4。- 绝对差值是
|7 - 4| = 3。
提示:
1 <= nums.length <= 105-109 <= nums[i] <= 109
方法一:埃氏筛 + 模拟 · Go 实现
思考
答案是质数下标之和与非质数下标之和的差的绝对值,等价于对每个 把 加上或减去。下标范围到 ,每次试除判断质数偏慢。
埃氏筛一次性标记 的质数性。遍历时质数下标取正、否则取负,最后取绝对值和。
筛表可预处理复用,单次询问只需线性扫一遍数组。
我们可以用埃氏筛法预处理出 范围内的所有质数。然后遍历数组 ,对于 ,如果 是质数,则将 加到答案中,否则将 加到答案中。最后返回答案的绝对值。
忽略预处理的时间和空间,时间复杂度 ,其中 是数组 的长度,空间复杂度 。
const M = 100000 + 10
var primes [M]bool
func init() {
for i := 0; i < M; i++ {
primes[i] = true
}
primes[0], primes[1] = false, false
for i := 2; i < M; i++ {
if primes[i] {
for j := i + i; j < M; j += i {
primes[j] = false
}
}
}
}
func splitArray(nums []int) (ans int64) {
for i, num := range nums {
if primes[i] {
ans += int64(num)
} else {
ans -= int64(num)
}
}
return max(ans, -ans)
}
3622. 判断整除性 ​
题目、示例与约束
给你一个正整数 n。请判断 n 是否可以被以下两值之和 整除:
-
n的 数字和(即其各个位数之和)。 -
n的 数字积(即其各个位数之积)。
如果 n 能被该和整除,返回 true;否则,返回 false。
示例 1:
输入: n = 99
输出: true
解释:
因为 99 可以被其数字和 (9 + 9 = 18) 与数字积 (9 * 9 = 81) 之和 (18 + 81 = 99) 整除,因此输出为 true。
示例 2:
输入: n = 23
输出: false
解释:
因为 23 无法被其数字和 (2 + 3 = 5) 与数字积 (2 * 3 = 6) 之和 (5 + 6 = 11) 整除,因此输出为 false。
提示:
1 <= n <= 106
方法一:模拟 · Go 实现
思考
,取出各位数字后求和与求积即可。用 从低位剥离,避免转成字符串。
积的初值须为 而非 。判断 是否整除 。位数为 。
我们可以遍历整数 的每一位数字,计算出数字和 和数字积 。最后判断 是否能被 整除。
时间复杂度 ,其中 为整数 的值。空间复杂度 。
func checkDivisibility(n int) bool {
s, p := 0, 1
x := n
for x != 0 {
v := x % 10
x /= 10
s += v
p *= v
}
return n%(s+p) == 0
}
3623. 统计梯形的数目 I ​
题目、示例与约束
给你一个二维整数数组 points,其中 points[i] = [xi, yi] 表示第 i 个点在笛卡尔平面上的坐标。
水平梯形 是一种凸四边形,具有 至少一对 水平边(即平行于 x 轴的边)。两条直线平行当且仅当它们的斜率相同。
返回可以从 points 中任意选择四个不同点组成的 水平梯形 数量。
由于答案可能非常大,请返回结果对 109 + 7 取余数后的值。
示例 1:
输入: points = [[1,0],[2,0],[3,0],[2,2],[3,2]]
输出: 3
解释:

有三种不同方式选择四个点组成一个水平梯形:
- 使用点
[1,0]、[2,0]、[3,2]和[2,2]。 - 使用点
[2,0]、[3,0]、[3,2]和[2,2]。 - 使用点
[1,0]、[3,0]、[3,2]和[2,2]。
示例 2:
输入: points = [[0,0],[1,0],[0,1],[2,1]]
输出: 1
解释:

只有一种方式可以组成一个水平梯形。
提示:
4 <= points.length <= 105–108 <= xi, yi <= 108- 所有点两两不同。
方法一:枚举 · Go 实现
思考
题目只统计两腰为水平边的梯形,因此一对水平边由同一 上至少两点确定。 ,枚举四点不可行。
按 分组后,一组 个点贡献 条水平边。两条位于不同 的水平边恰确定一个梯形。
扫描各组,用 记录此前水平边总数,当前组贡献 条边时累加 ,再把 加入 。对 取模。
根据题目描述,水平边满足 坐标相同,因此我们可以根据 坐标将点进行分组,统计每个 坐标对应的点的数量。
我们用一个哈希表 来存储每个 坐标对应的点的数量。对于每个 坐标 ,假设对应的点的数量为 ,那么从这些点中选择两点作为水平边的方式有 种,记为 。
我们用一个变量 来记录之前所有 坐标对应的水平边的数量之和。那么,我们可以将当前 坐标对应的水平边的数量 与之前所有 坐标对应的水平边的数量之和 相乘,得到以当前 坐标为一对水平边的梯形的数量,并将其累加到答案中。最后,我们将当前 坐标对应的水平边的数量 累加到 中,以便后续计算。
注意,由于答案可能非常大,我们需要对 取余数。
时间复杂度 ,空间复杂度 。其中 是点的数量。
func countTrapezoids(points [][]int) int {
const mod = 1_000_000_007
cnt := make(map[int]int)
for _, p := range points {
cnt[p[1]]++
}
var ans, s int64
for _, v := range cnt {
t := int64(v) * int64(v-1) / 2
ans = (ans + s*t) % mod
s += t
}
return int(ans)
}
3625. 统计梯形的数目 II ​
题目、示例与约束
给你一个二维整数数组 points,其中 points[i] = [xi, yi] 表示第 i 个点在笛卡尔平面上的坐标。
返回可以从 points 中任意选择四个不同点组成的梯形的数量。
梯形 是一种凸四边形,具有 至少一对 平行边。两条直线平行当且仅当它们的斜率相同。
示例 1:
输入: points = [[-3,2],[3,0],[2,3],[3,2],[2,-3]]
输出: 2
解释:

有两种不同方式选择四个点组成一个梯形:
- 点
[-3,2], [2,3], [3,2], [2,-3]组成一个梯形。 - 点
[2,3], [3,2], [3,0], [2,-3]组成另一个梯形。
示例 2:
输入: points = [[0,0],[1,0],[0,1],[2,1]]
输出: 1
解释:

只有一种方式可以组成一个梯形。
提示:
4 <= points.length <= 500–1000 <= xi, yi <= 1000- 所有点两两不同。
方法一:哈希表 + 枚举 · Go 实现
思考
一般梯形由两对平行但长度不同的边确定, ,枚举四点为 ,而枚举点对为 可接受。
同一直线由斜率 与截距 标识。斜率相同、截距不同的线段两两可组成梯形;平行四边形的两组对边会被各计一次,须再减去。
平行四边形的对角线中点重合。用 统计点对所在直线,用 统计中点与斜率。对同一 下不同 的计数两两相乘累加,再对同一中点下不同斜率的计数两两相乘从答案中扣除。
我们可以把所有点两两组合,计算出每一对点所对应的直线的斜率和截距,并使用哈希表进行记录,计算斜率相同且截距不同的直线两两组合得到的数量之和。注意,对于平行四边形,我们在上述计算中会被重复计算两次,因此我们需要将其减去。
平行四边形的对角线中点重合,因此我们同样把所有点两两组合,计算出每一对点的中点坐标和斜率,并使用哈希表进行记录,计算斜率相同且中点坐标相同的点对两两组合得到的数量之和。
具体地,我们使用两个哈希表 和 分别记录以下信息:
- 其中 记录斜率 和截距 出现的次数,键为斜率 ,值为另一个哈希表,记录截距 出现的次数;
- 其中 记录点对的中点坐标和斜率 出现的次数,键为点对的中点坐标 ,值为另一个哈希表,记录斜率 出现的次数。
对于点对 和 ,我们记 ,并且 。如果 ,则说明两点在同一条垂直线上,我们记斜率 ,截距 ;否则斜率 ,截距 。点对的中点坐标 可以表示为 。加上偏移量是为了避免负数。两个平移后的和都落在 ,因此步长必须是 ;步长取 时, 与 会得到同一个键。
接下来,我们遍历所有点对,计算出对应的斜率 、截距 和中点坐标 ,并更新哈希表 和 。
然后,我们遍历哈希表 ,对于每一个斜率 ,我们计算所有截距 出现次数的两两组合之和,并累加到答案中。最后,我们遍历哈希表 ,对于每一个中点坐标 ,我们计算所有斜率 出现次数的两两组合之和,并从答案中减去。
时间复杂度 ,空间复杂度 。其中 是点的数量。
func countTrapezoids(points [][]int) int {
n := len(points)
cnt1 := make(map[float64]map[float64]int, n*n)
cnt2 := make(map[int]map[float64]int, n*n)
for i := 0; i < n; i++ {
x1, y1 := points[i][0], points[i][1]
for j := 0; j < i; j++ {
x2, y2 := points[j][0], points[j][1]
dx, dy := x2-x1, y2-y1
var k, b float64
if dx == 0 {
k = 1e9
b = float64(x1)
} else {
k = float64(dy) / float64(dx)
b = float64(int64(y1)*int64(dx)-int64(x1)*int64(dy)) / float64(dx)
}
if cnt1[k] == nil {
cnt1[k] = make(map[float64]int)
}
cnt1[k][b]++
p := (x1+x2+2000)*4001 + (y1 + y2 + 2000)
if cnt2[p] == nil {
cnt2[p] = make(map[float64]int)
}
cnt2[p][k]++
}
}
ans := 0
for _, e := range cnt1 {
s := 0
for _, t := range e {
ans += s * t
s += t
}
}
for _, e := range cnt2 {
s := 0
for _, t := range e {
ans -= s * t
s += t
}
}
return ans
}
3648. 覆盖网格的最少传感器数目 ​
题目、示例与约束
给你一个 n × m 的网格和一个整数 k。
一个放置在单元格 (r, c) 的传感器可以覆盖所有与 (r, c) 的 切比雪夫距离不超过 k 的单元格。
两个单元格 (r1, c1) 和 (r2, c2) 之间的 切比雪夫距离 为 max(|r1 − r2|,|c1 − c2|)。
你的任务是返回覆盖整个网格所需传感器的 最少 数量。
示例 1:
输入: n = 5, m = 5, k = 1
输出: 4
解释:
在位置 (0, 3)、(1, 0)、(3, 3) 和 (4, 1) 放置传感器可以确保网格中的每个单元格都被覆盖。因此,答案是 4。
示例 2:
输入: n = 2, m = 2, k = 2
输出: 1
解释:
当 k = 2 时,无论传感器放在哪个位置,单个传感器都可以覆盖整个 2 * 2 的网格。因此,答案是 1。
提示:
1 <= n <= 1031 <= m <= 1030 <= k <= 103
方法一:Go 实现(灵茶山艾府) · Go 实现
灵茶山艾府 · 源码 · MIT 许可 · 原题解
根据题意,覆盖范围是一个正方形。从正方形中心往左最多走 步,往右最多走 步,所以边长为 。
问题变成用 的正方形覆盖 的网格,最少要用多少个正方形。
每 行分成一段,一共分 段,每一段需要 个正方形。
所以一共需要
个正方形。
注:如果正方形比 的网格还大,那么上式算出的结果是 ,符合实际情况。
根据 上取整下取整转换公式的证明,我们可以将式子中的上取整转化成下取整,方便计算机计算。
具体请看 视频讲解,欢迎点赞关注~
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府
// https://space.bilibili.com/206214
func minSensors(n, m, k int) int {
size := k*2 + 1
return ((n-1)/size + 1) * ((m-1)/size + 1)
}
3658. 奇数和与偶数和的最大公约数 ​
题目、示例与约束
给你一个整数 n。请你计算以下两个值的 最大公约数(GCD):
-
sumOdd:最小的n个正奇数的总和。 -
sumEven:最小的n个正偶数的总和。
返回 sumOdd 和 sumEven 的 GCD。
示例 1:
输入: n = 4
输出: 4
解释:
- 前 4 个奇数的总和
sumOdd = 1 + 3 + 5 + 7 = 16 - 前 4 个偶数的总和
sumEven = 2 + 4 + 6 + 8 = 20
因此,GCD(sumOdd, sumEven) = GCD(16, 20) = 4。
示例 2:
输入: n = 5
输出: 5
解释:
- 前 5 个奇数的总和
sumOdd = 1 + 3 + 5 + 7 + 9 = 25 - 前 5 个偶数的总和
sumEven = 2 + 4 + 6 + 8 + 10 = 30
因此,GCD(sumOdd, sumEven) = GCD(25, 30) = 5。
提示:
1 <= n <= 1000
方法一:数学 · Go 实现
思考
前 个奇数之和为 ,前 个偶数之和为 。二者的公约数至少含因子 。
与 互质,故 。无需枚举或调用通用 。
直接返回 。
前 个奇数的总和为 ,而前 个偶数的总和为 。这两者的最大公约数至少为 ,又由于 和 互质,因此答案就是 。
时间复杂度 ,空间复杂度 。
func gcdOfOddEvenSums(n int) int {
return n
}
3663. 出现频率最低的数字 ​
题目、示例与约束
给你一个整数 n,找出在其十进制表示中出现频率 最低 的数字。如果多个数字的出现频率相同,则选择 最小 的那个数字。
以整数形式返回所选的数字。
数字 x 的出现频率是指它在 n 的十进制表示中的出现次数。
示例 1:
输入: n = 1553322
输出: 1
解释:
在 n 中,出现频率最低的数字是 1,它只出现了一次。所有其他数字都出现了两次。
示例 2:
输入: n = 723344511
输出: 2
解释:
在 n 中,出现频率最低的数字是 7、2 和 5,它们都只出现了一次。
提示:
1 <= n <= 231 - 1
方法一:计数 · Go 实现
思考
要在 的十进制数字中找出现次数最少者,并列取最小数字。十个桶即可。
反复 累加 的次数,再扫一遍桶,更新「次数为正且更小」的数字。
不出现的数字不参与比较。位数为 。
我们用一个数组 来统计每个数字出现的频率。遍历数字 的每一位,更新 数组。
然后,我们用一个变量 来记录当前出现频率最低的数字的频率,以及一个变量 来记录对应的数字。
接下来,我们遍历 数组,如果 ,说明我们找到了出现频率更低的数字,更新 ,以及 。
遍历结束后,返回答案 即可。
时间复杂度 ,空间复杂度 。
func getLeastFrequentDigit(n int) (ans int) {
cnt := [10]int{}
for ; n > 0; n /= 10 {
cnt[n%10]++
}
f := 1 << 30
for x, v := range cnt {
if v > 0 && v < f {
f = v
ans = x
}
}
return
}
3697. 计算十进制表示 ​
题目、示例与约束
给你一个 正整数 n。
如果一个正整数可以表示为 1 到 9 的单个数字和 10 的非负整数次幂的乘积,则称这个整数是一个 10 进制分量。例如,500、30 和 7 是 10 进制分量 ,而 537、102 和 11 则不是。
请将 n 表示为若干 仅由 10 进制分量组成的和,且使用的 10 进制分量个数 最少 。
返回一个包含这些 10 进制分量 的数组,并按分量大小 降序 排列。
示例 1:
输入:n = 537
输出:[500,30,7]
解释:
我们可以将 537 表示为500 + 30 + 7。无法用少于 3 个 10 进制分量表示 537。
示例 2:
输入:n = 102
输出:[100,2]
解释:
我们可以将 102 表示为100 + 2。102 不是一个 10 进制分量,因此需要 2 个 10 进制分量。
示例 3:
输入:n = 6
输出:[6]
解释:
6 是一个 10 进制分量。
提示:
1 <= n <= 109
方法一:模拟 · Go 实现
思考
把 拆成 形式的分量并按降序输出。从低位取模即可,零分量不输出。
维护位权 ,每次 得到数字 ,若非零则写入 ,然后 。
最后反转列表得到从高位到低位的顺序。
我们可以不断地对 进行取模和整除操作,取模得到的值乘以当前的位值 就是一个 10 进制分量。如果取模的结果不为 ,我们就将这个分量加入答案中。然后我们将 乘以 ,继续处理下一个位。
最后,我们将答案反转,使其按降序排列。
时间复杂度 ,其中 是输入的正整数。空间复杂度 ,用于存储答案。
func decimalRepresentation(n int) []int {
ans := []int{}
p := 1
for n > 0 {
v := n % 10
n /= 10
if v != 0 {
ans = append(ans, p*v)
}
p *= 10
}
slices.Reverse(ans)
return ans
}
3726. 移除十进制表示中的所有零 ​
题目、示例与约束
给你一个正整数n。
返回一个整数,该整数是将 n 的十进制表示中所有的零都移除后得到的结果。
示例 1:
输入: n = 1020030
输出: 123
解释:
从 1020030 中移除所有的零后,得到 123。
示例 2:
输入: n = 1
输出: 1
解释:
1 的十进制表示中没有零,因此结果为 1。
提示:
1 <= n <= 1015
方法一:模拟 · Go 实现
思考
可达 ,转成字符串再拼回去也可以,但按位处理不必额外存串。从低位取模 ,非零位按当前权 累加到答案,零位跳过, 只在写入时乘 。
我们从 的最低位开始,逐位检查每个数字。如果该数字不为零,则将其添加到结果中。我们还需要一个变量来跟踪当前的位数,以便正确地构建最终的整数。
具体来说,我们可以使用一个变量 来表示当前的位数,然后从最低位开始逐位检查每个数字。如果该数字不为零,则将其乘以 并加到结果中,同时将 乘以 10,以便在下一位时使用。
最终,我们将得到一个不包含零的整数。
时间复杂度 ,其中 是整数 的位数。空间复杂度 。
func removeZeros(n int64) (ans int64) {
k := int64(1)
for n > 0 {
x := n % 10
if x > 0 {
ans = k*x + ans
k *= 10
}
n /= 10
}
return
}
3736. 最小操作次数使数组元素相等 III ​
题目、示例与约束
给你一个整数数组 nums。
在一步操作中,你可以将任意单个元素 nums[i] 的值 增加 1。
返回使数组中的所有元素都 相等 所需的 最小总操作次数 。
示例 1:
输入: nums = [2,1,3]
输出: 3
解释:
使所有元素相等的操作如下:
- 将
nums[0] = 2增加 1, 变为 3。 - 将
nums[1] = 1增加 1, 变为 2。 - 将
nums[1] = 2增加 1, 变为 3。
现在,nums 中的所有元素都等于 3。最小总操作次数为 3。
示例 2:
输入: nums = [4,4,5]
输出: 2
解释:
使所有元素相等的操作如下:
- 将
nums[0] = 4增加 1, 变为 5。 - 将
nums[1] = 4增加 1, 变为 5。
现在,nums 中的所有元素都等于 5。最小总操作次数为 2。
提示:
1 <= nums.length <= 1001 <= nums[i] <= 100
方法一: 计算总和与最大值 · Go 实现
思考
每次只能把某个元素加 ,最终值不会低于当前最大值,否则最大值还得继续加。因此目标就是数组最大值, 即为总增量。
本题要求将数组中的所有元素变为相等,且每次只能将某个元素增加 1。为了使操作次数最少,我们应当将所有元素都变为数组中的最大值。
因此,我们可以先计算数组的最大值 和数组元素的总和 ,那么将所有元素变为 所需的操作次数即为 ,其中 是数组的长度。
时间复杂度 ,其中 是数组的长度。空间复杂度 。
func minMoves(nums []int) int {
mx, s := 0, 0
for _, x := range nums {
mx = max(mx, x)
s += x
}
return mx*len(nums) - s
}
3754. 连接非零数字并乘以其数字和 I ​
题目、示例与约束
给你一个整数 n。
将 n 中所有的 非零数字 按照它们的原始顺序连接起来,形成一个新的整数 x。如果不存在 非零数字 ,则 x = 0。
sum 为 x 中所有数字的 数字和 。
返回一个整数,表示 x * sum 的值。
示例 1:
输入: n = 10203004
输出: 12340
解释:
- 非零数字是 1、2、3 和 4。因此,
x = 1234。 - 数字和为
sum = 1 + 2 + 3 + 4 = 10。 - 因此,答案是
x * sum = 1234 * 10 = 12340。
示例 2:
输入: n = 1000
输出: 1
解释:
- 非零数字是 1,因此
x = 1且sum = 1。 - 因此,答案是
x * sum = 1 * 1 = 1。
提示:
0 <= n <= 109
方法一:模拟 · Go 实现
思考
的位数很少,按定义去掉零再求和即可。从低位取数字,非零位同时累加到 与数位和 ,最后返回 。
我们可以通过对数字逐位处理来模拟题目要求的操作。在处理每一位数字时,我们将非零数字连接起来形成新的整数 ,同时计算数字和 ,最后返回 即可。
时间复杂度 ,空间复杂度 。
func sumAndMultiply(n int) int64 {
p := 1
x := 0
s := 0
for n > 0 {
v := n % 10
if v != 0 {
s += v
x += p * v
p *= 10
}
n /= 10
}
return int64(x) * int64(s)
}
3761. 镜像对之间最小绝对距离 ​
题目、示例与约束
给你一个整数数组 nums。
镜像对 是指一对满足下述条件的下标 (i, j):
0 <= i < j < nums.length,并且reverse(nums[i]) == nums[j],其中reverse(x)表示将整数x的数字反转后形成的整数。反转后会忽略前导零,例如reverse(120) = 21。
返回任意镜像对的下标之间的 最小绝对距离。下标 i 和 j 之间的绝对距离为 abs(i - j)。
如果不存在镜像对,返回 -1。
示例 1:
输入: nums = [12,21,45,33,54]
输出: 1
解释:
镜像对为:
- (0, 1),因为
reverse(nums[0]) = reverse(12) = 21 = nums[1],绝对距离为abs(0 - 1) = 1。 - (2, 4),因为
reverse(nums[2]) = reverse(45) = 54 = nums[4],绝对距离为abs(2 - 4) = 2。
所有镜像对中的最小绝对距离是 1。
示例 2:
输入: nums = [120,21]
输出: 1
解释:
只有一个镜像对 (0, 1),因为 reverse(nums[0]) = reverse(120) = 21 = nums[1]。
最小绝对距离是 1。
示例 3:
输入: nums = [21,120]
输出: -1
解释:
数组中不存在镜像对。
提示:
1 <= nums.length <= 1051 <= nums[i] <= 109
方法一:哈希表 · Go 实现
思考
镜像对要求 。最近的 一定来自该反转值最近一次出现的位置,从左到右扫描并用哈希表记下 的最后下标即可。
我们可以用一个哈希表 来记录每个反转后的数字最后一次出现的位置。
我们首先初始化答案 ,其中 是数组 的长度。
接下来,遍历数组 ,对于每个下标 和对应的数字 ,如果 中存在键 ,则说明存在一个下标 满足 反转后等于 ,此时我们更新答案为 。然后,我们将 更新为 。继续这个过程直到遍历完整个数组。
最后,如果答案 仍然等于 ,则说明不存在镜像对,我们返回 ;否则,返回答案 。
时间复杂度 ,其中 是数组 的长度,而 是数组中数字的最大值。空间复杂度 ,用于存储哈希表 。
func minMirrorPairDistance(nums []int) int {
n := len(nums)
pos := map[int]int{}
ans := n + 1
reverse := func(x int) int {
y := 0
for ; x > 0; x /= 10 {
y = y*10 + x%10
}
return y
}
for i, x := range nums {
if j, ok := pos[x]; ok {
ans = min(ans, i-j)
}
pos[reverse(x)] = i
}
if ans > n {
return -1
}
return ans
}
3765. 完全质数 ​
题目、示例与约束
给你一个整数 num。
如果一个数 num 的每一个 前缀 和每一个 后缀 都是 质数,则称该数为 完全质数。
如果 num 是完全质数,返回 true,否则返回 false。
注意:
- 一个数的 前缀 是由该数的 前
k位数字构成的。 - 一个数的 后缀 是由该数的 后
k位数字构成的。 - 质数 是大于 1 且只有两个因子(1 和它本身)的自然数。
- 个位数只有在它是 质数 时才被视为完全质数。
示例 1:
输入:num = 23
输出:true
解释:
num = 23的前缀是 2 和 23,它们都是质数。num = 23的后缀是 3 和 23,它们都是质数。- 所有的前缀和后缀都是质数,所以 23 是完全质数,答案是
true。
示例 2:
输入:num = 39
输出:false
解释:
num = 39的前缀是 3 和 39。3 是质数,但 39 不是质数。num = 39的后缀是 9 和 39。9 和 39 都不是质数。- 至少有一个前缀或后缀不是质数,所以 39 不是完全质数,答案是
false。
示例 3:
输入:num = 7
输出:true
解释:
- 7 是质数,所以它的所有前缀和后缀都是质数,答案是
true。
提示:
1 <= num <= 109
方法一:数学 · Go 实现
思考
完全质数要求每一个前缀与每一个后缀都是质数。 ,前缀、后缀各 个,对每个数做 判定即可。从左累加构造前缀,从右按权构造后缀。
我们定义一个函数 来判断一个数 是否为质数。具体地,如果 ,则 不是质数;否则,我们检查从 到 的所有整数 ,如果存在某个 能整除 ,则 不是质数,否则 是质数。
接下来,我们将整数 转换为字符串 ,并依次检查 的每个前缀和后缀所对应的整数是否为质数。对于前缀,我们从左到右构造整数 ,对于后缀,我们从右到左构造整数 。如果在检查过程中发现某个前缀或后缀对应的整数不是质数,则返回 ;如果所有前缀和后缀对应的整数都是质数,则返回 。
时间复杂度 ,空间复杂度 ,其中 是整数 的大小。
func completePrime(num int) bool {
isPrime := func(x int) bool {
if x < 2 {
return false
}
for i := 2; i*i <= x; i++ {
if x%i == 0 {
return false
}
}
return true
}
s := strconv.Itoa(num)
x := 0
for i := 0; i < len(s); i++ {
x = x*10 + int(s[i]-'0')
if !isPrime(x) {
return false
}
}
x = 0
p := 1
for i := len(s) - 1; i >= 0; i-- {
x = p*int(s[i]-'0') + x
p *= 10
if !isPrime(x) {
return false
}
}
return true
}
3770. 可表示为连续质数和的最大质数 ​
题目、示例与约束
给你一个整数 n。
返回小于或等于 n 的最大质数,该质数可以表示为从 2 开始的一个或多个 连续质数 之和。如果不存在这样的质数,则返回 0。
质数是大于 1 的自然数,且只有两个因数:1 和它本身。
示例 1:
输入: n = 20
输出: 17
解释:
小于或等于 n = 20,且是连续质数和的质数有:
-
2 = 2 -
5 = 2 + 3 -
17 = 2 + 3 + 5 + 7
其中最大的质数是 17,因此答案是 17。
示例 2:
输入: n = 2
输出: 2
解释:
唯一小于或等于 2 的连续质数和是 2 本身。
提示:
1 <= n <= 5 * 105
方法一:预处理 + 二分查找 · Go 实现
思考
,且必须从 开始的连续质数之和。筛出上限内全部质数后,累加前缀和并保留其中仍为质数的值;询问时在该递增序列上二分不超过 的最大值。
我们可以预处理出所有小于等于 的质数列表,然后计算从 2 开始的连续质数和,并将这些和中是质数的数存储在一个数组中 中。
对于每个查询,我们只需要在数组 中使用二分查找找到小于等于 的最大值即可。
时间复杂度方面,预处理质数的时间复杂度为 ,每次查询的时间复杂度为 ,其中 是预处理的上限,而 是数组 的长度,本题中 。
const MX = 500000
var (
isPrime = make([]bool, MX+1)
primes []int
S []int
)
func init() {
for i := range isPrime {
isPrime[i] = true
}
isPrime[0] = false
isPrime[1] = false
for i := 2; i <= MX; i++ {
if isPrime[i] {
primes = append(primes, i)
if i*i <= MX {
for j := i * i; j <= MX; j += i {
isPrime[j] = false
}
}
}
}
S = append(S, 0)
t := 0
for _, x := range primes {
t += x
if t > MX {
break
}
if isPrime[t] {
S = append(S, t)
}
}
}
func largestPrime(n int) int {
i := sort.SearchInts(S, n+1)
return S[i-1]
}
3782. 交替删除操作后最后剩下的整数 ​
题目、示例与约束
给你一个整数 n。
我们将 1 到 n 的整数按从左到右的顺序排成一个序列。然后,交替 地执行以下两种操作,直到只剩下一个整数为止,从操作 1 开始:
- 操作 1:从左侧开始,隔一个数删除一个数。
- 操作 2:从右侧开始,隔一个数删除一个数。
返回最后剩下的那个整数。
示例 1:
输入: n = 8
输出: 3
解释:
- 写下序列
[1, 2, 3, 4, 5, 6, 7, 8]。 - 从左侧开始,我们删除每隔一个数字:
[1, 2, 3, 4, 5, 6, 7, 8]。剩下的整数是[1, 3, 5, 7]。 - 从右侧开始,我们删除每隔一个数字:
[1, 3, 5, 7]。剩下的整数是[3, 7]。 - 从左侧开始,我们删除每隔一个数字:
[3, 7]。剩下的整数是[3]。
示例 2:
输入: n = 5
输出: 1
解释:
- 写下序列
[1, 2, 3, 4, 5]。 - 从左侧开始,我们删除每隔一个数字:
[1, 2, 3, 4, 5]。剩下的整数是[1, 3, 5]。 - 从右侧开始,我们删除每隔一个数字:
[1, 3, 5]。剩下的整数是[1, 5]。 - 从左侧开始,我们删除每隔一个数字:
[1, 5]。剩下的整数是[1]。
示例 3:
输入: n = 1
输出: 1
解释:
- 写下序列
[1]。 - 最后剩下的整数是 1。
提示:
1 <= n <= 1015
方法一:Go 实现(灵茶山艾府) · Go 实现
灵茶山艾府 · 源码 · MIT 许可 · 原题解
方法一:用等差数列模拟
示例 1 的操作流程如下:
- 序列一开始是 ,这是一个首项为 ,公差为 的等差数列。
- 删除所有奇数下标数字,得到序列 。由于下次操作要从右侧开始删,我们可以把序列反转,得到 ,那么下次操作相当于还是从左侧开始删。序列 是一个首项为 ,公差为 的等差数列。
- 删除所有奇数下标数字,得到序列 。由于下次操作要从右侧开始删,我们可以把序列反转,得到 ,那么下次操作相当于还是从左侧开始删。序列 是一个首项为 ,公差为 的等差数列。
- 删除所有奇数下标数字,得到序列 。
考察上述流程:
- 每次操作,删除所有奇数下标数字。当 是偶数时,删除一半,当 是奇数时,删除 个(比如 时删除 个)。所以每次都会删除 个元素,序列元素个数从 变成 。
- 间隔地删除一个等差数列中的元素,得到的仍然是一个等差数列。
- 公差初始为 ,每次操作把 乘以 。
- 如果 是奇数,那么序列的最后一个数一定保留。操作后,首项 变成序列的最后一个数 ,其中 和 都是操作之前的值。
- 如果 是偶数,那么序列的最后一个数被删除,倒数第二个数保留。操作后,首项 变成序列的倒数第二个数 ,其中 和 都是操作之前的值。
本题视频讲解,欢迎点赞关注~
复杂度分析
- 时间复杂度:。根据 下取整恒等式及其应用, 次操作后序列长度为 ,所以操作次数为 。
- 空间复杂度:。
方法二:位运算
为方便计算,把初始序列改成从 开始,即 。
第一次操作,我们删除了所有的奇数,剩余的都是偶数。这意味着,(从 开始的)最终答案,二进制最低位一定是 。
把剩余元素 全部右移一位,我们又得到了序列 。在此基础上,执行第二次操作。
在第二次操作中,我们要从右往左删除:
- 如果序列最后一个数是偶数,例如 (对应着一开始 或者 ),那么我们会删除所有的奇数,剩余的都是偶数,这和序列最后一个数的奇偶性相同。注意序列最后一个数等于 右移一位。
- 如果序列最后一个数是奇数,例如 (对应着一开始 或者 ),那么我们会删除所有的偶数,剩余的都是奇数,这和序列最后一个数的奇偶性相同。注意序列最后一个数等于 右移一位。
这意味着,(从 开始的)最终答案,二进制从低到高第二位一定等于 从低到高第二位。
依此类推。
一般地,(从 开始的)最终答案,二进制从低到高第 位一定是 ;第 位一定和 的第 位相同。
算出答案后,把答案加一(因为原题的序列是从 开始的)。
复杂度分析
- 时间复杂度:。
- 空间复杂度:。
相似题目
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
// https://space.bilibili.com/206214
func lastInteger1(n int64) int64 {
start, d := int64(1), int64(1) // 等差数列首项,公差
for ; n > 1; n = (n + 1) / 2 {
start += (n - 2 + n%2) * d
d *= -2
}
return start
}
// https://oeis.org/A090569
func lastInteger(n int64) int64 {
const mask = 0xAAAAAAAAAAAAAAA // ...1010
return (n-1)&mask + 1 // 取出 n-1 的从低到高第 2,4,6,... 位,最后再加一(从 1 开始)
}
3783. 整数的镜像距离 ​
题目、示例与约束
给你一个整数 n。
定义它的 镜像距离 为:abs(n - reverse(n)),其中 reverse(n) 表示将 n 的数字反转后形成的整数。
返回表示 n 的镜像距离的整数。
其中,abs(x) 表示 x 的绝对值。
示例 1:
输入: n = 25
输出: 27
解释:
reverse(25) = 52。- 因此,答案为
abs(25 - 52) = 27。
示例 2:
输入: n = 10
输出: 9
解释:
reverse(10) = 01,即 1。- 因此,答案为
abs(10 - 1) = 9。
示例 3:
输入: n = 7
输出: 0
解释:
reverse(7) = 7。- 因此,答案为
abs(7 - 7) = 0。
提示:
1 <= n <= 109
方法一:模拟 · Go 实现
思考
镜像距离就是 与其数字反转之差的绝对值。反转去掉前导零,把 转成字符串再反转,或按位弹出最低位拼成新整数,二者等价。
我们定义一个函数 来反转整数 的数字。具体地,我们初始化一个变量 为 ,然后不断地将 的最后一位数字添加到 的末尾,并将 去掉最后一位,直到 变为 。最终 即为反转后的整数。
接下来,我们计算整数 的镜像距离,即 ,并返回结果。
时间复杂度 ,空间复杂度 ,其中 是输入整数的大小。
func mirrorDistance(n int) int {
reverse := func(x int) int {
y := 0
for ; x > 0; x /= 10 {
y = y*10 + x%10
}
return y
}
return abs(n - reverse(n))
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
3790. 最小全 1 倍数 ​
题目、示例与约束
给你一个正整数 k。
找出满足以下条件的 最小 整数 n:n 能被 k 整除,且其十进制表示中 只包含数字 1(例如:1、11、111、……)。
返回一个整数,表示 n 的十进制表示的 位数 。如果不存在这样的 n,则返回 -1。
示例 1:
输入: k = 3
输出: 3
解释:
n = 111,因为 111 能被 3 整除,但 1 和 11 不能。n = 111 的长度为 3。
示例 2:
输入: k = 7
输出: 6
解释:
n = 111111。n = 111111 的长度为 6。
示例 3:
输入: k = 2
输出: -1
解释:
不存在满足条件且为 2 的倍数的有效 n。
提示:
2 <= k <= 105
方法一:模拟 + 取模运算 · Go 实现
思考
全 的数对偶数 永不能整除。其余情况让余数按 迭代,余数空间只有 种,出现 即找到位数;循环 次仍非 则不存在。
首先,如果 是偶数,那么不存在满足条件的 ,直接返回 。
接下来,我们可以通过模拟构造全 数字 的过程,同时对 取模,来判断是否存在满足条件的 。
我们循环 次,判断这 次内是否存在一个全 数字 能被 整除。在每次循环中,我们将当前的余数乘以 并加上 ,然后对 取模。如果在某次循环中余数变为 ,则说明找到了满足条件的 ,返回当前循环的次数(即全 数字的位数)。如果循环结束后仍未找到,则返回 。
时间复杂度 ,空间复杂度 。
func minAllOneMultiple(k int) int {
if k&1 == 0 {
return -1
}
x := 1 % k
ans := 1
for i := 0; i < k; i++ {
x = (x*10 + 1) % k
ans++
if x == 0 {
return ans
}
}
return -1
}
继续阅读
- 实践案例
- 实践案例
- 实践案例