「新」动计划 · 编程入门 Python版本
「新」动计划 · 编程入门
1. 两整数相加
给你两个整数 num1 和 num2,返回这两个整数的和。
- 示例 1
输入:num1 = 12, num2 = 5
输出:17
- 示例 2
输入:num1 = -10, num2 = 4
输出:-6
- 提示
-100 <= num1, num2 <= 100
代码及注释
class Solution:
def sum(self, num1: int, num2: int) -> int:
# num1:第一个整数,范围 [-100, 100]
# num2:第二个整数,范围 [-100, 100]
# 直接返回两数相加的结果
return num1 + num2
知识点讲解
- LeetCode 类/方法格式:所有解答封装在
class Solution
中,线上由框架调用对应方法。 - Python 类型提示:
num1: int, num2: int
与-> int
提升可读性,但不影响运行。 - 算术运算:使用
+
运算符对整数执行加法,支持正数和负数。 - 输入约束:
[-100, 100]
范围内计算,无需考虑溢出或大数处理。
代码详解
定义类与方法
class Solution:
def sum(self, num1: int, num2: int) -> int:
- Solution:题解类名。
- sum:方法名,与题目要求一致。
- 参数
num1
,num2
均为整数,返回值类型为整数。
核心逻辑
return num1 + num2
- 直接计算并返回两数之和,没有额外操作。
复杂度分析
- 时间复杂度:\(O(1)\)
- 空间复杂度:\(O(1)\)
2. 温度转换
给你一个 四舍五入到小数点后两位 的非负浮点数 celsius
表示温度,以摄氏度(Celsius)为单位。
将其分别转换为 开氏度(Kelvin)和 华氏度(Fahrenheit),并以数组 ans = [kelvin, fahrenheit]
的形式返回结果。
与实际答案误差不超过 \(10⁻⁵\) 的会视为正确答案。
- 开氏度 = 摄氏度 + 273.15
- 华氏度 = 摄氏度 * 1.80 + 32.00
示例 1
输入:celsius = 36.50
输出:[309.65000,97.70000]
示例 2
输入:celsius = 122.11
输出:[395.26000,251.79800]
提示
0 <= celsius <= 1000
代码及注释
class Solution:
def convertTemperature(self, celsius: float) -> List[float]:
# celsius:摄氏度,输入范围 [0,1000]
k: float = celsius + 273.15 # 计算开氏度(Kelvin)
h: float = celsius * 1.80 + 32.00 # 计算华氏度(Fahrenheit)
return [k, h] # 返回 [开氏度, 华氏度]
知识点讲解
- 浮点数运算:Python
float
支持小数计算,满足题目精度要求。 - 类型提示:
celsius: float
、-> List[float]
仅作可读性提升,不影响运行。 - 列表返回:直接将两个计算结果组成列表返回,LeetCode 自动比对元素误差。
- 常数运算:加法与乘法均为 \(O(1)\) 级别操作。
代码详解
class Solution:
- 定义解题类,LeetCode 后台实例化并调用其方法。
def convertTemperature(self, celsius: float) -> List[float]:
- 方法名与题目要求一致,接收一个浮点数
celsius
。
k: float = celsius + 273.15
- 根据公式计算开氏度。
h: float = celsius * 1.80 + 32.00
- 根据公式计算华氏度。
return [k, h]
- 将两种单位的温度值封装到列表中返回。
复杂度分析
- 时间复杂度:\(O(1)\)
- 空间复杂度:\(O(1)\)
3. 最小偶倍数
给你一个正整数 n
,返回 2 和 n
的最小公倍数(正整数)。
-
示例 1:
输入:n = 5
输出:10
解释:5 和 2 的最小公倍数是 10。 -
示例 2:
输入:n = 6
输出:6
解释:6 和 2 的最小公倍数是 6。注意数字会是它自身的倍数。 -
提示:
1 <= n <= 150
代码及注释
class Solution:
def smallestEvenMultiple(self, n: int) -> int:
# n: 正整数,范围 [1, 150]
# 如果 n 为奇数 (n % 2 != 0),最小偶倍数是 n * 2
# 如果 n 为偶数,n 本身就是最小偶倍数
return n * 2 if n % 2 != 0 else n
知识点讲解
- 最小公倍数(LCM)
- 若 a | b(a 能整除 b),则 LCM(a, b) = b;否则 LCM(a, b) = a * b / GCD(a, b)。
- 本题中特殊化为 a=2:
- 当n
为偶数时,2 | n → LCM(2, n)=n;
- 当n
为奇数时,GCD(2,n)=1 → LCM=2*n。 - 取模运算判断奇偶
-n % 2 != 0
判断奇数。 - 条件表达式
-X if 条件 else Y
,用于在一行内返回不同结果。
代码详解
- 执行
n % 2
,判断n
是奇数还是偶数。 - 条件表达式:
- 若n % 2 != 0
为真,返回n * 2
;
- 否则返回n
。
复杂度分析
- 时间复杂度:\(O(1)\) — 仅做一次取模与乘法/返回操作。
- 空间复杂度:\(O(1)\) — 只使用常数级额外空间。
4. 判断根结点是否等于子结点之和
给你一个二叉树的根结点 root
,该树恰好包含三个结点:根结点、左子结点和右子结点。如果根结点的值等于两个子结点值之和,返回 true
,否则返回 false
。
-
示例 1
输入:root = [10,4,6]
输出:true -
示例 2
输入:root = [5,3,1]
输出:false
代码及注释
# Definition for a binary tree node.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class Solution:
def checkTree(self, root: Optional[TreeNode]) -> bool:
# root.left.val ← 左子结点的值
# root.right.val ← 右子结点的值
# root.val ← 根结点的值
# 判断左右子结点之和是否等于根结点的值
return root.left.val + root.right.val == root.val
知识点讲解
- 二叉树节点结构:
TreeNode
包含val
、left
、right
属性。 - 类型提示:
Optional[TreeNode]
表明root
可能为None
(本题保证存在两个子结点),使用类型提示有助于可读性。 - 属性访问:
root.left.val
和root.right.val
直接获取子结点的值。 - 运算顺序:Python 中先执行加法
root.left.val + root.right.val
,再进行等值比较==
,返回True
或False
。
代码详解
- 取出左右子结点的值:
L = root.left.val
,R = root.right.val
。 - 计算
L + R
。 - 与根结点
root.val
比较,表达式L + R == root.val
即为最终布尔结果。 - 将该布尔值返回。
复杂度分析
- 时间复杂度:\(O(1)\) — 固定次数的属性访问与算术/比较运算。
- 空间复杂度:\(O(1)\) — 使用常量级额外空间。
5. 数组异或操作
给你两个整数 n
和 start
。
数组 nums
定义为:nums[i] = start + 2*i
(下标从 0 开始),且 n == nums.length
请返回 nums
中所有元素按位异或(XOR)后得到的结果。
-
示例 1:
输入:n = 5, start = 0
输出:8 -
示例 2:
输入:n = 4, start = 3
输出:8 -
示例 3:
输入:n = 1, start = 7
输出:7 -
示例 4:
输入:n = 10, start = 5
输出:2 -
提示:
1 <= n <= 1000
0 <= start <= 1000
n == nums.length
代码及注释
class Solution:
def xorOperation(self, n: int, start: int) -> int:
# nums[0] = start,将其作为初始异或结果
res = start
# 从 i = 1 到 n-1,生成 nums[i] 并与 res 进行异或
for i in range(1, n):
# nums[i] = start + 2*i
res = res ^ (start + 2 * i)
# 返回所有元素异或后的结果
return res
知识点讲解
- 数组生成:无需显式存储整个数组,通过公式
start + 2*i
动态计算每个元素。 - 异或运算性质:
- a ^ a = 0,a ^ 0 = a
- 异或具备交换律和结合律,可任意顺序累积。 - 位运算效率:异或操作在硬件层面高效,单次为 \(O(1)\)。
代码详解
res = start
:将nums[0]
设为初始值。for i in range(1, n):
遍历剩余下标。start + 2 * i
:按题意计算当前元素值。res = res ^ (...)
:将当前元素与已有结果异或,更新res
。- 循环结束后,
res
即为所有nums
元素的异或结果,直接返回。
复杂度分析
- 时间复杂度:\(O(n)\) — 需遍历 n 次。
- 空间复杂度:\(O(1)\) — 仅使用常数级额外变量。
6. 好数对的数目
给你一个整数数组 nums
。如果一组数字 (i, j) 满足 nums[i] == nums[j]
且 i < j
,就可以认为是一组 好数对。
返回好数对的数目。
- 示例 1
输入:nums = [1,2,3,1,1,3]
输出:4
解释:共有 4 组好数对,分别是 (0,3)、(0,4)、(3,4)、(2,5)。 - 示例 2
输入:nums = [1,1,1,1]
输出:6
解释:数组中的每组数字都是好数对,总共 C(4,2)=6 组。 - 示例 3
输入:nums = [1,2,3]
输出:0
代码及注释
class Solution:
def numIdenticalPairs(self, nums: List[int]) -> int:
# 结果计数器
res = 0
# 数组长度
n = len(nums)
# 枚举所有 i<j 的索引对
for i in range(n):
for j in range(i+1, n):
# 若值相等,则构成好数对
if nums[i] == nums[j]:
res += 1
return res
知识点讲解
- 双重循环枚举:通过两层 for 循环穷举所有 (i,j) 且 i<j 的组合。
- 相等判断:用
nums[i] == nums[j]
识别好数对。 - 数学组合:当所有元素相同时,总对数等于组合数 \(C(n,2)=n*(n-1)/2\)。
- 优化思路:可用哈希表记录每个数出现次数,遍历一次时动态累加已有次数,实现 \(O(n)\) 时间。
代码详解
res = 0
:初始化好数对计数。n = len(nums)
:获得数组长度。- 外层
for i in range(n)
:枚举左侧下标。 - 内层
for j in range(i+1, n)
:枚举右侧下标,保证 i<j。 if nums[i] == nums[j]
:判断当前一对值是否相等。res += 1
:若相等则累计计数。- 循环结束后返回
res
。
复杂度分析
- 时间复杂度:\(O(n²)\)
- 空间复杂度:\(O(1)\)
可选优化:
使用哈希表,在一次遍历中对每个x = nums[i]
,令res += count[x]
,再执行count[x] += 1
,将时间降为 \(O(n)\),空间 \(O(n)\)。
7. 统计好三元组
给你一个整数数组 arr
以及三个整数 a
, b
, c
,请统计其中的好三元组数目。
如果三元组 (arr[i], arr[j], arr[k])
满足以下全部条件,则认为它是一个好三元组:
- 0 ≤ i < j < k < arr.length
- |arr[i] - arr[j]| ≤ a
- |arr[j] - arr[k]| ≤ b
- |arr[i] - arr[k]| ≤ c
返回好三元组的数量。
代码及注释
class Solution:
def countGoodTriplets(self, arr: List[int], a: int, b: int, c: int) -> int:
# arr:整数数组,长度范围 [3, 100]
# a, b, c:阈值,范围 [0, 1000]
n = len(arr) # 数组长度
count = 0 # 好三元组计数器
# 枚举所有三元组下标 (i, j, k),满足 0 ≤ i < j < k < n
for i in range(n):
for j in range(i + 1, n):
for k in range(j + 1, n):
# 同时检查三对元素差值是否均满足条件
if abs(arr[i] - arr[j]) <= a \
and abs(arr[j] - arr[k]) <= b \
and abs(arr[i] - arr[k]) <= c:
count += 1 # 满足条件则累加
return count # 返回结果
知识点讲解
- 三重循环枚举
通过三层嵌套for
循环,生成所有满足i < j < k
的下标组合。 - 绝对值运算
使用abs(x - y)
计算两个元素值之差的绝对值,分别与a
,b
,c
进行比较。 - 条件累加
在判断语句中同时检查三组差值,当且仅当三者都不超过对应阈值时,才将count
加一。
代码详解
初始化
- n = len(arr)
:获取数组长度。
- count = 0
:初始化好三元组计数器。
枚举下标
- 第一层 for i in range(n)
:取第一个元素下标。
- 第二层 for j in range(i+1, n)
:取第二个元素下标,保证 j > i
。
- 第三层 for k in range(j+1, n)
:取第三个元素下标,保证 k > j
。
条件判断
- abs(arr[i] - arr[j]) ≤ a
- abs(arr[j] - arr[k]) ≤ b
- abs(arr[i] - arr[k]) ≤ c
三个条件均满足时,count += 1
。
返回结果
- 循环结束后,count
即为符合要求的好三元组总数。
复杂度分析
- 时间复杂度:\(O(n³)\),三重循环遍历所有下标组合,n 最多 100,符合题目限制。
- 空间复杂度:\(O(1)\),仅使用常数级辅助变量。
8. 转换成小写字母
给你一个字符串 s
,将其中的所有大写字母转换成对应的小写字母,返回新的字符串。
代码及注释
class Solution:
def toLowerCase(self, s: str) -> str:
# 调用 Python 内置方法,将所有字符按 Unicode 小写映射返回新字符串
return s.lower()
知识点讲解
- 字符串方法:
str.lower()
会遍历字符串,对每个大写字母做小写转换,不影响非字母字符。 - Unicode/ASCII 映射:在底层,
.lower()
根据字符的码点表将大写字母映射到对应的小写字母。 - 不可变性:Python 字符串是不可变对象,
lower()
返回一个新字符串,原字符串不变。
代码详解
s.lower()
:对字符串s
中的每个字符调用小写映射,生成一个全新的字符串。- 直接
return
该结果,无需额外操作。
复杂度分析
- 时间复杂度:\(O(n)\),需扫描并转换每个字符。
- 空间复杂度:\(O(n)\),返回的新字符串长度与输入相同。
9. 各位相加
给定一个非负整数 num
,重复将各个位上的数字相加,直到结果为一位数,返回这个结果。
-
示例 1
输入:num = 38
输出:2
解释:38 → 3 + 8 = 11 → 1 + 1 = 2 -
示例 2
输入:num = 0
输出:0 -
提示
0 <= num <= 2^31 - 1
代码及注释
class Solution:
def addDigits(self, num: int) -> int:
# 当 num 至少两位时,继续循环
while num >= 10:
tmp: int = 0 # 累加当前 num 每一位的和
# 将 num 各位数字累加至 tmp
while num > 0:
tmp += num % 10 # 取最低位并累加
num //= 10 # 去掉最低位
num = tmp # 更新 num 为各位之和
return num # 返回最后一位数
知识点讲解
- 取余与整除:
num % 10
得到最低位,num // 10
去掉最低位。 - 循环嵌套:外层循环判断结果是否为多位数,内层循环求当前各位之和。
- 数字根概念:该过程即数字根迭代求和,虽可用 \(O(1)\) 公式优化,此处为直观实现。
- 常量空间:仅用若干整型变量,无需额外数组或递归栈。
代码详解
外层判断
while num >= 10
:只要num
不止一位,就继续拆分求和。
内层拆分累加
tmp = 0
while num > 0:
tmp += num % 10
num //= 10
- 每次取
num % 10
累加到tmp
,再num //= 10
逐位剥离。
更新与重复
num = tmp
:将累加结果赋回num
,可能仍为多位,回到外层继续。
返回结果
- 当
num < 10
时跳出,return num
。
复杂度分析
- 时间复杂度:\(O(\log n)\) — d 位数每次 \(O(d)\),迭代次数亦为 \(O(d)\)。
- 空间复杂度:\(O(1)\) — 仅使用常数个整型变量。
10. 整数的各位积和之差
给你一个整数 n
,请计算并返回该整数 各位数字之积 与 各位数字之和 的差。
代码及注释
class Solution:
def subtractProductAndSum(self, n: int) -> int:
# 计算各位数字的乘积
def pro(x: int) -> int:
tmp = 1
while x > 0:
tmp *= x % 10 # 取最低位并累乘
x //= 10 # 去掉最低位
return tmp
# 计算各位数字的和
def summation(x: int) -> int:
tmp = 0
while x > 0:
tmp += x % 10 # 取最低位并累加
x //= 10 # 去掉最低位
return tmp
# 返回乘积与和的差
return pro(n) - summation(n)
知识点讲解
- 取余与整除:
x % 10
可得到最低位数字,x // 10
可去掉最低位。 - 循环累积:分别使用乘法和加法在循环中累积各位的乘积与和。
- 局部函数:在主方法中定义
pro
和summation
两个局部函数以提高代码可读性。 - 返回差值:最终用乘积减去和得到结果。
代码详解
- 调用
pro(n)
:
- 初始化tmp = 1
,遍历n
的每一位,累乘到tmp
。
- 循环结束后,tmp
即为所有数字的乘积。 - 调用
summation(n)
:
- 初始化tmp = 0
,遍历n
的每一位,累加到tmp
。
- 循环结束后,tmp
即为所有数字的和。 return pro(n) - summation(n)
:返回两者之差。
复杂度分析
- 时间复杂度:\(O(d)\),其中 d 是 n 的十进制位数,最多约 \(O(\log n)\)。
- 空间复杂度:\(O(1)\),只使用常数级额外空间。
11. 2 的幂
给你一个整数 n
,请判断该整数是否是 2 的幂次方。如果是,返回 true
;否则,返回 false
。
如果存在一个整数 x 使得 \(n == 2^x\),则认为 n 是 2 的幂次方。
- 示例 1
输入:n = 1
输出:true - 示例 2
输入:n = 16
输出:true - 示例 3
输入:n = 3
输出:false - 提示
\(-2^{31} \le n \le 2^{31} - 1\)
代码及注释
class Solution:
def isPowerOfTwo(self, n: int) -> bool:
# 排除非正数情况
# 2 的幂在二进制中只有一个 '1',n & (n-1) 会将最低位的 '1' 清零
# 若结果为 0,则说明原数只有这一位 '1'
return n > 0 and n & (n - 1) == 0
知识点讲解
- 二进制位与幂次关系:2 的幂在二进制表示下恰好只有一位是1,其余位全为0。
- 位运算清零技巧:
n & (n-1)
会将n
二进制中最低位的1置为0,其余位保持不变。 - 判断唯一性:如果
n
只有一个1,则n & (n-1)
结果为0;若有多于一个1,则结果非0。 - 非正数排除:对非正整数(包括0和负数)直接返回
false
。
代码详解
n > 0
:先确保n
是正数。n & (n - 1)
:对n
和n-1
进行按位与运算。== 0
:当且仅当n
二进制中只有一位1时,上述运算结果才为0。- 整体表达式
n > 0 and n & (n - 1) == 0
:先判断正数,再进行位运算判断,返回布尔值。
复杂度分析
- 时间复杂度:\(O(1)\),仅包含常量次位运算和比较。
- 空间复杂度:\(O(1)\),只使用固定数量的额外变量。
12. 3 的幂
给定一个整数 n
,判断它是否是 3 的幂次方。如果存在整数 x 使得 \(n == 3^x\),则认为 n 是 3 的幂次方。返回 true
或 false
。
代码及注释
class Solution:
def isPowerOfThree(self, n: int) -> bool:
# 只对正数有效
# 3^19 = 1162261467,是 32 位有符号整数范围内最大的 3 的幂
# 如果 n 是 3 的幂,则必能整除 3^19
return n > 0 and pow(3, 19) % n == 0
知识点讲解
- 最大幂选择:32 位有符号整型上限约为 \(2^{31}−1\),3 的最大幂是 \(3^{19} = 1162261467\)。
- 整除判定:若
n
是 3 的幂,则 n | \(3^{19}\),即 \(3^{19} \text{ % } n == 0\);反之若能整除,则n
只能是 3 的幂。 - 常数时间:无需循环或递归,直接常数次运算判断。
代码详解
n > 0
:剔除非正数(0 或负数)。pow(3, 19)
:计算 3 的 19 次方常量。pow(3, 19) % n == 0
:检查n
是否为该常量的约数。- 结合逻辑与返回最终布尔值。
复杂度分析
- 时间复杂度:\(O(1)\) — 仅做常数次幂运算和取模。
- 空间复杂度:\(O(1)\) — 只使用固定数量的变量和常量。
13. 丑数
丑数 就是只包含质因数 2
、3
和 5
的正整数。
给你一个整数 n
,判断其是否为丑数。如果是,返回 true
;否则,返回 false
。
代码及注释
class Solution:
def isUgly(self, n: int) -> bool:
# 丑数必须为正整数
if n <= 0:
return False
# 依次剥除所有 2、3、5 因子
for p in (2, 3, 5):
# 只要能被 p 整除,就不断除以 p
while n % p == 0:
n //= p
# 如果剩下 1,则只有 2、3、5 作为质因数
return n == 1
知识点讲解
- 质因数剥除:通过不断除以允许的质因数,将其它因子“过滤”掉,最终判断剩余是否为 1。
- 循环/条件判断:外层遍历每个质因数,内层
while
循环剥除所有相同因子。 - 正整数检查:非正整数(0 或负数)直接排除,不可能是丑数。
代码详解
if n <= 0: return False
:排除非正数。for p in (2, 3, 5)
:遍历可接受的质因数。while n % p == 0: n //= p
:只要当前因子p
能整除n
,就将其剔除,并更新n
。- 循环结束后,如果
n
被完全剥除至 1,说明原始n
仅含 2、3、5 这三种质因数,否则包含其它质因数。 return n == 1
:返回布尔判断结果。
复杂度分析
- 时间复杂度:\(O(\log n)\),每次除法操作使 n 至少减半或更快,三种质因数共计操作次数为 \(O(\log n)\)。
- 空间复杂度:\(O(1)\),仅使用常数级额外变量。
14. 重新排列数组
给你一个数组 nums
,数组中有 2n
个元素,按 [x₁,x₂,…,xₙ,y₁,y₂,…,yₙ]
的格式排列。请你将数组按 [x₁,y₁,x₂,y₂,…,xₙ,yₙ]
格式重新排列,返回重排后的数组。
- 示例 1
输入:nums = [2,5,1,3,4,7], n = 3
输出:[2,3,5,4,1,7] - 示例 2
输入:nums = [1,2,3,4,4,3,2,1], n = 4
输出:[1,4,2,3,3,2,4,1] - 示例 3
输入:nums = [1,1,2,2], n = 2
输出:[1,2,1,2]
代码及注释
class Solution:
def shuffle(self, nums: List[int], n: int) -> List[int]:
li: list[int] = [] # 存放重排结果
for i in range(n): # 遍历前 n 个 xi
li.append(nums[i]) # 依次添加 xi
li.append(nums[i + n]) # 依次添加对应的 yi
return li # 返回重排后的数组
知识点讲解
- 列表拼接:通过
append
逐对加入元素,避免创建中间子列表。 - 下标计算:
nums[i]
对应 \(x_i\),nums[i+n]
对应 \(y_i\)。 - 空间使用:额外列表
li
长度为2n
,与输入规模同量级。
代码详解
- 初始化空列表
li
用于存放结果。 - 使用
for i in range(n)
遍历索引 0 到 n-1:
-nums[i]
为第 i+1 个前半部分元素 \(x_i\),加入li
;
-nums[i+n]
为对应的后半部分元素 \(y_i\),随后加入。 - 循环完成后,
li
即为[x₁,y₁,x₂,y₂,…,xₙ,yₙ]
。 - 返回
li
。
复杂度分析
- 时间复杂度:\(O(n)\),遍历 n 次并执行常数次
append
。 - 空间复杂度:\(O(n)\),额外使用长度为
2n
的列表。
15. 转置矩阵
给你一个二维整数数组 matrix
,返回 matrix
的转置矩阵。
矩阵的 转置 是指将矩阵的主对角线翻转,交换矩阵的行索引与列索引。
代码及注释
class Solution:
def transpose(self, matrix: List[List[int]]) -> List[List[int]]:
# 原矩阵的行数
n: int = len(matrix)
# 原矩阵的列数(假设所有行长度相同)
m: int = len(matrix[0])
# 初始化 m 行 n 列的零矩阵,用来存放转置结果
result: List[List[int]] = [[0] * n for _ in range(m)]
# 遍历原矩阵所有位置
for i in range(n):
for j in range(m):
# 将 matrix[i][j] 放到 result[j][i]
result[j][i] = matrix[i][j]
return result
知识点讲解
- 矩阵转置:将原矩阵的第 i 行第 j 列元素,放到新矩阵的第 j 行第 i 列。
- 列表推导式初始化二维数组:
[[0] * n for _ in range(m)]
生成 m 行 n 列的零矩阵。 - 双重循环遍历:外层遍历行索引
i
,内层遍历列索引j
,保证每个元素都被访问并赋值。 - 索引映射:转置时行列索引互换,即 (i, j) → (j, i)。
代码详解
n = len(matrix)
、m = len(matrix[0])
:分别获取原矩阵的行数和列数。result = [[0] * n for _ in range(m)]
:构造一个 m×n 的全零矩阵用于存放结果。- 双层 for 循环:
-for i in range(n)
:枚举原矩阵的行索引;
-for j in range(m)
:枚举原矩阵的列索引; - 在循环体内执行
result[j][i] = matrix[i][j]
,完成行列互换的赋值。 - 循环结束后返回
result
。
复杂度分析
- 时间复杂度:\(O(n × m)\),需访问并赋值原矩阵中所有 n×m 个元素。
- 空间复杂度:\(O(n × m)\),额外开辟一个尺寸为 m×n 的结果矩阵。
16. 分割字符串的最大得分
给你一个由字符 '0' 和 '1' 组成的字符串 s
,请你将字符串 s
按某个位置 分割 成两个 非空 子字符串(左子字符串和右子字符串),使得得分最大。
- 分割字符串的 得分 定义为:左子字符串中 '0' 的数量 + 右子字符串中 '1' 的数量。
- 返回可以得到的 最大得分。
示例 1
- 输入:s = "011101"
输出:5
解释:
将 s 分成 "0" | "11101",得分 = 1 + 4 = 5
其他分割方式得分都不高于 5
示例 2
输入:s = "00111"
输出:5
解释:将 s 分成 "00" | "111",得分 = 2 + 3 = 5
示例 3
输入:s = "1111"
输出:3
代码及注释
class Solution:
def maxScore(self, s: str) -> int:
# left:当前分割点左边子串的长度,最小为 1
# right:字符串总长度
# score:记录遍历所有分割后能得到的最大得分
left, right, score = 1, len(s), 0
# 尝试所有可能的分割位置,分割点从 1 到 len(s)-1
while left < right:
score_tmp = 0 # 本次分割的得分
# 统计左子串中 '0' 的个数
for i in range(left):
if s[i] == '0':
score_tmp += 1
# 统计右子串中 '1' 的个数
for i in range(left, right):
if s[i] == '1':
score_tmp += 1
# 更新最大得分
score = max(score, score_tmp)
left += 1 # 移动分割点到下一个位置
return score
知识点讲解
- 分割枚举:要使左右子串都非空,分割点
left
从 1 遍历到len(s)-1
。 - 计数统计:分别对左边区间和右边区间进行遍历统计,累加零和一的数量。
- 临时和与最大值:每次分割都算出一个临时得分
score_tmp
,并与当前最大值score
比较、更新。 - 可优化思路:利用前缀和/后缀和数组预先统计零和一的累计次数,可以将统计从 \(O(n)\) 降到 \(O(1)\),整体时间降为 \(O(n)\)。
代码详解
初始化
left = 1
:首个分割点在第一个字符和第二个字符之间;right = len(s)
:总长度,用于确定右子串区间终点;score = 0
:存放迄今为止的最优得分。
分割循环
while left < right:
score_tmp = 0
...
left += 1
- 保证左、右子串都非空;每次循环
left
向右移动一个位置。
左子串统计
for i in range(left):
if s[i] == '0':
score_tmp += 1
- 遍历
[0, left-1]
,统计 '0' 的个数。
右子串统计
for i in range(left, right):
if s[i] == '1':
score_tmp += 1
- 遍历
[left, len(s)-1]
,统计 '1' 的个数。
更新答案
score = max(score, score_tmp)
- 将本次分割得分与全局最优比较,取较大者。
返回结果
- 循环结束后,
score
即为最大可得分。
复杂度分析
- 时间复杂度:\(O(n²)\),共有 \(O(n)\) 个分割点,每次需要 \(O(n)\) 遍历统计。
- 空间复杂度:\(O(1)\),只使用常数级额外变量,不依赖于输入大小。
优化提示:
通过一次前缀统计 zeros[i]
(前 i 个字符中 '0' 的累计数)和一次后缀统计 ones[i]
(从 i 到结尾中 '1' 的累计数),即可在 \(O(1)\) 时间内计算每个分割点的得分,使总时间降为 \(O(n)\)。
17. 统计范围内的元音字符串数
给你一个下标从 0 开始的字符串数组 words
和两个整数 left
和 right
。
如果字符串以元音字母开始并以元音字母结束,那么该字符串就是元音字符串,其中元音字母是 'a'、'e'、'i'、'o'、'u'。
返回 words[i]
是元音字符串的数目,其中 i 在区间 [left, right]
内。
代码及注释
class Solution:
def vowelStrings(self, words: List[str], left: int, right: int) -> int:
# words:字符串数组
# left, right:统计范围左右索引(包含)
res: int = 0
vowels: list[str] = ['a', 'e', 'i', 'o', 'u'] # 元音字母集合
# 遍历索引 left..right
for i in range(left, right + 1):
w = words[i]
# 判断首尾字符是否都是元音
if w[0] in vowels and w[-1] in vowels:
res += 1
return res
知识点讲解
- 索引访问:
words[i]
获取第 i 个字符串,w[0]
和w[-1]
分别取首尾字符。 - 列表成员判断:
x in vowels
用于判断字符x
是否为元音。 - 循环遍历区间:
for i in range(left, right+1)
枚举所有待检查索引。 - 计数累加:符合条件时
res += 1
,最终返回累计值。
代码详解
初始化
res = 0
:用于记录元音字符串数量;vowels = ['a','e','i','o','u']
:列出所有元音字符。
遍历索引区间
for i in range(left, right + 1):
- 枚举从
left
到right
(包含)的每个下标。
取出当前字符串
w = words[i]
判断首尾元音
if w[0] in vowels and w[-1] in vowels:
res += 1
w[0]
是首字符,w[-1]
是尾字符,二者都在vowels
中即为元音字符串。
返回结果
- 遍历结束后,
res
即为所求,直接返回。
复杂度分析
- 时间复杂度:\(O(m)\),其中 m = right − left + 1。每个字符串只做常数次索引和成员判断。
- 空间复杂度:\(O(1)\),仅使用常数级额外空间(计数器与元音列表)。
18. 山脉数组的峰顶索引
给定一个长度为 n 的整数 山脉 数组 arr
,其中的值先严格递增到一个峰值元素,然后严格递减。返回峰值元素的下标。你必须设计并实现时间复杂度为 \(O(\log n)\) 的解决方案。
示例 1
输入:arr = [0,1,0]
输出:1
示例 2
输入:arr = [0,2,1,0]
输出:1
示例 3
输入:arr = [0,10,5,2]
输出:1
提示
- 3 ≤ arr.length ≤ \(10^5\)
- 0 ≤ arr[i] ≤ \(10^6\)
- 题目数据保证
arr
是一个山脉数组(先严格递增到峰顶,再严格递减)。
代码及注释
class Solution:
def peakIndexInMountainArray(self, arr: List[int]) -> int:
# 在 0 .. len(arr)-2 范围内遍历每个下标 i
# 找到第一个满足 arr[i] > arr[i+1] 的位置,即从增转降的转折点
return next(i for i in range(len(arr) - 1)
if arr[i] > arr[i + 1])
知识点讲解
- 山脉数组性质:数组先严格递增到峰顶,再严格递减。峰顶左侧
arr[i] < arr[i+1]
,峰顶右侧arr[i] > arr[i+1]
。 - 转折点即峰顶:第一个满足
arr[i] > arr[i+1]
的位置就是峰顶的下标。 - 生成器表达式:
(i for i in … if …)
能按需惰性生成符合条件的下标,不用遍历完所有元素。 - next() 取第一个:
next(...)
直接返回生成器的第一个元素,遇到第一个符合条件的位置立即停止,避免多余遍历。 - 时间复杂度要求:虽然此写法最坏为 \(O(n)\),但题目鼓励使用二分查找将时间降至 \(O(\log n)\)。
代码详解
range(len(arr) - 1)
- 枚举所有可能的 “切换点” 下标
i
,最后一个可比较位置是len(arr)-2
。
条件判断 arr[i] > arr[i+1]
- 当且仅当到达峰顶时,当前元素大于下一个元素,表示从增转降。
next(...)
- 会遍历生成器得到的下标序列,返回第一个满足条件的
i
,并终止迭代。
整体逻辑
- 不需要显式写循环和中断,借助生成器和
next
一行搞定峰顶定位。
复杂度分析
- 时间复杂度:\(O(n)\) — 在最坏情况下需扫描到峰顶位置。但理想的二分查找解法可达到 \(O(\log n)\)。
- 空间复杂度:\(O(1)\) — 只使用常数级额外空间。