当前位置:   article > 正文

Python面试——剑指offer&leedcode刷题整理(动态规划)_剑指offer 动态规划 python

剑指offer 动态规划 python

1、变位词

判断两个单词是否是变位词

s1 = 'abcde'
s2 = 'acbde'

test = AnagramDetection()

方法一:

  1. class AnagramDetection:
  2. # 先对两个字符串进行list化
  3. # 对字符串对应的两个list进行排序
  4. # 依次比较字符是否匹配
  5. def anagramSolution1(self, s1, s2):
  6. alist1 = list(s1)
  7. alist2 = list(s2)
  8. alist1.sort()
  9. alist2.sort()
  10. pos = 0
  11. matches = True
  12. while pos < len(s1) and matches:
  13. if alist1[pos] == alist2[pos]:
  14. pos = pos + 1
  15. else:
  16. matches = False
  17. return matches

方法二:

  1. # 首先将两个字符串list化
  2. # 将两个list中的字符生成两个set
  3. # 比较两个set, 如果不相等直接返回false
  4. # 如果两个set相等, 比较每个set中字符在相应list中的个数, 个数不同返回false
  5. def anagramSolution3(self, s1, s2):
  6. alist1 = list(s1)
  7. alist2 = list(s2)
  8. aset1 = set(alist1)
  9. aset2 = set(alist2)
  10. if aset1 != aset2:
  11. return False
  12. else:
  13. for ch in aset1:
  14. if alist1.count(ch) != alist2.count(ch):
  15. return False
  16. return True

把若干单词按照变位词进行分类:

  1. def string_to_sort_list(word):
  2. l1 = list(word)
  3. l1.sort()
  4. return ''.join(l1)
  5. def AnagramDetection(word_list):
  6. temp_dict = dict()
  7. for word in word_list:
  8. new_word = string_to_sort_list(word)
  9. temp_dict.setdefault(new_word, [])
  10. temp_dict[new_word].append(word)
  11. ret = []
  12. for key, value in temp_dict.iteritems():
  13. ret.append(value)
  14. print ret
  15. word_list = ["cars", "thing", "scar", "dog", "god", "arcs", "the"]
  16. AnagramDetection(word_list)
  17. #print [['cars', 'scar', 'arcs'], ['the'], ['thing'], ['dog', 'god']]

2、动态规划

1、硬币最大值问题

  1. # 解决动态规划中的币值最大化问题---在满足所选硬币不相邻的条件下,从一堆硬币中选择最大金额的硬币
  2. # 输入数组C[1..n]保存n个硬币的面值
  3. # 输出可选硬币的最大金额
  4. def coinRow(coinrow):
  5. alist = [0]*(len(coinrow)+1)
  6. alist[1] = coinrow[0]
  7. for i in range(2, len(coinrow)+1):
  8. alist[i] = max(coinrow[i-1]+alist[i-2], alist[i-1])
  9. return alist.pop()
  10. print(coinRow([5, 1, 2, 10, 6, 2]))
  1. private int rob(int[] nums, int first, int last) {
  2. int pre2 = 0, pre1 = 0;
  3. for (int i = first; i <= last; i++) {
  4. int cur = Math.max(pre1, pre2 + nums[i]);
  5. pre2 = pre1;
  6. pre1 = cur;
  7. }
  8. return pre1;
  9. }

2、0-1背包问题:

参照博文:https://blog.csdn.net/wulitaotao96/article/details/95097133

  1. w= [0 , 2 , 3 , 4 , 5 ] #商品的体积2、3、4、5
  2. v = [0 , 3 , 4 , 5 , 6 ] #商品的价值3、4、5、6
  3. bagV = 8; #背包大小
  4. dp = [[0 for i in range(9) ]for i in range(5)] #动态规划表
  5. item = [0]*5 #最优解情况
  6. def findMax(): #动态规划
  7. for i in range (5):
  8. for j in range(bagV+1):
  9. if j < w[i]:
  10. dp[i][j] = dp[i - 1][j];
  11. else:
  12. dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
  13. def findWhat(i,j): #最优解情况
  14. if i>=0:
  15. if dp[i][j] == dp[i-1][j]:
  16. item[i] = 0
  17. findWhat(i-1,j)
  18. elif j - w[i] >= 0 and dp[i][j] == dp[i - 1][j - w[i]] + v[i]:
  19. item[i] = 1
  20. findWhat(i-1,j-w[i])
  21. def print1():
  22. for i in range(5):
  23. res = []
  24. for j in range(9):
  25. res.append(dp[i][j])
  26. print(res)
  27. print(item)
  28. findMax()
  29. findWhat(4,8)
  30. print1()

 3、斐波那契数列

 爬楼梯

70. Climbing Stairs (Easy)

Leetcode / 力扣

