文章目录
任务7:13~14天
1. 学习目标
递归(保留往期第五天任务)
通过LeetCode上【70. 爬楼梯】学习(建议)
1.1 回溯
利用回溯算法求解八皇后问题
利用回溯算法求解 0-1 背包问题
1.2 分治
利用分治算法求一组数据的逆序对个数
1.3动态规划
0-1 背包问题
最小路径和(详细可看 Minimum Path Sum)
编程实现莱文斯坦最短编辑距离
编程实现查找两个字符串的最长公共子序列
编程实现一个数据序列的最长递增子序列
1.4 对应的 LeetCode 练习题
实战递归:完成Leetcode上的Letter Combinations of a Phone Number(17)及permutations(46)
(保留往期第六天任务)
实战DP:完成0-1背包问题实现(自我实现)及Leetcode上Palindrome Partitioning II(132)
(保留往期第七天任务)
Regular Expression Matching(正则表达式匹配)
英文版:Loading…
中文版:力扣
Minimum Path Sum(最小路径和)
英文版:Loading…
中文版:力扣
Coin Change (零钱兑换)
英文版:Loading…
中文版:力扣
Best Time to Buy and Sell Stock(买卖股票的最佳时机)
英文版:Loading…
中文版:力扣
Maximum Product Subarray(乘积最大子序列)
英文版:Loading…
中文版:力扣
Triangle(三角形最小路径和)
英文版:Loading…
中文版:力扣递归
2. 学习内容
爬楼梯问题(70)
思想:
到f(n)阶楼梯,方法有两种
第i-1阶楼梯经过1步
第i-2阶楼梯经过2步
总的方案数量:
f(n) = f(n-1) + f(n-2) [n = 1时为1,n = 2时为2]
递归的速度比较慢,数组比较简单
class Solution:
def climbStairs(self, n: int) -> int:
count = [1,2]
for i in range(2,n):
count.append(count[i-1]+count[i-2])
return count[n-1]
2.1 回溯
定义
回溯法是一个既带有系统性又带有跳跃性的搜索算法。它在包含问题的所有解的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一结点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树搜索,逐层向其祖先结点回溯;否则 ,进入该子树,继续按深度优先策略搜索。
八皇后问题
将八位皇后放在一张8*8的棋盘上,使得每位皇后都无法吃掉别的皇后,即任意两个皇后都不在同一条横线,竖线和斜线上),问一共有多少种摆法?
def is_rule(queen_tup, new_queen):
"""
:param queen_tup: 棋子队列,用于保存已经放置好的棋子,数值代表相应棋子列号
:param new_queen: 被检测棋子,数值代表列号
:return: True表示符合规则,False表示不符合规则
"""
num = len(queen_tup)
for index, queen in enumerate(queen_tup):
if new_queen == queen: # 判断列号是否相等
return False
if abs(new_queen-queen) == num-index: # 斜线方向的位置
# 判断列号之差绝对值是否与行号之差相等
return False
return True
def arrange_queen(num, queen_tup=list()):
"""
:param num:棋盘的的行数,当然数值也等于棋盘的列数
:param queen_tup: 设置一个空队列,用于保存符合规则的棋子的信息
"""
for new_queen in range(num): # 遍历一行棋子的每一列
if is_rule(queen_tup, new_queen): # 判断是否冲突
if len(queen_tup) == num-1: # 判断是否是最后一行
yield [new_queen] # yield关键字
else:
# 若果不是最后一行,递归函数接着放置棋子
for result in arrange_queen(num, queen_tup+[new_queen]):
yield [new_queen] + result
for i in arrange_queen(8):
print(i)
0-1背包问题
假设我们有n件物品,分别编号为1,2,3…n。其中编号为i的物品价值为vi,重量为wi
假设我们有一个背包,它能够承载的重量是C。往包里装些物品,使包里的物品价值最大化
0-1背包问题:不能只取一个物品的一部分
bestV=0
curW=0
curV=0
bestx=None
def backtrack(i):
global bestV,curW,curV,x,bestx
if i>=n:
if bestV<curV:
bestV=curV
bestx=x[:]
else:
if curW+w[i]<=c:
x[i]=True
curW+=w[i]
curV+=v[i]
backtrack(i+1)
curW-=w[i]
curV-=v[i]
x[i]=False
backtrack(i+1)
if __name__=='__main__':
n=5
c=10
w=[2,2,6,5,4]
v=[6,3,5,4,6]
x=[False for i in range(n)]
backtrack(0)
print(bestV)
print(bestx)
2.2 分治
特点
分:将问题分解为规模更小的子问题
治:将这些规模更小的问题逐个击破
合:将已解决的子问题合并,最终得出母问题的解
应用
归并排序
常用算法:替换法、递归树、主方法
一组数据的逆序对个数
对于一个序列a1,a2,a3…an,若存在i,j,使得i < j 且 ai > aj,则称为一个逆序对
输入:一个list对象
输出:list中逆序对数目
def InversionNum(lst):
# 改写归并排序,在归并排序中,每当R部分元素先于L部分元素插入原列表时,逆序对数要加L剩余元素数
if len(lst) == 1:
return lst,0
else:
n = len(lst) // 2
lst1,count1 = InversionNum(lst[0:n])
lst2,count2 = InversionNum(lst[n:len(lst)])
lst,count = Count(lst1,lst2,0)
return lst,count1+count2+count
def Count(lst1, lst2,count):
i = 0
j = 0
res = []
while i < len(lst1) and j < len(lst2):
if lst1[i] <= lst2[j]:
res.append(lst1[i])
i += 1
else:
res.append(lst2[j])
count += len(lst1)-i
# 当右半部分的元素先于左半部分元素进入有序列表时,逆序对数量增加左半部分剩余的元素数
j += 1
res += lst1[i:]
res += lst2[j:]
return res,count
print(InversionNum([11,10,9,8,7,6,5,4,3,2,1]))
# 输出为:[1,2,3,4,5,6,7,8,9,10,11] 55
2.3 动态规划
0-1背包问题
import numpy as np
def solve(vlist,wlist,totalWeight,totalLength):
resArr = np.zeros((totalLength+1,totalWeight+1),dtype=np.int32)
for i in range(1,totalLength+1):
for j in range(1,totalWeight+1):
if wlist[i] <= j:
resArr[i,j] = max(resArr[i-1,j-wlist[i]]+vlist[i],resArr[i-1,j])
else:
resArr[i,j] = resArr[i-1,j]
return resArr[-1,-1]
if __name__ == '__main__':
v = [0,60,100,120]
w = [0,10,20,30]
weight = 50
n = 3
result = solve(v,w,weight,n)
print(result)
最小路径和
class Solution:
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
if grid == None :
return 0
if len(grid)==0 or len(grid[0])==0:
return sum(grid)
row = len(grid)
loc = len(grid[0])
dp = grid
for i in range(1,row):
dp[i][0] = dp[i-1][0] + grid[i][0]
for i in range(1,loc):
dp[0][i] = dp[0][i-1] + grid[0][i]
for i in range(1,row):
for j in range(1,loc):
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
return dp[i][j]
莱文斯坦最短编辑距离
查找两个字符串的最长公共子序列
import numpy
def find_lcseque(s1, s2):
# 生成字符串长度加1的0矩阵,m用来保存对应位置匹配的结果
m = [ [ 0 for x in range(len(s2)+1) ] for y in range(len(s1)+1) ]
# d用来记录转移方向
d = [ [ None for x in range(len(s2)+1) ] for y in range(len(s1)+1) ]
for p1 in range(len(s1)):
for p2 in range(len(s2)):
if s1[p1] == s2[p2]: #字符匹配成功,则该位置的值为左上方的值加1
m[p1+1][p2+1] = m[p1][p2]+1
d[p1+1][p2+1] = 'ok'
elif m[p1+1][p2] > m[p1][p2+1]: #左值大于上值,则该位置的值为左值,并标记回溯时的方向
m[p1+1][p2+1] = m[p1+1][p2]
d[p1+1][p2+1] = 'left'
else: #上值大于左值,则该位置的值为上值,并标记方向up
m[p1+1][p2+1] = m[p1][p2+1]
d[p1+1][p2+1] = 'up'
(p1, p2) = (len(s1), len(s2))
print numpy.array(d)
s = []
while m[p1][p2]: #不为None时
c = d[p1][p2]
if c == 'ok': #匹配成功,插入该字符,并向左上角找下一个
s.append(s1[p1-1])
p1-=1
p2-=1
if c =='left': #根据标记,向左找下一个
p2 -= 1
if c == 'up': #根据标记,向上找下一个
p1 -= 1
s.reverse()
return ''.join(s)
print find_lcseque('abdfg','abcdfg'
一个数据序列的最长递增子序列
'''
最长连续递增子序列
dp[i]以nums[i]结尾的最长递增子序列的长度
if nums[i] > nums[j], 说明nums[i]能缀到nums[j]后面,
那么dp[j]就能+1了
dp[i+1] = max(dp[i + 1], dp[j] + 1)
'''
def length_of_lis(nums):
len_nums = len(nums)
if len_nums == 0:
return 0
dp = [1] * len_nums
for i in range(len_nums - 1):
for j in range(i + 1):
# 如果nums[i+1]能缀在nums[j]后面的话,就dp[j]+1
if nums[i + 1] > nums[j]:
# 缀完的结果还要看看是不是比我大
dp[i + 1] = max(dp[i + 1], dp[j] + 1)
return max(dp)
if __name__ == '__main__':
nums = [int(x) for x in input().split()]
res = length_of_lis(nums)
print(res)
2.4 对应的Leetcode练习题
正则表达式的匹配(10)
class Solution(object):
def isMatch(self, s, p):
"""
:type s: str
:type p: str
:rtype: bool
"""
# 判断匹配规则是否为空
if p == "":
# p为空的时候,判断s是否为空,则知道返回True 或 False
return s == ""
# 判断匹配规则是否只有一个
if len(p) == 1:
# 判断匹配字符串长度是否为1,和两者的第一个元素是否相同,或匹配规则使用.
return len(s) == 1 and (s[0] == p[0] or p[0] == '.')
# 匹配规则的第二个字符串不为*,当匹配字符串不为空的时候
# 返回 两者的第一个元素是否相同,或匹配规则使用. and 递归新的字符串(去掉第一个字符的匹配字符串 和 去掉第一个字符的匹配规则)
if p[1] != "*":
if s == "":
return False
return (s[0] == p[0] or p[0] == '.') and self.isMatch(s[1:], p[1:])
# 当匹配字符串不为空 and (两者的第一个元素是否相同 or 匹配规则使用.)
while s and (s[0] == p[0] or p[0] == '.'):
# 到了while循环,说明p[1]为*,所以递归调用匹配s和p[2:](*号之后的匹配规则)
# 用于跳出函数,当s循环到和*不匹配的时候,则开始去匹配p[2:]之后的规则
if self.isMatch(s, p[2:]):
return True
# 当匹配字符串和匹配规则*都能匹配的时候,去掉第一个字符成为新的匹配字符串,循环
s = s[1:]
# 假如第一个字符和匹配规则不匹配,则去判断之后的是否匹配
return self.isMatch(s, p[2:])
if __name__ == '__main__':
s = Solution()
print(s.isMatch("aa", "a")) # false
print(s.isMatch("aa", "aa")) # true
print(s.isMatch("aaa", "aa")) # false
print(s.isMatch("aa", "a*")) # true
print(s.isMatch("aa", ".*")) # true
print(s.isMatch("ab", ".*")) # true
print(s.isMatch("aab", "c*a*b")) # true
最小路径和(64)
class Solution:
def minPathSum(self, grid):
"""
:type grid: List[List[int]]
:rtype: int
"""
if grid == None :
return 0
if len(grid)==0 or len(grid[0])==0:
return sum(grid)
row = len(grid)
loc = len(grid[0])
dp = grid
for i in range(1,row):
dp[i][0] = dp[i-1][0] + grid[i][0]
for i in range(1,loc):
dp[0][i] = dp[0][i-1] + grid[0][i]
for i in range(1,row):
for j in range(1,loc):
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
return dp[i][j]
零钱兑换(322)
def coinChange(self, coins, amount):
"""
:type coins: List[int]
:type amount: int
:rtype: int
"""
dp=[-1]*(amount+1)
dp[0]=0
for i in range(1,amount+1):
for j in range(0,len(coins)):
if i>=coins[j] and dp[i-coins[j]]!=-1:
if dp[i]==-1 or dp[i]>dp[i-coins[j]]+1:
dp[i]=dp[i-coins[j]]+1
return dp[amount]
买卖股票的最佳时期(121)
class Solution:
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
if not prices:#为空时返回0
return 0
maxProfit=0#最大值至少为0
minPurchase=prices[0]#初始化最小购买值
for price in prices:
maxProfit=max(maxProfit,price-minPurchase)
# 最大利润为当前值与最小购买值之差和maxProfit的比较
minPurchase=min(price,minPurchase)
# 最小购买值为当前值与minPurchase之间的比较
return maxProfit
乘积最大子序列(152)
class Solution(object):
def maxProduct(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
maxvalue = minvalue = nums[0]
globalmax = nums[0]
for i in range(1, len(nums)):
lastmax = maxvalue
maxvalue = max(minvalue * nums[i], lastmax * nums[i], nums[i])
minvalue = min(minvalue * nums[i], lastmax * nums[i], nums[i])
globalmax = max(globalmax, maxvalue)
return globalmax
三角形最小路径和(120)
class Solution(object):
def minimumTotal(self, triangle):
"""
:type triangle: List[List[int]]
:rtype: int
"""
size = len(triangle)
if size == 0:
return 0
ans = [triangle[0][0]]
i = 1
while i < size:
tmp = []
for j in range(len(triangle[i]) - 1):
if j == 0:
tmp.append(ans[0] + triangle[i][j])
else:
tmp.append(min(ans[j],ans[j-1])+triangle[i][j])
tmp.append(ans[-1] + triangle[i][-1])
ans = tmp[:]
i += 1
minans = ans[0]
for i in ans:
if i < minans:
minans = i
return minans