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:
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user