Datawhale编程学习之算法思想(7)

  • Post author:
  • Post category:其他

任务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

3. 参考文献

https://shimo.im/docs/z1IkZzi9Koc0Cadk/read


版权声明:本文为dzysunshine原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。