二分查找
LeetCode704题 二分查找
搜索区间为左闭右闭[left, right]
class Solution(object):
def search(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: int
"""
left = 0
right = len(nums) - 1
while left <= right:
middle = (left + right) // 2
if nums[middle] < target:
left = middle + 1
elif nums[middle] > target:
right = middle - 1
elif nums[middle] == target:
return middle
return -1
LeetCode35题 搜索插入位置
二分查找变体-查找目标的插入位置
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
return left
LeetCode34题 在排序数组中查找元素的第一个和最后一个位置
查找有序重复数组的左右边界问题,与二分查找基础题相比,主要差异在对nums[mid]==target时的情况处理
class Solution:
def searchRange(self, nums: List[int], target: int) -> List[int]:
if len(nums) == 0:
return [-1, -1]
return [self.searchBound(nums, target, 'left'), self.searchBound(nums, target, 'right')]
def searchBound(self, nums, target, side):
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] < target:
left = mid + 1
elif nums[mid] > target:
right = mid - 1
else:
if side == 'left':
# 收缩右侧边界
right = mid - 1
else:
# 收缩左侧边界
left = mid + 1
if side == 'left':
return left if 0 <= left < len(nums) and nums[left] == target else -1
else:
return right if 0 <= right < len(nums) and nums[right] == target else -1
LeetCode69题 x的平方根
本质是在找符合r*r<=x的r的右边界问题,所以返回right
class Solution:
def mySqrt(self, x: int) -> int:
left = 1
right = x
while left <= right:
mid = left + (right - left) // 2
r = mid * mid
if r == x:
return mid
elif r < x:
left = mid + 1
elif r > x:
right = mid - 1
return right
二分查找-数组变异问题
LeetCode240题 搜索二维矩阵II
借助递归函数进行搜索,每次搜索排除1/3的搜索区域
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
self.res = False
m, n = len(matrix), len(matrix[0])
self.search(matrix, target, 0, m - 1, 0, n - 1)
return self.res
def search(self, matrix, target, x1, x2, y1, y2):
if self.res or x1 > x2 or y1 > y2:
return
x_mid = x1 + (x2 - x1) // 2
y_mid = y1 + (y2 - y1) // 2
if matrix[x_mid][y_mid] == target:
self.res = True
elif matrix[x_mid][y_mid] > target:
self.search(matrix, target, x1, x_mid - 1, y1, y2) # 上方
self.search(matrix, target, x_mid, x2, y1, y_mid - 1) # 左下方
elif matrix[x_mid][y_mid] < target:
self.search(matrix, target, x_mid + 1, x2, y1, y2) # 下方
self.search(matrix, target, x1, x_mid, y_mid + 1, y2) # 右上方
LeetCode153题 寻找旋转排序数组中的最小值
设nums[mid]是现在二分取到的数,可以把nums[mid]与nums[−1]比大小:
- 如果nums[mid]>nums[−1],那么可以推出nums一定被分成左右两个递增段,第一段的所有元素均大于第二段的所有元素,最小值在mid右侧
- 如果nums[mid]<nums[−1],最小值在mid左侧
所以,只需要比较nums[mid]和nums[−1]的大小关系,从而不断地缩小数组最小值所在位置的范围,二分找到数组最小值
class Solution:
def findMin(self, nums: List[int]) -> int:
left, right = 0, len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] > nums[-1]:
left = mid + 1
else:
right = mid - 1
return nums[left]
LeetCode33题 搜索旋转排序数组
先比较nums[mid]、nums[-1],确定mid的位置
然后比较nums[mid]、nums[-1]、target三者关系,确定搜索方向
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
if nums[-1] == target:
return len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
# mid位于转折点前半部分
if nums[mid] > nums[-1]:
if target > nums[mid]:
left = mid + 1
elif nums[-1] < target < nums[mid]:
right = mid - 1
elif target < nums[-1]:
left = mid + 1
# mid位于转折点后半部分/未翻转
else:
if target > nums[-1]:
right = mid - 1
elif nums[mid] < target < nums[-1]:
left = mid + 1
elif target < nums[mid]:
right = mid - 1
return -1
LeetCode4题 寻找两个正序数组的中位数
划分数组法:中位数可以通过将所有元素划分到a、b两个数组中,然后取a[-1]、b[0]计算得到
对于下面两种边界情况,需要特殊考虑
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
# 划分数组法:中位数可以通过将所有元素划分到a、b两个数组中,然后取a[-1]、b[0]计算得到
# 其中a有 (m + n + 1) // 2个元素,b有 m + n - (m + n + 1) // 2个元素
# 为了减少时间复杂度,需要确保nums1是较短的数组,使得始终对较短的数组进行二分查找
if len(nums1) > len(nums2):
nums1, nums2 = nums2, nums1
m, n = len(nums1), len(nums2)
left, right = 0, m
while left <= right:
# 在 nums1 中找到一个划分点
partition1 = left + (right - left) // 2
# 在 nums2 中找到一个划分点,使得左半部分的总长度为 (m + n + 1) // 2
partition2 = (m + n + 1) // 2 - partition1
# 找到划分点两侧的最大值和最小值
max_left1 = float('-inf') if partition1 == 0 else nums1[partition1 - 1]
min_right1 = float('inf') if partition1 == m else nums1[partition1]
max_left2 = float('-inf') if partition2 == 0 else nums2[partition2 - 1]
min_right2 = float('inf') if partition2 == n else nums2[partition2]
# 如果划分点正确,返回中位数
if max_left1 <= min_right2 and max_left2 <= min_right1:
if (m + n) % 2 == 0:
return (max(max_left1, max_left2) + min(min_right1, min_right2)) / 2
else:
return max(max_left1, max_left2)
# 如果划分点不正确,调整划分点
elif max_left1 > min_right2:
right = partition1 - 1
elif max_left2 > min_right1:
left = partition1 + 1
二分查找-最值问题
LeetCode793题 阶乘函数后K个零
logk * logk时间复杂度解法,先圈定可能满足k个零的
class Solution:
def preimageSizeFZF(self, k: int) -> int:
left = 0
right = 5 * k
while left <= right:
mid = (left + right) // 2
mid_res = self.trailingZeroes(mid)
if mid_res == k:
return 5
elif mid_res < k:
left = mid + 1
else:
right = mid - 1
return 0
# 统计数字n的末尾0数量,按照5/25/125...层层统计
def trailingZeroes(self, n: int) -> int:
p = 1
res = 0
while p * 5 <= n:
p *= 5
res += n // p
return res
LeetCode875题 爱吃香蕉的珂珂
吃香蕉速度k和耗时h是负相关关系,所以可以用二分查找,查找符合条件的k的左边界
class Solution:
def minEatingSpeed(self, piles: List[int], h: int) -> int:
m = max(piles)
left = 1
right = m
while left <= right:
mid = left + (right - left) // 2
mid_res = self.cal_hours(piles, mid)
if mid_res > h:
left = mid + 1
else:
right = mid - 1
if right < m:
return left
else:
return right
def cal_hours(self, piles, k):
res = 0
for i in piles:
if i % k != 0:
res += i // k + 1
else:
res += i // k
return res
LeetCode1011题 在D天内送达包裹的能力
运载能力和耗费天数是负相关关系,所以可以用二分查找,查找符合条件的运载能力的左边界
class Solution:
def shipWithinDays(self, weights: List[int], days: int) -> int:
max_val = float('-inf')
sum_val = 0
for i in weights:
max_val = max(max_val, i)
sum_val += i
# 最小运载量是max_val,否则有的包裹永远无法被装载
# 最大运载量时sum_val,对应days=1情况
left = max_val
right = sum_val
while left <= right:
mid = left + (right - left) // 2
mid_res = self.cal_days(weights, mid)
if mid_res > days:
left = mid + 1
else:
right = mid - 1
if right < sum_val:
return left
else:
return right
def cal_days(self, weights, carrying):
res = 0
cur = carrying
for i in weights:
# 剩余运载能力装不下时,天数加一
if cur < i:
res += 1
cur = carrying
cur -= i
return res + 1
LeetCode410题 分割数组的最大值
这道题在问分割数组和的最大值的最小值,可以用二分查找搜索这个最小值,也就是二分查找左边界问题(如果要直接求解使得数组和的最大值最小的分割数组方案,复杂度会高一些)
class Solution:
def splitArray(self, nums: List[int], k: int) -> int:
max_val = float('-inf')
sum_val = 0
for i in nums:
max_val = max(max_val, i)
sum_val += i
left = max_val
right = sum_val
while left <= right:
mid = left + (right - left) // 2
is_valid = self.valid(nums, k, mid)
if not is_valid:
left = mid + 1
else:
# 向左侧压缩
right = mid - 1
return left if right < sum_val else right
def valid(self, nums, k, x):
# 判断是否满足最大分割子数组和不超过x
total = 0
cnt = 1
for num in nums:
if total + num > x:
cnt += 1
total = num
else:
total += num
return cnt <= k
二分查找-第K大/第K小问题
第k小的元素 等价于 <=x的元素有k个
第k大的元素 等价于 >=x的元素有k个
LeetCode378题 有序矩阵中第K小的元素
时间复杂度为n*log(matrix[-1][-1] - matrix[0][0])
class Solution:
def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
# 第k小的元素 等价于 <=x的元素有k个
# 确定x的取值范围
left, right = matrix[0][0], matrix[-1][-1]
while left <= right:
mid = left + (right - left) // 2
if self.get_num(matrix, mid) < k:
left = mid + 1
else:
right = mid - 1
return left
def get_num(self, matrix, target):
# 计算矩阵中<=target的元素数量
# 从矩阵左下角开始移动,直至出界
# 若matrix[i][j]<=target,则将当前所在列上方的元素累加到答案中,并向右移动
# 若matrix[i][j]>target,则向上移动
n = len(matrix)
i, j = n - 1, 0
num = 0
while i >= 0 and j < n:
if matrix[i][j] <= target:
num += i + 1
j += 1
else:
i -= 1
return num
LeetCode719题 找出第K小的数对距离
class Solution:
def smallestDistancePair(self, nums: List[int], k: int) -> int:
# <= x的个数为k的数对距离
# 0 <= x <= max(nums) - min(nums)
nums.sort()
left, right = 0, max(nums) - min(nums)
while left <= right:
mid = left + (right - left) // 2
if self.get_num(nums, mid) < k:
left = mid + 1
else:
right = mid - 1
return left
def get_num(self, nums, x):
# 用双指针计算nums中<=x的数对个数
num = 0
cur = 0 # 记录左端点索引
# 遍历右端点
for i in range(len(nums)):
while nums[i] - nums[cur] > x:
cur += 1
num += i - cur
return num
双指针-同向双指针
LeetCode26题 删除有序数组中的重复项
快慢指针:让慢指针走在后面(使得慢指针之前的元素都是无重复的),快指针走在前面探路,找到一个不重复的元素就赋值给慢指针,并让慢指针前进一步
class Solution:
def removeDuplicates(self, nums: List[int]) -> int:
slow = 1
for i in range(1, len(nums)):
if nums[i] != nums[slow - 1]:
nums[slow] = nums[i]
slow += 1
return slow
LeetCode283题 移动零
思路同上面两个题,快慢指针,但本题需要保证数组中元素和原有数组一致,只是改变顺序,所以需要对快慢指针元素进行交换
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
slow = 0
for fast in range(len(nums)):
if nums[fast] != 0:
nums[slow], nums[fast] = nums[fast], nums[slow]
slow += 1
LeetCode581题 最短无序连续子数组
class Solution:
def findUnsortedSubarray(self, nums: List[int]) -> int:
start, end = -1, -1
# 求结束位置
pre_max = nums[0]
for i in range(1, len(nums)):
if nums[i] < pre_max:
end = i
else:
pre_max = nums[i]
# 求开始位置
pre_min = nums[-1]
for i in range(len(nums) - 2, -1, -1):
if nums[i] > pre_min:
start = i
else:
pre_min = nums[i]
if start < 0 and end < 0:
return 0
return end - start + 1
LeetCode795题 区间子数组个数
class Solution:
def numSubarrayBoundedMax(self, nums: List[int], left: int, right: int) -> int:
res = 0
# l代表上一个不能包含的索引,r代表必须包含的索引
l, r = -1, -1
for i in range(len(nums)):
if nums[i] > right:
l = i
if nums[i] >= left:
r = i
res += r - l
return res
双指针-相向双指针
LeetCode11题 盛最多水的容器
class Solution:
def maxArea(self, height: List[int]) -> int:
res = 0
left, right = 0, len(height) - 1
while left < right:
res = max(res, min(height[left], height[right]) * (right - left))
# 优先移动矮的柱子
if height[left] <= height[right]:
left += 1
else:
right -= 1
return res
LeetCode42题 接雨水
class Solution:
def trap(self, height: List[int]) -> int:
res = 0
# 分别代表前后缀最大值
left_max, right_max = 0, 0
# 双指针
left, right = 0, len(height) - 1
while left < right:
# 更新前后缀最大值
left_max = max(left_max, height[left])
right_max = max(right_max, height[right])
# 若前缀最大值<=后缀最大值,说明此时left位置能接多少水 只取决于前缀最大值。反之亦然
if left_max <= right_max:
res += left_max - height[left]
left += 1
else:
res += right_max - height[right]
right -= 1
return res
LeetCode167题 两数之和II-输入有序数组
从两端不断收缩两个指针,直到满足条件,将复杂度降至O(n)
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
left, right = 0, len(numbers) - 1
while left < right:
sum_val = numbers[left] + numbers[right]
if sum_val == target:
return [left + 1, right + 1]
elif sum_val < target:
left += 1
else:
right -= 1
LeetCode611题 有效三角形的个数
利用双指针将复杂度降至O(n*n)
class Solution:
def triangleNumber(self, nums: List[int]) -> int:
res = 0
nums.sort()
for i in range(len(nums) - 1, 1, -1):
# i代表三角形最长边的长度
# 用户双指针枚举其他两条边
left, right, = 0, i - 1
while left < right:
if nums[left] + nums[right] > nums[i]:
res += right - left
right -= 1
else:
left += 1
return res
前缀和数组
前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和
LeetCode303题 区域和检索-数组不可变
前缀和数组应用,前缀和数组索引i存储的是nums[:i]的和,可以使区域和检索复杂降为O(1)
class NumArray:
def __init__(self, nums: List[int]):
self.pre_sum_list = nums
for i in range(1, len(nums)):
self.pre_sum_list[i] += self.pre_sum_list[i - 1]
def sumRange(self, left: int, right: int) -> int:
if left == 0:
return self.pre_sum_list[right]
else:
return self.pre_sum_list[right] - self.pre_sum_list[left - 1]
# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# param_1 = obj.sumRange(left,right)
LeetCode560题 和为K的子数组
前缀和 + 哈希表
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
res = 0
hash_map = dict()
# 对应sum(num[:i + 1]) = k的情况
hash_map[0] = 1
total = 0
for i in range(len(nums)):
total += nums[i]
if total - k in hash_map:
res += hash_map[total - k]
hash_map[total] = hash_map.get(total, 0) + 1
return res
LeetCode974题 和可被K整除的子数组
前缀和 + 哈希表 + 同余定理
- 同余的基本定义:对于整数a、b和k,如果a-b能被k整除,则称a与b对模k同余,记作:a≡b(mod k)。a与b对模k同余等价于a与b分别用k去除,余数相同
- 基本性质:
-
传递性:若a≡b(mod m),b≡c(mod m),则a≡c(mod m)
-
同余式相加:若a≡b(mod m),c≡d(mod m),则a+c≡b+d(mod m)
-
同余式相乘:若a≡b(mod m),c≡d(mod m),则ac≡bd(mod m)
-
class Solution:
def subarraysDivByK(self, nums: List[int], k: int) -> int:
res = 0
total = 0
hash_map = dict()
hash_map[0] = 1
for i in range(len(nums)):
total += nums[i]
# 根据同余定理中的传递性,只要a mod k == b mod k,就可以保证 (a - b) % k == 0成立
if total % k in hash_map:
res += hash_map[total % k]
hash_map[total % k] = hash_map.get(total % k, 0) + 1
return res
LeetCode437题 路径总和III
树形前缀和,前缀和 + 哈希表 + 后序遍历
# 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 pathSum(self, root: Optional[TreeNode], targetSum: int) -> int:
self.hash_map = {0: 1}
return self.dfs(root, targetSum, 0)
def dfs(self, root, targetSum, total):
if root is None:
return 0
total += root.val
r = self.hash_map.get(total - targetSum, 0)
self.hash_map[total] = self.hash_map.get(total, 0) + 1
left = self.dfs(root.left, targetSum, total)
right = self.dfs(root.right, targetSum, total)
self.hash_map[total] -= 1
return r + left + right
LeetCode304题 二维区域和检索-矩阵不可变
二维前缀和
class NumMatrix:
def __init__(self, matrix: List[List[int]]):
m, n = len(matrix), len(matrix[0])
self.pre_sum_matrix = [[0] * n for _ in range(m)]
# 初始化
row_sum = col_sum = 0
for i in range(n):
row_sum += matrix[0][i]
self.pre_sum_matrix[0][i] = row_sum
for i in range(m):
col_sum += matrix[i][0]
self.pre_sum_matrix[i][0] = col_sum
# 通过dp数组方式得到二维前缀和数组
for i in range(1, m):
for j in range(1, n):
self.pre_sum_matrix[i][j] = self.pre_sum_matrix[i - 1][j] + self.pre_sum_matrix[i][j - 1] + \
matrix[i][j] - self.pre_sum_matrix[i - 1][j - 1]
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
if row1 == 0 and col1 == 0:
return self.pre_sum_matrix[row2][col2]
elif row1 == 0:
return self.pre_sum_matrix[row2][col2] - self.pre_sum_matrix[row2][col1 - 1]
elif col1 == 0:
return self.pre_sum_matrix[row2][col2] - self.pre_sum_matrix[row1 - 1][col2]
else:
return self.pre_sum_matrix[row2][col2] + self.pre_sum_matrix[row1 - 1][col1 - 1] - \
self.pre_sum_matrix[row1 - 1][col2] - self.pre_sum_matrix[row2][col1 - 1]
差分数组
差分数组的主要适用场景是频繁对原始数组某个区间的元素进行增减
定义差分数组为diff,diff[i] = nums[i] - nums[i-1](diff[0]=nums[0]),这样就可以快速进行区间增减的操作,如果你想对区间i~j的元素全部加3,那么只需要让diff[i] + 3,然后再让diff[j+1] - 3即可。最后根据diff再进行数组还原
LeetCode1094题 拼车
class Solution:
def carPooling(self, trips: List[List[int]], capacity: int) -> bool:
diff_nums = [0] * 1001
for num, f, t in trips:
diff_nums[f] += num
diff_nums[t] -= num
# 数组还原,判断是否超载
for i in range(1001):
if i > 0:
diff_nums[i] += diff_nums[i - 1]
if diff_nums[i] > capacity:
return False
return True
LeetCode995题 K连续位的最小翻转次数
对于若干个k位翻转操作,改变先后顺序并不影响最终翻转的结果。不妨从nums[0] 开始考虑,若 nums[0]=0,则必定要翻转从位置 0 开始的子数组;若 nums[0]=1,则不翻转从位置 0 开始的子数组。按照这一策略,我们从左到右地执行这些翻转操作,若最终数组元素均为1,则执行的翻转次数就是最小的,时间复杂度是 O(nk)
进一步进行优化:我们考虑不去翻转数字,而是统计每个数字需要翻转的次数。对于一次翻转操作,相当于把长度为k的子数组中所有数字的翻转次数加1。我们可以用差分数组来计算当前数字翻转的次数,可以维护一个差分数组diff,其diff[i]表示两个相邻元素nums[i−1]和nums[i] 的翻转次数的差,这样我们在循环中不断还原差分数组,以判断是否需要再次翻转,以及无法进行翻转时是否还有0,时间复杂度是 O(n)
class Solution:
def minKBitFlips(self, nums: List[int], k: int) -> int:
res = 0
diff_arr = [0] * len(nums)
for i in range(len(nums)):
# 还原差分数组
if i > 0:
diff_arr[i] = diff_arr[i] + diff_arr[i - 1]
if i > len(nums) - k:
# 无法进行翻转时,判断是否还有0
# if (diff_arr[i] % 2 == 0 and nums[i] == 0) or (diff_arr[i] % 2 == 1 and nums[i] == 1):
if diff_arr[i] % 2 == nums[i]:
return -1
else:
# 判断是否需要k次翻转
# if (diff_arr[i] % 2 == 0 and nums[i] == 0) or (diff_arr[i] % 2 == 1 and nums[i] == 1):
if diff_arr[i] % 2 == nums[i]:
if i < len(nums) - 1:
diff_arr[i + 1] += 1
if i < len(nums) - k:
diff_arr[i + k] -= 1
res += 1
return res
LeetCode2536题 子矩阵元素加1
二维差分数组
利用差分数组、前缀和数组的关系:差分数组 的 前缀和数组 = 原数组
class Solution:
def rangeAddQueries(self, n: int, queries: List[List[int]]) -> List[List[int]]:
# 二维差分数组
diff = [[0] * (n + 1) for _ in range(n + 1)]
for r1, c1, r2, c2 in queries:
diff[r1][c1] += 1
diff[r1][c2 + 1] -= 1
diff[r2 + 1][c1] -= 1
diff[r2 + 1][c2 + 1] += 1
# 计算diff的二维前缀和(原地修改),差分数组 的 前缀和 = 原数组
for i in range(n):
for j in range(n):
if i == 0 and j == 0:
continue
elif i == 0:
diff[i][j] += diff[i][j - 1]
elif j == 0:
diff[i][j] += diff[i - 1][j]
else:
diff[i][j] += diff[i][j - 1] + diff[i - 1][j] - diff[i - 1][j - 1]
# 保留中间 n*n 的部分,即为答案
diff = diff[:-1]
for i, row in enumerate(diff):
diff[i] = row[:-1]
return diff
原地哈希
LeetCode41题 缺失的第一个正数
将每个数不断移动到其应该在的位置,直到所有数移动完,时间复杂度为O(n)
class Solution:
def firstMissingPositive(self, nums: List[int]) -> int:
for i in range(len(nums)):
while 0 <= nums[i] - 1 <= len(nums) - 1 and nums[i] != nums[nums[i] - 1]:
nums[nums[i] - 1], nums[i] = nums[i], nums[nums[i] - 1]
for i in range(len(nums)):
if i + 1 != nums[i]:
return i + 1
return len(nums) + 1
数组花式遍历
LeetCode48题 旋转图像
先按左上右下转置矩阵,然后交换每一行元素位置即可
class Solution:
def rotate(self, matrix: List[List[int]]) -> None:
"""
Do not return anything, modify matrix in-place instead.
"""
n = len(matrix[0])
# 按左上右下转置矩阵
for i in range(n):
for j in range(i, n):
matrix[j][i], matrix[i][j] = matrix[i][j], matrix[j][i]
# 交换每一行元素位置
for i in range(n):
matrix[i].reverse()
LeetCode54题 螺旋矩阵
在循环中,保持左闭右开逻辑(循环不变量)
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
res = []
m, n = len(matrix), len(matrix[0])
min_val = min(m, n)
num = min_val // 2 + min_val % 2
for i in range(num):
if i < n - i - 1 and i < m - i - 1:
for j in range(i, n - i - 1):
res.append(matrix[i][j])
for j in range(i, m - i - 1):
res.append(matrix[j][n - i - 1])
for j in range(n - i - 1, i, -1):
res.append(matrix[m - i - 1][j])
for j in range(m - i - 1, i, -1):
res.append(matrix[j][i])
elif i == n - i - 1 and i == m - i - 1:
res.append(matrix[i][i])
elif i < n - i - 1:
res.extend(matrix[i][i: n - i])
elif i < m - i - 1:
for j in range(i, m - i):
res.append(matrix[j][i])
return res