题目描述:有 N 阶楼梯,每次可以上一阶或者两阶,求有多少种上楼梯的方法。

定义一个数组 dp 存储上楼梯的方法数(为了方便讨论,数组下标从 1 开始),dp[i] 表示走到第 i 个楼梯的方法数目。

第 i 个楼梯可以从第 i-1 和 i-2 个楼梯再走一步到达,走到第 i 个楼梯的方法数为走到第 i-1 和第 i-2 个楼梯的方法数之和。

考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2],使得原来的 O(N) 空间复杂度优化为 O(1) 复杂度。

  1. public int climbStairs(int n) {
  2. if (n <= 2) {
  3. return n;
  4. }
  5. int pre2 = 1, pre1 = 2;
  6. for (int i = 2; i < n; i++) {
  7. int cur = pre1 + pre2;
  8. pre2 = pre1;
  9. pre1 = cur;
  10. }
  11. return pre1;
  12. }

4、矩阵的最小路径和

参照:https://blog.csdn.net/FiveStarsGeneral/article/details/90941097

(1)leetcode 64题

题目描述:

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

    输入:
    [
      [1,3,1],
      [1,5,1],
      [4,2,1]
    ]
    输出: 7
    解释: 因为路径 1→3→1→1→1 的总和最小。

Solution:

又涉及到最小路径,或是求最优解的题目,基本上就套动态规划...就要考虑状态转移方程:

设最短路径为dp[][]这道题目的状态转移方程存在这么几种情况:

    如果i=j=0,即当前位置为左上角:
    dp[i][j] = grid[0][0]
    2.如果i=0,j!=0,即当前位置在第一行:
    dp[i][j] = grid[i][j] + dp[i][j-1]
    因为上一个位置只能是左面的位置
    3.如果j=0,i!=0,即当前位置在第一列:
    dp[i][j] = grid[i][j] + dp[i-1][j]
    因为上一个位置只能是上面的位置
    4.其他情况,即在除第一行第一列其他位置:
    dp[i][j] = grid[i][j] + min(dp[i][j-1],dp[i-1][j])
    这就要看左面位置和上面位置哪个数小

CODE:

  1. v = np.array([[1, 3, 5, 9], [8, 1, 3, 4], [5, 0, 6, 1], [8, 8, 4, 0]])
  2. class Solution(object):
  3. def minPathSum(self, grid):
  4. """
  5. :type grid: List[List[int]]
  6. :rtype: int
  7. :m 行数
  8. :n 列数
  9. """
  10. m,n = len(grid),len(grid[0])
  11. dp = [[0 for i in range(n)] for j in range(m)]
  12. for i in range(m):
  13. for j in range(n):
  14. if i == 0 and j == 0:
  15. dp[0][0] = grid[0][0]
  16. elif i == 0:
  17. dp[i][j] = grid[i][j] + dp[i][j - 1]
  18. elif j == 0:
  19. dp[i][j] = grid[i][j] + dp[i - 1][j]
  20. else:
  21. dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1])
  22. return dp[m-1][n-1]
  23. mt = Solution()
  24. print(mt.minPathSum(v))

当然也可以不使用额外的数组存储路径值,直接用grid自身就可以。用一个dp二维数组可以更清晰一些
(2)leetcode 62题

题目描述:统计从矩阵左上角到右下角的路径总数,每次只能向右或者向下移动。

  1. class Solution(object):
  2. def uniquePaths(self, m, n):
  3. """
  4. :type m: int
  5. :type n: int
  6. :rtype: int
  7. """
  8. res=[[1 for i in range(n)] for i in range(m)]
  9. #m与n的位置别弄反了,m是行,n是列,第一行,第一列初始值都赋值为1
  10. for i in range(1,m):
  11. for j in range(1,n):
  12. res[i][j]=res[i-1][j]+res[i][j-1]
  13. return res[m-1][n-1]

(3)编辑距离
链接:https://www.zhihu.com/question/23995189/answer/1094101149

这次给的这道题比上面的难一些,在 leetcdoe 的定位是 hard 级别。好像是 leetcode 的第 72 号题。

问题描述

给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符 删除一个字符 替换一个字符

  1. 示例:
  2. 输入: word1 = "horse", word2 = "ros"
  3. 输出: 3
  4. 解释:
  5. horse -> rorse (将 'h' 替换为 'r')
  6. rorse -> rose (删除 'r')
  7. rose -> ros (删除 'e')

解答

还是老样子,按照上面三个步骤来,并且我这里可以告诉你,90% 的字符串问题都可以用动态规划解决,并且90%是采用二维数组。

步骤一、定义数组元素的含义

由于我们的目的求将 word1 转换成 word2 所使用的最少操作数 。那我们就定义 dp[i] [j]的含义为:当字符串 word1 的长度为 i,字符串 word2 的长度为 j 时,将 word1 转化为 word2 所使用的最少操作次数为 dp[i] [j]

