一、二分搜索
搜索模板:O(logn),对于有序数组查找
-
初始化:start=0,end=len-1
-
循环退出条件:start+1<end
-
比较中点和目标值:A[mid]==、<、>目标值
-
判断最后两个元素:A[start]、A[end]与目标值
![](https://img-blog.csdnimg.cn/20210227111729535.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BvbW91dGFvOTE1MQ==,size_16,color_FFFFFF,t_70)
搜索:
1.搜索目标范围
def searchRange(self, nums: List[int], target: int) -> List[int]:
start, end, mark = 0, len(nums) - 1, -1
while start <= end:
mid = int((start + end)/2)
if nums[mid] == target:
mark = mid
break
elif nums[mid] < target:
start = mid + 1
else:
end = mid - 1
if mark == -1:
return [-1, -1]
else:
start = end = mark
while end < len(nums):
if nums[end] != target:
break
end += 1
while start >= 0:
if nums[start] != target:
break
start -= 1
return [start + 1, end - 1]
2.搜索目标
def searchInsert(self, nums: List[int], target: int) -> int:
#要先判断target是否在范围内
if target < nums[0]:
return 0
left, right = 0, len(nums)
#循环l<r(最后取的区间会含1个元素)与l<=r(最后区间元素为空)
while left < right:
#严格限制了mid的计算结果为整型
mid = (left + right) // 2
if nums[mid] < target:
left = mid + 1
else:
right = mid
return left
(法2)
def searchInsert(self, nums: List[int], target: int) -> int:
start, end, mark = 0, len(nums) - 1, 0
while start + 1 < end:
mid = (start + end) // 2
if nums[mid] < target:
start = mid
else:
end = mid
# 剩余start和end,必在这两个位置左右
if nums[start] >= target:
mark = start
elif nums[end] >= target:
mark = end
else:
mark = end + 1
return mark
3.从数组中搜索目标
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
if not matrix:
return False
# 先确定行
start, end, mark = 0, len(matrix) - 1, 0
while start + 1 < end:
mid = (start + end) // 2
temp = matrix[mid]
if matrix[mid][len(temp)-1] < target:
start = mid
else:
end = mid
if matrix[start][0] <= target and matrix[start][len(matrix[start]) - 1] >= target:
mark = start
elif matrix[end][0] <= target and matrix[end][len(matrix[end]) - 1] >= target:
mark = end
else:
return False
# 对行找数
start, end = 0, len(matrix[mark]) - 1
while start + 1 < end:
mid = (start + end) // 2
if matrix[mark][mid] < target:
start = mid
else:
end = mid
if matrix[mark][start] == target or matrix[mark][end] == target:
return True
else:
return False
(法2:降多维为1维)
旋转数组:
1.找出旋转数组中的最小值
def findMin(self, nums: List[int]) -> int:
if not nums:
return 0
start, end = 0, len(nums) - 1
while start + 1 < end:
mid = (start + end) // 2
# 判断912345678,中间大说明最小在右侧,否则在左侧
if nums[mid] > nums[end]:
start = mid
else:
end = mid
if nums[start] < nums[end]:
return nums[start]
else:
return nums[end]
2.找出有重复的旋转数组中的最小值
def findMin(self, nums: List[int]) -> int:
# 一般情况和无重复一样,中点与最右值比较,再移动端点
# 有重复值需要考虑222221222的情况:
# 此时最右与中点一样,无法判断情况,谨慎考虑:移动端点
if not nums:
return 0
start, end = 0, len(nums) - 1
while start + 1 < end:
mid = (start + end) // 2
if nums[mid] > nums[end]:
start = mid
elif nums[mid] < nums[end]:
end = mid
else:
end -= 1
if nums[start] < nums[end]:
return nums[start]
else:
return nums[end]
3.在旋转数组中搜索数
def search(self, nums: List[int], target: int) -> int:
start, end = 0, len(nums) - 1
while start + 1 < end:
mid = (start + end) // 2
# 在中点右侧是递增情况下
if nums[mid] < nums[end]:
if nums[end] >= target and nums[mid] <= target:
start = mid
else:
end = mid
# 中点左侧是递增
else:
if nums[start] <= target and nums[mid] >= target:
end = mid
else:
start = mid
if nums[start] == target:
return start
elif nums[end] == target:
return end
else:
return -1
4.在有重复的旋转数组中搜索数
def search(self, nums: List[int], target: int) -> bool:
start, end, mark = 0, len(nums) - 1, -1
while start + 1 < end:
mid = (start + end) // 2
# 有重复的旋转数组中存在3种情况
# 当中点小于右点,说明中点右侧是递增的
if nums[mid] < nums[end]:
if nums[mid] <= target and nums[end] >= target:
start = mid
else:
end = mid
# 当中点大于右点,说明中点左侧的递增的
elif nums[mid] > nums[end]:
if nums[start] <= target and nums[mid] >= target:
end = mid
else:
start = mid
# 当中点等于右点时,无法判定哪侧递增2222122
else:
if nums[end] == target:
mark = end
break
else:
end -= 1
if mark != -1 or nums[start] == target or nums[end] == target:
return True
else:
return False
二、排序算法
插入排序
选择排序
冒泡排序
-
快速排序
def sortArray(self, nums: List[int]) -> List[int]:
self.quickSort(nums, 0, len(nums) - 1)
return nums
def quickSort(self, nums:List[int], start:int, end:int):
if start < end:
# 快排核心:找出中轴,中轴左边与右边各自分治(中轴已站定,不需要动)
pivot = self.partition(nums, start, end)
self.quickSort(nums, start, pivot - 1)
self.quickSort(nums, pivot + 1, end)
def partition(self, nums:List[int], start:int, end:int) -> int:
mark = nums[end]
# 找中轴:用i记录中轴位置,用j来循环数组
i = j = start
for j in range(start, end):
if nums[j] < mark:
self.swap(nums, i, j)
i += 1
self.swap(nums, i, end)
return i
def swap(self, nums:List[int], i:int, j:int):
temp = nums[j]
nums[j] = nums[i]
nums[i] = temp
-
归并排序
def sortArray(self, nums: List[int]) -> List[int]:
return self.mergeSort(nums)
def mergeSort(self, nums:List[int]) -> List[int]:
if len(nums) <= 1:
return nums
mid = len(nums) // 2
left = self.mergeSort(nums[:mid])
right = self.mergeSort(nums[mid:])
return self.mergeTwo(left, right)
def mergeTwo(self, left:List[int], right:List[int]) -> List[int]:
res = []
l, r = 0, 0
while l < len(left) and r < len(right):
if left[l] <= right[r]:
res.append(left[l])
l += 1
else:
res.append(right[r])
r += 1
if l < len(left):
res.extend(left[l:])
if r < len(right):
res.extend(right[r:])
return res
-
堆排序
def sortArray(self, nums: List[int]) -> List[int]:
# 构造大顶堆,交换、取头,再交换、取头
return self.maxStack(nums)
def maxStack(self, nums:List[int]) -> List[int]:
# 构造大顶堆,为长度/2,再减一为序号
l = len(nums) // 2 - 1
while l >= 0:
self.maxCheck(nums, len(nums) - 1, l)
l -= 1
l = len(nums) - 1
while l > 0:
# 对序号末尾到1的位置都要交换
self.swap(nums, 0, l)
l -= 1
self.maxCheck(nums, l, 0)
return nums
def maxCheck(self, nums:List[int], end:int, idx:int):
left, right = idx * 2 + 1, idx * 2 + 2
# 需要比较多次,采用mark记录大值序号的方法
mark = idx
if left <= end and nums[left] > nums[mark]:
mark = left
if right <= end and nums[right] > nums[mark]:
mark = right
if mark != idx:
self.swap(nums, mark, idx)
self.maxCheck(nums, end, mark)
对链表进行插入排序:
def insertionSortList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
pre = ListNode()
pre.next = head
point = head
while point.next != None:
node = point.next
# 当节点可以在当前位置时
if point.val <= node.val:
point = point.next
continue
# 需要插入时
else:
point.next = node.next
po = pre
while po.next != None and po.next.val < node.val:
po = po.next
node.next = po.next
po.next = node
return pre.next
三、动态规划
使用原理:将问题划分为许多个重复的小问题,大问题结果由小问题的结果得到。
使用场景:
-
问题为:
-
求最大值/最小值
-
求是否可行
-
求可行个数
-
-
满足不能交换、排序
四要素
:
-
状态
-
方程
-
初始化
-
答案
矩阵类型:
1.二维矩阵树的最短路径
def minimumTotal(self, triangle: List[List[int]]) -> int:
res, l = [], len(triangle)
if l == 0:
return 0
for tri in triangle:
temp = []
for t in tri:
temp.append(t)
res.append(temp)
# 获取倒数第二行的坐标
l -= 2
# 自底向上计算
while l >= 0:
for i in range(0, len(triangle[l])):
res[l][i] += min(res[l + 1][i], res[l + 1][i + 1])
l -= 1
return res[0][0]
(法2:自顶向下)
def minimumTotal(self, triangle: List[List[int]]) -> int:
res, l = [], len(triangle)
if l == 0:
return 0
for tri in triangle:
temp = []
for t in tri:
temp.append(t)
res.append(temp)
# 自顶向下
for i in range(1, l):
t = len(triangle[i])
for j in range(0, t):
# 判断该点上一层的左右是否都存在
if j > 0 and j < len(triangle[i - 1]):
res[i][j] += min(res[i - 1][j - 1], res[i - 1][j])
elif j > 0:
res[i][j] += res[i - 1][j - 1]
else:
res[i][j] += res[i - 1][j]
return min(res[l - 1])
2.找出mxn矩阵自左上至右下的最短路径
def minPathSum(self, grid: List[List[int]]) -> int:
if not grid:
return 0
m, n = len(grid), len(grid[0])
for i in range(0, m):
for j in range(0, n):
# 每个点的路径由左或者上的值取最小值决定
# 判断没点的左或上是否存在点
if i > 0 and j > 0:
grid[i][j] += min(grid[i - 1][j], grid[i][j - 1])
elif i > 0:
grid[i][j] += grid[i - 1][j]
elif j > 0:
grid[i][j] += grid[i][j - 1]
return grid[m - 1][n - 1]
3.找出mxn中,机器人的可能路线
def uniquePaths(self, m: int, n: int) -> int:
dp = []
for i in range(0, m):
temp = []
for j in range(0, n):
temp.append(1)
dp.append(temp)
# 因为机器人往右下路线固定
# 左侧与上侧的的值都仅有1条路线
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[m - 1][n - 1]
4.找出mxn中,存在障碍物情况下,机器人的可能路线【medium】
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
if not obstacleGrid or obstacleGrid[0][0] == 1:
return 0
dp, m, n = [], len(obstacleGrid), len(obstacleGrid[0])
for i in range(0, m):
temp = []
for j in range(0, n):
temp.append(1)
dp.append(temp)
i, j = 1, 1
# 处理[[0,0],[1,1],[0,0]]的数组,设置好左侧与上侧
while i < m:
if obstacleGrid[i][0] == 1 or dp[i - 1][0] == 0:
dp[i][0] = 0
i += 1
while j < n:
if obstacleGrid[0][j] == 1 or dp[0][j - 1] == 0:
dp[0][j] = 0
j += 1
for i in range(1, m):
for j in range(1, n):
if obstacleGrid[i][j] == 1:
dp[i][j] = 0
else:
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[m - 1][n - 1]
序列类型:
1.上楼梯,一次上1级或2级
def climbStairs(self, n: int) -> int:
dp = [1, 1]
for i in range(2, n + 1):
dp.append(dp[i - 1] + dp[i - 2])
return dp[n]
2.数组跳步问题
def canJump(self, nums: List[int]) -> bool:
# dp[i]表示能否从0跳到i
if not nums:
return True
maxlen = nums[0]
for i in range(1, len(nums)):
if i <= maxlen:
maxlen = max(maxlen, i + nums[i])
# 保证可到的最大长度大于等于数组长度,且[0]能通过
if maxlen >= len(nums) - 1:
return True
else:
return False
3.数组跳步升级版(记跳数)
def jump(self, nums: List[int]) -> int:
n = len(nums)
if n == 0:
return 0
end, maxidx, step = 0, 0, 0
# 假设你总是可以到达数组的最后一个位置。
# 在遍历数组时,不访问最后一个元素,
# 因为在访问最后一个元素之前,边界一定大于等于最后一个位置
# 如果访问最后一个元素,在边界正好为最后一个位置的情况下
# 会增加一次「不必要的跳跃次数」
for i in range(0, n - 1):
# 一直记录最大范围
maxidx = max(maxidx, i + nums[i])
# 遇到上次的边界,则更新边界
if i == end:
end = maxidx
step += 1
return step
4.最长回文子串问题
def longestPalindrome(self, s: str) -> str:
n = len(s)
# 新赋值数组方法
dp = [[False] * n for _ in range(n)]
res = ""
# 利用串长度的方式展开,以小段得大段
for l in range(n):
for i in range(n):
j = i + l
if j >= n:
break
if l == 0:
dp[i][j] = True
elif l == 1:
dp[i][j] = (s[i] == s[j])
else:
dp[i][j] = (s[i] == s[j] and dp[i + 1][j - 1])
if dp[i][j] and l + 1 > len(res):
res = s[i:j+1]
return res
5.回文子串分割次数计算【hard】
def minCut(self, s: str) -> int:
n = len(s)
# 预处理子串是否回文
ispali = [[False] * n for _ in range(n)]
for l in range(n):
for i in range(n):
j = i + l
if j >= n:
break
# 跨度为0,表示1个字符
if l == 0:
ispali[i][j] = True
# 跨度为1,表示2个字符
elif l == 1:
ispali[i][j] = (s[i] == s[j])
else:
ispali[i][j] = (s[i] == s[j] and ispali[i + 1][j - 1])
# 分割次数计算:
# dp[i]表示从0~i分割次数
# 1.回文,分割为0;
# 2.不回文,则j~i回文(0<j<i),得dp[j] + 1.
dp = [i for i in range(n)]
for i in range(n):
if ispali[0][i]:
dp[i] = 0
else:
dp[i] = min([dp[j] + 1 for j in range(i) if ispali[j + 1][i]])
return dp[n - 1]
6.找出数组中最长的严格递增序列(可不连续)
def lengthOfLIS(self, nums: List[int]) -> int:
# dp[i]表示从0~i最长递增序列的长度
# dp[i] = max(dp[j]) + 1, if nums[i]>nums[j]
# 初始值dp[i] = 1
# 返回值max(dp[i])
n = len(nums)
dp = [1 for i in range(n)]
for i in range(1, n):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
7.判断字符串是否能完全拆分为单词表中的单词
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
# dp[i] = true 表示从0~i正好能覆盖
# dp[i] = s[:i+1] in wordDict
# 或 dp[i] = dp[j] and s[j+1:i+1] in word Dict
# 初始值dp[i] = false
# 返回值dp[n-1]
n = len(s)
dp = [False for _ in range(n)]
for i in range(n):
if s[:i + 1] in wordDict:
dp[i] = True
else:
for j in range(i):
if dp[j] and s[j + 1: i + 1] in wordDict:
dp[i] = True
return dp[n - 1]
双序列类型:
1.最长公共子序列【medium】
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
l1, l2 = len(text1), len(text2)
if l1 == 0 or l2 == 0:
return 0
# 发现两个字符串的规律:用二位动态规划数组表示。
# dp[i][j]表示str2[0~i]与str1[0~j]的公共子序列字符个数
# dp[i][j] = dp[i-1][j-1] + 1 if str2[i]==str1[j]
# 或者dp[i][j] = 左侧或上侧的最大数字(等同于str2[i-1]与str1[j]或str2[i]与str1[j-1]的公共子序列个数)
dp = []
for i in range(l2 + 1):
temp = []
for j in range(l1 + 1):
temp.append(0)
dp.append(temp)
for i in range(l2):
for j in range(l1):
if text2[i] == text1[j]:
dp[i + 1][j + 1] = dp[i][j] + 1
else:
dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j])
return dp[l2][l1]
2.将字符串a转换为字符串b需要变化的最小次数【hard】
def minDistance(self, word1: str, word2: str) -> int:
l1, l2 = len(word1), len(word2)
dp = [[0] * (l1 + 1) for _ in range(l2 + 1)]
for i in range(l2 + 1):
dp[i][0] = i
for j in range(l1 + 1):
dp[0][j] = j
# 动态规划数组:dp[i][j]表示str2[0~i]和str1[0~j]子串内从str1转换成str2的变换次数
# dp[i][j] = dp[i-1][j-1] if 字符相等,则变换次数不变
# 或dp[i][j] = 1+上一次最小变换次数min(dp[i][j],dp[i][j-1],dp[i-1][j])
for i in range(l2):
for j in range(l1):
if word2[i] == word1[j]:
dp[i + 1][j + 1] = dp[i][j]
else:
dp[i + 1][j + 1] = 1 + min(dp[i][j + 1], dp[i + 1][j], dp[i][j])
return dp[l2][l1]
3.零钱兑换【medium】
def coinChange(self, coins: List[int], amount: int) -> int:
# dp[i]表示总金额为i的时候,硬币个数
# dp[i] = min(dp[i], dp[i-k]+1) if i>=k
# dp[i]表示最少个数,因此初始值最大为amount+1
dp = [(amount + 1) for _ in range(amount + 1)]
dp[0] = 0
for i in range(1, amount + 1):
for c in coins:
# 判断c硬币是否放入总金额
if (i - c) >= 0:
dp[i] = min(dp[i], dp[i - c] + 1)
# 设置数额无法达到的默认值-1
for i in range(amount + 1):
if dp[i] == amount + 1:
dp[i] = -1
return dp[amount]
4. 0-1背包问题,求n个物品中选取满足容量m的最大价值
def backPackII(self, m, A, V):
# dp[i][j]表示前i个物品,容量是否能为j
# dp[i][j] = dp[i-1][j] 不装第i个物品的容量
# 或者 dp[i][j] = dp[i-1][j-a[i]] 装第i个物品
# 初始值dp[0][0] = 0
n, dp = len(A), []
for i in range(n + 1):
temp = []
for j in range(m + 1):
temp.append(0)
dp.append(temp)
# i、j表示第i个物品,j+1表示真实容量
for i in range(n):
for j in range(m):
dp[i + 1][j + 1] = dp[i][j + 1]
if A[i] <= j + 1:
dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i][j + 1 - A[i]] + V[i])
return dp[n][m]