71 简化路径
定义一个函数simplifyPath来简化Unix风格的文件路径,参数为原始路径path。
使用栈来存储简化后的路径。
将原始路径按’/‘分割成目录名列表。
遍历目录名列表,处理每个目录名:
如果目录名为空或为当前目录’.‘,则忽略;
如果目录名为上级目录’…‘,则弹出栈顶目录;
其他情况,将目录名压入栈中。
将栈中的目录名用’/‘连接起来形成简化后的路径。
在简化后的路径前面添加’/'。
返回简化后的路径。
class Solution:
def simplifyPath(self, path: str) -> str:
# 使用栈来存储简化路径
stack = []
# 将路径按'/'分割成目录名列表
dirs = path.split('/')
# 遍历目录名列表
for directory in dirs:
# 忽略空目录名或当前目录'.'
if directory == '' or directory == '.':
continue
# 如果目录名为上级目录'..',则弹出栈顶目录
elif directory == '..':
if stack: # stack不为空
stack.pop()
# 其他情况,压入栈中
else:
stack.append(directory)
# 将栈中的目录名用'/'连接起来形成简化后的路径
simplified_path = '/' + '/'.join(stack)
# 返回简化后的路径
return simplified_path
72 编辑距离
定义一个函数minDistance来计算将word1转换成word2所使用的最少操作数,参数为两个单词word1和word2。
获取word1和word2的长度。
初始化一个动态规划矩阵dp,其中dp[i][j]表示将word1的前i个字符转换成word2的前j个字符所需的最少操作数。
初始化边界条件:
当word1为空时,插入word2的所有字符;
当word2为空时,删除word1的所有字符。
使用动态规划求解,遍历word1和word2的每个字符:
如果当前字符相等,则不需要操作,继承前一状态的操作数;
否则,考虑插入、删除、替换操作中的最小操作数,并加上当前操作。
最终dp[m][n]即为将word1转换成word2所使用的最少操作数。
返回最少操作数。
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
# 获取两个单词的长度
m, n = len(word1), len(word2)
# 初始化动态规划矩阵,dp[i][j]表示将word1的前i个字符转换成word2的前j个字符所需的最少操作数
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化边界条件
for i in range(m + 1): # 当word1为空时,插入word2的所有字符。循环取不到m+1
dp[i][0] = i # 挨个插入
for j in range(n + 1): # 当word2为空时,删除word1的所有字符。循环取不到n+1
dp[0][j] = j # 挨个删除,需要的最小操作数
# 动态规划求解
for i in range(1, m + 1): # 从1开始到m
for j in range(1, n + 1):
# 如果word1的第i个字符与word2的第j个字符相等,则不需要操作,继承前一状态的操作数
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
# 否则,考虑插入、删除、替换操作中的最小操作数,并加上当前操作
else:
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1 # dp[i - 1][j]是表示插入,dp[i][j - 1]删除word2的j;dp[i - 1][j - 1]是替换
# 返回word1转换成word2所使用的最少操作数
return dp[m][n]
73 矩阵置零
定义一个函数setZeroes来实现矩阵置零,参数为一个二维矩阵matrix。
获取矩阵的行数和列数。
定义两个标记变量row_zero和col_zero,用来标记第一行和第一列是否需要置零,初始化为False。
检查第一行和第一列是否有0,并更新对应的标记变量。
遍历矩阵的除第一行和第一列外的所有元素,如果某个元素为0,则将对应的行和列的第一个元素置零,作为标记。
根据标记,将对应行和列的剩余元素置零。
如果第一行或第一列需要置零,则将第一行或第一列的所有元素置零。
完成矩阵置零。
class Solution:
def setZeroes(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
# 获取矩阵的行数和列数
m, n = len(matrix), len(matrix[0])
# 定义两个标记变量,用来标记第一行和第一列是否需要置零
row_zero, col_zero = False, False
# 检查第一行是否有0
for j in range(n): #对第一行每列遍历
if matrix[0][j] == 0:
row_zero = True
break # 直接退出for整个循环
# 检查第一列是否有0
for i in range(m): # 对第一列每行元素遍历
if matrix[i][0] == 0:
col_zero = True
break
# 遍历矩阵,将出现0的行和列的第一个元素置零
for i in range(1, m): # 从1开始,第二行第二列那个元素,从左往右循环
for j in range(1, n):
if matrix[i][j] == 0: # 如果当前元素为0,
matrix[i][0] = 0 # 该行的第一列令为0,算作打个标记 。只改变第一行或者第一列的对应元素。不然还要写多个for循环,复杂度大
matrix[0][j] = 0
# 根据第一列的标记,将对应行置零
for i in range(1, m):
if matrix[i][0] == 0:
for j in range(1, n):
matrix[i][j] = 0
# 根据第一行的标记,将对应列置零
for j in range(1, n):
if matrix[0][j] == 0:
for i in range(1, m):
matrix[i][j] = 0
# 如果第一行或第一列需要置零,则将第一行或第一列置零
if row_zero:
for j in range(n):
matrix[0][j] = 0 # 第一行的所有列令为0
if col_zero:
for i in range(m):
matrix[i][0] = 0
74 搜索二维矩阵
定义一个函数searchMatrix来判断矩阵中是否存在目标值,参数为二维矩阵matrix和目标值target。
首先检查矩阵是否为空,如果为空则直接返回False。
获取矩阵的行数和列数。
初始化二分查找的左右边界,左边界为0,右边界为行数乘以列数减1。
进行二分查找,直到左边界大于右边界。
在每一次循环中,计算中间元素的索引mid,并获取对应的元素值mid_val。
如果mid_val等于目标值target,则返回True。
如果mid_val小于目标值target,则将左边界更新为mid + 1。
如果mid_val大于目标值target,则将右边界更新为mid - 1。
如果循环结束仍然没有找到目标值,则返回False。
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if not matrix or not matrix[0]: # 矩阵为空
return False
rows, cols = len(matrix), len(matrix[0])
# 初始化行和列的左右边界
left, right = 0, rows * cols - 1 # 所有元素的最后一位
# 二分查找
while left <= right: # 左右指针
mid = (left + right) // 2 # 取商的整数部分,4.5取4。矩阵从大到小排序的
mid_val = matrix[mid // cols][mid % cols] # 矩阵对应值,mid // cols整数除法表示在第几行;mid % cols 余数,表示第几列,整除了cols后
if mid_val == target:
return True
elif mid_val < target: # 目标值更大,更新左指针
left = mid + 1
else:
right = mid - 1
return False
75 颜色分类
定义一个函数sortColors来对颜色数组进行排序,参数为颜色数组nums。
初始化三个指针:curr表示当前遍历到的位置,red表示红色区域的右边界,blue表示蓝色区域的左边界。
遍历数组,直到当前位置超过蓝色区域的左边界。
如果当前元素为红色(0),则将当前元素与红色区域的右边界交换,并向后移动当前指针和红色区域的右边界。
如果当前元素为蓝色(2),则将当前元素与蓝色区域的左边界交换,并向前移动蓝色区域的左边界。
如果当前元素为白色(1),则只需要向后移动当前指针,不需要交换元素。
排序完成后,数组中红色元素在前,白色元素在中,蓝色元素在后。
返回排序后的数组。
class Solution:
def sortColors(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
# 初始化三个指针,分别指向当前遍历到的位置,红色的右边界和蓝色的左边界
curr, red, blue = 0, 0, len(nums) - 1 # 蓝色的左边界为末尾的,红色的右边界为开头;这两个中间区域为白色
# 遍历数组,直到当前位置超过蓝色的左边界
while curr <= blue:
if nums[curr] == 0: # 如果当前元素为红色, 为零
nums[curr], nums[red] = nums[red], nums[curr] # 将当前元素与红色区域的右边界交换
curr += 1 # 向后移动当前指针和红色区域的右边界
red += 1
elif nums[curr] == 2: # 如果当前元素为蓝色
nums[curr], nums[blue] = nums[blue], nums[curr] # 将当前元素与蓝色区域的左边界交换
blue -= 1 # 向前移动蓝色区域的左边界, 说明蓝色的元素增加一个,curr不动
else: # 如果当前元素为白色
curr += 1 # 只需要向后移动当前指针,不需要交换元素
# 排序完成后,数组中红色元素在前,白色元素在中,蓝色元素在后
return nums
77 组合
定义一个函数combine来生成所有可能的组合,参数为整数n和k,分别表示数字范围和组合长度。
在函数内部定义一个辅助函数backtrack来进行递归生成组合,参数为当前数字start和当前已经生成的组合path。
如果当前组合的长度等于k,将其添加到结果集中,并返回。
遍历从start到n的数字,依次尝试将每个数字添加到组合中。
将当前数字添加到组合中后,递归调用backtrack函数,继续向后添加数字。
在递归返回后,进行回溯操作,将当前数字从组合中移出,继续尝试下一个数字。
最终返回结果集中存储的所有可能的组合。
# 代码随想录:k层数,第一层n个选择,第二层每个节点展开变少,因为只往右选
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
def backtrack(start, path): # path为当前一个小组合,list组合结果
if len(path) == k: # 如果当前组合的长度等于k,将其添加到结果集中
res.append(path[:])
return
for i in range(start, n + 1): # 遍历从start到n的数字, 注意写的n+1
path.append(i) # 将当前数字添加到组合中
backtrack(i + 1, path) # 递归调用,继续向后添加数字 ;树结构的第二层
path.pop() # 回溯,将当前数字移出组合;
res = [] # 用于存储结果集
backtrack(1, []) # 从数字1开始递归生成组合
return res
78 子集
定义一个函数subsets来生成数组的所有子集,参数为整数数组nums。
在函数内部定义一个辅助函数backtrack来进行递归生成子集,参数为当前遍历的起始位置start和当前已经生成的子集path。
将当前子集添加到结果集中,即空集是任何集合的子集,因此将空集添加到结果中。
从start位置开始遍历nums数组,依次尝试将每个元素添加到子集中。
将当前元素添加到子集中后,递归调用backtrack函数,继续向后生成子集。
在递归返回后,进行回溯操作,将当前元素从子集中移出,继续尝试下一个元素。
最终返回结果集中存储的所有可能的子集。
class Solution:
def subsets(nums):
def backtrack(start, path):
res.append(path[:]) # 将当前子集添加到结果集中
for i in range(start, len(nums)): # 从start位置开始遍历nums数组
path.append(nums[i]) # 将当前元素添加到子集中
backtrack(i + 1, path) # 递归调用,继续向后生成子集
path.pop() # 回溯,将当前元素移出子集
res = [] # 用于存储结果集
backtrack(0, []) # 从数组的第一个元素开始递归生成子集
return res
79 单词搜索
定义一个函数exist来判断给定单词是否存在于二维网格中,参数为二维网格board和目标单词word。
在函数内部定义一个辅助函数backtrack来进行回溯搜索,参数为当前位置的行索引i、列索引j和单词中的字母索引k。
如果当前位置越界或当前字母不匹配目标单词中的字母,则返回False。
如果已经匹配到目标单词的最后一个字母,则返回True。
标记当前位置已访问,并尝试向当前位置的上、下、左、右四个相邻位置继续搜索。
如果其中任意一个方向搜索到了匹配的单词,返回True。
回溯完成后,还原当前位置的字母,并返回搜索结果。
遍历整个二维网格,对每个位置调用backtrack函数进行搜索,如果存在匹配的单词则返回True,否则返回False。
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
def backtrack(i, j, k): #当前位置的行索引i、列索引j和单词中的字母索引k。
# 如果索引越界或者当前字母不匹配,返回False
if not (0 <= i < len(board) and 0 <= j < len(board[0])) or board[i][j] != word[k]:
return False
# 如果单词的所有字母都匹配,返回True
if k == len(word) - 1:
return True
# 标记当前位置已访问
temp, board[i][j] = board[i][j], '/'
# 检查当前位置的上、下、左、右四个相邻位置,这里相当于 检查树结构下一层
res = (backtrack(i + 1, j, k + 1) or
backtrack(i - 1, j, k + 1) or
backtrack(i, j + 1, k + 1) or
backtrack(i, j - 1, k + 1))
# 还原当前位置的字母
board[i][j] = temp
return res
# 遍历整个二维网格,寻找是否存在单词
for i in range(len(board)):
for j in range(len(board[0])):
if backtrack(i, j, 0): # 如果为真,返回true
return True
return False
80 删除有序数组中的重复项
定义函数removeDuplicates来删除排序数组中的重复项,参数为排序后的数组nums。
如果数组长度小于等于2,无需进行修改,直接返回数组长度。
使用快慢指针来标记数组中不重复元素的位置。初始化慢指针为2,表示前两个元素必定保留。
从第三个元素开始遍历数组,使用快指针。
如果当前元素与慢指针之前的第二个元素不相等,则说明当前元素是新的不重复元素,需要保留。
将当前元素移到慢指针位置,并更新慢指针。
遍历完成后,慢指针的位置即为修改后数组的长度。
返回修改后数组的长度。
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
if len(nums) <= 2:
return len(nums)
# 定义快慢指针,快指针用于遍历数组,慢指针用于标记不重复元素的位置
slow = 2 # 初始化慢指针为2,表示前两个元素必定保留。
for fast in range(2, len(nums)):
# 如果当前元素与慢指针之前的第二个元素不相等,则说明当前元素是新的不重复元素
if nums[fast] != nums[slow - 2]: # 只允许同一数字只出现两次,!=不等于表示该数字没有出现第三次,满足要求。nums是有序数组。
# 将当前元素移到慢指针位置,并更新慢指针
nums[slow] = nums[fast]
slow += 1
return slow