1
0
mirror of https://github.com/SunnyQjm/algorithm-review.git synced 2026-06-03 08:16:43 +08:00

add: chapter9

This commit is contained in:
2020-06-21 21:29:26 +08:00
parent fb0503025f
commit ff2342f2d6
7 changed files with 515 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env python
# coding=utf-8
#######################################################################################
# Leetcode 72 编辑距离
#
# 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
# 你可以对一个单词进行如下三种操作:
# 1. 插入一个字符
# 2. 删除一个字符
# 3. 替换一个字符
#
# 示例 1
# 输入:word1 = "horse", word2 = "ros"
# 输出:3
# 解释:
# horse -> rorse (将 'h' 替换为 'r')
# rorse -> rose (删除 'r')
# rose -> ros (删除 'e')
#
# 示例 2
# 输入:word1 = "intention", word2 = "execution"
# 输出:5
# 解释:
# intention -> inention (删除 't')
# inention -> enention (将 'i' 替换为 'e')
# enention -> exention (将 'n' 替换为 'x')
# exention -> exection (将 'n' 替换为 'c')
# exection -> execution (插入 'u')
#######################################################################################
class Solution:
def minDistance(self, word1, word2):
"""
:type word1: str
:type word2: str
:rtype int
(knowledge)
思路:
其实总共有四种操作,插入、删除、替换和什么都不操作
1. 使用动态规划;
2. 定义状态:dp[i][j] => word1前i个字符构成的子串与word2前j个字符构成的子串的编辑距离;
3. base case => dp[i][0] = i, dp[0][j] = j => 表示其中一个子串为空的情形
4. 状态转移方程:
f(i, j) = 0 i == 0 || j == 0
f(i - 1, j - 1) i > 0 && j > 0 && word1[i] == word2[j]
min { i > 0 && j > 0 && word1[i] != word2[j]
f(i - 1, j) + 1,
f(i, j - 1) + 1,
f(i - 1, j - 1) + 1
}
tip: 可以参考 => https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/bian-ji-ju-li
"""
m, n = len(word1), len(word2)
dp = [[0] * (n + 1) for i in range(m + 1)]
for i in range(1, m + 1):
dp[i][0] = i
for j in range(1, n + 1):
dp[0][j] = j
for i in range(1, m + 1):
for j in range(1, n + 1):
if word1[i - 1] == word2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(
dp[i - 1][j] + 1,
dp[i][j - 1] + 1,
dp[i - 1][j - 1] + 1
)
return dp[-1][-1]
if __name__ == '__main__':
solution = Solution()
print(solution.minDistance("horse", "ros"), "= 3")
print(solution.minDistance("intention", "execution"), "= 5")
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env python
# coding=utf-8
#######################################################################################
# Leetcode 312 戳气球
#
# 有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
# 现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i
# 后,气球 left 和气球 right 就变成了相邻的气球。
#
# 求所能获得硬币的最大数量。
#
# 说明:
# - 你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
# - 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
#
# 示例:
# 输入: [3,1,5,8]
# 输出: 167
# 解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
# coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
#######################################################################################
class Solution:
def maxCoins(self, nums):
"""
:type nums: List[int]
:rtype int
(knowledge)
思路:
1. 根据题意,我们在数组的左右各加一个元素,代表不能被戳破的假气球,其值为1,此时气球的编号为0~n+1(首尾两个气球不能戳破,1~n号气球对应题目中的编号0~n-1);
2. 使用动态规划;
3. 定义状态:dp[i][j] => 戳破编号i和编号j(开区间,不包括编号为i和编号为j的气球)之间的所有气球所能获得硬币的最大数量;
4. base case => dp[i][j] = 0 (i <= j <= i + 1), 由于状态定义的时候是开区间,所以i <= j <= i + 1时,dp[i][j]表示没有气球可以被戳破,所以为0;
5. 状态转移方程:
f(i, j) = 0 i <= j <= i + 1
min{f(i, k) + f(k, j) + nums[i] * nums[k] * nums[j] | i < k < j} j > i + 1
如何确定遍历顺序呢?
tip:参考 => https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/za-qi-qiu
"""
length = len(nums)
# 在前后各添加一个不能戳破的假气球,其值为1
nums.insert(0, 1)
nums.append(1)
# 初始化dp数组
dp = [[0] * (length + 2) for i in range(length + 2)]
# 从下往上,从左往右遍历
for i in range(length, -1, -1):
for j in range(i + 2, length + 2):
for k in range(i + 1, j):
dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + nums[i] * nums[k] * nums[j])
return dp[0][-1]
if __name__ == '__main__':
solution = Solution()
print(solution.maxCoins([3, 1, 5, 8]), "= 167")
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python
# coding=utf-8
#######################################################################################
# Leetcode 729 我的日程安排表 I
#
# 实现一个 MyCalendar 类来存放你的日程安排。如果要添加的时间内没有其他安排,则可以存储这个新的日程安排。
# MyCalendar 有一个 book(int start, int end)方法。它意味着在 start 到 end 时间内增加一个日程安排,注意,这里的时间是半开区间,即 [start, end), 实数 x 的范围为, start <= x < end。
# 当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生重复预订。
# 每次调用 MyCalendar.book方法时,如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true。否则,返回 false 并且不要将该日程安排添加到日历中。
# 请按照以下步骤调用 MyCalendar 类: MyCalendar cal = new MyCalendar(); MyCalendar.book(start, end)
#
# 示例 1:
# MyCalendar();
# MyCalendar.book(10, 20); // returns true
# MyCalendar.book(15, 25); // returns false
# MyCalendar.book(20, 30); // returns true
# 解释:
# 第一个日程安排可以添加到日历中. 第二个日程安排不能添加到日历中,因为时间 15 已经被第一个日程安排预定了。
# 第三个日程安排可以添加到日历中,因为第一个日程安排并不包含时间 20 。
#
# 说明:
# - 每个测试用例,调用 MyCalendar.book 函数最多不超过 100次。
# - 调用函数 MyCalendar.book(start, end)时, start 和 end 的取值范围为 [0, 10^9]。
#######################################################################################
# Your MyCalendar object will be instantiated and called as such:
# obj = MyCalendar()
# param_1 = obj.book(start,end)
class MyCalendar:
def __init__(self):
# 定义一个数组,用于记录行程
self.calendar = []
def book(self, start, end):
"""
:type start: int
:type end: int
:rtype bool
(knowledge)
思路:
1. 用一个数组记录行程安排,其中每个元素为一个二元组,表示左闭又开的行程安排;
2. 每次插入之前判断当前行程是否与既定行程重叠,重叠则返回False;
3. 如果不重叠,则在适当位置插入行程,保证整体有序;
"""
# 处理行程安排为空的情况
if len(self.calendar) == 0:
self.calendar.append((start, end))
return True
# 处理当前要插入的行程,比所有之前插入的行程安排都要早,且不重叠的情况
if self.calendar[0][0] >= end:
self.calendar.insert(0, (start, end))
return True
# 处理当前要插入的行程,比所有之前插入的行程安排都要迟,且不重叠的情况
if self.calendar[-1][1] <= start:
self.calendar.append((start, end))
return True
for i in range(1, len(self.calendar)):
# 找到合适的位置,插入新的行程
if self.calendar[i][0] >= end:
if self.calendar[i - 1][1] <= start:
self.calendar.insert(i, (start, end))
return True
else:
return False
return False
if __name__ == '__main__':
obj = MyCalendar()
print(obj.book(10, 20), "= True")
print(obj.book(15, 25), "= False")
print(obj.book(20, 30), "= True")
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env python
# coding=utf-8
#######################################################################################
# Leetcode 100 相同的树
#
# 给定两个二叉树,编写一个函数来检验它们是否相同。
# 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
#
# 示例 1:
# 输入: 1 1
# / \ / \
# 2 3 2 3
#
# [1,2,3], [1,2,3]
#
# 输出: true
#
# 示例 2:
# 输入: 1 1
# / \
# 2 2
#
# [1,2], [1,null,2]
#
# 输出: false
#
# 示例 3:
# 输入: 1 1
# / \ / \
# 2 1 1 2
#
# [1,2,1], [1,1,2]
#
# 输出: false
#######################################################################################
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def isSameTree(self, p, q):
"""
:type p: TreeNode
:type q: TreeNode
:rtype bool
(knowledge)
思路:
1. 判断当前两个根节点的值是否相等,不相等则返回false
2. 递归判断对应的左子树和右子树是否相等
"""
if not p or not q:
if not q and not p:
return True
else:
return False
if p.val != q.val:
return False
return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
if __name__ == '__main__':
solution = Solution()
p = TreeNode(1)
p.left = TreeNode(2)
p.right = TreeNode(3)
q = TreeNode(1)
q.left = TreeNode(2)
q.right = TreeNode(3)
print(solution.isSameTree(p, q), "= True")
p = TreeNode(1)
p.left = TreeNode(2)
q = TreeNode(1)
q.right = TreeNode(2)
print(solution.isSameTree(p, q), "= False")
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env python
# coding=utf-8
#######################################################################################
# Leetcode 112 路径总和
#
# 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
#
# 说明: 叶子节点是指没有子节点的节点。
#
# 示例:
# 给定如下二叉树,以及目标和 sum = 22,
#
# 5
# / \
# 4 8
# / / \
# 11 13 4
# / \ \
# 7 2 1
# 返回 true, 因为存在目标和为 22 的根节点到叶子节点的路径 5->4->11->2。
#######################################################################################
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def hasPathSum(self, root, sum):
"""
:type root: TreeNode
:type sum: int
:rtype bool
(knowledge)
思路:
1. 递归进行先序遍历;
2. 如果遍历到某个叶子节点,发现路径总和为目标值,则返回True
"""
if not root:
return False
# 找到叶子节点进行判断
if not root.left and not root.right and root.val == sum:
return True
return self.hasPathSum(root.left, sum - root.val) or self.hasPathSum(root.right, sum - root.val)
if __name__ == '__main__':
solution = Solution()
root = TreeNode(5)
root.left = TreeNode(4)
root.left.left = TreeNode(11)
root.left.left.left = TreeNode(7)
root.left.left.right = TreeNode(2)
root.right = TreeNode(8)
root.right.left = TreeNode(13)
root.right.right = TreeNode(4)
root.right.right.right = TreeNode(1)
print(solution.hasPathSum(root, 22), "= True")
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env python
# coding=utf-8
#######################################################################################
# Leetcode 226 翻转二叉树
#
# 翻转一棵二叉树。
#
# 示例:
# 输入:
# 4
# / \
# 2 7
# / \ / \
# 1 3 6 9
#
# 输出:
# 4
# / \
# 7 2
# / \ / \
# 9 6 3 1
#
# 备注:
# 这个问题是受到 Max Howell 的 原问题 启发的 :
#
# 谷歌:我们90%的工程师使用您编写的软件(Homebrew),但是您却无法在面试时在白板上写出翻转二叉树这道题,这太糟糕了。
#######################################################################################
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
def __repr__(self):
if self:
return "{}->{}->{}".format(self.val, repr(self.left), repr(self.right))
class Solution:
def invertTree(self, root):
"""
:type root: TreeNode
:rtype TreeNode
(knowledege)
思路:
1. 每次交换当前节点的两个子树;
2. 然后递归对左右子树进行翻转
"""
if not root:
return None
root.left, root.right = root.right, root.left
self.invertTree(root.left)
self.invertTree(root.right)
return root
if __name__ == '__main__':
solution = Solution()
root = TreeNode(4)
root.left = TreeNode(2)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
root.right = TreeNode(7)
root.right.left = TreeNode(6)
root.right.right = TreeNode(9)
print(solution.invertTree(root))
@@ -0,0 +1,56 @@
#!/usr/bin/env python
# coding=utf-8
#######################################################################################
# Leetcode 144 二叉树的前序遍历
#
# 给定一个二叉树,返回它的前序遍历。
#
# 示例:
# 输入: [1,null,2,3]
# 1
# \
# 2
# /
# 3
#
# 输出: [1,2,3]
#
# 进阶: 递归算法很简单,你可以通过迭代算法完成吗?
#######################################################################################
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
def __repr__(self):
if self:
return "{}->{}->{}".format(self.val, repr(self.left), repr(self.right))
class Solution:
def preorderTraversal(self, root):
"""
:type root: TreeNode
:rtype List[int]
(knowledge)
思路:
1. 使用一个列表记录遍历过的值;
2. 每次先将当前节点的值加入结果集,然后递归遍历左右子树;
"""
def _preorderTraversal(root, result):
if not root:
return result
result.append(root.val)
_preorderTraversal(root.left, result)
_preorderTraversal(root.right, result)
return result
result = []
return _preorderTraversal(root, result)