更新ing,还没做完
06.01.01 练习题目(第 01 天)
1. 0054. 螺旋矩阵
1.1 题目大意
描述:给定一个 m * n
大小的二维矩阵 matrix
。
要求:按照顺时针旋转的顺序,返回矩阵中的所有元素。
说明:
- m = = m a t r i x . l e n g t h m == matrix.length m==matrix.length。
- n = = m a t r i x [ i ] . l e n g t h n == matrix[i].length n==matrix[i].length。
- 1 ≤ m , n ≤ 10 1 \le m, n \le 10 1≤m,n≤10。
- − 100 ≤ m a t r i x [ i ] [ j ] ≤ 100 -100 \le matrix[i][j] \le 100 −100≤matrix[i][j]≤100。
示例:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
思路
按照顺时针螺旋的顺序逐层访问。
- 首先检查输入矩阵是否为空,如果为空,直接返回空列表。
- 使用二维列表visited来标记已访问过的元素,默认初始化为False。
- 使用一个列表directions来存储四个可能的移动方向,即向右、向下、向左、向上。使用direction_index来表示当前的移动方向的索引。
- 使用一个循环来遍历矩阵中的所有元素,循环次数为矩阵中元素的总个数。在循环中,首先将当前位置的元素添加到结果列表中,然后将对应的visited位置标记为True,表示已访问过。计算下一个要访问的位置next_row和next_col,其值为当前位置加上当前移动方向的偏移量。如果下一个位置超出了矩阵边界,或者下一个位置已经被访问过,则说明需要改变移动方向,更新direction_index为下一个方向。
根据当前的移动方向更新当前位置的行和列。
循环结束后,返回结果列表。
通过不断更新移动方向和当前位置,这个算法可以按照顺时针螺旋的顺序遍历矩阵的所有元素。在遍历过程中,通过标记已访问过的元素,避免重复访问。
代码
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
# 如果矩阵为空
if not matrix or not matrix[0]:
return []
# m行 n列
rows, cols = len(matrix), len(matrix[0])
visited = []
# 维护一个 visited 矩阵, 用于标记已访问的元素
visited = [[False]*cols for _ in range(rows) ]
# 四个方向
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
direction_index = 0
row, col = 0, 0
# result 用来存储 最后输出的结果
result = []
# 循环遍历矩阵中的所有元素
for _ in range(rows * cols):
# 将当前位置的元素添加到结果列表中
result.append(matrix[row][col])
# 标记当前位置为已访问
visited[row][col] = True
# 计算下一个要访问的位置
next_row = row + directions[direction_index][0]
next_col = col + directions[direction_index][1]
# 如果下一个位置超出了矩阵边界,或者下一个位置已经被访问过
if not (0 <= next_row < rows and 0 <= next_col < cols and not visited[next_row][next_col]):
# 改变移动方向,更新direction_index
direction_index = (direction_index + 1) % 4
# 更新当前位置的行和列
row += directions[direction_index][0]
col += directions[direction_index][1]
# 返回结果列表
return result
2. 0048. 旋转图像
2.1 题目大意
描述:给定一个 n * n
大小的二维矩阵(代表图像)matrix
。
要求:将二维矩阵 matrix
顺时针旋转 90°。
说明:
- 不能使用额外的数组空间。
- n = = m a t r i x . l e n g t h = = m a t r i x [ i ] . l e n g t h n == matrix.length == matrix[i].length n==matrix.length==matrix[i].length。
- 1 ≤ n ≤ 20 1 \le n \le 20 1≤n≤20。
- − 1000 ≤ m a t r i x [ i ] [ j ] ≤ 1000 -1000 \le matrix[i][j] \le 1000 −1000≤matrix[i][j]≤1000。
示例:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
输入: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]]
思路
本题要求将二维矩阵顺时针旋转90°,而且要求不能使用额外的数组空间。可以想象成中心对称,首先我们可以先将矩阵转置,即将矩阵的行变为列,列变为行。然后再翻转矩阵。
步骤为:
- 转置操作(就是把行变成列),通过两层循环来遍历矩阵的上三角部分,将第 i 行和第 j 列的元素与第 j 行和第 i 列的元素进行交换,完成矩阵转置。
- 逐行反转矩阵(相当于沿着中心线对折)。通过两层循环,将每一行的元素进行反转操作,即将第 i 行的第 j 个元素与第 i 行的倒数第 j+1 个元素进行交换。
代码
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
n = len(matrix)
# 步骤 1:转置矩阵
# 遍历矩阵的上三角部分,将第 i 行和第 j 列的元素与第 j 行和第 i 列的元素进行交换,完成矩阵转置。
for i in range(n):
for j in range(i, n):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
# 步骤 2:逐行反转矩阵
# 将第 i 行的第 j 个元素与第 i 行的倒数第 j+1 个元素进行交换。
for i in range(n):
for j in range(n // 2):
matrix[i][j], matrix[i][n-j-1] = matrix[i][n-j-1], matrix[i][j]
3. 0215. 数组中的第K个最大元素
3.1 题目大意
描述:给定一个未排序的整数数组 nums
和一个整数 k
。
要求:返回数组中第 k
个最大的元素。
说明:
- 要求使用时间复杂度为 O ( n ) O(n) O(n) 的算法解决此问题。
- 1 ≤ k ≤ n u m s . l e n g t h ≤ 1 0 5 1 \le k \le nums.length \le 10^5 1≤k≤nums.length≤105。
- − 1 0 4 ≤ n u m s [ i ] ≤ 1 0 4 -10^4 \le nums[i] \le 10^4 −104≤nums[i]≤104。
示例:
输入: [3,2,1,5,6,4], k = 2
输出: 5
输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4
思路
先构造一个空间大小为k的(小顶)堆,再从nums[k+1]开始与堆顶元素比较,如果nums[i] > heap则将当前堆顶元素出堆,并将nums[i]入堆,这样遍历完数组后堆顶元素就是ans。
我们共执行了n轮入堆和出堆,堆的最大长度为k,入堆和出堆的时间复杂度为O(logk)
所以整个算法的时间复杂度为O(nlogk)
投机取巧地调了python小顶堆,有时间把基于快排的/分治的思路弄清楚。
代码
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
# 将前K个元素入堆,然后依次弹出堆
heap = [nums[i] for i in range(0,k)]
# 小顶堆
heapq.heapify(heap)
# 遍历数组中剩余的元素
for i in range(k,len(nums)):
# 如果当前元素比堆顶元素大
if nums[i] > heap[0]:
heapq.heappop(heap) # 弹出堆顶元素
heapq.heappush(heap,nums[i]) # nums[i] 入堆
return heap[0]
06.01.03 练习题目(第 02 天)
1. 0912. 排序数组
- 这个超时了,重做
1.1 题目大意
描述:给定一个整数数组 nums
。
要求:将该数组升序排列。
说明:
- 1 ≤ n u m s . l e n g t h ≤ 5 ∗ 1 0 4 1 \le nums.length \le 5 * 10^4 1≤nums.length≤5∗104。
- − 5 ∗ 1 0 4 ≤ n u m s [ i ] ≤ 5 ∗ 1 0 4 -5 * 10^4 \le nums[i] \le 5 * 10^4 −5∗104≤nums[i]≤5∗104。
示例:
输入:nums = [5,2,3,1]
输出:[1,2,3,5]
输入:nums = [5,1,1,2,0,0]
输出:[0,0,1,1,2,5]
思路
快速排序,分治法
代码
class Solution:
def sortArray(self, nums: List[int]) -> List[int]:
if len(nums) <= 1 :
return nums
return quickSort(nums, 0, len(nums)-1)
def quickSort(nums, start, end):
if start >= end :
return # 递归结束条件
# 选取基准 pivot
pivot_index = partition(nums, start, end)
# 递归对左侧部分进行快速排序
quickSort(nums, start, pivot_index - 1)
# 递归对右侧部分进行快速排序
quickSort(nums, pivot_index + 1, end)
# 返回排序后的nums
return nums
def partition(nums, start, end):
# 假设最后一个元素为基准点
pivot = nums[end]
index = start - 1
for i in range(start, end) :
if nums[i] <= pivot :
index += 1
# 交换
nums[index], nums[i] = nums[i], nums[index]
# 最后将基准点元素放到较小元素指针的后一位,即分割点
nums[index + 1], nums[end] = nums[end], nums[index + 1]
return index + 1
这样实现会超时?
2. 0088. 合并两个有序数组
2.1 题目大意
描述:给定两个有序数组 nums1
、nums2
。
要求:将 nums2
合并到 nums1
中,使 nums1
成为一个有序数组。
说明:
- 给定数组
nums1
空间大小为m + n
个,其中前m
个为nums1
的元素。nums2
空间大小为n
。这样可以用nums1
的空间来存储最终的有序数组。 - n u m s 1. l e n g t h = = m + n nums1.length == m + n nums1.length==m+n。
- n u m s 2. l e n g t h = = n nums2.length == n nums2.length==n。
- 0 ≤ m , n ≤ 200 0 \le m, n \le 200 0≤m,n≤200。
- 1 ≤ m + n ≤ 200 1 \le m + n \le 200 1≤m+n≤200。
- − 1 0 9 ≤ n u m s 1 [ i ] , n u m s 2 [ j ] ≤ 1 0 9 -10^9 \le nums1[i], nums2[j] \le 10^9 −109≤nums1[i],nums2[j]≤109。
示例:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
思路
一开始的思路是新开辟一个数组num3,逐一比较num1&num2,归并的思想,更小的放入num3中。但是这样用到了额外的储存空间,题目要求的是直接合并到num1。其实也是一样的,只不过num1就是num3,合并的时候我们从后往前,选更大的放在num1的末尾就可以了。
因此可以维护一个指针p,指向合并后的num1的末尾,即p = m + n - 1(m = nums1.length, n = nums2.length),
分别从后往前比较两个数组的元素(nums1[p1] 和 nums2[p2]),更大的那个放到p指向的位置,同时,更大的那个指针往前移一个位置,然后p再向前一个位置。
代码
def merge(nums1, m, nums2, n):
# 定义两个指针,分别指向 nums1 和 nums2 的末尾
p1 = m - 1
p2 = n - 1
# 定义一个指针,指向 nums1 的末尾
p = m + n - 1
# 从后往前遍历两个数组,比较当前元素的大小,并将较大的元素放到 nums1 的末尾
while p1 >= 0 and p2 >= 0:
if nums1[p1] <= nums2[p2]:
nums1[p] = nums2[p2]
p2 -= 1
else:
nums1[p] = nums1[p1]
p1 -= 1
p -= 1
# 将 nums2 中剩余的元素拷贝到 nums1 的前面
nums1[:p2 + 1] = nums2[:p2 + 1]
3. 0169. 多数元素
3.1 题目大意
描述:给定一个大小为
n
n
n 的数组 nums
。
要求:返回其中相同元素个数最多的元素。
说明:
- n = = n u m s . l e n g t h n == nums.length n==nums.length。
- 1 ≤ n ≤ 5 ∗ 1 0 4 1 \le n \le 5 * 10^4 1≤n≤5∗104。
- − 1 0 9 ≤ n u m s [ i ] ≤ 1 0 9 -10^9 \le nums[i] \le 10^9 −109≤nums[i]≤109。
示例:
输入:nums = [3,2,3]
输出:3
输入:nums = [2,2,1,1,1,2,2]
输出:2
思路
如果不考虑空间复杂度,那么可以直接用桶的思想,用一个新数组bucket用于计数,遍历nums,bucket[nums[i]] ++ ,再遍历bucket中的数,找到bucket值最大的下标。
- 进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
代码
class Solution:
def majorityElement(self, nums: List[int]) -> int:
# 创建一个哈希表用于存储每个元素的出现次数
bucket = {}
# 遍历数组,统计每个元素的出现次数
for num in nums:
if num in bucket:
bucket[num] += 1
else:
bucket[num] = 1
# 找到出现次数最多的元素
max_count = 0
majority = None
for key, value in bucket.items():
if value > max_count:
max_count = value
majority = key
return majority
06.01.03 练习题目(第 03 天)
1. 0136. 只出现一次的数字
1.1 题目大意
描述:给定一个非空整数数组 nums
,nums
中除了某个元素只出现一次以外,其余每个元素均出现两次。
要求:找出那个只出现了一次的元素。
说明:
- 要求不能使用额外的存储空间。
示例:
输入: [2,2,1]
输出: 1
输入: [4,1,2,1,2]
输出: 4
思路
因为要求不能使用额外的存储空间,因此不能简单地使用空间换时间对每个出现的数进行计数。
这道题用到异或的重要性质。两个相同数字异或为 0,即对于任意整数 a 有:
a
⊕
a
=
0
,
0
⊕
a
=
a
a \oplus a = 0, 0 \oplus a = a
a⊕a=0,0⊕a=a
因此若nums = [a,a,a,x],
a
⊕
a
⊕
a
⊕
x
=
0
⊕
x
=
x
a \oplus a \oplus a \oplus x = 0 \oplus x = x
a⊕a⊕a⊕x=0⊕x=x
遍历 nums 执行异或运算后得到的结果就是只出现一次的数。
代码
class Solution:
def singleNumber(self, nums: List[int]) -> int:
result = 0
for num in nums:
result ^= num
return result
2. 0056. 合并区间
2.1 题目大意
描述:给定数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。
要求:合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
说明:
- 1 ≤ i n t e r v a l s . l e n g t h ≤ 1 0 4 1 \le intervals.length \le 10^4 1≤intervals.length≤104。
- i n t e r v a l s [ i ] . l e n g t h = = 2 intervals[i].length == 2 intervals[i].length==2。
- 0 ≤ s t a r t i ≤ e n d i ≤ 1 0 4 0 \le starti \le endi \le 10^4 0≤starti≤endi≤104。
示例:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
思路
对区间数组 intervals 按照起始位置(starti)进行排序,以便后续合并重叠的区间。
创建一个空的结果数组 merged,用于存储合并后的区间。遍历区间数组 intervals。如果结果数组 merged 为空,或者当前区间与结果数组中的最后一个区间不重叠,则将当前区间直接加入结果数组。否则,将当前区间与结果数组中的最后一个区间合并。合并的方法是取两个区间的起始位置的最小值和结束位置的最大值作为合并后的区间的起始位置和结束位置。
代码
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
# 对区间按照起始位置(start)进行排序
intervals.sort(key=lambda x: x[0])
# 创建一个结果数组,用于存储合并后的区间
merged = []
# 遍历区间数组
for interval in intervals:
# 如果结果数组为空,或者当前区间与结果数组中的最后一个区间不重叠,则将当前区间直接加入结果数组
# not merged 数组不为空
# merged[-1]指的是merged中最后一个元素 merged[-1][1]指的是最后一个区间的结束位置
if not merged or merged[-1][1] < interval[0]:
merged.append(interval)
# 否则,将当前区间与结果数组中的最后一个区间合并
else:
# 合并的时候,取两个区间的结束位置的最大值作为合并后的结束位置。
merged[-1][1] = max(merged[-1][1], interval[1])
return merged
3. 0179. 最大数
3.1 题目大意
描述:给定一个非负整数数组 nums
。
要求:重新排列数组中每个数的顺序,使之将数组中所有数字按顺序拼接起来所组成的整数最大。
说明:
- 1 ≤ n u m s . l e n g t h ≤ 100 1 \le nums.length \le 100 1≤nums.length≤100。
- 0 ≤ n u m s [ i ] ≤ 1 0 9 0 \le nums[i] \le 10^9 0≤nums[i]≤109。
示例:
输入:nums = [10,2]
输出:"210"
输入:nums = [3,30,34,5,9]
输出:"9534330"
06.01.04 练习题目(第 04 天)
1. 0704. 二分查找
1.1 题目大意
描述:给定一个升序的数组 nums
,和一个目标值 target
。
要求:返回 target
在数组中的位置,如果找不到,则返回 -1。
说明:
- 你可以假设
nums
中的所有元素是不重复的。 n
将在[1, 10000]
之间。nums
的每个元素都将在[-9999, 9999]
之间。
示例:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
2. 0034. 在排序数组中查找元素的第一个和最后一个位置
2.1 题目大意
描述:给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。
要求:找出给定目标值在数组中的开始位置和结束位置。
说明:
- 要求使用时间复杂度为 O ( log n ) O(\log n) O(logn) 的算法解决问题。
示例:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
3. 0153. 寻找旋转排序数组中的最小值
3.1 题目大意
描述:给定一个数组 nums
,nums
是有升序数组经过「旋转」得到的。但是旋转次数未知。数组中不存在重复元素。
要求:找出数组中的最小元素。
说明:
- 旋转操作:将数组整体右移若干位置。
- n = = n u m s . l e n g t h n == nums.length n==nums.length。
- 1 ≤ n ≤ 5000 1 \le n \le 5000 1≤n≤5000。
- − 5000 ≤ n u m s [ i ] ≤ 5000 -5000 \le nums[i] \le 5000 −5000≤nums[i]≤5000。
- n u m s nums nums 中的所有整数互不相同。
- n u m s nums nums 原来是一个升序排序的数组,并进行了 1 1 1 至 n n n 次旋转。
示例:
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。