具体算法思想参见
team-learning-program/LeetCodeClassification/2.动态规划.md
下面是对上面链接中的各个leetcode题给出的代码测试版本(注意函数括号中的一些内容被删掉了,因为不需要)。另外,子序列和子串是不一样的概念,子序列可以是原字符串中不连续的字符组成的字符串,只要每个字符出现的顺序和原字符串一致,而子串必须是由原字符串中的连续字符组成的串串。
# Leetcode 300.最长上升子序列
def lengthOfLIS(nums):
if not nums: return 0 # 判断边界条件
dp = [1] * len(nums) # 初始化dp数组状态
for i in range(len(nums)):
for j in range(i):
if nums[i] > nums[j]: # 根据题目所求得到状态转移方程
dp[i] = max(dp[i], dp[j] + 1)
return max(dp) # 确定输出状态
# Leetcode 674.最长连续递增序列
def findLengthOfLCIS(nums):
if not nums: return 0 # 判断边界条件
dp = [1] * len(nums) # 初始化dp数组状态
# 注意需要得到前一个数,所以从1开始遍历,否则会超出范围
for i in range(1, len(nums)):
if nums[i] > nums[i - 1]: # 根据题目所求得到状态转移方程
dp[i] = dp[i - 1] + 1
else:
dp[i] = 1
return max(dp) # 确定输出状态
# Leetcode5. 最长回文子串
def longestPalindrome(s):
length = len(s)
if length < 2: # 判断边界条件
return s
dp = [[False for _ in range(length)] for _ in range(length)] # 定义dp状态矩阵
max_len = 1
start = 0 # 后续记录回文串初试位置
for j in range(1, length):
for i in range(j):
# 矩阵中逐个遍历
if s[i] == s[j]:
if j - i < 3:
dp[i][j] = True
else:
dp[i][j] = dp[i + 1][j - 1]
if dp[i][j]: # 记录位置,返回有效答案
cur_len = j - i + 1
if cur_len > max_len:
max_len = cur_len
start = i
return s[start:start + max_len]
# Leetcode516. 最长回文子序列
def longestPalindromeSubseq(s):
n = len(s)
dp = [[0] * n for _ in range(n)] # 定义动态规划状态转移矩阵
for i in range(n): # 初始化对角线,单个字符子序列就是1
dp[i][i] = 1
for i in range(n, -1, -1): # 从右下角开始往上遍历
for j in range(i + 1, n):
if s[i] == s[j]: # 当两个字符相等时,直接子字符串加2
dp[i][j] = dp[i + 1][j - 1] + 2
else: # 不相等时,取某边最长的字符
dp[i][j] = max(dp[i][j - 1], dp[i + 1][j])
return dp[0][-1] # 返回右上角位置的状态就是最长
# Leetcode72. 编辑距离
def minDistance(word1, word2):
# m,n 表示两个字符串的长度
m = len(word1)
n = len(word2)
# 构建二维数组来存储子问题
dp = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
# 考虑边界条件,第一行和第一列的条件
for i in range(n + 1):
dp[0][i] = i # 对于第一行,每次操作都是前一次操作基础上增加一个单位的操作
for j in range(m + 1):
dp[j][0] = j # 对于第一列也一样,所以应该是1,2,3,4,5...
for i in range(1, m + 1): # 对其他情况进行填充
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]: # 当最后一个字符相等的时候,就不会产生任何操作代价,所以与dp[i-1][j-1]一样
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1 # 分别对应删除,添加和替换操作
return dp[-1][-1] # 返回最终状态就是所求最小的编辑距离
# Leetcode198. 打家劫舍
def rob(nums):
if not nums: # 特殊情况处理
return 0
if len(nums) == 1:
return nums[0]
n = len(nums)
dp = [0] * n # 初始化状态转移数组
dp[0] = nums[0] # 第一个边界值处理
dp[1] = max(nums[0], nums[1]) # 第二个边界值处理
for i in range(2, n):
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) # 状态转移方程
return dp[-1]
# Leetcode213. 打家劫舍 II
def robC(nums):
if not nums:
return 0
elif len(nums) <= 2:
return max(nums)
def helper(nums):
if len(nums) <= 2:
return max(nums)
dp = [0] * len(nums)
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, len(nums)):
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
return dp[-1]
return max(helper(nums[1:]), helper(nums[:-1]))
if __name__ == "__main__":
# Leetcode 300.最长上升子序列
nums = [10, 9, 2, 5, 3, 7, 101, 18]
maxLeng = lengthOfLIS(nums)
print("最长上升子序列长度: {0}".format(maxLeng))
# Leetcode 674.最长连续递增序列
nums1 = [1, 3, 5, 4, 7]
maxLenAse = findLengthOfLCIS(nums1)
print("最长连续递增序列长度: {0}".format(maxLenAse))
# Leetcode5. 最长回文子串
strIN = "babad"
cycSub = longestPalindrome(strIN)
print("最长回文子串的一种情况: {0}".format(cycSub))
# Leetcode516. 最长回文子序列
strIn = "bbbab"
cycSubSeqL = longestPalindromeSubseq(strIn)
print("最长回文子序列长度: {0}".format(cycSubSeqL))
# Leetcode72. 编辑距离
word1 = "horse"
word2 = "ros"
minDist = minDistance(word1, word2)
print("将 '{0}' 转换成 '{1}' 所使用的最少操作数: {2}".format(word1, word2, minDist))
# Leetcode198. 打家劫舍
numsR = [1, 2, 3, 1]
robMon = rob(numsR)
print("偷窃到的最高金额: {0}".format(robMon))
# Leetcode213. 打家劫舍 II
numsRC = [2, 3, 2]
robMonC = robC(numsRC)
print("偷窃到的最高金额: {0}".format(robMonC))