有时候,数组的含义并不容易找,所以还是那句话,我给你们一个套路,剩下的还得看你们去领悟。

步骤二:找出关系数组元素间的关系式

接下来我们就要找 dp[i] [j] 元素之间的关系了,比起其他题,这道题相对比较难找一点,但是,不管多难找,大部分情况下,dp[i] [j] 和 dp[i-1] [j]、dp[i] [j-1]、dp[i-1] [j-1] 肯定存在某种关系。因为我们的目标就是,**从规模小的,通过一些操作,推导出规模大的。对于这道题,我们可以对 word1 进行三种操作

插入一个字符 删除一个字符 替换一个字符

由于我们是要让操作的次数最小,所以我们要寻找最佳操作。那么有如下关系式:

一、如果我们 word1[i] 与 word2 [j] 相等,这个时候不需要进行任何操作,显然有 dp[i] [j] = dp[i-1] [j-1]。(别忘了 dp[i] [j] 的含义哈)。

二、如果我们 word1[i] 与 word2 [j] 不相等,这个时候我们就必须进行调整,而调整的操作有 3 种,我们要选择一种。三种操作对应的关系试如下(注意字符串与字符的区别):

(1)、如果把字符 word1[i] 替换成与 word2[j] 相等,则有 dp[i] [j] = dp[i-1] [j-1] + 1;

(2)、如果在字符串 word1末尾插入一个与 word2[j] 相等的字符,则有 dp[i] [j] = dp[i] [j-1] + 1;

(3)、如果把字符 word1[i] 删除,则有 dp[i] [j] = dp[i-1] [j] + 1;

那么我们应该选择一种操作,使得 dp[i] [j] 的值最小,显然有

dp[i] [j] = min(dp[i-1] [j-1],dp[i] [j-1],dp[[i-1] [j]]) + 1;

于是,我们的关系式就推出来了,

步骤三、找出初始值

显然,当 dp[i] [j] 中,如果 i 或者 j 有一个为 0,那么还能使用关系式吗?答是不能的,因为这个时候把 i - 1 或者 j - 1,就变成负数了,数组就会出问题了,所以我们的初始值是计算出所有的 dp[0] [0….n] 和所有的 dp[0….m] [0]。这个还是非常容易计算的,因为当有一个字符串的长度为 0 时,转化为另外一个字符串,那就只能一直进行插入或者删除操作了。

代码如下

  1. public int minDistance(String word1, String word2) {
  2. int n1 = word1.length();
  3. int n2 = word2.length();
  4. int[][] dp = new int[n1 + 1][n2 + 1];
  5. // dp[0][0...n2]的初始值
  6. for (int j = 1; j <= n2; j++)
  7. dp[0][j] = dp[0][j - 1] + 1;
  8. // dp[0...n1][0] 的初始值
  9. for (int i = 1; i <= n1; i++) dp[i][0] = dp[i - 1][0] + 1;
  10. // 通过公式推出 dp[n1][n2]
  11. for (int i = 1; i <= n1; i++) {
  12. for (int j = 1; j <= n2; j++) {
  13. // 如果 word1[i] 与 word2[j] 相等。第 i 个字符对应下标是 i-1
  14. if (word1.charAt(i - 1) == word2.charAt(j - 1)){
  15. p[i][j] = dp[i - 1][j - 1];
  16. }else {
  17. dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;
  18. }
  19. }
  20. }
  21. return dp[n1][n2];
  22. }

5、数组区间

(1)leetcode 303题

给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。

示例:

给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()

sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3

说明:

你可以假设数组不可变。
会多次调用 sumRange 方法。

Solution:(由于当array很长时,使用切片的方法求和会导致时间超时。所以可以新建一个array,它的每个元素代表对应前n个元素的和,例如nums_dp[1]表示数组nums前1个元素的和。对于给定区间[2, 5]对应的就是前6个元素的和减去前2个元素的和)

  1. class NumArray(object):
  2. def __init__(self, nums):
  3. """
  4. :type nums: List[int]
  5. """
  6. self.nums_dp = [0] * (len(nums)+1)
  7. for i in range(1, len(nums)+1):
  8. self.nums_dp[i] = self.nums_dp[i-1] + nums[i-1]
  9. def sumRange(self, i, j):
  10. """
  11. :type i: int
  12. :type j: int
  13. :rtype: int
  14. """
  15. return self.nums_dp[j+1] - self.nums_dp[i]
  16. solution = NumArray([-2,0,3,-5,2,-1])
  17. print(solution.sumRange(0, 2), solution.sumRange(2, 5), solution.sumRange(0, 5))
  18. #输出 1 -1 -3

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/一键难忘520/article/detail/1005097
推荐阅读
相关标签
  

闽ICP备14008679号