mirror of
https://github.com/SunnyQjm/algorithm-review.git
synced 2026-06-05 15:39:29 +08:00
add: chapter7
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
#######################################################################################
|
||||||
|
# Leetcode 5 最长回文子串
|
||||||
|
#
|
||||||
|
# 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
|
||||||
|
#
|
||||||
|
# 示例 1:
|
||||||
|
# 输入: "babad"
|
||||||
|
# 输出: "bab"
|
||||||
|
# 注意: "aba" 也是一个有效答案。
|
||||||
|
#
|
||||||
|
# 示例 2:
|
||||||
|
# 输入: "cbbd"
|
||||||
|
# 输出: "bb"
|
||||||
|
#######################################################################################
|
||||||
|
|
||||||
|
class Solution:
|
||||||
|
def longestPalindrome(self, s):
|
||||||
|
"""
|
||||||
|
:type s: str
|
||||||
|
:rtype str
|
||||||
|
|
||||||
|
(knowledge)
|
||||||
|
|
||||||
|
思路:
|
||||||
|
1. 使用动态规划
|
||||||
|
2. dp[i][j] => s[i:j] 子串是否是一个回文字符串
|
||||||
|
3. 状态转移方程
|
||||||
|
f(i, j) = True i == j
|
||||||
|
s[i] == s[j] i + 1 == j
|
||||||
|
f(i + 1, j - 1) && s[i] == s[j] i + 1 < j
|
||||||
|
"""
|
||||||
|
dp, resultStart, resultEnd = [[True if i == j else False for j in range(len(s))] for i in range(len(s))], 0, 1
|
||||||
|
|
||||||
|
for j in range(0, len(s) - 1):
|
||||||
|
dp[j][j + 1] = s[j] == s[j + 1]
|
||||||
|
if dp[j][j + 1]:
|
||||||
|
resultStart, resultEnd = j, j + 2
|
||||||
|
|
||||||
|
for i in range(2, len(s)):
|
||||||
|
for j in range(0, len(s) - i):
|
||||||
|
if dp[j + 1][j + i - 1] and s[j] == s[j + i]:
|
||||||
|
find, resultStart, resultEnd, dp[j][j + i] = True, j, j + i + 1, True
|
||||||
|
return s[resultStart: resultEnd]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
solution = Solution()
|
||||||
|
print(solution.longestPalindrome("abcba"), "= abcba")
|
||||||
|
print(solution.longestPalindrome("babad"), "= aba")
|
||||||
|
print(solution.longestPalindrome("cbbd"), "= bb")
|
||||||
|
print(solution.longestPalindrome("ccc"), "= ccc")
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
#######################################################################################
|
||||||
|
# Leetcode 64 最小路径和
|
||||||
|
#
|
||||||
|
# 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
|
||||||
|
# 说明:每次只能向下或者向右移动一步。
|
||||||
|
#
|
||||||
|
# 示例:
|
||||||
|
# 输入:
|
||||||
|
# [
|
||||||
|
# [1,3,1],
|
||||||
|
# [1,5,1],
|
||||||
|
# [4,2,1]
|
||||||
|
# ]
|
||||||
|
# 输出: 7
|
||||||
|
# 解释: 因为路径 1->3->1->1->1 的总和最小。
|
||||||
|
#######################################################################################
|
||||||
|
|
||||||
|
class Solution:
|
||||||
|
def minPathSum(self, grid):
|
||||||
|
"""
|
||||||
|
:type grid: List[List[int]]
|
||||||
|
:rtype int
|
||||||
|
|
||||||
|
(knowledge)
|
||||||
|
|
||||||
|
思路:
|
||||||
|
1. 采用动态规划;
|
||||||
|
2. dp[i][j] => 表示第i + 1行第j + 1列的网格到右下角网格的最短路径开销(本题可以直接复用grid作为dp数组)
|
||||||
|
3. 状态转移方程:
|
||||||
|
f(i, j) = grid[i][j] i = m-1 && j = n - 1
|
||||||
|
grid[i][j] + f(i + 1, j) i < m - 1 && j = n - 1
|
||||||
|
grid[i][j] + f(i, j + 1) i = m - 1 && j < n - 1
|
||||||
|
grid[i][j] + min{f(i + 1, j), f(i, j + 1)} i < m - 1 && j < n - 1
|
||||||
|
"""
|
||||||
|
m, n = len(grid), len(grid[0])
|
||||||
|
|
||||||
|
# 先处理最右侧一排
|
||||||
|
for i in range(m - 2, -1, -1):
|
||||||
|
grid[i][n - 1] += grid[i + 1][n - 1]
|
||||||
|
|
||||||
|
# 先处理最底下一行
|
||||||
|
for j in range(n - 2, -1, -1):
|
||||||
|
grid[m - 1][j] += grid[m - 1][j + 1]
|
||||||
|
|
||||||
|
|
||||||
|
for i in range(m - 2, -1, -1):
|
||||||
|
for j in range(n - 2, -1, -1):
|
||||||
|
grid[i][j] += min(grid[i + 1][j], grid[i][j + 1])
|
||||||
|
|
||||||
|
return grid[0][0]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
solution = Solution()
|
||||||
|
print(solution.minPathSum([[1, 3, 1], [1, 5, 1], [4, 2, 1]]), "= 7")
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
#######################################################################################
|
||||||
|
# Leetcode 120 三角形最小路径和
|
||||||
|
#
|
||||||
|
# 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
|
||||||
|
# 相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。
|
||||||
|
#
|
||||||
|
# 例如,给定三角形:
|
||||||
|
# [
|
||||||
|
# [2],
|
||||||
|
# [3,4],
|
||||||
|
# [6,5,7],
|
||||||
|
# [4,1,8,3]
|
||||||
|
# ]
|
||||||
|
# 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。
|
||||||
|
#
|
||||||
|
# 说明:
|
||||||
|
# 如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。
|
||||||
|
#######################################################################################
|
||||||
|
|
||||||
|
class Solution:
|
||||||
|
def minimumTotal(self, triangle):
|
||||||
|
"""
|
||||||
|
:type triangle: List[List[int]]
|
||||||
|
:rtype int
|
||||||
|
|
||||||
|
(knowledge)
|
||||||
|
|
||||||
|
思路:
|
||||||
|
1. 采用动态规划;
|
||||||
|
2. dp[i][j] => 表示triangle[i][j](i,j从0开始)到达底部所需的最小路径和(这边可以复用triangle作为dp数组)
|
||||||
|
3. 状态转移方程:
|
||||||
|
f(i, j) = triangle[i][j] j = n - 1
|
||||||
|
triangle[i][j] + min{f(i, j + 1), f(i + 1, j + 1)} j < n - 1
|
||||||
|
"""
|
||||||
|
for i in range(len(triangle) - 2, -1, -1):
|
||||||
|
for j in range(len(triangle[i])):
|
||||||
|
triangle[i][j] += min(triangle[i + 1][j], triangle[i + 1][j + 1])
|
||||||
|
return triangle[0][0]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
solution = Solution()
|
||||||
|
print(solution.minimumTotal([[2], [3, 4], [6, 5, 7], [4, 1, 8, 3]]), "= 11")
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
#######################################################################################
|
||||||
|
# Leetcode 123 买卖股票的最佳时机 III
|
||||||
|
#
|
||||||
|
# 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
|
||||||
|
# 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
|
||||||
|
# 注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
|
||||||
|
#
|
||||||
|
# 示例 1:
|
||||||
|
# 输入: [3,3,5,0,0,3,1,4]
|
||||||
|
# 输出: 6
|
||||||
|
# 解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
|
||||||
|
# 随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 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)
|
||||||
|
|
||||||
|
思路:
|
||||||
|
prices => size = n
|
||||||
|
1. 首先,反向遍历一遍,算出[i:n-1](i的取值位0~n-1)范围内完成一次交易最多可得多少收益
|
||||||
|
- dp[i] => 第i天到最后一天的范围内,完成一次交易最多可以获得的收益
|
||||||
|
- maxV => 在反向遍历过程中,记录的已遍历部分的最高价格
|
||||||
|
- 状态转移方程:
|
||||||
|
dp[i] = 0 i == n - 1
|
||||||
|
= max(dp[i + 1], max - prices[i]) i < n - 1
|
||||||
|
2. 接着进行正向遍历, 计算[0:i]范围内完成一次交易最多可得多少收益:
|
||||||
|
- min => 记录在正向遍历过程中,已遍历部分的最低价格
|
||||||
|
- last => 记录[0:i-1]范围内完成一次交易最多可得多少收益
|
||||||
|
- 状态转移方程:
|
||||||
|
cur = 0 i == 0
|
||||||
|
max(last, prices[i] - min) i > 0
|
||||||
|
3. 在正向遍历过程中,通过查询第一步的dp表,可以知道两次交易的收益和,记录最大值
|
||||||
|
"""
|
||||||
|
if not prices:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
length = len(prices)
|
||||||
|
|
||||||
|
# 反向遍历初始化(可以只交易一次,第二次不交易,所以dp数组要比length+1,并且最后一个元素值为0,代表不做交易)
|
||||||
|
dp = [0 for i in range(length + 1)]
|
||||||
|
dp[length - 1], maxV = 0, prices[length - 1]
|
||||||
|
|
||||||
|
# 反向遍历
|
||||||
|
for i in range(len(prices) - 1, -1, -1):
|
||||||
|
dp[i] = max(dp[i + 1], maxV - prices[i])
|
||||||
|
if prices[i] > maxV:
|
||||||
|
maxV = prices[i]
|
||||||
|
|
||||||
|
# 正向遍历初始化
|
||||||
|
last, min, result = 0, prices[0], 0
|
||||||
|
for i in range(1, len(prices)):
|
||||||
|
cur = max(last, prices[i] - min)
|
||||||
|
last, result = cur, max(result, cur + dp[i + 1])
|
||||||
|
if prices[i] < min:
|
||||||
|
min = prices[i]
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
solution = Solution()
|
||||||
|
print(solution.maxProfit([3, 3, 5, 0, 0, 3, 1, 4]), "= 6")
|
||||||
|
print(solution.maxProfit([1, 2, 3, 4, 5]), "= 4")
|
||||||
|
print(solution.maxProfit([7, 6, 4, 3, 1]), "= 0")
|
||||||
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
#######################################################################################
|
||||||
|
# Leetcode 300 最长上升子序列
|
||||||
|
#
|
||||||
|
# 给定一个无序的整数数组,找到其中最长上升子序列的长度。
|
||||||
|
#
|
||||||
|
# 示例:
|
||||||
|
# 输入: [10,9,2,5,3,7,101,18]
|
||||||
|
# 输出: 4
|
||||||
|
# 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
|
||||||
|
#
|
||||||
|
# 说明:
|
||||||
|
# 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
|
||||||
|
# 你算法的时间复杂度应该为 O(n2) 。
|
||||||
|
#
|
||||||
|
# 进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
|
||||||
|
#######################################################################################
|
||||||
|
|
||||||
|
class Solution:
|
||||||
|
def lengthOfLIS(self, nums):
|
||||||
|
"""
|
||||||
|
:type nums: List[int]
|
||||||
|
:rtype int
|
||||||
|
|
||||||
|
(knowledge)
|
||||||
|
|
||||||
|
思路:
|
||||||
|
nums => size = n
|
||||||
|
1. 采用动态规划的思想;
|
||||||
|
2. dp[i] => 表示[i:n-1]范围内的最长的上升子序列的长度;
|
||||||
|
3. 状态转移方程:
|
||||||
|
f(i) = 1 i == n - 1
|
||||||
|
1 + max{f(j) | nums[j] > nums[i] && i < j < n} i < n - 1
|
||||||
|
"""
|
||||||
|
if not nums:
|
||||||
|
return 0
|
||||||
|
length = len(nums)
|
||||||
|
dp, result = [1 for i in range(length)], 1
|
||||||
|
|
||||||
|
for i in range(length - 2, -1, -1):
|
||||||
|
for j in range(i + 1, length):
|
||||||
|
if nums[j] > nums[i]:
|
||||||
|
dp[i] = max(dp[i], 1 + dp[j])
|
||||||
|
return max(dp)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
solution = Solution()
|
||||||
|
print(solution.lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18]), "= 4")
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
#######################################################################################
|
||||||
|
# Leetcode 53 最大子序和
|
||||||
|
#
|
||||||
|
# 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
|
||||||
|
#
|
||||||
|
# 示例:
|
||||||
|
# 输入: [-2,1,-3,4,-1,2,1,-5,4],
|
||||||
|
# 输出: 6
|
||||||
|
# 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
|
||||||
|
#
|
||||||
|
# 进阶:
|
||||||
|
# 如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
|
||||||
|
#######################################################################################
|
||||||
|
|
||||||
|
class Solution:
|
||||||
|
def maxSubArray(self, nums):
|
||||||
|
"""
|
||||||
|
:type nums: List[int]
|
||||||
|
:rtype int
|
||||||
|
|
||||||
|
(knowledge)
|
||||||
|
|
||||||
|
思路:
|
||||||
|
1. 使用动态规划;
|
||||||
|
2. 定义状态: dp[i] => 表示包含[0, i]区间内包含nums[i]的连续子数组的最大和
|
||||||
|
3. 状态转移方程:
|
||||||
|
f(i) = nums[0] i == 0
|
||||||
|
max{f(i - 1) + nums[i], nums[i]} i > 0
|
||||||
|
"""
|
||||||
|
for i in range(1, len(nums)):
|
||||||
|
nums[i] = max(nums[i-1] + nums[i], nums[i])
|
||||||
|
return max(nums)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
solution = Solution()
|
||||||
|
print(solution.maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4]), "= 6")
|
||||||
|
|
||||||
Reference in New Issue
Block a user