Leetcode刷题记录51-70,python语言
53 最大子数组和
这段代码使用了动态规划的思想来解决问题,具体步骤如下:
首先,定义两个变量 max_sum 和 current_sum,分别用于记录最大和 和 当前连续子数组的和,初始值都为数组的第一个元素 nums[0]。
从数组的第二个元素开始遍历。
对于每个元素 num,更新当前连续子数组的和 current_sum:
如果 current_sum + num 大于 num,说明加上当前元素后的和仍然是正增益,所以继续累加;
否则,重新开始计算当前连续子数组的和,即将 current_sum 更新为 num。
在每次更新 current_sum 后,都比较当前 current_sum 和 max_sum,将较大的值更新为 max_sum。
最终返回 max_sum,即最大和。
这个算法的时间复杂度是 O(n),因为只需要一次遍历数组即可找到最大和的连续子数组。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
if not nums: # 如果nums为空,执行下面return 0
return 0
max_sum = nums[0] # 初始化最大和为第一个元素
current_sum = nums[0] # 初始化当前连续子数组的和为第一个元素
# 从第二个元素开始遍历数组
for num in nums[1:]:
# 更新当前连续子数组的和,如果当前和加上当前元素大于当前元素,则继续累加,否则从当前元素重新开始计算
current_sum = max(num, current_sum + num)
# 更新最大和,如果当前连续子数组的和大于最大和,则更新最大和
max_sum = max(max_sum, current_sum)
return max_sum
54 螺旋矩阵
这段代码通过模拟螺旋遍历的过程来遍历矩阵中的所有元素,具体步骤如下:
定义四个变量 left、right、top、bottom,分别表示当前遍历的矩阵的左边界、右边界、上边界和下边界。
使用一个循环来遍历整个矩阵,循环条件是 left <= right 且 top <= bottom,确保仍然存在未遍历的元素。
在每一次循环中,按照螺旋的顺序依次遍历四个边界:
从左到右遍历顶部行,将元素添加到结果列表中,并将 top 加 1;
从上到下遍历右侧列,将元素添加到结果列表中,并将 right 减 1;
如果还存在下方行,则从右到左遍历底部行,将元素添加到结果列表中,并将 bottom 减 1;
如果还存在左侧列,则从下到上遍历左侧列,将元素添加到结果列表中,并将 left 加 1。
循环结束后,返回结果列表。
这个算法的时间复杂度是 O(m*n),其中 m 和 n 分别是矩阵的行数和列数,因为需要遍历整个矩阵中的每个元素。
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix: # 如果matrix为空(false),return []
return []
result = []
rows, cols = len(matrix), len(matrix[0]) # matrix中每个小list为一行,有多少个小list即为行数len(matrix)
left, right, top, bottom = 0, cols - 1, 0, rows - 1
while left <= right and top <= bottom:
# 从左到右遍历顶部行
for j in range(left, right + 1):
result.append(matrix[top][j]) # 第top行第j列
top += 1 # 向下移动一行
# 从上到下遍历右侧列
for i in range(top, bottom + 1):
result.append(matrix[i][right]) # 第i行最右边一列
right -= 1
# 如果还存在下方行,则从右到左遍历底部行
if top <= bottom:
for j in range(right, left - 1, -1): # 从右到左 ,倒着循环
result.append(matrix[bottom][j])
bottom -= 1 # 记得在if判断里面bottom-=1不在外面
# 如果还存在左侧列,则从下到上遍历左侧列
if left <= right:
for i in range(bottom, top - 1, -1): # 从下到上,bottom数字大,top数字小,所以step=-1
result.append(matrix[i][left])
left += 1
return result
55 跳跃游戏
这段代码使用贪心算法来解决问题,具体步骤如下:
定义一个变量 max_reachable,用于记录当前能够到达的最远位置,初始值为 0。
遍历数组 nums,同时更新 max_reachable:
对于当前位置 i,如果 i 已经超过了 max_reachable,说明无法继续往前跳跃,直接返回 False;
否则,更新 max_reachable,使其表示当前位置能够跳跃的最远位置。
如果遍历完成后能够到达数组的最后一个位置,则返回 True,否则返回 False。
这个算法的时间复杂度是 O(n),其中 n 是数组 nums 的长度,因为只需要一次遍历数组即可得出结果。
class Solution:
def canJump(self, nums: List[int]) -> bool:
if not nums: # 如果nums为空(false)执行下面return false
return False
max_reachable = 0 # 记录当前能够到达的最远位置
# 遍历数组,更新最远可达位置
for i, num in enumerate(nums): # i为index下标,
if i > max_reachable: # 如果当前位置已经超过了最远可达位置,无法继续往前跳跃
return False
max_reachable = max(max_reachable, i + num) # 更新最远可达位置
return True
56 合并区间
这段代码通过对区间列表按照起始位置进行排序,然后逐个遍历区间,根据当前区间与已合并区间的关系来更新合并后的区间列表。
具体步骤如下:
首先,对区间列表 intervals 按照区间的起始位置进行排序。
初始化一个空列表 merged,用于存储合并后的区间结果。
遍历排序后的区间列表 intervals:
如果 merged 中的最后一个区间的结束位置小于当前区间的起始位置,说明两个区间不重叠,直接将当前区间添加到 merged 中;
否则,两个区间重叠,更新 merged 中最后一个区间的结束位置为两个区间结束位置的最大值。
遍历完成后,merged 中存储的就是合并后的区间结果。
这个算法的时间复杂度是 O(nlogn),其中 n 是区间列表 intervals 的长度,因为需要对区间列表进行排序。
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
if not intervals: # intervals为空时,return[]
return []
# 按照区间的起始位置进行排序
intervals.sort(key=lambda x: x[0]) # 按每个区间起始值,从小到大排列
merged = [] # 存储合并后的区间结果
# 遍历排序后的区间列表
for interval in intervals: # 遍历每个小区间
# 如果 merged 中的最后一个区间的结束位置小于当前区间的起始位置,说明两个区间不重叠,直接添加当前区间到 merged 中
if not merged or merged[-1][1] < interval[0]: # 当merged为空或者 merged 中的最后一个区间的结束位置小于当前区间的起始位置
merged.append(interval)
else:
# 否则,两个区间重叠,更新 merged 中最后一个区间的结束位置为两个区间结束位置的最大值
merged[-1][1] = max(merged[-1][1], interval[1])
return merged
57 插入区间
这段代码首先遍历原始区间列表,将所有在新区间之前的不重叠区间添加到结果列表中。然后,它遍历剩余的区间,合并与新区间重叠的区间,并更新新区间的起始和结束端点。最后,将新区间添加到结果列表中,并返回结果列表。
class Solution:
def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
merged = []
i = 0
n = len(intervals)
# 找到所有在新区间之前的不重叠区间
while i < n and intervals[i][1] < newInterval[0]: # 在新区间起始值之前的,第i个区间
merged.append(intervals[i])
i += 1 # i是在一个一个增加,不满足条件就进行下一个while循环了, 第一个while走到所有新区间之前的不重叠。当intervals[i][1] >= newInterval[0]就跳到第二个while循环,有重叠部分了!
# 合并重叠的区间
while i < n and intervals[i][0] <= newInterval[1]: # 每个区间的起始值小于新区间的结束值
newInterval[0] = min(newInterval[0], intervals[i][0])# 构造扩展的新区间起始值和结束值
newInterval[1] = max(newInterval[1], intervals[i][1])
i += 1
merged.append(newInterval) # 在while的外面
# 添加剩余的不重叠区间
while i < n:
merged.append(intervals[i])
i += 1
return merged
58 最后一个单词的力度
这段代码通过从字符串末尾开始逆序遍历,统计最后一个单词的长度。
具体步骤如下:
使用 rstrip() 方法去除字符串末尾的空格,确保最后一个单词后面没有多余的空格。
初始化变量 length 为 0,用于记录最后一个单词的长度。
从字符串末尾开始逆序遍历字符串 s:
如果遇到空格,说明当前单词已结束,直接返回 length;
否则,更新 length,继续向前统计单词的长度。
如果字符串中没有空格,则说明整个字符串就是一个单词,直接返回整个字符串的长度。
这个算法的时间复杂度是 O(n),其中 n 是字符串 s 的长度,因为只需要一次遍历字符串即可得出结果。
class Solution:
def lengthOfLastWord(self, s: str) -> int:
# 先去除字符串末尾的空格
s = s.rstrip() # rstrip函数返回移除指定字符或字符集合后的字符串,一般用于去除末尾的空格感叹号这些
# 初始化变量 length 为 0,用于记录最后一个单词的长度
length = 0
# 从字符串末尾开始逆序遍历
for i in range(len(s)-1, -1, -1): # 从最后开始循环
# 如果遇到空格,说明当前单词已结束,直接返回 length
if s[i] == ' ':
return length
else:
# 否则,更新 length
length += 1
# 如果字符串中没有空格,直接返回整个字符串的长度
return length
59 螺旋矩阵 II
这段代码通过模拟填充螺旋矩阵的过程,依次从左到右、上到下、右到左、下到上填充矩阵的边界。
具体步骤如下:
初始化一个 n x n 的矩阵 matrix,全部填充为 0。
初始化要填充的数字 num 为 1,以及矩阵的上下左右边界 top, bottom, left, right。
使用 while 循环,当 num 小于等于 n x n 时,继续填充矩阵。
依次按照顺时针螺旋的顺序填充矩阵的四个边界:
从左到右填充上边界,更新 top,并将 num 加一;
从上到下填充右边界,更新 right,并将 num 加一;
从右到左填充下边界,更新 bottom,并将 num 加一;
从下到上填充左边界,更新 left,并将 num 加一。
循环直到 num 大于 n x n,返回填充后的矩阵。
这个算法的时间复杂度是 O(n^2),其中 n 是矩阵的行数或列数,因为需要填充 n x n 个元素。
class Solution:
def generateMatrix(self, n: int) -> List[List[int]]:
# 初始化一个 n x n 的矩阵,全部填充为 0
matrix = [[0] * n for _ in range(n)] # n行,每行n个[0]
num = 1 # 要填充的数字
top, bottom, left, right = 0, n - 1, 0, n - 1 # 定义矩阵的上下左右边界,理解为第一行最后一行,第一列最后一列
while num <= n * n: # 从1到n方,递增, 不断加一即可
# 从左到右填充上边界
for i in range(left, right + 1):
matrix[top][i] = num # 第一行
num += 1 # 不断加一
top += 1 # 第二行
# 从上到下填充右边界
for i in range(top, bottom + 1): # 在最右列,从上到下
matrix[i][right] = num # 从第二行top开始,对最右列进行向下填充
num += 1 #不断加一
right -= 1 # 最右列的左边列
# 从右到左填充下边界
for i in range(right, left - 1, -1):
matrix[bottom][i] = num # 对最后一行填充
num += 1
bottom -= 1 # 倒数第二行
# 从下到上填充左边界
for i in range(bottom, top - 1, -1):
matrix[i][left] = num # 对最左列填充
num += 1
left += 1
return matrix
61 旋转链表
这段代码实现了将链表向右旋转k个位置的功能。让我逐步解释:
首先,如果k为0,或者链表为空,或者链表只有一个节点,则直接返回原链表头节点,因为不需要进行旋转。
接着,用cur指针遍历整个链表,统计出链表的节点个数count。同时,将cur指向链表的最后一个节点。
将链表的末尾与头部相连,形成环形链表,这样方便后续操作。
计算出应该在哪个位置断开环形链表,即cut的位置。这个位置是通过count - k % count计算得到的,其中k % count是为了处理k大于链表长度的情况。
使用一个循环找到断开位置,将cur移动到新头节点的前一个节点。
找到新的头节点new_head,即cur的下一个节点。
将cur的next指针设为None,断开环形链表。
最后返回新的头节点new_head。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if k == 0 or not head or not head.next: # k为0,head为空,head指针为空
return head
# 首先统计出来链表的节点个数
cur = head
count = 1
while cur.next: # 当下一个为真,直到cur指针走到链表最后一位,cur.next为空了,停止while循环
count += 1 # 计数
cur = cur.next
# 现在cur在最后的位置
cur.next = head # 成环
cut = count - k % count # %是除法的余数。记录要在哪个位点断开
while cut:
cur = cur.next
cut -= 1 # 这个循环因为cur是从末尾开始 所以走n-k步就是下一个头的位置
new_head = cur.next # 从new_head开始就是要返回的旋转链表
cur.next = None # 实现断开
return new_head
62 不同路径
定义一个函数uniquePaths来计算不同路径的数量。
创建一个二维数组dp来存储到达每个位置的路径数,其中dp[i][j]表示从起点到达位置(i, j)的路径数。
初始化边界条件:对于第一行和第一列的位置,由于机器人只能向下或向右移动,所以到达这些位置的路径数都是1。
使用动态规划的方法递推计算其他位置的路径数:对于位置(i, j),可以从上方位置(i-1, j)或左方位置(i, j-1)到达,因此到达位置(i, j)的路径数等于到达这两个位置的路径数之和。
最后返回右下角位置的路径数,即dp[m-1][n-1]。
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
# 创建一个二维数组来存储到达每个位置的路径数
dp = [[0] * n for _ in range(m)] # m行,n列的矩阵,四行二列[[0, 0], [0, 0], [0, 0], [0, 0]]
# 初始化边界条件 , 等于1是说到达该位置的路径数只有一条路,向下或者向右
for i in range(m):
dp[i][0] = 1 # 第i行第一列
for j in range(n):
dp[0][j] = 1 # 第一行第j列
# 递推计算其他位置的路径数
for i in range(1, m): # 从第二行
for j in range(1, n): # 从第二列
dp[i][j] = dp[i-1][j] + dp[i][j-1] # 当前i行j列位置,分两部分,从左边,从上边过来,路径数求和即可
# 返回右下角位置的路径数
return dp[m-1][n-1]
63 不同路径 II
定义一个函数uniquePathsWithObstacles来计算不同路径的数量,参数为包含障碍物信息的二维网格obstacleGrid。
获取网格的行数m和列数n。
创建二维数组dp来存储到达每个位置的路径数,其中dp[i][j]表示从起点到达位置(i, j)的路径数。
初始化边界条件:根据起点是否有障碍物来确定起点的路径数。
初始化第一行和第一列的路径数:如果没有障碍物,则当前位置的路径数等于上方或左方位置的路径数。
使用动态规划的方法递推计算其他位置的路径数:如果当前位置有障碍物,则路径数为0;否则,路径数等于上方和左方位置的路径数之和。
最后返回右下角位置的路径数,即dp[m-1][n-1]。
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m, n = len(obstacleGrid), len(obstacleGrid[0])
# 创建二维数组来存储到达每个位置的路径数
dp = [[0] * n for _ in range(m)]
# 初始化边界条件
dp[0][0] = 1 if obstacleGrid[0][0] == 0 else 0
# 初始化第一行和第一列
for i in range(1, m):
if obstacleGrid[i][0] == 0: # 没障碍
dp[i][0] = dp[i-1][0] # 第一列
for j in range(1, n):
if obstacleGrid[0][j] == 0:
dp[0][j] = dp[0][j-1] # 第一行
# 递推计算其他位置的路径数
for i in range(1, m):
for j in range(1, n):
if obstacleGrid[i][j] == 0: # 没障碍的话,就将路径求和,动态规划f(i,j) = f(i-1,j) + f(i,j-1)
dp[i][j] = dp[i-1][j] + dp[i][j-1]
# 返回右下角位置的路径数
return dp[m-1][n-1]
64 最小路径和
定义一个函数minPathSum来计算最小路径和,参数为包含非负整数的二维网格grid。
获取网格的行数m和列数n。
创建二维数组dp来存储到达每个位置的最小路径和,其中dp[i][j]表示从起点到达位置(i, j)的最小路径和。
初始化第一行和第一列的最小路径和:第一行和第一列的最小路径和分别等于从起点到达每个位置的累计和。
使用动态规划的方法递推计算其他位置的最小路径和:对于位置(i, j),可以从上方或左方位置到达,选取其中最小路径和并加上当前位置的值。
最后返回右下角位置的最小路径和,即dp[m-1][n-1]。
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0]) # m行 n列
# 创建二维数组来存储到达每个位置的最小路径和
dp = [[0] * n for _ in range(m)]
# 初始化第一行和第一列;边界条件
dp[0][0] = grid[0][0]
for i in range(1, m): # 第二行开始
dp[i][0] = dp[i-1][0] + grid[i][0] # 第一列的每个元素,f(i)都是上一行的f(i-1)加上v(i)
for j in range(1, n): # 第二列开始
dp[0][j] = dp[0][j-1] + grid[0][j] # 第一行遍历
# 递推计算其他位置的最小路径和
for i in range(1, m):
for j in range(1, n):
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j] # 取最小,比较它左边和上边的两部分 看走哪边是最短路径和,再加自己的数字
# 返回右下角位置的最小路径和
return dp[m-1][n-1]
66 加一
定义一个函数plusOne来实现加一操作,参数为由整数组成的非空数组digits。
获取数组的长度n。
从数组的最后一位开始向前遍历,遍历过程中将每一位数字加一。
如果当前位数字小于10,说明不需要进位,直接返回结果。
如果当前位数字等于10,说明需要进位,则将当前位数字变为0,继续处理前一位数字。
如果遍历完数组所有位仍然需要进位,说明最高位需要进位,此时在数组头部插入1,表示进位。
返回加一后的结果数组。
class Solution:
def plusOne(self, digits: List[int]) -> List[int]:
n = len(digits)
# 从数组的最后一位开始向前遍历
for i in range(n - 1, -1, -1):
# 当前位数字加一
digits[i] += 1
# 如果当前位数字小于10,说明不需要进位,直接返回结果
if digits[i] < 10: # 一般走到位置0就,小于10的话,直接返回,不进位
return digits
# 否则,当前位数字变为0,继续进位
digits[i] = 0
# 如果遍历完数组所有位仍然需要进位,说明最高位需要进位,此时在数组头部插入1
return [1] + digits
67 二进制求和
bin()函数返回的是字符串对应的二进制,若要从这些进制的字符串表达形式转换回十进制,可以使用 int() 函数,并指定相应的进制
class Solution:
def addBinary(self, a: str, b: str) -> str:
return (bin(int(a,2)+int(b,2)))[2:]
法二
定义一个函数addBinary来实现二进制求和操作,参数为两个二进制字符串a和b。
初始化结果字符串result为空字符串。
初始化进位标志carry为0。
从两个字符串的末尾开始向前遍历,使用指针i和j分别指向字符串a和b的末尾。
获取当前位置的数字digit_a和digit_b,如果已经遍历完字符串a或b,则将对应位置的数字设为0。
计算当前位置的和temp_sum,包括当前位置的数字和前一位的进位。
更新进位标志carry为当前和除以2的商。
更新结果字符串result为当前和除以2的余数加上之前的结果。
向前移动指针i和j。
如果最高位有进位,则在最前面添加1。
返回结果字符串。
class Solution:
def addBinary(self, a: str, b: str) -> str:
# 初始化结果字符串
result = ""
# 初始化进位标志
carry = 0
# 从字符串的末尾开始遍历
i, j = len(a) - 1, len(b) - 1
while i >= 0 or j >= 0:
# 获取当前位置的数字,如果已经遍历完a或b,则补0
digit_a = int(a[i]) if i >= 0 else 0
digit_b = int(b[j]) if j >= 0 else 0
# 计算当前位置的和,考虑上一位的进位
temp_sum = digit_a + digit_b + carry
# 更新进位标志
carry = temp_sum // 2
# 更新当前位置的结果
result = str(temp_sum % 2) + result
# 向前移动指针
i -= 1
j -= 1
# 如果最高位有进位,则在最前面添加1
if carry:
result = "1" + result
return result
69 x的平方根
定义一个函数mySqrt来实现求x的平方根操作,参数为非负整数x。
特殊情况处理:如果x为0或1,直接返回x,因为它们的平方根即为自身。
使用二分查找,初始化左边界为1,右边界为x//2,因为x的平方根不会超过x//2。
在循环中,不断将中间值的平方与x进行比较,逐步缩小搜索范围。
如果中间值的平方等于x,直接返回中间值。
如果中间值的平方小于x,说明解在右边,更新左边界为mid + 1。
如果中间值的平方大于x,说明解在左边,更新右边界为mid - 1。
循环结束后,right指向最后一个小于等于sqrt(x)的整数,因此返回right即可。
class Solution:
def mySqrt(self, x: int) -> int:
# 特殊情况处理:如果x为0或1,直接返回x
if x == 0 or x == 1:
return x
# 二分查找,左边界为1,右边界为x//2;定一个取值区间
left, right = 1, x // 2 # //表示除以2,返回不大于结果的一个最大的整数
while left <= right: # 左右指针
# 取中间值
mid = (left + right) // 2 # 除以2,返回不大于结果的一个最大的整数
# 如果中间值的平方等于x,直接返回中间值
if mid * mid == x:
return mid
# 如果中间值的平方小于x,说明解在右边,更新左边界
elif mid * mid < x:
left = mid + 1
# 如果中间值的平方大于x,说明解在左边,更新右边界
else:
right = mid - 1
# 循环结束后,right指向最后一个小于等于sqrt(x)的整数,返回right即可
return right
70 爬楼梯
定义一个函数climbStairs来求解爬楼梯的方法数,参数为楼梯的阶数n。
特殊情况处理:当n为0或1时,只有一种爬楼梯的方式。
初始化动态规划数组dp,其中dp[i]表示爬到第i阶楼梯的方法数。
初始化dp[0]和dp[1]为1,表示爬到第0阶和第1阶楼梯的方法数都为1。
使用动态规划求解,从第2阶楼梯开始遍历到第n阶。
状态转移方程为dp[i] = dp[i-1] + dp[i-2],即爬到第i阶楼梯的方法数等于爬到第i-1阶楼梯的方法数加上爬到第i-2阶楼梯的方法数。
循环结束后,dp[n]即为爬到楼顶的方法数。
返回爬到楼顶的方法数。
class Solution:
def climbStairs(self, n: int) -> int:
# 特殊情况处理:当n为0或1时,只有一种爬楼梯的方式
if n == 0 or n == 1:
return 1
# 初始化dp数组,dp[i]表示爬到第i阶楼梯的方法数
dp = [0] * (n + 1)
dp[0], dp[1] = 1, 1 # 边界条件
# 动态规划求解
for i in range(2, n + 1): # 从第二阶梯子开始遍历
# 状态转移方程:dp[i] = dp[i-1] + dp[i-2]
dp[i] = dp[i - 1] + dp[i - 2] # 分两部分,上一阶,上上一阶
# 返回爬到楼顶的方法数
return dp[n]