week11:
文章目录
- week11:
- 知识点记录:
- 简单:
- [415. 字符串相加](https://leetcode.cn/problems/add-strings/)
- [70. 爬楼梯](https://leetcode.cn/problems/climbing-stairs/)
- [509. 斐波那契数](https://leetcode.cn/problems/fibonacci-number/)
- [1137. 第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/)
- [1351. 统计有序矩阵中的负数](https://leetcode.cn/problems/count-negative-numbers-in-a-sorted-matrix/)
- [1. 两数之和](https://leetcode.cn/problems/two-sum/)
- [283. 移动零](https://leetcode.cn/problems/move-zeroes/)
- 中等:
- [49. 字母异位词分组](https://leetcode.cn/problems/group-anagrams/)
- [128. 最长连续序列](https://leetcode.cn/problems/longest-consecutive-sequence/)
- [53. 最大子数组和](https://leetcode.cn/problems/maximum-subarray/)
- [56. 合并区间](https://leetcode.cn/problems/merge-intervals/)
- [189. 轮转数组](https://leetcode.cn/problems/rotate-array/)
知识点记录:
divmod
直接得到两个非负整数相加的商和余数:
使用Python内置的divmod()
函数。divmod(x, y)
函数返回一个元组,包含x
除以y
的商和余数。
下面是一个示例代码:
class Solution:
def addStrings(self, num1: str, num2: str) -> str:
# 将字符串转换为整数
n1 = int(num1)
n2 = int(num2)
# 直接使用 divmod() 函数计算商和余数
quotient, remainder = divmod(n1 + n2, 10)
# 将商和余数转换为字符串
return str(quotient) + str(remainder)
这个解法直接将两个字符串转换为整数,然后使用divmod()
函数得到相加的商和余数。最后将商和余数转换为字符串并返回。
注意,这个解法使用了Python内置的整数运算,可能会对大整数的计算产生溢出。如果需要处理大整数,建议使用字符串相加的方法。
哈希表(Hash Table)
也称为哈希映射(Hash Map),是一种数据结构,用于实现键-值(Key-Value)对之间的映射关系。它可以通过将键映射到特定的索引位置来快速查找对应的值,从而实现高效的数据查找和插入操作。
哈希表的基本思想是使用哈希函数将键(Key)转换为索引(Index),然后在索引位置存储对应的值(Value)。哈希函数的设计决定了键与索引之间的映射关系,一个好的哈希函数应该能够尽量均匀地将不同的键映射到不同的索引位置,从而减少冲突和碰撞。
哈希表的优势在于其高效的查找和插入操作,平均情况下的时间复杂度为 O(1)。当需要在大量数据中快速查找某个键对应的值时,哈希表是一种非常高效的数据结构。
在 Python 中,哈希表的实现由内置的 dict
(字典)数据类型来完成。使用字典可以方便地进行键值对的操作,例如增删改查等。字典是 Python 中非常常用的数据类型,它在实际编程中起到了非常重要的作用。
简单:
415. 字符串相加
题目描述:
给定两个字符串形式的非负整数 num1
和num2
,计算它们的和并同样以字符串形式返回。
你不能使用任何內建的用于处理大整数的库(比如 BigInteger
), 也不能直接将输入的字符串转换为整数形式。
示例 1:
输入:num1 = "11", num2 = "123"
输出:"134"
代码:
class Solution:
def addStrings(self, num1: str, num2: str) -> str:
i = len(num1) - 1
j = len(num2) - 1 # 获取两个数字起始的索引
carry = 0 # 进位
res = [] # 结果列表形式,存储每一位的和
while i >= 0 or j >= 0:
digit1 = int(num1[i]) if i >= 0 else 0
digit2 = int(num2[j]) if j >= 0 else 0 # 获取当前索引上的数字
carry, num_sum = divmod(digit1 + digit2 + carry, 10) # 更新进位和当前位的和
res.append(str(num_sum)) # 将当前位的和添加到结果列表
i -= 1 # 更新索引
j -= 1
if carry:
res.append(str(carry)) # 若最后仍有进位,添加到结果列表的最后一位
return ''.join(res[::-1]) # 将结果列表转换为字符串形式,并反转顺序
解题思路:
使用了双指针和进位的思想来进行相加操作。我们从两个字符串的末尾开始逐位相加,同时维护一个进位值。将相加结果的个位数加入到结果列表中,并更新进位值。最后再将列表中的元素逆序拼接成字符串,即为最终的结果。
70. 爬楼梯
题目描述:
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
代码:
class Solution:
def climbStairs(self, n: int) -> int:
if n <= 2:
return n
dp = [0] * (n+1) # 创建一个长度为 n+1 的数组,用于保存中间结果
dp[1] = 1 # 第一阶楼梯的方法数为 1
dp[2] = 2 # 第二阶楼梯的方法数为 2
for i in range(3, n+1):
dp[i] = dp[i-1] + dp[i-2] # 使用递推关系计算中间结果
return dp[n] # 返回最终结果,即爬上 n 阶楼梯的方法数
解题思路:
动态规划算法:
爬n
阶楼梯,可以分成两种情况:
- 最后一步是爬一节楼梯:前面
n-1
阶楼梯的总共方法就是f(n-1)
中 - 最后一步是爬二节楼梯:前面
n-2
阶楼梯的总共方法就是f(n-2)
中
因此,爬上 n
阶楼梯的总方法数就是这两种情况的总和,即 f(n) = f(n-1) + f(n-2)
。
基于上述关系,使用一个dp
数组保存计算的中间结果,dp[n
]就是我们要找的答案。
509. 斐波那契数
题目描述:
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n
,请计算 F(n)
。
示例 1:
输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
代码:
class Solution:
def fib(self, n: int) -> int:
if n == 0:
return 0
dp = [0] * (n + 1) # 创建一个长度为 n+1 的列表用于存储斐波那契数列
dp[0], dp[1] = 0, 1 # 初始化前两个数值
for i in range(2, n + 1):
dp[i] = dp[i-1] + dp[i-2] # 根据斐波那契数列的递推公式计算每个数值
return dp[n] # 返回第 n 个数值作为结果
解题思路:
动态规划算法:
计算 f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n) = f(n-1) + f(n-2) f(n)=f(n−1)+f(n−2), 两种情况 :
- n ∈ [ 0 , 1 ] n\in[0,1] n∈[0,1]特殊情况,不调用公式。
- n ≥ 2 n\geq2 n≥2正常情况,调用公式。
基于上述关系,使用一个dp
数组保存计算的中间结果,dp[n
]就是我们要找的答案。
1137. 第 N 个泰波那契数
题目描述:
泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2
给你整数 n
,请返回第 n 个泰波那契数 Tn 的值。
示例 1:
输入:n = 4
输出:4
解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4
代码:
class Solution:
def tribonacci(self, n: int) -> int:
if n == 0:
return 0
if n == 1:
return 1
dp = [0] * (n+1) # 创建一个长度为 n+1 的列表用于存储 Tribonacci 数列
dp[0], dp[1], dp[2] = 0, 1, 1 # 初始化前三个数值
for i in range(3, n+1):
dp[i] = dp[i-1] + dp[i-2] + dp[i-3] # 根据 Tribonacci 数列的递推公式计算每个数值
return dp[n] # 返回第 n 个数值作为结果
解题思路:
动态规划算法:
计算$ T_{n+2} = T_n + T_{n+1} + T_{n+2}$, 两种情况 :
- n ∈ [ 0 , 1 , 2 ] n\in[0,1,2] n∈[0,1,2]特殊情况,不调用公式。
- n ≥ 3 n\geq3 n≥3正常情况,调用公式。
基于上述关系,使用一个dp
数组保存计算的中间结果,dp[n
]就是我们要找的答案。
1351. 统计有序矩阵中的负数
题目描述
给你一个 m * n
的矩阵 grid
,矩阵中的元素无论是按行还是按列,都以非递增顺序排列。 请你统计并返回 grid
中 负数 的数目。
示例 1:
输入:grid = [[4,3,2,-1],[3,2,1,-1],[1,1,-1,-2],[-1,-1,-2,-3]]
输出:8
解释:矩阵中共有 8 个负数。
代码:
class Solution:
def countNegatives(self, grid: List[List[int]]) -> int:
row, col = 0, len(grid[0]) - 1 # 起始位置为矩阵的右上角
count = 0 # 计数器,用于统计负数的个数
m, n = len(grid), len(grid[0]) # 矩阵的行数和列数
while row < m and col >= 0:
if grid[row][col] < 0: # 如果当前元素是负数
count += m - row # 将当前列中剩余的所有元素都加入负数的计数
col -= 1 # 移动到左侧的列
else:
row += 1 # 移动到下一行
return count
解题思路:
分治:每一列,每一行都是非递增顺序排列, 计算每一列负数起始的的位置,该列的负数数量之和为m - row
累加。从右上角开始向下滑动。
1. 两数之和
题目描述:
给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
code_one:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
n = len(nums)
for i in range(n-1):
for j in range(i+1, n):
if nums[i] + nums[j] == target:
return [i, j]
解题思路:暴力穷举法
- 首先,获取给定列表 nums 的长度 n。
- 接着,使用两个嵌套的循环遍历列表 nums 中的每个元素组合。外层循环遍历第一个数的索引 i(从 0 到 n-2),内层循环遍历第二个数的索引 j(从 i+1 到 n-1)。
- 在每一对组合中,判断 nums[i] 和 nums[j] 的和是否等于目标值 target。如果相等,说明找到了符合条件的组合,直接返回这两个数的索引 [i, j]。
- 如果循环结束仍未找到符合条件的组合,则返回一个空列表。
code_two:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
hash_map = {} # 创建空的哈希表
for i, value in enumerate(nums): # 遍历列表,同时获取索引和元素值
complement = target - value # 计算目标值
if complement in hash_map: # 查找配对元素是否存在于哈希表中
return [hash_map[complement], i] # 找到配对元素,返回索引
hash_map[value] = i # 将当前元素及其索引添加到哈希表
return [] # 没有找到符合条件的组合,返回空列表
解题思路:哈希表(字典)
- 创建一个空的哈希表
hash_map
,用于存储元素及其索引。 - 遍历列表
nums
,对于每个元素nums[i]
,计算目标值a = target - nums[i]
。 - 检查
a
是否存在于哈希表中,如果存在,则说明找到了符合条件的组合,直接返回对应元素的索引。 - 如果
a
不在哈希表中,则将当前元素及其索引添加到哈希表中。 - 继续遍历下一个元素,重复上述步骤,直到找到符合条件的组合或遍历结束。
- 空间换取时间
283. 移动零
题目描述:
给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
code:
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
l = 0 # 遍历数组
r = 0 # 记录0的索引
while l < n:
if nums[l] != 0:
nums[l], nums[r] = nums[r], nums[l] # 交换距离最近的非零元素,保持相对顺序
r += 1
l += 1
解题思路:
使用了两个指针 l 和 r。l 指针遍历整个数组,找到非零元素,并与 r 指针指向的位置进行交换。r 指针指向的位置表示非零元素应该放置的位置。每当 l 指针找到一个非零元素时,就将该元素与 r 指针指向的位置进行交换,并将 r 指针右移一位。这样,所有的非零元素都会被移到数组的前面,而剩下的位置都会被填充为零。
中等:
49. 字母异位词分组
题目描述:
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
示例 1:
输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
code:key = 排序后的词
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
hash_res = {} # 创建哈希表用于存储结果,键是排序后的词,值是由相同字母异位词组成的列表
for word in strs: # 遍历字符串数组中的每个单词
sorted_word = ''.join(sorted(word)) # 对当前单词进行排序,并将排序后的词作为键
if sorted_word in hash_res: # 如果排序后的词已经在哈希表中存在,说明是字母异位词
hash_res[sorted_word].append(word) # 将当前单词添加到已存在的字母异位词列表中
else: # 如果排序后的词在哈希表中不存在,说明是一个新的词
hash_res[sorted_word] = [word] # 创建一个新的列表,将当前单词作为第一个元素
return list(hash_res.values()) # 返回哈希表中的所有值,即分组后的字母异位词列表
解题思路:
- 创建一个空的哈希表,用于存储字母异位词分组的结果。
- 遍历字符串数组中的每个单词,对每个单词进行排序。
- 将排序后的单词作为键,在哈希表中查找对应的值(即已经分组的字母异位词列表)。
- 如果在哈希表中找到了对应的值,则将当前单词添加到该值所对应的列表中。
- 如果在哈希表中没有找到对应的值,则创建一个新的列表,将当前单词作为第一个元素,并将排序后的单词作为键,将新列表作为值存入哈希表中。
- 最后,将哈希表中的所有值(即分组后的字母异位词列表)添加到结果列表中,即可得到最终结果。
128. 最长连续序列
题目描述:
给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n)
的算法解决此问题。
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
code:
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
hash_nums = set(nums) # 将 nums 转换成哈希集合,以提高查找效率
max_length = 0 # 用于记录最长连续序列的长度
for num in hash_nums:
if num - 1 not in hash_nums: # 判断当前 num 是否为连续序列的起点
current_num = num # 当前处理的数字
current_length = 1 # 当前连续序列的长度
while current_num + 1 in hash_nums: # 判断后续的数字是否在哈希集合中,即是否构成连续序列
current_num += 1
current_length += 1
max_length = max(max_length, current_length) # 更新最长连续序列的长度
return max_length # 返回
解题思路:
哈希表
- 构建哈希集合剔除重复无用数据
- 遍历哈希集合的
key
- 寻找起点
- 计算当前序列的长度
- 比较最大结果并返回
53. 最大子数组和
题目描述:
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
code:
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
max_num = cur_num = nums[0] # 初始化最大值和当前子数组和为数组的第一个元素
for num in nums[1:]: # 从数组的第二个元素开始遍历
cur_num = max(num, num + cur_num) # 更新当前子数组和,取当前元素和当前子数组和加上当前元素的较大值
max_num = max(cur_num, max_num) # 更新最大值,取当前子数组和和最大值的较大值
return max_num # 返回最大值作为结果
解题思路:
比较简单的动态规划,遍历整个数组,对于每个元素,有两种选择:将其添加到当前子数组中,或者从当前元素开始构成一个新的子数组。我们需要比较这两种选择得到的子数组和,取较大的那个作为当前子数组的和。
56. 合并区间
题目描述:
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
code:
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort() # 按照starti的大小排序
res = [intervals[0]] # 创建结果数组,并将第一个区间加入其中
for i in range(1, len(intervals)):
starti, endi = intervals[i] # 取出当前遍历到的区间的起始位置和结束位置
if starti <= res[-1][1]: # 如果当前区间的左边界小于等于结果数组中最后一个区间的右边界,说明有重叠
res[-1][1] = max(endi, res[-1][1]) # 更新结果数组中最后一个区间的右边界为当前区间和最后一个区间的较大值
else:
res.append([starti, endi]) # 否则,当前区间与结果数组中最后一个区间不重叠,将当前区间加入到结果数组中
return res # 返回合并后的结果数组
解题思路:
简单排序
这个算法首先对区间按照起始位置进行排序,然后遍历所有的区间,如果当前区间与结果数组中最后一个区间有重叠,则更新结果数组中最后一个区间的右边界为当前区间和最后一个区间的较大值,否则将当前区间加入到结果数组中。这样就能合并所有重叠的区间,并返回一个不重叠的区间数组,该数组恰好覆盖输入中的所有区间。
189. 轮转数组
题目描述:
给定一个整数数组 nums
,将数组中的元素向右轮转 k
个位置,其中 k
是非负数。
示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
code:
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
n = len(nums)
k = k % n # 处理 k 大于数组长度的情况,取 k 对 n 的模
# 反转整个数组
nums.reverse()
# 反转前 k 个元素
nums[:k] = nums[:k][::-1]
# 反转后 n-k 个元素
nums[k:] = nums[k:][::-1]
解题思路:
刚开始写的时候想的是直接赋值就是nums = nums[k+1:] + nums[:k+1]
,犯了个错误
这个写法在函数内部使用 nums
的切片进行重新赋值,但这样做并不能修改原数组 nums
的内容。切片操作生成了一个新的列表,而 nums
这个变量指向的仍然是原来的列表对象。所以,即使在函数内部对 nums
进行了赋值操作,原数组的内容并没有被修改。
正确的方式是在原数组 nums
上进行操作,而不是重新赋值。可以使用类似于之前的解法,先对 k 进行处理,然后使用反转的方法对数组元素进行移动。这样就能实现原地修改数组,而不需要重新赋值。
modify nums in-place instead.
“”"
n = len(nums)
k = k % n # 处理 k 大于数组长度的情况,取 k 对 n 的模
# 反转整个数组
nums.reverse()
# 反转前 k 个元素
nums[:k] = nums[:k][::-1]
# 反转后 n-k 个元素
nums[k:] = nums[k:][::-1]
#### 解题思路:
刚开始写的时候想的是直接赋值就是`nums = nums[k+1:] + nums[:k+1]`,犯了个错误
这个写法在函数内部使用 `nums` 的切片进行重新赋值,但这样做并不能修改原数组 `nums` 的内容。切片操作生成了一个新的列表,而 `nums` 这个变量指向的仍然是原来的列表对象。所以,即使在函数内部对 `nums` 进行了赋值操作,原数组的内容并没有被修改。
正确的方式是在原数组 `nums` 上进行操作,而不是重新赋值。可以使用类似于之前的解法,先对 k 进行处理,然后使用反转的方法对数组元素进行移动。这样就能实现原地修改数组,而不需要重新赋值。