From fa2f28659e7b403a7eed0336a8ff6d394f693a01 Mon Sep 17 00:00:00 2001 From: SunnyQjm Date: Sat, 6 Jun 2020 22:26:01 +0800 Subject: [PATCH] add: chapter6 --- chapter6/1_merge-intervals.py | 55 +++++++++++ chapter6/2_sort-list.py | 91 +++++++++++++++++++ chapter6/3_h-index.py | 40 ++++++++ .../4_best-time-to-buy-and-sell-stock-ii.py | 56 ++++++++++++ chapter6/5_is-subsequence.py | 53 +++++++++++ chapter6/6_ugly-number.py | 53 +++++++++++ chapter6/7_coin-change.py | 54 +++++++++++ chapter6/8_unique-paths.py | 58 ++++++++++++ chapter6/9_unique-paths-ii.py | 67 ++++++++++++++ 9 files changed, 527 insertions(+) create mode 100644 chapter6/1_merge-intervals.py create mode 100644 chapter6/2_sort-list.py create mode 100644 chapter6/3_h-index.py create mode 100644 chapter6/4_best-time-to-buy-and-sell-stock-ii.py create mode 100644 chapter6/5_is-subsequence.py create mode 100644 chapter6/6_ugly-number.py create mode 100644 chapter6/7_coin-change.py create mode 100644 chapter6/8_unique-paths.py create mode 100644 chapter6/9_unique-paths-ii.py diff --git a/chapter6/1_merge-intervals.py b/chapter6/1_merge-intervals.py new file mode 100644 index 0000000..9f8fd65 --- /dev/null +++ b/chapter6/1_merge-intervals.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# coding=utf-8 + +####################################################################################### +# Leetcode 56 合并区间 +# https://leetcode-cn.com/problems/merge-intervals/ +# +# 给出一个区间的集合,请合并所有重叠的区间。 +# +# 示例 1: +# 输入: [[1,3],[2,6],[8,10],[15,18]] +# 输出: [[1,6],[8,10],[15,18]] +# 解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. +# +# 示例 2: +# 输入: [[1,4],[4,5]] +# 输出: [[1,5]] +# 解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。 +####################################################################################### + +class Solution: + def merge(self, intervals): + """ + :type intervals: List[List[int]] + :rtype List[List[int]] + + (Knowledge) + + 思路: + 1. 首先对输入的区间数组进行排序; + 2. 接着用两个指针,cur指向当前处理的区间,next指向下一个要处理的区间; + 3. 根据cur和next所指区间的情况,分别处理: + - intervals[cur] 和 intervals[next]区间没有重叠,则令intervals[cur + 1] = intervals[next], cur++, next++ + - intervals[cur][1] >= intervals[next][0],则令intervals[cur][1] = max(intervals[cur][1], intervals[next][1]), next++ + 4. 最后返回intervals[:cur + 1] + """ + + # 首先对输入的区间数组进行排序 + intervals = sorted(intervals) + cur, next = 0, 1 + + while next < len(intervals): + if intervals[next][0] > intervals[cur][1]: # 处理没有重叠的情况 + intervals[cur + 1], cur, next = intervals[next], cur + 1, next + 1 + else: # 处理有重叠的情况 + intervals[cur][1], next = max(intervals[cur][1], intervals[next][1]), next + 1 + + return intervals[:cur + 1] + + +if __name__ == '__main__': + solution = Solution() + print(solution.merge([[1, 3], [2, 6], [8, 10], [15, 18]]), "= [[1, 6], [8, 10], [15, 18]]") + print(solution.merge([[1, 4], [4, 5]]), "= [[1, 5]]") + diff --git a/chapter6/2_sort-list.py b/chapter6/2_sort-list.py new file mode 100644 index 0000000..3166acf --- /dev/null +++ b/chapter6/2_sort-list.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +# coding=utf-8 + +####################################################################################### +# Leetcode 148 排序链表 +# +# 在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。 +# +# 示例 1: +# 输入: 4->2->1->3 +# 输出: 1->2->3->4 +# +# 示例 2: +# 输入: -1->5->3->4->0 +# 输出: -1->0->3->4->5 +####################################################################################### + +class ListNode: + def __init__(self, x): + self.val = x + self.next = None + + def __repr__(self): + if self: + return "{}->{}".format(self.val, repr(self.next)) + + +class Solution: + def sortList(self, head): + """ + :type head: ListNode + :rtype ListNode + + (knowledge) + + 思路: + 1. 使用递归方式实现链表的归并排序算法; + 2. 对链表进行分治的时候,可以使用快慢指针(快指针一次走两步,慢指针一次走一步,同时从head出发,当快指针走到链表尾的时候,慢指针就走到中间位置); + """ + + # 链表为空或者只有一个元素,直接返回 + if not head or not head.next: + return head + + # low -> 右子链的头部 + # pre -> 左子链最后一个节点(未断开之前,指向low) + low, fast, pre = head, head, None + while fast and fast.next: + low, fast, pre = low.next, fast.next.next, low + + # 断开左右子链之间的链接 + pre.next = None + + # 对左右子链分别递归的进行归并排序 + head = self.sortList(head) + low = self.sortList(low) + + # 对排好序的两部分进行归并 + if not low: + return head + + result, cur = self, None + while head and low: + if head.val <= low.val: + result.next, head = head, head.next + else: + result.next, low = low, low.next + result = result.next + + if not head: + result.next = low + elif not low: + result.next = head + else: + result.next = None + return self.next + + +if __name__ == '__main__': + solution = Solution() + h1 = ListNode(4) + h1.next = ListNode(2) + h1.next.next = ListNode(1) + h1.next.next.next = ListNode(3) + print(solution.sortList(h1), "= 1->2->3->4") + + + + + + diff --git a/chapter6/3_h-index.py b/chapter6/3_h-index.py new file mode 100644 index 0000000..8e7a554 --- /dev/null +++ b/chapter6/3_h-index.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# coding=utf-8 + +####################################################################################### +# Leetcode 274 H 指数 +# +# 给定一位研究者论文被引用次数的数组(被引用次数是非负整数)。编写一个方法,计算出研究者的 h 指数。 +# h 指数的定义:h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (N 篇论文中)总共有 h 篇论文分别被引用了至少 h 次。(其余的 N - h 篇论文每篇被引用次数 不超过 h 次。) +# 例如:某人的 h 指数是 20,这表示他已发表的论文中,每篇被引用了至少 20 次的论文总共有 20 篇。 +# +# 示例: +# 输入:citations = [3,0,6,1,5] +# 输出:3 +# 解释:给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 3, 0, 6, 1, 5 次。 +# 由于研究者有 3 篇论文每篇 至少 被引用了 3 次,其余两篇论文每篇被引用 不多于 3 次,所以她的 h 指数是 3。 +####################################################################################### + +class Solution: + def hIndex(self, citations): + """ + :type citations: List[int] + :rtype int + + (knowledge) + + 思路: + 1. 对输入的数组进行排序(从大到小排序); + 2. 依次从头开始遍历,直到一个索引为index的元素的值小于小于(index + 1)为止; + 3. 返回index + """ + citations = sorted(citations, reverse=True) + for i in range(len(citations)): + if citations[i] < i + 1: + return i + return len(citations) + + +if __name__ == '__main__': + solution = Solution() + print(solution.hIndex([3, 0, 6, 1, 5]), "= 3") diff --git a/chapter6/4_best-time-to-buy-and-sell-stock-ii.py b/chapter6/4_best-time-to-buy-and-sell-stock-ii.py new file mode 100644 index 0000000..80999b7 --- /dev/null +++ b/chapter6/4_best-time-to-buy-and-sell-stock-ii.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# coding=utf-8 + +####################################################################################### +# Leetcode 122 买卖股票的最佳时机 II +# +# 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 +# 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 +# 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 +# +# 示例 1: +# 输入: [7,1,5,3,6,4] +# 输出: 7 +# 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 +# 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 +# +# 示例 2: +# 输入: [1,2,3,4,5] +# 输出: 4 +# 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 +# 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 +# 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 +# +# 示例 3: +# 输入: [7,6,4,3,1] +# 输出: 0 +# 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 +####################################################################################### + +class Solution: + def maxProfit(self, prices): + """ + :type prices: List[int] + :rtype int + + (knowledge) + + 思路: + 1. 因为不限制购买次数,所以这边可以使用贪心策略: + - 如果第二天比当天高,则当天买入,第二天卖出; + - 如果第二天比当天低,则不操作; + """ + proft = 0 + for i in range(len(prices) - 1): + if prices[i + 1] > prices[i]: + proft += (prices[i + 1] - prices[i]) + return proft + + +if __name__ == '__main__': + solution = Solution() + print(solution.maxProfit([7, 1, 5, 3, 6, 4]), "= 7") + print(solution.maxProfit([1, 2, 3, 4, 5]), "= 4") + print(solution.maxProfit([7, 6, 4, 3, 1]), "= 0") + + diff --git a/chapter6/5_is-subsequence.py b/chapter6/5_is-subsequence.py new file mode 100644 index 0000000..244d040 --- /dev/null +++ b/chapter6/5_is-subsequence.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# coding=utf-8 + +####################################################################################### +# Leetcode 392 判断子序列 +# +# 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 +# 你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。 +# 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 +# +# 示例 1: +# s = "abc", t = "ahbgdc" +# 返回 true. +# +# 示例 2: +# s = "axc", t = "ahbgdc" +# 返回 false. +# +# 后续挑战 : +# 如果有大量输入的 S,称作S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码? +# +####################################################################################### + + +class Solution: + def isSubsequence(self, s, t): + """ + :type s: str + :type t: str + :rtype bool + + (knowledge) + + 思路: + 1. 直接用两个指针,sPtr指向短串s,tPtr指向长串t; + 2. 依次按需再长串中找到子串中的每一个字符,都找到返回true,否则返回false + """ + if len(s) > len(t): + return False + + sPtr, tPtr, lenS, lenT = 0, 0, len(s), len(t) + while sPtr < lenS and tPtr < lenT: + if s[sPtr] == t[tPtr]: + sPtr += 1 + tPtr += 1 + return sPtr == len(s) + + +if __name__ == '__main__': + solution = Solution() + print(solution.isSubsequence("abc", "ahbgdc"), "= True") + print(solution.isSubsequence("acb", "ahbgdc"), "= False") + print(solution.isSubsequence("axc", "ahbgdc"), "= False") diff --git a/chapter6/6_ugly-number.py b/chapter6/6_ugly-number.py new file mode 100644 index 0000000..d0f5c36 --- /dev/null +++ b/chapter6/6_ugly-number.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# coding=utf-8 + +####################################################################################### +# Leetcode 263 丑数 +# +# 编写一个程序判断给定的数是否为丑数。 +# 丑数就是只包含质因数 2, 3, 5 的正整数。 +# +# 示例 1: +# 输入: 6 +# 输出: true +# 解释: 6 = 2 × 3 +# +# 示例 2: +# 输入: 8 +# 输出: true +# 解释: 8 = 2 × 2 × 2 +# +# 示例 3: +# 输入: 14 +# 输出: false +# 解释: 14 不是丑数,因为它包含了另外一个质因数 7。 +####################################################################################### + +class Solution: + def isUgly(self, num): + """ + :type num: int + :rtype bool + + (knowledge) + + 思路: + 1. 依次判断能否用2, 3, 5整除,可以则将num处理对应质因子; + 2. 判断最后结果是否为1 + """ + if num == 0: + return False + while not num % 5: + num /= 5 + while not num % 3: + num /= 3 + while not num % 2: + num /= 2 + return num == 1 + + +if __name__ == '__main__': + solution = Solution() + print(solution.isUgly(6), "= True") + print(solution.isUgly(8), "= True") + print(solution.isUgly(14), "= False") diff --git a/chapter6/7_coin-change.py b/chapter6/7_coin-change.py new file mode 100644 index 0000000..f2e50cd --- /dev/null +++ b/chapter6/7_coin-change.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# coding=utf-8 + +####################################################################################### +# Leetcode 322 零钱兑换 +# +# 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 +# +# 示例 1: +# 输入: coins = [1, 2, 5], amount = 11 +# 输出: 3 +# 解释: 11 = 5 + 5 + 1 +# +# 示例 2: +# 输入: coins = [2], amount = 3 +# 输出: -1 +# +# PS: PPT中关于凑零钱的问题和这题Leetcode类似,且本题比PPT上更通用 +####################################################################################### + +class Solution: + def coinChange(self, coins, amount): + """ + :type coins: List[int], + :type amount: int + :rtype int + + (knowledge) + + 思路: + 1. 使用动态规划; + 2. 定义f(n)为状态转移方程,表示凑n元所需的最少硬币数: + -1 n < 0 + 0 n == 0 + min{f(n - c) + 1 | c ∈ coins} + """ + + dp = [amount + 1 for i in range(amount + 1)] + + dp[0] = 0 + + for i in range(len(dp)): + for coin in coins: + if i - coin < 0: + continue + dp[i] = min(dp[i], dp[i - coin] + 1) + return dp[amount] if dp[amount] != amount + 1 else -1 + + +if __name__ == '__main__': + solution = Solution() + print(solution.coinChange([1, 2, 5], 11), "= 3") + print(solution.coinChange([2], 3), "= -1") + diff --git a/chapter6/8_unique-paths.py b/chapter6/8_unique-paths.py new file mode 100644 index 0000000..15439cb --- /dev/null +++ b/chapter6/8_unique-paths.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +# coding=utf-8 + +####################################################################################### +# Leetcode 62 不同路径 +# +# 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 +# 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 +# 问总共有多少条不同的路径? +# +# 示例 1: +# 输入: m = 3, n = 2 +# 输出: 3 +# 解释: +# 从左上角开始,总共有 3 条路径可以到达右下角。 +# 1. 向右 -> 向右 -> 向下 +# 2. 向右 -> 向下 -> 向右 +# 3. 向下 -> 向右 -> 向右 +# +# 示例 2: +# 输入: m = 7, n = 3 +# 输出: 28 +####################################################################################### + +class Solution: + def uniquePaths(self, m, n): + """ + :type m: int + :type n: int + :rtype int + + (knowledge) + + 思路: + 1. 采用动态规划 + 2. dp[i][j] => 第处于第i + 1行第j + 1列的方格,到目的地可走的路径数量 + 3. 状态转移方程: + f(i, j) = f(i + 1, j) + f(i, j + 1) i+1 < m && j+1 < n + f(i + 1, j) i+1 < m && j+1 == n + f(i, j + 1) i+1 == m && j+1 < n + 0 i+1 == m && j+1 == n + """ + dp = [[0 for i in range(n)] for i in range(m)] + dp[m - 1][n - 1] = 1 + + for i in range(m - 1, -1, -1): + for j in range(n - 1, -1, -1): + if i + 1 < m: + dp[i][j] += dp[i + 1][j] + if j + 1 < n: + dp[i][j] += dp[i][j + 1] + return dp[0][0] + + +if __name__ == '__main__': + solution = Solution() + print(solution.uniquePaths(3, 2), "= 3") + print(solution.uniquePaths(7, 3), "= 28") diff --git a/chapter6/9_unique-paths-ii.py b/chapter6/9_unique-paths-ii.py new file mode 100644 index 0000000..b72aab1 --- /dev/null +++ b/chapter6/9_unique-paths-ii.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# coding=utf-8 + +####################################################################################### +# Leetcode 63 不同路径 II +# +# 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 +# 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 +# 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? +# 网格中的障碍物和空位置分别用 1 和 0 来表示。 +# +# 说明:m 和 n 的值均不超过 100。 +# +# 示例 1: +# +# 输入: +# [ +# [0,0,0], +# [0,1,0], +# [0,0,0] +# ] +# 输出: 2 +# 解释: +# 3x3 网格的正中间有一个障碍物。 +# 从左上角到右下角一共有 2 条不同的路径: +# 1. 向右 -> 向右 -> 向下 -> 向下 +# 2. 向下 -> 向下 -> 向右 -> 向右 +####################################################################################### + +class Solution: + def uniquePathsWithObstacles(self, obstacleGrid): + """ + :type obstacleGrid: List[List[int]] + :rtype int + + (knowledge) + + 思路: + 1. 动态规划 + 2. dp[i][j] => 第处于第i + 1行第j + 1列的方格,到目的地可走的路径数量 + 3. + 3. 状态转移方程: + f(i, j) = f(i + 1, j) + f(i, j + 1) i+1 < m && j+1 < n && obstacleGrid[i][j] != 1 + f(i + 1, j) i+1 < m && j+1 == n && obstacleGrid[i][j] != 1 + f(i, j + 1) i+1 == m && j+1 < n && obstacleGrid[i][j] != 1 + 0 (i+1 == m && j+1 == n) || obstacleGrid[i][j] != 1 + """ + m, n = len(obstacleGrid), len(obstacleGrid[0]) + dp = [[0 for i in range(n)] for i in range(m)] + dp[m - 1][n - 1] = 1 + + for i in range(m - 1, -1, -1): + for j in range(n - 1, -1, -1): + if obstacleGrid[i][j]: + dp[i][j] = 0 + continue + if i + 1 < m: + dp[i][j] += dp[i + 1][j] + if j + 1 < n: + dp[i][j] += dp[i][j + 1] + return dp[0][0] + + +if __name__ == '__main__': + solution = Solution() + print(solution.uniquePathsWithObstacles([[0, 0, 0], [0, 1, 0], [0, 0, 0]]), "= 2") +