python数据结构实现(七):回溯、分治和动态规划及相关LeetCode题

    xiaoxiao2025-09-10  62

    python数据结构实现(七)

    1 回溯1.1 python利用回溯算法求解八皇后问题1.2 python利用回溯算法求解 0-1 背包问题 2 分治2.1 python利用分治算法求一组数据的逆序对个数 3 动态规划3.1 python利用动态规划求解0-1 背包问题3.2 python实现求解最小路径和3.3 python实现莱文斯坦最短编辑距离3.4 python实现查找两个字符串的最长公共子序列3.5 python实现一个数据序列的最长递增子序列 4 LeetCode相关习题4.1 Letter Combinations of a Phone Number(17)(电话号码的字母组合)4.2 permutations(46)4.3 Coin Change (零钱兑换)4.4

    1 回溯

    1.1 python利用回溯算法求解八皇后问题

    ''' 利用回溯算法求解八皇后问题 ''' class FindQueen: def __init__(self): self.total = 0 # 八皇后解的个数 self.table = [[0 for i in range(8)] for i in range(8)] # 8 x 8 的棋盘 def findQueen(self, row): if row > 7: # 八皇后问题有解了(已经排到了第9行) self.total += 1 self.printQueen() return for coloumn in range(8): if self.check(row, coloumn): self.table[row][coloumn] = 1 self.findQueen(row+1) self.table[row][coloumn] = 0 # 每趟递归退出后都需将这趟递归每行置1的位置清零 def check(self, row, col): for n in range(8): # 检查行列两个方向的有效性 if self.table[n][col] or self.table[row][n]: return False # 检查左对角线 lf = [self.table[row+i][col+j] for i,j in zip(range(-7,8),range(7,-8,-1)) \ if 0 <= row+i < 8 and 0 <= col+j < 8 and self.table[row+i][col+j]==1] if len(lf): return False # 检查右对角线 rt = [self.table[row+i][col+j] for i,j in zip(range(-7,8),range(-7,8)) \ if 0 <= row+i < 8 and 0 <= col+j < 8 and self.table[row+i][col+j]==1] if len(rt): return False return True def printQueen(self): for val in self.table: print(val) print('\n') solutions = FindQueen() solutions.findQueen(0) print(solutions.total)

    1.2 python利用回溯算法求解 0-1 背包问题

    def bag01(N, V, C, W): ''' :param N:N件物品 :param V:Value数组,对应每件物品的价值 :param C:Capacity,指背包的最大容量 :param W:Weight数组,对应每件物品的重量 ''' bestResult = [0] * N; curResult = [0] * N curCost = 0; curValue = 0; bestValue = 0 def backtracking(depth): nonlocal curCost,curValue,bestValue if depth > N-1: if curValue > bestValue: bestValue = curValue bestResult[:] = curResult[:] print(bestResult) print(bestValue) else: for i in [0, 1]: # 取或不取这件物品 curResult[depth] = i if i == 0: # 不取这件物品 backtracking(depth+1) else: if curCost + W[depth] <= C: curCost += W[depth] curValue += V[depth] backtracking(depth+1) # 往上回溯,恢复现场 curCost -= W[depth] curValue -= V[depth] backtracking(0) return bestResult, bestValue bag01(4,[1500,3000,2000,1000],4,[1,4,3,0.5]) ================================================= ([1, 0, 1, 0], 3500)

    2 分治

    2.1 python利用分治算法求一组数据的逆序对个数

    ''' 利用归并排序的思想,在归并排序的过程中找到各个逆序对输出即可 ''' count = 0 def mergeSort(array1, low, high): array2 = [None] * 100 if low == high: return else: mid = (low + high) // 2 mergeSort(array1, low, mid) mergeSort(array1, mid+1, high) merge(array1, array2, low, mid, high) array1[low:high+1] = array2[low:high+1] # 恢复原数组 def merge(a1, a2, low, mid, high): i = low; j = mid+1; k = i; global count while i <= mid and j <= high: if a1[i] <= a1[j]: a2[k] = a1[i] # 保护原数组 i += 1; k += 1 else: count += mid-i+1 for _ in range(i,mid+1): print(a1[_],a1[j]) a2[k] = a1[j];k+=1;j+=1 while i <= mid: a2[k] = a1[i] k += 1; i += 1 while j <= high: a2[k] = a1[j] k += 1; j += 1 r =[23,13,35,6,19,50,28,38,26,17,45] mergeSort(r, 0, 10) print(count)

    3 动态规划

    3.1 python利用动态规划求解0-1 背包问题

    问题描述:有n件物品,每件物品重量为w[i],价值为c[i],每种物品只有一件 用dp[i][v]表示前i件物品(1<=i<=n,0<=v<=V)恰好装入容量为v的背包中所能获得的最大价值 则对第i件物品的选择策略有两种: 1:不放入第i件物品,则问题转化为前i-1件物品恰好装入容量为v的背包中所能获得的最大价值,即dp[i-1][v] 2:放入第i件物品,则问题转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值,即dp[i-1][v-w[i]]+c[i] 则可得状态转移方程:

    dp[i][v] = max{dp[i-1][v],dp[i-1][v-w[i]]+c[i]}

    我们可以通过边界dp[0][v]=0开始将整个dp数组递推出来,dp数组中最大的值即对应最优解

    时间复杂度没法优化,这里优化空间复杂度:

    通过状态转移方程,我们可以看出,对于二维数组dp,每次计算dp[i]行都要用的数据在dp[i-1]行准确来说,在计算dp[i][v]时,我们要用的数据为dp[i-1][v]和dp[i-1][v]左侧的数据dp[i-1][v-w[i]];在计算dp[i+1]行的数据时,dp[i-1]行的数据就不需要了

    由此可以想到,我们可以通过构建这样一个滚动一维数组dp[]:

    对于dp[v],它在转移前代表之前的dp[i-1][v],它的左侧是之前dp[i-1][v]左侧的数据,它的右侧是之前dp[i][v]右边的数据(供dp[i+1][v]计算使用),即这个一维数组在计算转移后的dp[v]时是通过转移前的dp[v]和dp[v]前面的数据计算出来的,dp[v]后的结果又继续提供给下一件物品继续重复这样的使用

    于是,状态转移方程改为:

    dp[v] = max{dp[v],dp[v-w[i]]+c[i]} def bag01DP(N, C, W, V): ''' 动态规划求解01背包问题 :param N: 物品的件数 :param C: 物品的价值数组 :param W: 物品的重量数组 :param V: 背包的容量 ''' dp = [0] * (V+1) # 0 到 V for index in range(N): # 每行都是要将那个index对应的物品放入包中再进行计算 for v in range(V, W[index]-1, -1): ''' 这里v表示容量从W[index]到V的背包,每次减1,这是由物品重量的粒度决定的 若物品重量不全是整数,则需要调整减少的粒度大小 如物品重量有为5.5的,则每次减少的则为0.5 ''' dp[v] = max(dp[v], dp[v-W[index]]+ C[index]) return max(dp) bag01DP(5,[4,5,2,1,3],[3,5,1,2,2],8) ================ 10

    3.2 python实现求解最小路径和

    def minPathSum(grid): sum = 0; height = len(grid); width = len(grid[0]) dp = [[0 for x in range(width)] for y in range(height)] # 注意不能使用 [[0] * width] * height,此为浅复制,不同坐标指向了同一块内存 dp[0][0] = grid[0][0] for col in range(1,width): # 初始化第一行 dp[0][col] = dp[0][col-1] + grid[0][col] for row in range(1,height): # 初始化第一列 dp[row][0] = dp[row-1][0] + grid[row][0] for i in range(1,height): for j in range(1,width): dp[i][j] = min(dp[i-1][j]+grid[i][j],dp[i][j-1]+grid[i][j]) return dp[height-1][width-1]

    3.3 python实现莱文斯坦最短编辑距离

    import numpy as np def LevenshteinDistance(str1, str2): ''' str1与str2的莱文斯坦最短编辑距离 ''' len1 = len(str1); len2 = len(str2) ''' 创建一个dp数组存储状态转移方程的结果 dp[i][j]中i对应str1,j对应str2 考虑到还有空串的情况,对应位置在i==0或j==0的位置,故数组下标为0 到 len1,len2 ''' dp = np.zeros((len1+1,len2+1)) # 当str1或str2为空串时,最小编辑距离即为非空串的长度,以此初始化第一行和第一列 for col in range(1,len2+1): # 初始化第一行 dp[0][col] = col for row in range(1,len1+1): # 初始化第一列 dp[row][0] = row for i in range(1,len1+1): for j in range(len2+1): k = 0 if str1[i-1] == str2[j-1] else 1 # 注意字符串中的下标比数组的下标要少1才是对应的字符位置 dp[i][j] = min(dp[i][j-1] + 1, dp[i-1][j] + 1, dp[i-1][j-1] + k) return dp[-1][-1] LevenshteinDistance('kitten','sitting') ====================== 3.0

    3.4 python实现查找两个字符串的最长公共子序列

    ''' 最长公共子序列的状态转移方程为:(和最短编辑距离类似,只和左方,上方,左上方的元素有关) ①当str1[i] == str2[j]时:dp[i][j] = dp[i-1][j-1] + 1 ②当str1[i] != str2[j]时:dp[i][j] = max(dp[i][j-1], dp[i-1][j]) ''' def LCS(str1, str2): # 寻找str1和str2的最长公共子序列 len1 = len(str1); len2 = len(str2) dp = [[0 for x in range(len2)] for y in range(len1)] # 开辟一个二维数组dp,初始全为0 dp[0][0] = 1 if str1[0] == str2[0] else 0 for col in range(1,len2): dp[0][col] = 1 if str1[0] == str2[col] else dp[0][col-1] # 初始化第一行 for row in range(1,len1): dp[row][0] = 1 if str1[row] == str2[0] else dp[row-1][0] # 初始化第一列 for i in range(1,len1): for j in range(1,len2): dp[i][j] = max(dp[i][j-1], dp[i-1][j]) if str1[i] != str2[j] else dp[i-1][j-1] + 1 return dp[-1][-1] LCS('sitting','kittenkkg') ======================== 5

    3.5 python实现一个数据序列的最长递增子序列

    def LIS(lst): length = len(lst) dp = [0 for _ in range(length)] dp[0] = 1; maxdp = 1 for i in range(1, length): for j in range(i): if lst[j] < lst[i] and dp[j] >= dp[i]: dp[i] = dp[j] + 1 # dp数组最大值若大于1,则它一定是通过上一条语句计算出来的 maxdp = dp[i] return maxdp LIS([1,3,5,2,4,6,7,8,0]) ===================== 6

    4 LeetCode相关习题

    4.1 Letter Combinations of a Phone Number(17)(电话号码的字母组合)

    执行用时 : 40 ms, 在Letter Combinations of a Phone Number的Python3提交中击败了99.19% 的用户 内存消耗 : 13 MB, 在Letter Combinations of a Phone Number的Python3提交中击败了95.74% 的用户

    class Solution: def letterCombinations(self, digits: str) -> List[str]: if digits: len1 = len(digits) num2letter = {'2':'abc','3':'def','4':'ghi','5':'jkl','6':'mno','7':'pqrs','8':'tuv','9':'wxyz'} set1 = num2letter[digits[0]] result = [] def backtrack(level, str1, set1): '''if level == len1-1: result.append(str1) return ''' for letter in set1: str1 += letter if level < len1-1: backtrack(level+1, str1, num2letter[digits[level+1]]) if len(str1) == len1: result.append(str1) str1 = str1[:-1] return result return backtrack(0,'',num2letter[digits[0]]) return []

    4.2 permutations(46)

    class Solution: def permute(self, nums: List[int]) -> List[List[int]]: result = [] def perm(nums, end): if end == 0: result.append(nums[:]) else: for i in range(end+1): nums[i], nums[end] = nums[end], nums[i] perm(nums, end-1) nums[i], nums[end] = nums[end], nums[i] return result return perm(nums, len(nums)-1)

    4.3 Coin Change (零钱兑换)

    本质为完全背包问题,dp[i]表示金额为i时所需的最少硬币数

    class Solution: def coinChange(self, coins: List[int], amount: int) -> int: if amount < 0:return -1 dp = [float('inf')] * (amount+1) dp[0] = 0 for i in range(1,amount+1): for coin in coins: if coin <= i: dp[i] = min(dp[i], dp[i-coin]+1) # 状态转移方程 return dp[amount] if dp[amount] != float('inf') else -1

    4.4

    minnum记录第i天前的最小价格(在那天买入),maxnum记录在第i天卖出时所能获得的最大利益

    class Solution: def maxProfit(self, prices: List[int]) -> int: if prices: len1 = len(prices) if len1 == 1:return 0 minnum = prices[0]; maxnum = -float('inf') for i in range(1, len1): minnum = min(minnum, prices[i]) maxnum = max(maxnum, prices[i]-minnum) return maxnum return 0
    最新回复(0)