From ff2342f2d6ec340f54519f3cc26a57c7c4d1a476 Mon Sep 17 00:00:00 2001 From: SunnyQjm Date: Sun, 21 Jun 2020 21:29:26 +0800 Subject: [PATCH] add: chapter9 --- chapter9/1_edit-distance.py | 83 +++++++++++++++++++ chapter9/2_burst-ballons.py | 66 +++++++++++++++ chapter9/3_my-calendar-i.py | 84 +++++++++++++++++++ chapter9/4_same-tree.py | 86 ++++++++++++++++++++ chapter9/5_path-sum.py | 67 +++++++++++++++ chapter9/6_invert-binary-tree.py | 73 +++++++++++++++++ chapter9/7_binary-tree-preorder-traversal.py | 56 +++++++++++++ 7 files changed, 515 insertions(+) create mode 100644 chapter9/1_edit-distance.py create mode 100644 chapter9/2_burst-ballons.py create mode 100644 chapter9/3_my-calendar-i.py create mode 100644 chapter9/4_same-tree.py create mode 100644 chapter9/5_path-sum.py create mode 100644 chapter9/6_invert-binary-tree.py create mode 100644 chapter9/7_binary-tree-preorder-traversal.py diff --git a/chapter9/1_edit-distance.py b/chapter9/1_edit-distance.py new file mode 100644 index 0000000..ed5d5a4 --- /dev/null +++ b/chapter9/1_edit-distance.py @@ -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") diff --git a/chapter9/2_burst-ballons.py b/chapter9/2_burst-ballons.py new file mode 100644 index 0000000..def6545 --- /dev/null +++ b/chapter9/2_burst-ballons.py @@ -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") diff --git a/chapter9/3_my-calendar-i.py b/chapter9/3_my-calendar-i.py new file mode 100644 index 0000000..29c6ff3 --- /dev/null +++ b/chapter9/3_my-calendar-i.py @@ -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") diff --git a/chapter9/4_same-tree.py b/chapter9/4_same-tree.py new file mode 100644 index 0000000..bfb848b --- /dev/null +++ b/chapter9/4_same-tree.py @@ -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") + diff --git a/chapter9/5_path-sum.py b/chapter9/5_path-sum.py new file mode 100644 index 0000000..8734a18 --- /dev/null +++ b/chapter9/5_path-sum.py @@ -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") + diff --git a/chapter9/6_invert-binary-tree.py b/chapter9/6_invert-binary-tree.py new file mode 100644 index 0000000..710c513 --- /dev/null +++ b/chapter9/6_invert-binary-tree.py @@ -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)) diff --git a/chapter9/7_binary-tree-preorder-traversal.py b/chapter9/7_binary-tree-preorder-traversal.py new file mode 100644 index 0000000..52c23ff --- /dev/null +++ b/chapter9/7_binary-tree-preorder-traversal.py @@ -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)