LeetCode Go · 数学与几何(1) ​
来源与许可
题目版权归 LeetCode;题解来自 Doocs/leetcode,按 CC BY-SA 4.0 共享。补充 Go 代码来自灵茶山艾府、halfrost,保留 MIT 许可及逐题署名。本站提取 Go、调整分类及折叠排版;标注“本站整理”的 Go 解法依据题意与参考思路补写,按 CC BY-SA 4.0 共享。收录日期:2026-10-31;主题源版本:91ef8c241a5e。
本卷 30 题 · 点击题目展开,先独立思考,再查看解法。
7. 整数反转 ​
题目、示例与约束
给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
示例 1:
输入:x = 123 输出:321
示例 2:
输入:x = -123 输出:-321
示例 3:
输入:x = 120 输出:21
示例 4:
输入:x = 0 输出:0
提示:
-231 <= x <= 231 - 1
方法一:数学 · Go 实现
思考
若将整数转为字符串再反转,不必处理算术溢出。题目假定环境无法存储 位整数,因此需要在 位范围内逐位还原,并在运算过程中判断是否越界。
我们可以不断取出 的个位 ,再累加到答案末尾,等价于 。在乘 之前就必须判断结果是否仍落在 内,一旦越界则返回 。
该判断可以化简为:当前 是否仍位于 。若在此范围内,再接入一位数字一定合法;若已经越出,则必然溢出。
我们不妨记 和 分别为 和 ,则 的反转结果 需要满足 。
我们可以通过不断地对 取余来获取 的最后一位数字 ,并将 添加到 的末尾。在添加 之前,我们需要判断 是否溢出。即判断 是否在 的范围内。
若 ,那么需要满足 ,即 。整理得 。
下面我们讨论上述不等式成立的条件:
- 当 时,不等式显然成立;
- 当 时,不等式成立的充要条件是 。如果 并且还能继续添加数字,说明此时数字是最高位,即此时 一定不超过 ,因此,不等式一定成立;
- 当 时,不等式显然不成立。
综上,当 时,不等式成立的充要条件是 。
同理,当 时,不等式成立的充要条件是 。
因此,我们可以通过判断 是否在 的范围内来判断 是否溢出。若溢出,则返回 。否则,将 添加到 的末尾,然后将 的最后一位数字去除,即 。
时间复杂度 ,其中 为 的绝对值。空间复杂度 。
func reverse(x int) (ans int) {
for ; x != 0; x /= 10 {
if ans < math.MinInt32/10 || ans > math.MaxInt32/10 {
return 0
}
ans = ans*10 + x%10
}
return
}
9. 回文数 ​
题目、示例与约束
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
- 例如,
121是回文,而123不是。
示例 1:
输入:x = 121 输出:true
示例 2:
输入:x = -121 输出:false 解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入:x = 10 输出:false 解释:从右向左读, 为 01 。因此它不是一个回文数。
提示:
-231 <= x <= 231 - 1
进阶:你能不将整数转为字符串来解决这个问题吗?
方法一:反转一半数字 · Go 实现
思考
判断 是否回文,一种做法是转为字符串再比较。题目进阶要求不使用字符串;若将整个 反转后再比较,则可能超出 位整数范围。
我们只需反转后半部分:不断将 的个位接到 上,直到 不再小于剩余的前半部分。负数带有负号,不可能是回文数;非零却以 结尾的数,反转后会丢失末尾的 ,也可以直接返回
false。循环在两半长度对齐时停止。位数为偶数时比较 与 ;位数为奇数时,中间一位落在 上,比较 与 。
我们先判断特殊情况:
- 如果 ,那么 不是回文数,直接返回
false; - 如果 且 的个位数是 ,那么 不是回文数,直接返回
false; - 如果 的个位数不是 ,那么 可能是回文数,继续执行下面的步骤。
我们将 的后半部分反转,与前半部分进行比较,如果相等,那么 是回文数,否则 不是回文数。
举个例子,例如 ,我们可以将数字后半部分从 “21” 反转为 “12”,并将其与前半部分 “12” 进行比较,因为二者相等,我们得知数字 是回文。
让我们看看如何将后半部分反转。
对于数字 ,如果执行 ,我们将得到最后一位数字 ,要得到倒数第二位数字,我们可以先通过除以 将最后一位数字从 中移除, ,再求出上一步结果除以 的余数, ,就可以得到倒数第二位数字。
如果继续这个过程,我们将得到更多位数的反转数字。
通过将最后一位数字不断地累乘到取出数字的变量 上,我们可以得到以相反顺序的数字。
在代码实现上,我们可以反复“取出” 的最后一位数字,并将其“添加”到 的后面,循环直到 ,如果此时 ,或者 ,那么 就是回文数。
时间复杂度 ,其中 是 。对于每次迭代,我们会将输入除以 ,因此时间复杂度为 。空间复杂度 。
func isPalindrome(x int) bool {
if x < 0 || (x > 0 && x%10 == 0) {
return false
}
y := 0
for ; y < x; x /= 10 {
y = y*10 + x%10
}
return x == y || x == y/10
}
12. 整数转罗马数字 ​
题目、示例与约束
七个不同的符号代表罗马数字,其值如下:
| 符号 | 值 |
|---|---|
| I | 1 |
| V | 5 |
| X | 10 |
| L | 50 |
| C | 100 |
| D | 500 |
| M | 1000 |
罗马数字是通过添加从最高到最低的小数位值的转换而形成的。将小数位值转换为罗马数字有以下规则:
- 如果该值不是以 4 或 9 开头,请选择可以从输入中减去的最大值的符号,将该符号附加到结果,减去其值,然后将其余部分转换为罗马数字。
- 如果该值以 4 或 9 开头,使用 减法形式,表示从以下符号中减去一个符号,例如 4 是 5 (
V) 减 1 (I):IV,9 是 10 (X) 减 1 (I):IX。仅使用以下减法形式:4 (IV),9 (IX),40 (XL),90 (XC),400 (CD) 和 900 (CM)。 - 只有 10 的次方(
I,X,C,M)最多可以连续附加 3 次以代表 10 的倍数。你不能多次附加 5 (V),50 (L) 或 500 (D)。如果需要将符号附加4次,请使用 减法形式。
给定一个整数,将其转换为罗马数字。
示例 1:
输入:num = 3749
输出: "MMMDCCXLIX"
解释:
3000 = MMM 由于 1000 (M) + 1000 (M) + 1000 (M) 700 = DCC 由于 500 (D) + 100 (C) + 100 (C) 40 = XL 由于 50 (L) 减 10 (X) 9 = IX 由于 10 (X) 减 1 (I) 注意:49 不是 50 (L) 减 1 (I) 因为转换是基于小数位
示例 2:
输入:num = 58
输出:"LVIII"
解释:
50 = L 8 = VIII
示例 3:
输入:num = 1994
输出:"MCMXCIV"
解释:
1000 = M 900 = CM 90 = XC 4 = IV
提示:
1 <= num <= 3999
方法一:贪心 · Go 实现
思考
按千、百、十、个分别查表,再单独处理 、 的减法写法,在 的范围内可以接受,但普通累加与减法形式要分成两套规则,分支多、容易遗漏。
注意到,若把 、 、 等也视为独立面额,罗马数字就变成一组从大到小的固定面值。题目保证表示唯一,因此每次选取还能放下的最大面额一定合法。我们按面额从大到小扫描,当前剩余值不小于该面额时便减去并拼接对应符号。
我们可以先将所有可能的符号 和对应的数值 列出来,然后从大到小枚举每个数值 ,每次尽可能多地使用该数值对应的符号 ,直到数字 变为 。
时间复杂度为 ,空间复杂度为 。其中 为符号的个数。
func intToRoman(num int) string {
cs := []string{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}
vs := []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
ans := &strings.Builder{}
for i, v := range vs {
for num >= v {
num -= v
ans.WriteString(cs[i])
}
}
return ans.String()
}
13. 罗马数字转整数 ​
题目、示例与约束
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值 I 1 V 5 X 10 L 50 C 100 D 500 M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I可以放在V(5) 和X(10) 的左边,来表示 4 和 9。X可以放在L(50) 和C(100) 的左边,来表示 40 和 90。C可以放在D(500) 和M(1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。
示例 1:
输入: s = "III" 输出: 3
示例 2:
输入: s = "IV" 输出: 4
示例 3:
输入: s = "IX" 输出: 9
示例 4:
输入: s = "LVIII" 输出: 58 解释: L = 50, V= 5, III = 3.
示例 5:
输入: s = "MCMXCIV" 输出: 1994 解释: M = 1000, CM = 900, XC = 90, IV = 4.
提示:
1 <= s.length <= 15s仅含字符('I', 'V', 'X', 'L', 'C', 'D', 'M')- 题目数据保证
s是一个有效的罗马数字,且表示整数在范围[1, 3999]内 - 题目所给测试用例皆符合罗马数字书写规则,不会出现跨位等情况。
- IL 和 IM 这样的例子并不符合题目要求,49 应该写作 XLIX,999 应该写作 CMXCIX 。
- 关于罗马数字的详尽书写规则,可以参考 罗马数字 - 百度百科。
方法一:哈希表 + 模拟 · Go 实现
思考
从左到右扫描,遇到 、 等六种减法组合再特殊处理,其余字符直接累加,在 的范围内可以接受,但特例容易遗漏。
一个字符应当减去,当且仅当它比右侧相邻字符小;末位永远是加。因此不必枚举那六种组合。我们可以用哈希表记录每个字符的数值,从左到右比较相邻两位以决定正负,最后补上末位即可。
我们先用哈希表 记录每个字符对应的数值,然后从左到右遍历字符串 ,如果当前字符对应的数值小于右边字符对应的数值,则减去当前字符对应的数值,否则加上当前字符对应的数值。
时间复杂度 ,空间复杂度 。其中 和 分别为字符串 的长度和字符集的大小。
func romanToInt(s string) (ans int) {
d := map[byte]int{'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
for i := 0; i < len(s)-1; i++ {
if d[s[i]] < d[s[i+1]] {
ans -= d[s[i]]
} else {
ans += d[s[i]]
}
}
ans += d[s[len(s)-1]]
return
}
43. 字符串相乘 ​
题目、示例与约束
给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。
注意:不能使用任何内置的 BigInteger 库或直接将输入转换为整数。
示例 1:
输入: num1 = "2", num2 = "3" 输出: "6"
示例 2:
输入: num1 = "123", num2 = "456" 输出: "56088"
提示:
1 <= num1.length, num2.length <= 200num1和num2只能由数字组成。num1和num2都不包含任何前导零,除了数字0本身。
方法一:数学乘法模拟 · Go 实现
思考
题目不允许将输入直接转为整数,也不允许使用内置大整数库。两串长度可达 ,普通整型无法容纳乘积。
竖式乘法给出位置关系: 的第 位与 的第 位之积只贡献到固定的数位,各位的进位可以在全部累加之后再统一处理。
乘积长度至多为 。我们可以申请长度为 的数组作为草稿纸,按位累加后再从低位向高位进位,最后去掉可能的前导零。
假设 和 的长度分别为 和 ,则它们的乘积的长度最多为 。
证明如下:
- 如果 和 都取最小值,那么它们的乘积为 ,长度为 。
- 如果 和 都取最大值,那么它们的乘积为 ,长度为 。
因此,我们可以申请一个长度为 的数组,用于存储乘积的每一位。
从低位到高位,依次计算乘积的每一位,最后将数组转换为字符串即可。
注意判断最高位是否为 ,如果是,则去掉。
时间复杂度 ,空间复杂度 。其中 和 分别为 和 的长度。
func multiply(num1 string, num2 string) string {
if num1 == "0" || num2 == "0" {
return "0"
}
m, n := len(num1), len(num2)
arr := make([]int, m+n)
for i := m - 1; i >= 0; i-- {
a := int(num1[i] - '0')
for j := n - 1; j >= 0; j-- {
b := int(num2[j] - '0')
arr[i+j+1] += a * b
}
}
for i := len(arr) - 1; i > 0; i-- {
arr[i-1] += arr[i] / 10
arr[i] %= 10
}
i := 0
if arr[0] == 0 {
i = 1
}
ans := []byte{}
for ; i < len(arr); i++ {
ans = append(ans, byte('0'+arr[i]))
}
return string(ans)
}
48. 旋转图像 ​
题目、示例与约束
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]] 输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
提示:
n == matrix.length == matrix[i].length1 <= n <= 20-1000 <= matrix[i][j] <= 1000
方法一:原地翻转 · Go 实现
思考
顺时针旋转 的映射是 。若新开矩阵按此赋值,时间和空间都是 。 时额外空间足够,但题目要求原地修改。
该映射可拆成两个可原地完成的操作:先上下翻转,再沿主对角线翻转(转置)。
先到 ,再转到 ,恰好是目标位置。两次交换即可,额外空间 。
根据题目要求,我们实际上需要将 旋转至 。
我们可以先对矩阵进行上下翻转,即 和 进行交换,然后再对矩阵进行主对角线翻转,即 和 进行交换。这样就能将 旋转至 了。
时间复杂度 ,其中 是矩阵的边长。空间复杂度 。
func rotate(matrix [][]int) {
n := len(matrix)
for i := 0; i < n>>1; i++ {
for j := 0; j < n; j++ {
matrix[i][j], matrix[n-i-1][j] = matrix[n-i-1][j], matrix[i][j]
}
}
for i := 0; i < n; i++ {
for j := 0; j < i; j++ {
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
}
}
}
50. Pow(x, n) ​
题目、示例与约束
实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,xn )。
示例 1:
输入:x = 2.00000, n = 10 输出:1024.00000
示例 2:
输入:x = 2.10000, n = 3 输出:9.26100
示例 3:
输入:x = 2.00000, n = -2 输出:0.25000 解释:2-2 = 1/22 = 1/4 = 0.25
提示:
-100.0 < x < 100.0-231 <= n <= 231-1n是一个整数- 要么
x不为零,要么n > 0。 -104 <= xn <= 104
方法一:数学(快速幂) · Go 实现
思考
若把 连乘 次, 最大约 ,线性乘法容易超时;还要单独处理 时取倒数。
一次只乘一个 没有利用幂的倍增结构。注意到 , ,指数每次可以折半。
因此按 的二进制位累乘:当前底不断平方,遇到 位就乘进答案。负数指数先算正幂再取倒数。快速幂把 压成 。
快速幂算法的核心思想是将幂指数 拆分为若干个二进制位上的 的和,然后将 的 次幂转化为 的若干个幂的乘积。
时间复杂度 ,空间复杂度 。其中 为幂指数。
func myPow(x float64, n int) float64 {
qpow := func(a float64, n int) float64 {
ans := 1.0
for ; n > 0; n >>= 1 {
if n&1 == 1 {
ans *= a
}
a *= a
}
return ans
}
if n >= 0 {
return qpow(x, n)
}
return 1 / qpow(x, -n)
}
60. 排列序列 ​
题目、示例与约束
给出集合 [1,2,3,...,n],其所有元素共有 n! 种排列。
按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:
"123""132""213""231""312""321"
给定 n 和 k,返回第 k 个排列。
示例 1:
输入:n = 3, k = 3 输出:"213"
示例 2:
输入:n = 4, k = 9 输出:"2314"
示例 3:
输入:n = 3, k = 1 输出:"123"
提示:
1 <= n <= 91 <= k <= n!
方法一:枚举 · Go 实现
思考
第 个排列不必先列出前面的 个。若生成全部 种排列再按下标取值, 时可以枚举完,但其余排列都用不上。
确定某一位后,剩余数字的排列恰好占 种。用 与这个块大小比较,即可知道当前位应取第几个尚未使用的数字,并一次性跳过整块。
为此从左到右填每一位,用阶乘定位、用 记录已用数字,时间 。
我们知道,集合 一共有 种排列,如果我们确定首位,那剩余位能组成的排列数量为 。
因此,我们枚举每一位 ,如果此时 大于当前位置确定后的排列数量,那么我们可以直接减去这个数量;否则,说明我们找到了当前位置的数。
对于每一位 ,其中 ,剩余位能组成的排列数量为 ,我们记为 。过程中已使用的数记录在 vis 中。
时间复杂度 ,空间复杂度 。
func getPermutation(n int, k int) string {
ans := make([]byte, n)
vis := make([]bool, n+1)
for i := 0; i < n; i++ {
fact := 1
for j := 1; j < n-i; j++ {
fact *= j
}
for j := 1; j <= n; j++ {
if !vis[j] {
if k > fact {
k -= fact
} else {
ans[i] = byte('0' + j)
vis[j] = true
break
}
}
}
}
return string(ans)
}
66. 加一 ​
题目、示例与约束
给定一个表示 大整数 的整数数组 digits,其中 digits[i] 是整数的第 i 位数字。这些数字按从左到右,从最高位到最低位排列。这个大整数不包含任何前导 0。
将大整数加 1,并返回结果的数字数组。
示例 1:
输入:digits = [1,2,3] 输出:[1,2,4] 解释:输入数组表示数字 123。 加 1 后得到 123 + 1 = 124。 因此,结果应该是 [1,2,4]。
示例 2:
输入:digits = [4,3,2,1] 输出:[4,3,2,2] 解释:输入数组表示数字 4321。 加 1 后得到 4321 + 1 = 4322。 因此,结果应该是 [4,3,2,2]。
示例 3:
输入:digits = [9] 输出:[1,0] 解释:输入数组表示数字 9。 加 1 得到了 9 + 1 = 10。 因此,结果应该是 [1,0]。
提示:
1 <= digits.length <= 1000 <= digits[i] <= 9digits不包含任何前导0。
方法一:模拟 · Go 实现
思考
数组表示的整数最长可达 位,远超普通整型范围,不能拼成整数再加一。
加一产生的进位至多为 ,且只会从最低位向高位传播。因此从右向左模拟即可:若某位加一后不再进位,则后续高位不变,可以直接返回;若每一位都变为 ,再在开头补上 。不必将整个大数转换成其他表示。
我们从数组的最后一个元素开始遍历,将当前元素加一,然后对 取模,如果取模后的结果不为 ,说明当前元素没有进位,直接返回数组即可。否则,当前元素为 ,需要进位,继续遍历前一个元素,重复上述操作。如果遍历完数组后,仍然没有返回,说明数组中所有元素都为 ,需要在数组的头部插入一个 。
时间复杂度 ,其中 是数组的长度。忽略答案的空间消耗,空间复杂度 。
func plusOne(digits []int) []int {
n := len(digits)
for i := n - 1; i >= 0; i-- {
digits[i]++
digits[i] %= 10
if digits[i] != 0 {
return digits
}
}
return append([]int{1}, digits...)
}
149. 直线上最多的点数 ​
题目、示例与约束
给你一个数组 points ,其中 points[i] = [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。
示例 1:
输入:points = [[1,1],[2,2],[3,3]] 输出:3
示例 2:
输入:points = [[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]] 输出:4
提示:
1 <= points.length <= 300points[i].length == 2-104 <= xi, yi <= 104points中的所有点 互不相同
方法一:暴力枚举 · Go 实现
思考
求共线点的最大个数。点互不相同, 较小,枚举两点确定直线再扫其余点是否共线,用叉积避免除法,时间 可以过。
我们可以枚举任意两个点 ,把这两个点连成一条直线,那么此时这条直线上的点的个数就是 2,接下来我们再枚举其他点 ,判断它们是否在同一条直线上,如果在,那么直线上的点的个数就加 1,如果不在,那么直线上的点的个数不变。找出所有直线上的点的个数的最大值,即为答案。
时间复杂度 ,空间复杂度 。其中 是数组 points 的长度。
func maxPoints(points [][]int) int {
n := len(points)
ans := 1
for i := 0; i < n; i++ {
x1, y1 := points[i][0], points[i][1]
for j := i + 1; j < n; j++ {
x2, y2 := points[j][0], points[j][1]
cnt := 2
for k := j + 1; k < n; k++ {
x3, y3 := points[k][0], points[k][1]
a := (y2 - y1) * (x3 - x1)
b := (y3 - y1) * (x2 - x1)
if a == b {
cnt++
}
}
if ans < cnt {
ans = cnt
}
}
}
return ans
}
方法二:枚举 + 哈希表 · Go 实现
思考
方法一是 。固定一点后,其余点按与它的斜率分组,同一斜率即共线。斜率用约分后的 作键,避免浮点误差。对每个点 统计,总时间 。
我们可以枚举一个点 ,把其他所有点 与 连成的直线的斜率存入哈希表中,斜率相同的点在同一条直线上,哈希表的键为斜率,值为直线上的点的个数。找出哈希表中的最大值,即为答案。为了避免精度问题,我们可以将斜率 进行约分,约分的方法是求最大公约数,然后分子分母同时除以最大公约数,将求得的分子分母作为哈希表的键。
时间复杂度 ,空间复杂度 。其中 和 分别是数组 points 的长度和数组 points 所有横纵坐标差的最大值。
相似题目:
func maxPoints(points [][]int) int {
n := len(points)
ans := 1
type pair struct{ x, y int }
for i := 0; i < n; i++ {
x1, y1 := points[i][0], points[i][1]
cnt := map[pair]int{}
for j := i + 1; j < n; j++ {
x2, y2 := points[j][0], points[j][1]
dx, dy := x2-x1, y2-y1
g := gcd(dx, dy)
k := pair{dx / g, dy / g}
cnt[k]++
if ans < cnt[k]+1 {
ans = cnt[k] + 1
}
}
}
return ans
}
func gcd(a, b int) int {
if b == 0 {
return a
}
return gcd(b, a%b)
}
166. 分数到小数 ​
题目、示例与约束
给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以 字符串形式返回小数 。
如果小数部分为循环小数,则将循环的部分括在括号内。
如果存在多个答案,只需返回 任意一个 。
对于所有给定的输入,保证 答案字符串的长度小于 104 。
注意,如果分数可以表示为有限长度的字符串,则 必须 返回它。
示例 1:
输入:numerator = 1, denominator = 2 输出:"0.5"
示例 2:
输入:numerator = 2, denominator = 1 输出:"2"
示例 3:
输入:numerator = 4, denominator = 333 输出:"0.(012)"
提示:
-231 <= numerator, denominator <= 231 - 1denominator != 0
方法一:数学 + 哈希表 · Go 实现
思考
把分数转成小数,循环节要加括号。长除法产生循环,当且仅当余数重复。先处理符号与整数部分;小数阶段用哈希表记下每个余数第一次出现的位置,余数再现则在该位置插入括号。余数为 则是有限小数。
我们首先判断 是否为 ,如果是,则直接返回 "0"。
接着我们判断 和 是否异号,如果是,则结果为负数,我们将结果的第一个字符设为 "-"。
然后我们将 和 取绝对值,分别记为 和 。由于分子和分母的范围为 ,直接取绝对值可能会溢出,因此我们将 和 都转换为长整型。
接着我们计算整数部分,即 除以 的整数部分,将其转换为字符串并添加到结果中。然后我们将 取余 ,记为 。
如果 为 ,说明结果为整数,直接返回结果。
接着我们计算小数部分,我们使用哈希表 记录每个余数对应的结果的长度。我们不断将 乘以 ,然后将 除以 的整数部分添加到结果中,然后将 取余 ,记为 。如果 为 ,说明结果为有限小数,直接返回结果。如果 在哈希表中出现过,说明结果为循环小数,我们找到循环的起始位置,将结果插入括号中,然后返回结果。
时间复杂度 ,空间复杂度 ,其中 为结果的长度,本题中 。
func fractionToDecimal(numerator int, denominator int) string {
if numerator == 0 {
return "0"
}
ans := ""
if (numerator > 0) != (denominator > 0) {
ans += "-"
}
a := int64(numerator)
b := int64(denominator)
a = abs(a)
b = abs(b)
ans += strconv.FormatInt(a/b, 10)
a %= b
if a == 0 {
return ans
}
ans += "."
d := make(map[int64]int)
for a != 0 {
if pos, ok := d[a]; ok {
ans = ans[:pos] + "(" + ans[pos:] + ")"
break
}
d[a] = len(ans)
a *= 10
ans += strconv.FormatInt(a/b, 10)
a %= b
}
return ans
}
func abs(x int64) int64 {
if x < 0 {
return -x
}
return x
}
168. Excel 表列名称 ​
题目、示例与约束
给你一个整数 columnNumber ,返回它在 Excel 表中相对应的列名称。
例如:
A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ...
示例 1:
输入:columnNumber = 1 输出:"A"
示例 2:
输入:columnNumber = 28 输出:"AB"
示例 3:
输入:columnNumber = 701 输出:"ZY"
示例 4:
输入:columnNumber = 2147483647 输出:"FXSHRXW"
提示:
1 <= columnNumber <= 231 - 1
方法一 · Go 实现
思考
把正整数转成 Excel 列名,本质是 对应 的进制,没有 。普通 进制会把余 写成 而不是 。每次先减 再模 ,余数对上 到 ,商继续,最后反转字符。
func convertToTitle(columnNumber int) string {
res := []rune{}
for columnNumber != 0 {
columnNumber -= 1
res = append([]rune{rune(columnNumber%26 + int('A'))}, res...)
columnNumber /= 26
}
return string(res)
}
171. Excel 表列序号 ​
题目、示例与约束
给你一个字符串 columnTitle ,表示 Excel 表格中的列名称。返回 该列名称对应的列序号 。
例如:
A -> 1 B -> 2 C -> 3 ... Z -> 26 AA -> 27 AB -> 28 ...
示例 1:
输入: columnTitle = "A" 输出: 1
示例 2:
输入: columnTitle = "AB" 输出: 28
示例 3:
输入: columnTitle = "ZY" 输出: 701
提示:
1 <= columnTitle.length <= 7columnTitle仅由大写英文组成columnTitle在范围["A", "FXSHRXW"]内
方法一:进制转换 · Go 实现
思考
列名转序号,是 的逆过程:每位是 的 进制。从左到右累加 ,与把十进制字符串转整数同一套路。
Excel 表格中的列名称是一种 26 进制的表示方法。例如,"AB" 表示的列序号是 。
因此,我们可以遍历字符串 columnTitle,将每个字符转换为对应的数值,然后计算结果即可。
时间复杂度 ,其中 是字符串 columnTitle 的长度。空间复杂度 。
func titleToNumber(columnTitle string) (ans int) {
for _, c := range columnTitle {
ans = ans*26 + int(c-'A'+1)
}
return
}
172. 阶乘后的零 ​
题目、示例与约束
给定一个整数 n ,返回 n! 结果中尾随零的数量。
提示 n! = n * (n - 1) * (n - 2) * ... * 3 * 2 * 1
示例 1:
输入:n = 3 输出:0 解释:3! = 6 ,不含尾随 0
示例 2:
输入:n = 5 输出:1 解释:5! = 120 ,有一个尾随 0
示例 3:
输入:n = 0 输出:0
提示:
0 <= n <= 104
进阶:你可以设计并实现对数时间复杂度的算法来解决此问题吗?
方法一:数学 · Go 实现
思考
阶乘末尾零来自因子 ,而 的个数远多于 ,因此零的个数等于 中 的因子个数。直接算 在 都会溢出。进阶要对数时间:反复 累加,分别计入 的贡献。
题目实际上是求 中有多少个 的因数。
我们以 为例来分析:
- 第 次除以 ,得到 ,表示存在 个包含因数 的数;
- 第 次除以 ,得到 ,表示存在 个包含因数 的数;
- 第 次除以 ,得到 ,表示存在 个包含因数 的数;
- 累加得到从 中所有 的因数的个数。
时间复杂度 ,空间复杂度 。
func trailingZeroes(n int) int {
ans := 0
for n > 0 {
n /= 5
ans += n
}
return ans
}
204. 计数质数 ​
题目、示例与约束
给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。
示例 1:
输入:n = 10 输出:4 解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。
示例 2:
输入:n = 0 输出:0
示例 3:
输入:n = 1 输出:0
提示:
0 <= n <= 5 * 106
方法一:埃氏筛 · Go 实现
思考
对每个 试除到 可统计素数,但 可达 ,重复试除偏慢。若 为素数,则 必为合数。
为此用埃氏筛:从小到大扫描,遇到素数便将其倍数标记为合数,最后统计仍为素数的个数。每个合数只被其因子划去,时间约为 。
如果 是质数,那么大于 的 的倍数 ,,… 一定不是质数,因此我们可以从这里入手。
设 表示数 是不是质数,如果是质数则为 ,否则为 。
我们在 范围内顺序遍历每个数 ,如果这个数为质数( ),质数个数增 1,然后将其所有的倍数 都标记为合数(除了该质数本身),即 ,这样在运行结束的时候我们即能知道质数的个数。
时间复杂度 。
func countPrimes(n int) int {
primes := make([]bool, n)
for i := range primes {
primes[i] = true
}
ans := 0
for i := 2; i < n; i++ {
if primes[i] {
ans++
for j := i + i; j < n; j += i {
primes[j] = false
}
}
}
return ans
}
223. 矩形面积 ​
题目、示例与约束
给你 二维 平面上两个 由直线构成且边与坐标轴平行/垂直 的矩形,请你计算并返回两个矩形覆盖的总面积。
每个矩形由其 左下 顶点和 右上 顶点坐标表示:
- 第一个矩形由其左下顶点
(ax1, ay1)和右上顶点(ax2, ay2)定义。 - 第二个矩形由其左下顶点
(bx1, by1)和右上顶点(bx2, by2)定义。
示例 1:
输入:ax1 = -3, ay1 = 0, ax2 = 3, ay2 = 4, bx1 = 0, by1 = -1, bx2 = 9, by2 = 2 输出:45
示例 2:
输入:ax1 = -2, ay1 = -2, ax2 = 2, ay2 = 2, bx1 = -2, by1 = -2, bx2 = 2, by2 = 2 输出:16
提示:
-104 <= ax1 <= ax2 <= 104-104 <= ay1 <= ay2 <= 104-104 <= bx1 <= bx2 <= 104-104 <= by1 <= by2 <= 104
方法一:计算重叠面积 · Go 实现
思考
两矩形并面积等于各自面积之和减去重叠。重叠宽度为右边界较小者减去左边界较大者,高度同理;若为负则不相交。
为此先算两面积,再用 扣掉重叠。
我们先计算出两个矩形各自的面积,记为 和 ,然后计算重叠的宽度 和高度 ,那么重叠的面积为 ,最后将 , 和重叠面积相减即可。
时间复杂度 ,空间复杂度 。
func computeArea(ax1 int, ay1 int, ax2 int, ay2 int, bx1 int, by1 int, bx2 int, by2 int) int {
a := (ax2 - ax1) * (ay2 - ay1)
b := (bx2 - bx1) * (by2 - by1)
width := min(ax2, bx2) - max(ax1, bx1)
height := min(ay2, by2) - max(ay1, by1)
return a + b - max(height, 0)*max(width, 0)
}
258. 各位相加 ​
题目、示例与约束
给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。返回这个结果。
示例 1:
输入: num =38输出: 2 解释: 各位相加的过程为: 38 --> 3 + 8 --> 11 11 --> 1 + 1 --> 2 由于2是一位数,所以返回 2。
示例 2:
输入: num = 0 输出: 0
提示:
0 <= num <= 231 - 1
进阶:你可以不使用循环或者递归,在 O(1) 时间复杂度内解决这个问题吗?
方法一 · Go 实现
思考
反复求各位和最终得到一位数,即数根。对非负整数有闭式: 时为 ,否则为 。
func addDigits(num int) int {
if num == 0 {
return 0
}
return (num-1)%9 + 1
}
263. 丑数 ​
题目、示例与约束
丑数 就是只包含质因数 2、3 和 5 的 正 整数。
给你一个整数 n ,请你判断 n 是否为 丑数 。如果是,返回 true ;否则,返回 false 。
示例 1:
输入:n = 6 输出:true 解释:6 = 2 × 3
示例 2:
输入:n = 1 输出:true 解释:1 没有质因数。
示例 3:
输入:n = 14
输出:false
解释:14 不是丑数,因为它包含了另外一个质因数 7 。
提示:
-231 <= n <= 231 - 1
方法一 · Go 实现
思考
丑数的质因子只能是 。非正数直接否决;否则不断除掉这三个因子,最后剩 则为丑数。
func isUgly(n int) bool {
if n < 1 {
return false
}
for _, x := range []int{2, 3, 5} {
for n%x == 0 {
n /= x
}
}
return n == 1
}
273. 整数转换英文表示 ​
题目、示例与约束
将非负整数 num 转换为其对应的英文表示。
示例 1:
输入:num = 123 输出:"One Hundred Twenty Three"
示例 2:
输入:num = 12345 输出:"Twelve Thousand Three Hundred Forty Five"
示例 3:
输入:num = 1234567 输出:"One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven"
提示:
0 <= num <= 231 - 1
方法一 · Go 实现
思考
英文读法按千分位分组:Billion、Million、Thousand。每组至多三位数,可用二十以内、整十与 Hundred 拼出。
单独返回 Zero;从高到低取每组非零块,调用 后再接单位。
func numberToWords(num int) string {
if num == 0 {
return "Zero"
}
lt20 := []string{
"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight",
"Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen",
"Sixteen", "Seventeen", "Eighteen", "Nineteen",
}
tens := []string{
"", "Ten", "Twenty", "Thirty", "Forty", "Fifty",
"Sixty", "Seventy", "Eighty", "Ninety",
}
thousands := []string{"Billion", "Million", "Thousand", ""}
var transfer func(int) string
transfer = func(num int) string {
if num == 0 {
return ""
}
if num < 20 {
return lt20[num]
}
if num < 100 {
if num%10 == 0 {
return tens[num/10]
}
return tens[num/10] + " " + transfer(num%10)
}
if num%100 == 0 {
return lt20[num/100] + " Hundred"
}
return lt20[num/100] + " Hundred " + transfer(num%100)
}
res := ""
for i, j := 1000000000, 0; i > 0; i, j = i/1000, j+1 {
cur := num / i
if cur == 0 {
continue
}
if res != "" {
res += " "
}
res += transfer(cur)
if thousands[j] != "" {
res += " " + thousands[j]
}
num %= i
}
return res
}
292. Nim 游戏 ​
题目、示例与约束
你和你的朋友,两个人一起玩 Nim 游戏:
- 桌子上有一堆石头。
- 你们轮流进行自己的回合, 你作为先手 。
- 每一回合,轮到的人拿掉 1 - 3 块石头。
- 拿掉最后一块石头的人就是获胜者。
假设你们每一步都是最优解。请编写一个函数,来判断你是否可以在给定石头数量为 n 的情况下赢得游戏。如果可以赢,返回 true;否则,返回 false 。
示例 1:
输入:n = 4
输出:false
解释:以下是可能的结果:
1. 移除1颗石头。你的朋友移走了3块石头,包括最后一块。你的朋友赢了。
2. 移除2个石子。你的朋友移走2块石头,包括最后一块。你的朋友赢了。
3.你移走3颗石子。你的朋友移走了最后一块石头。你的朋友赢了。
在所有结果中,你的朋友是赢家。
示例 2:
输入:n = 1 输出:true
示例 3:
输入:n = 2 输出:true
提示:
1 <= n <= 231 - 1
方法一:找规律 · Go 实现
思考
每次可取 到 块,谁面对 的倍数都无法一次取完,对手总能补到 。因此先手获胜当且仅当 不能被 整除。
第一个得到 的倍数(即 能被 整除)的将会输掉比赛。
证明:
- 当 时,第一个玩家可以直接拿走所有的石头,所以第一个玩家将会赢得比赛。
- 当 ,无论第一个玩家选择 哪个数字,第二个玩家总能选择剩下的数字,所以第一个玩家将会输掉比赛。
- 当 时,即 ,第一个玩家可以相应地将数字减少为 ,那么 这个死亡数字给到了第二个玩家,第二个玩家将会输掉比赛。
- 当 ,无论第一个玩家选择 哪个数字,都会把 的数字留给第二个,所以第一个玩家将会输掉比赛。
- ...
- 依次类推,当玩家拿到 这个数字,且 能被 整除,他将会输掉比赛,否则他将赢得比赛。
时间复杂度 ,空间复杂度 。
func canWinNim(n int) bool {
return n%4 != 0
}
296. 最佳的碰头地点 🔒 ​
题目、示例与约束
给你一个 m x n 的二进制网格 grid ,其中 1 表示某个朋友的家所处的位置。返回 最小的 总行走距离 。
总行走距离 是朋友们家到碰头地点的距离之和。
我们将使用 曼哈顿距离 来计算,其中 distance(p1, p2) = |p2.x - p1.x| + |p2.y - p1.y| 。
示例 1:

输入: grid = [[1,0,0,0,1],[0,0,0,0,0],[0,0,1,0,0]] 输出: 6 解释: 给定的三个人分别住在(0,0),(0,4)和(2,2):(0,2)是一个最佳的碰面点,其总行走距离为 2 + 2 + 2 = 6,最小,因此返回 6。
示例 2:
输入: grid = [[1,1]] 输出: 1
提示:
m == grid.lengthn == grid[i].length1 <= m, n <= 200grid[i][j] ==0or1.grid中 至少 有两个朋友
方法一:排序 + 中位数 · Go 实现
思考
曼哈顿距离可拆成横坐标差之和与纵坐标差之和。一维上到各点距离之和在中位数处最小。
收集所有 的行号(已有序)与列号(再排序),取中位数为碰头点,再累加绝对差。
对于每一行,我们可以将所有的 的下标排序,然后取中位数 作为碰头地点的横坐标。
对于每一列,我们可以将所有的 的下标排序,然后取中位数 作为碰头地点的纵坐标。
最后,我们计算所有 到碰头地点 的曼哈顿距离之和即可。
时间复杂度 。最多有 个 ,排序的时间复杂度为 。
相似题目:
func minTotalDistance(grid [][]int) int {
rows, cols := []int{}, []int{}
for i, row := range grid {
for j, v := range row {
if v == 1 {
rows = append(rows, i)
cols = append(cols, j)
}
}
}
sort.Ints(cols)
i := rows[len(rows)>>1]
j := cols[len(cols)>>1]
f := func(arr []int, x int) int {
s := 0
for _, v := range arr {
s += abs(v - x)
}
return s
}
return f(rows, i) + f(cols, j)
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
319. 灯泡开关 ​
题目、示例与约束
初始时有 n 个灯泡处于关闭状态。第一轮,你将会打开所有灯泡。接下来的第二轮,你将会每两个灯泡关闭第二个。
第三轮,你每三个灯泡就切换第三个灯泡的开关(即,打开变关闭,关闭变打开)。第 i 轮,你每 i 个灯泡就切换第 i 个灯泡的开关。直到第 n 轮,你只需要切换最后一个灯泡的开关。
找出并返回 n 轮后有多少个亮着的灯泡。
示例 1:

输入:n = 3 输出:1 解释: 初始时, 灯泡状态 [关闭, 关闭, 关闭]. 第一轮后, 灯泡状态 [开启, 开启, 开启]. 第二轮后, 灯泡状态 [开启, 关闭, 开启]. 第三轮后, 灯泡状态 [开启, 关闭, 关闭]. 你应该返回 1,因为只有一个灯泡还亮着。
示例 2:
输入:n = 0 输出:0
示例 3:
输入:n = 1 输出:1
提示:
0 <= n <= 109
方法一:数学 · Go 实现
思考
第 盏灯被操作的次数等于 的因子个数;奇数次则最终亮着。模拟 轮开关为 ,而 可达 。
因子成对出现,仅完全平方数有奇数个因子。 中平方数的个数为 ,直接返回该值。
我们不妨将 个灯泡编号为 ,那么对于第 个灯泡,它会在第 轮被操作,当且仅当 是 的因子。
对于一个数 ,它的因子个数是有限的,且因子个数为奇数时,最后的状态是开启的,否则是关闭的。
因此,我们只需要找到 到 中因子个数为奇数的数的个数即可。
对于一个数 ,如果它有因子 ,那么它一定有因子 ,因此因子个数为奇数的数一定是平方数。
举个例子,数字 的因子有 ,因子个数为 ,是偶数;而对于数字 这个平方数,因子有 ,因子个数为 ,是奇数。
因此,我们只需要找到 到 中有多少个平方数即可,即 。
时间复杂度 ,空间复杂度 。
func bulbSwitch(n int) int {
return int(math.Sqrt(float64(n)))
}
326. 3 的幂 ​
题目、示例与约束
给定一个整数,写一个函数来判断它是否是 3 的幂次方。如果是,返回 true ;否则,返回 false 。
整数 n 是 3 的幂次方需满足:存在整数 x 使得 n == 3x
示例 1:
输入:n = 27 输出:true
示例 2:
输入:n = 0 输出:false
示例 3:
输入:n = 9 输出:true
示例 4:
输入:n = 45 输出:false
提示:
-231 <= n <= 231 - 1
进阶:你能不使用循环或者递归来完成本题吗?
方法一:试除法 · Go 实现
思考
判断 是否为 的幂。连乘直到溢出再比较亦可,但试除更直接:在 时若不能被 整除则否,否则除以 。最终只剩 则为是。 的情况自然落到最后的相等判断。
如果 ,我们可以不断地将 除以 ,如果不能整除,说明 不是 的幂,否则继续除以 ,直到 小于等于 。如果 等于 ,说明 是 的幂,否则不是 的幂。
时间复杂度 ,空间复杂度 。
func isPowerOfThree(n int) bool {
for n > 2 {
if n%3 != 0 {
return false
}
n /= 3
}
return n == 1
}
方法二:数学 · Go 实现
思考
试除需 次除法。 位内最大 的幂为 ,正整数 为 的幂当且仅当它整除该值。一次取模即可。
如果 是 的幂,那么 最大是 ,因此我们只需要判断 是否是 的约数即可。
时间复杂度 ,空间复杂度 。
func isPowerOfThree(n int) bool {
return n > 0 && 1162261467%n == 0
}
335. 路径交叉 ​
题目、示例与约束
给你一个整数数组 distance 。
从 X-Y 平面上的点 (0,0) 开始,先向北移动 distance[0] 米,然后向西移动 distance[1] 米,向南移动 distance[2] 米,向东移动 distance[3] 米,持续移动。也就是说,每次移动后你的方位会发生逆时针变化。
判断你所经过的路径是否相交。如果相交,返回 true ;否则,返回 false 。
示例 1:
输入:distance = [2,1,1,2] 输出:true
示例 2:
输入:distance = [1,2,3,4] 输出:false
示例 3:
输入:distance = [1,1,1,1] 输出:true
提示:
1 <= distance.length <= 1051 <= distance[i] <= 105
方法一 · Go 实现
思考
从原点交替向上、左、下、右走给定距离,判断折线是否自交。模拟坐标再判交为 ,且 可达 。
自交只可能发生在最近的三四段:第四段够到第一段、第五段补到第二段、第六段同时够到第三与第一段。对 检查这三种局部几何关系即可,无需存整条路径。
func isSelfCrossing(distance []int) bool {
d := distance
for i := 3; i < len(d); i++ {
if d[i] >= d[i-2] && d[i-1] <= d[i-3] {
return true
}
if i >= 4 && d[i-1] == d[i-3] && d[i]+d[i-4] >= d[i-2] {
return true
}
if i >= 5 && d[i-2] >= d[i-4] && d[i-1] <= d[i-3] && d[i] >= d[i-2]-d[i-4] && d[i-1]+d[i-5] >= d[i-3] {
return true
}
}
return false
}
356. 直线镜像 🔒 ​
题目、示例与约束
在一个二维平面空间中,给你 n 个点的坐标。问,是否能找出一条平行于 y 轴的直线,让这些点关于这条直线成镜像排布?
注意:题目数据中可能有重复的点。
示例 1:
输入:points = [[1,1],[-1,1]] 输出:true 解释:可以找出 x = 0 这条线。
示例 2:
输入:points = [[1,1],[-1,-1]] 输出:false 解释:无法找出这样一条线。
提示:
n == points.length1 <= n <= 10^4-10^8 <= points[i][j] <= 10^8
进阶:你能找到比 O(n2) 更优的解法吗?
方法一:哈希表 · Go 实现
思考
判断点集是否关于某条竖直线对称。枚举所有可能中线过多。若对称,中线必过最左与最右 的中点。
令 ,每个 的对称点 须在集合中。先把点放入哈希集合再一次检查。
我们先找出所有点中的最小、最大的 坐标 和 。若存在满足条件的直线,则直线 ,或者说 。
接下来,我们遍历每个点 $(x, y),若 不在点集里,说明不满足条件,直接返回 false。遍历结束返回 true。
时间复杂度 ,空间复杂度 。其中 是数组 的长度。
func isReflected(points [][]int) bool {
const inf = 1 << 30
minX, maxX := inf, -inf
pointSet := map[[2]int]bool{}
for _, p := range points {
minX = min(minX, p[0])
maxX = max(maxX, p[0])
pointSet[[2]int{p[0], p[1]}] = true
}
s := minX + maxX
for _, p := range points {
if !pointSet[[2]int{s - p[0], p[1]}] {
return false
}
}
return true
}
372. 超级次方 ​
题目、示例与约束
你的任务是计算 ab 对 1337 取模,a 是一个正整数,b 是一个非常大的正整数且会以数组形式给出。
示例 1:
输入: a = 2, b = [3]
输出: 8
解释:
数组 b = [3] 表示指数 3。因此,a3 = 23 = 8,所以结果为 8。
示例 2:
输入: a = 2, b = [1,0]
输出: 1024
解释:
数组 b = [1, 0] 表示指数 10。因此,a10 = 210 = 1024,所以结果为 1024。
示例 3:
输入: a = 1, b = [4,3,3,8,5,2]
输出: 1
解释:
数组 b = [4, 3, 3, 8, 5, 2] 表示一个正整数指数。由于 a = 1,1 的任意正整数次幂都等于 1。因此,结果为 1。
提示:
1 <= a <= 231 - 11 <= b.length <= 20000 <= b[i] <= 9b不含前导 0
方法一:快速幂 · Go 实现
思考
的指数是大整数 的十进制数组,求 。直接把 拼成整数再幂在语言限制下不便。按位展开 。
自低位向高位:答案乘 ,再把底数换成 。快速幂取模保证每步 。
我们初始化答案变量 。
接下来,倒序遍历数组 ,每次遍历到一个元素 ,我们将答案变量 自乘 并对 取模,然后将 自乘 次并对 取模。这里需要用到快速幂。
遍历完数组后,返回答案即可。
时间复杂度 ,空间复杂度 。
func superPow(a int, b []int) int {
const mod int = 1337
ans := 1
qpow := func(a, n int) int {
ans := 1
for ; n > 0; n >>= 1 {
if n&1 == 1 {
ans = ans * a % mod
}
a = a * a % mod
}
return ans
}
for i := len(b) - 1; i >= 0; i-- {
ans = ans * qpow(a, b[i]) % mod
a = qpow(a, 10)
}
return ans
}
390. 消除游戏 ​
题目、示例与约束
列表 arr 由在范围 [1, n] 中的所有整数组成,并按严格递增排序。请你对 arr 应用下述算法:
- 从左到右,删除第一个数字,然后每隔一个数字删除一个,直到到达列表末尾。
- 重复上面的步骤,但这次是从右到左。也就是,删除最右侧的数字,然后剩下的数字每隔一个删除一个。
- 不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。
给你整数 n ,返回 arr 最后剩下的数字。
示例 1:
输入:n = 9 输出:6 解释: arr = [1, 2, 3, 4, 5, 6, 7, 8, 9] arr = [2, 4, 6, 8] arr = [2, 6] arr = [6]
示例 2:
输入:n = 1 输出:1
提示:
1 <= n <= 109
方法一 · Go 实现
思考
从 交替从左、从右每隔一个删除,求最后剩下的数。模拟链表为 ,而 达 。每轮长度减半,首尾与步长有闭式。
维护当前区间首 、尾 、步长与剩余个数。从左删则首一定右移;从右删且长度为奇时首也右移。尾对称更新。剩一个时返回 。
func lastRemaining(n int) int {
a1, an, step := 1, n, 1
for i, cnt := 0, n; cnt > 1; cnt, step, i = cnt>>1, step<<1, i+1 {
if i%2 == 1 {
an -= step
if cnt%2 == 1 {
a1 += step
}
} else {
a1 += step
if cnt%2 == 1 {
an -= step
}
}
}
return a1
}
391. 完美矩形 ​
题目、示例与约束
给你一个数组 rectangles ,其中 rectangles[i] = [xi, yi, ai, bi] 表示一个坐标轴平行的矩形。这个矩形的左下顶点是 (xi, yi) ,右上顶点是 (ai, bi) 。
如果所有矩形一起精确覆盖了某个矩形区域,则返回 true ;否则,返回 false 。
示例 1:
输入:rectangles = [[1,1,3,3],[3,1,4,2],[3,2,4,4],[1,3,2,4],[2,3,3,4]] 输出:true 解释:5 个矩形一起可以精确地覆盖一个矩形区域。
示例 2:
输入:rectangles = [[1,1,2,3],[1,3,2,4],[3,1,4,2],[3,2,4,4]] 输出:false 解释:两个矩形之间有间隔,无法覆盖成一个矩形。
示例 3:
输入:rectangles = [[1,1,3,3],[3,1,4,2],[1,3,2,4],[2,2,4,4]] 输出:false 解释:因为中间有相交区域,虽然形成了矩形,但不是精确覆盖。
提示:
1 <= rectangles.length <= 2 * 104rectangles[i].length == 4-105 <= xi < ai <= 105-105 <= yi < bi <= 105
方法一 · Go 实现
思考
小矩形能否无重叠、无缝铺成一个大矩形。扫描线或暴力对齐复杂。完美覆盖当且仅当面积相等,且除大矩形四角外每个顶点出现偶数次( 或 )。
累加面积并统计顶点出现次数。大矩形四角须恰出现一次,其余顶点次数为 或 ,面积等于包围盒。
type pair struct {
first int
second int
}
func isRectangleCover(rectangles [][]int) bool {
area := 0
minX, minY := rectangles[0][0], rectangles[0][1]
maxX, maxY := rectangles[0][2], rectangles[0][3]
cnt := make(map[pair]int)
for _, r := range rectangles {
area += (r[2] - r[0]) * (r[3] - r[1])
minX = min(minX, r[0])
minY = min(minY, r[1])
maxX = max(maxX, r[2])
maxY = max(maxY, r[3])
cnt[pair{r[0], r[1]}]++
cnt[pair{r[0], r[3]}]++
cnt[pair{r[2], r[3]}]++
cnt[pair{r[2], r[1]}]++
}
if area != (maxX-minX)*(maxY-minY) ||
cnt[pair{minX, minY}] != 1 ||
cnt[pair{minX, maxY}] != 1 ||
cnt[pair{maxX, maxY}] != 1 ||
cnt[pair{maxX, minY}] != 1 {
return false
}
delete(cnt, pair{minX, minY})
delete(cnt, pair{minX, maxY})
delete(cnt, pair{maxX, maxY})
delete(cnt, pair{maxX, minY})
for _, c := range cnt {
if c != 2 && c != 4 {
return false
}
}
return true
}
398. 随机数索引 ​
题目、示例与约束
给你一个可能含有 重复元素 的整数数组 nums ,请你随机输出给定的目标数字 target 的索引。你可以假设给定的数字一定存在于数组中。
实现 Solution 类:
Solution(int[] nums)用数组nums初始化对象。int pick(int target)从nums中选出一个满足nums[i] == target的随机索引i。如果存在多个有效的索引,则每个索引的返回概率应当相等。
示例:
输入 ["Solution", "pick", "pick", "pick"] [[[1, 2, 3, 3, 3]], [3], [1], [3]] 输出 [null, 4, 0, 2] 解释 Solution solution = new Solution([1, 2, 3, 3, 3]); solution.pick(3); // 随机返回索引 2, 3 或者 4 之一。每个索引的返回概率应该相等。 solution.pick(1); // 返回 0 。因为只有 nums[0] 等于 1 。 solution.pick(3); // 随机返回索引 2, 3 或者 4 之一。每个索引的返回概率应该相等。
提示:
1 <= nums.length <= 2 * 104-231 <= nums[i] <= 231 - 1target是nums中的一个整数- 最多调用
pick函数104次
方法一 · Go 实现
思考
在数组中等概率返回 的一个下标。预处理下标列表需 空间。水塘抽样按流式处理。
扫到第 个匹配时以 替换答案下标。不存全部位置。
type Solution struct {
nums []int
}
func Constructor(nums []int) Solution {
return Solution{nums}
}
func (this *Solution) Pick(target int) int {
n, ans := 0, 0
for i, v := range this.nums {
if v == target {
n++
x := 1 + rand.Intn(n)
if n == x {
ans = i
}
}
}
return ans
}
/**
* Your Solution object will be instantiated and called as such:
* obj := Constructor(nums);
* param_1 := obj.Pick(target);
*/
412. Fizz Buzz ​
题目、示例与约束
给你一个整数 n ,返回一个字符串数组 answer(下标从 1 开始),其中:
answer[i] == "FizzBuzz"如果i同时是3和5的倍数。answer[i] == "Fizz"如果i是3的倍数。answer[i] == "Buzz"如果i是5的倍数。answer[i] == i(以字符串形式)如果上述条件全不满足。
示例 1:
输入:n = 3 输出:["1","2","Fizz"]
示例 2:
输入:n = 5 输出:["1","2","Fizz","4","Buzz"]
示例 3:
输入:n = 15 输出:["1","2","Fizz","4","Buzz","Fizz","7","8","Fizz","Buzz","11","Fizz","13","14","FizzBuzz"]
提示:
1 <= n <= 104
方法一:模拟 · Go 实现
思考
按规则输出 到 的读法,约束下线性扫描即可。
对每个 先判断能否被 整除,再判断 与 ,最后才写入数字本身。先判 ,避免同时满足两个条件时漏写 。
我们遍历从 1 到 n 的每个整数,对于每个整数,我们检查它是否是 3 和 5 的倍数,或者只是 3 的倍数,或者只是 5 的倍数。根据检查的结果,我们将相应的字符串添加到答案数组中。
时间复杂度 ,其中 是题目给定的整数。忽略答案数组的空间消耗,空间复杂度 。
func fizzBuzz(n int) []string {
ans := make([]string, 0, n)
for i := 1; i < n+1; i++ {
switch {
case i%15 == 0:
ans = append(ans, "FizzBuzz")
case i%3 == 0:
ans = append(ans, "Fizz")
case i%5 == 0:
ans = append(ans, "Buzz")
default:
ans = append(ans, strconv.Itoa(i))
}
}
return ans
}
继续阅读
- 实践案例
- 实践案例
- 实践案例