diff --git a/Assets/Origins/Categories-List.md b/Assets/Origins/Categories-List.md index c48edb07..de875a9a 100644 --- a/Assets/Origins/Categories-List.md +++ b/Assets/Origins/Categories-List.md @@ -307,7 +307,7 @@ ### [区间 DP 题目](../../Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md) -###### 0678. 有效的括号字符串、1995. 统计特殊四元组、0375. 猜数字大小 II、0516. 最长回文子序列、0730. 统计不同回文子序列、1039. 多边形三角剖分的最低得分、0664. 奇怪的打印机、0877. 石子游戏、2104. 子数组范围和、0312. 戳气球 +###### 0486. 预测赢家、0312. 戳气球、0877. 石子游戏、1000. 合并石头的最低成本、1547. 切棍子的最小成本、0664. 奇怪的打印机、1039. 多边形三角剖分的最低得分、0546. 移除盒子、1995. 统计特殊四元组、0375. 猜数字大小 II、0678. 有效的括号字符串、0005. 最长回文子串、0516. 最长回文子序列、0730. 统计不同回文子序列、2104. 子数组范围和 ### [树形 DP 题目](../../Contents/10.Dynamic-Programming/06.Tree-DP/02.Tree-DP-List.md) diff --git a/Assets/Origins/README-Head.md b/Assets/Origins/README-Head.md index 292e829c..3bc04776 100644 --- a/Assets/Origins/README-Head.md +++ b/Assets/Origins/README-Head.md @@ -26,7 +26,7 @@ 我是一名 iOS / macOS 的开发程序员,另外也是北航软院的一名非全硕士(在读)。曾在大学期间学习过算法知识,并参加过 3 年的 ACM 比赛, 但水平有限,未能取得理想成绩。但是这 3 年的 ACM 经历,给我最大的收获是锻炼了自己的逻辑思维和解决实际问题的能力,这种能力为我今后的工作、学习打下了坚实的基础。 -我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 700+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 +我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 750+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 在公众号 **「程序员充电站」** 里回复 "**算法打卡**",拉你进 LeetCode 算法打卡计划群一起组队打卡。 diff --git a/Assets/Origins/Root-Index-Head.md b/Assets/Origins/Root-Index-Head.md index 4c30ca08..71f4dc4f 100644 --- a/Assets/Origins/Root-Index-Head.md +++ b/Assets/Origins/Root-Index-Head.md @@ -69,7 +69,7 @@ 我是一名 iOS / macOS 的开发程序员,另外也是北航软院的一名非全硕士(在读)。曾在大学期间学习过算法知识,并参加过 3 年的 ACM 比赛, 但水平有限,未能取得理想成绩。但是这 3 年的 ACM 经历,给我最大的收获是锻炼了自己的逻辑思维和解决实际问题的能力,这种能力为我今后的工作、学习打下了坚实的基础。 -我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 700+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 +我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 750+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 ## 版权说明 diff --git a/Contents/00.Introduction/04.Solutions-List.md b/Contents/00.Introduction/04.Solutions-List.md index fc7724ea..6292cd81 100644 --- a/Contents/00.Introduction/04.Solutions-List.md +++ b/Contents/00.Introduction/04.Solutions-List.md @@ -1,4 +1,4 @@ -# LeetCode 题解(已完成 757 道) +# LeetCode 题解(已完成 759 道) | 题号 | 标题 | 题解 | 标签 | 难度 | | :------ | :------ | :------ | :------ | :------ | @@ -220,6 +220,7 @@ | 0304 | [二维区域和检索 - 矩阵不可变](https://leetcode.cn/problems/range-sum-query-2d-immutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0304.%20%E4%BA%8C%E7%BB%B4%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E7%9F%A9%E9%98%B5%E4%B8%8D%E5%8F%AF%E5%8F%98.md) | 设计、数组、矩阵、前缀和 | 中等 | | 0307 | [区域和检索 - 数组可修改](https://leetcode.cn/problems/range-sum-query-mutable/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0307.%20%E5%8C%BA%E5%9F%9F%E5%92%8C%E6%A3%80%E7%B4%A2%20-%20%E6%95%B0%E7%BB%84%E5%8F%AF%E4%BF%AE%E6%94%B9.md) | 设计、树状数组、线段树、数组 | 中等 | | 0309 | [最佳买卖股票时机含冷冻期](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0309.%20%E6%9C%80%E4%BD%B3%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E6%97%B6%E6%9C%BA%E5%90%AB%E5%86%B7%E5%86%BB%E6%9C%9F.md) | 数组、动态规划 | 中等 | +| 0312 | [戳气球](https://leetcode.cn/problems/burst-balloons/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0312.%20%E6%88%B3%E6%B0%94%E7%90%83.md) | 数组、动态规划 | 困难 | | 0315 | [计算右侧小于当前元素的个数](https://leetcode.cn/problems/count-of-smaller-numbers-after-self/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0315.%20%E8%AE%A1%E7%AE%97%E5%8F%B3%E4%BE%A7%E5%B0%8F%E4%BA%8E%E5%BD%93%E5%89%8D%E5%85%83%E7%B4%A0%E7%9A%84%E4%B8%AA%E6%95%B0.md) | 树状数组、线段树、数组、二分查找、分治、有序集合、归并排序 | 困难 | | 0316 | [去除重复字母](https://leetcode.cn/problems/remove-duplicate-letters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0316.%20%E5%8E%BB%E9%99%A4%E9%87%8D%E5%A4%8D%E5%AD%97%E6%AF%8D.md) | 栈、贪心、字符串、单调栈 | 中等 | | 0318 | [最大单词长度乘积](https://leetcode.cn/problems/maximum-product-of-word-lengths/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0318.%20%E6%9C%80%E5%A4%A7%E5%8D%95%E8%AF%8D%E9%95%BF%E5%BA%A6%E4%B9%98%E7%A7%AF.md) | 位运算、数组、字符串 | 中等 | @@ -531,6 +532,7 @@ | 1493 | [删掉一个元素以后全为 1 的最长子数组](https://leetcode.cn/problems/longest-subarray-of-1s-after-deleting-one-element/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1493.%20%E5%88%A0%E6%8E%89%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%E4%BB%A5%E5%90%8E%E5%85%A8%E4%B8%BA%201%20%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、动态规划、滑动窗口 | 中等 | | 1502 | [判断能否形成等差数列](https://leetcode.cn/problems/can-make-arithmetic-progression-from-sequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1502.%20%E5%88%A4%E6%96%AD%E8%83%BD%E5%90%A6%E5%BD%A2%E6%88%90%E7%AD%89%E5%B7%AE%E6%95%B0%E5%88%97.md) | 数组、排序 | 简单 | | 1523 | [在区间范围内统计奇数数目](https://leetcode.cn/problems/count-odd-numbers-in-an-interval-range/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1523.%20%E5%9C%A8%E5%8C%BA%E9%97%B4%E8%8C%83%E5%9B%B4%E5%86%85%E7%BB%9F%E8%AE%A1%E5%A5%87%E6%95%B0%E6%95%B0%E7%9B%AE.md) | 数学 | 简单 | +| 1547 | [切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1547.%20%E5%88%87%E6%A3%8D%E5%AD%90%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 数组、动态规划 | 困难 | | 1561 | [你可以获得的最大硬币数目](https://leetcode.cn/problems/maximum-number-of-coins-you-can-get) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1561.%20%E4%BD%A0%E5%8F%AF%E4%BB%A5%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E7%A1%AC%E5%B8%81%E6%95%B0%E7%9B%AE.md) | 贪心、数组、数学、博弈、排序 | 中等 | | 1567 | [乘积为正数的最长子数组长度](https://leetcode.cn/problems/maximum-length-of-subarray-with-positive-product/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1567.%20%E4%B9%98%E7%A7%AF%E4%B8%BA%E6%AD%A3%E6%95%B0%E7%9A%84%E6%9C%80%E9%95%BF%E5%AD%90%E6%95%B0%E7%BB%84%E9%95%BF%E5%BA%A6.md) | 贪心、数组、动态规划 | 中等 | | 1593 | [拆分字符串使唯一子字符串的数目最大](https://leetcode.cn/problems/split-a-string-into-the-max-number-of-unique-substrings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1593.%20%E6%8B%86%E5%88%86%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%BD%BF%E5%94%AF%E4%B8%80%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%95%B0%E7%9B%AE%E6%9C%80%E5%A4%A7.md) | 哈希表、字符串、回溯 | 中等 | diff --git a/Contents/00.Introduction/05.Categories-List.md b/Contents/00.Introduction/05.Categories-List.md index e4e75cdf..f52394af 100644 --- a/Contents/00.Introduction/05.Categories-List.md +++ b/Contents/00.Introduction/05.Categories-List.md @@ -978,16 +978,21 @@ | 题号 | 标题 | 题解 | 标签 | 难度 | | :------ | :------ | :------ | :------ | :------ | -| 0678 | [有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0678.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、贪心、字符串、动态规划 | 中等 | +| 0486 | 预测赢家 | | | | +| 0312 | [戳气球](https://leetcode.cn/problems/burst-balloons/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0312.%20%E6%88%B3%E6%B0%94%E7%90%83.md) | 数组、动态规划 | 困难 | +| 0877 | [石子游戏](https://leetcode.cn/problems/stone-game) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0877.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F.md) | 数组、数学、动态规划、博弈 | 中等 | +| 1000 | 合并石头的最低成本 | | | | +| 1547 | [切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1547.%20%E5%88%87%E6%A3%8D%E5%AD%90%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 数组、动态规划 | 困难 | +| 0664 | 奇怪的打印机 | | | | +| 1039 | 多边形三角剖分的最低得分 | | | | +| 0546 | 移除盒子 | | | | | 1995 | 统计特殊四元组 | | | | | 0375 | 猜数字大小 II | | | | +| 0678 | [有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0678.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、贪心、字符串、动态规划 | 中等 | +| 0005 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0005.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md) | 字符串、动态规划 | 中等 | | 0516 | [最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0516.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | | 0730 | 统计不同回文子序列 | | | | -| 1039 | 多边形三角剖分的最低得分 | | | | -| 0664 | 奇怪的打印机 | | | | -| 0877 | [石子游戏](https://leetcode.cn/problems/stone-game) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0877.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F.md) | 数组、数学、动态规划、博弈 | 中等 | | 2104 | 子数组范围和 | | | | -| 0312 | 戳气球 | | | | ### 树形 DP 题目 diff --git a/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md b/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md index d8d2e478..c867fd12 100644 --- a/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md +++ b/Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md @@ -4,17 +4,49 @@ > **区间动态规划**:线性 DP 的一种,简称为「区间 DP」。以「区间长度」划分阶段,以两个坐标(区间的左、右端点)作为状态的维度。一个状态通常由被它包含且比它更小的区间状态转移而来。 -区间 DP 的主要思想就是现在小区间内得到最优解,再利用小区间的最优解合并,从而得到大区间的最优解,最终得到整个区间的最优解。 +区间 DP 的主要思想就是:先在小区间内得到最优解,再利用小区间的最优解合并,从而得到大区间的最优解,最终得到整个区间的最优解。 -常见的区间 DP 的状态转移方程一般为:$dp[i][j] = max / min \lbrace dp[i][k] + dp[k + 1][j] + cost \rbrace,\quad i < k \le j$。 +根据小区间向大区间转移情况的不同,常见的区间 DP 问题可以分为两种: -1. 其中状态 $dp[i][j]$ 表示为:区间 $[i, j]$ (即下标位置 $i$ 到下标位置 $j$ 上所有元素)上的最大价值。 -2. $cost$ 表示为:将两个区间 $[i, k]$ 与 $[k + 1, j]$ 中所有元素合并起来的代价。 -3. 这里的 $max / min$ 取决于题目是求最大值还是求最小值。 +1. 单个区间从中间向两侧更大区间转移的区间 DP 问题。比如从区间 $[i + 1, j - 1]$ 转移到更大区间 $[i, j]$。 +2. 多个(大于等于 2ドル$ 个)小区间转移到大区间的区间 DP 问题。比如从区间 $[i, k]$ 和区间 $[k, j]$ 转移到区间 $[i, j]$。 + +下面我们讲解一下这两种区间 DP 问题的基本解题思路。 ### 1.2 区间 DP 问题的基本思路 -区间 DP 的基本解题思路比较固定,具体步骤如下: +#### 1.2.1 第 1 种区间 DP 问题基本思路 + +从中间向两侧转移的区间 DP 问题的状态转移方程一般为:$dp[i][j] = max \lbrace dp[i + 1][j - 1], \quad dp[i + 1][j], \quad dp[i][j - 1] \rbrace + cost[i][j], \quad i \le j$。 + +1. 其中 $dp[i][j]$ 表示为:区间 $[i, j]$(即下标位置 $i$ 到下标位置 $j$ 上所有元素)上的最大价值。 +2. $cost$ 表示为:从小区间转移到区间 $[i, j]$ 的代价。 +3. 这里的 $max / min$ 取决于题目是求最大值还是求最小值。 + +从中间向两侧转移的区间 DP 问题的基本解题思路如下: + +1. 枚举区间的起点; +2. 枚举区间的终点; +3. 根据状态转移方程计算从小区间转移到更大区间后的最优值。 + +对应代码如下: + +```Python +for i in range(size - 1, -1, -1): # 枚举区间起点 + for j in range(i + 1, size): # 枚举区间终点 + # 状态转移方程,计算转移到更大区间后的最优值 + dp[i][j] = max(dp[i + 1][j - 1], dp[i + 1][j], dp[i][j - 1]) + cost[i][j] +``` + +#### 1.2.3 第 2 种区间 DP 问题基本思路 + +多个(大于等于 2ドル$ 个)小区间转移到大区间的区间 DP 问题的状态转移方程一般为:$dp[i][j] = max / min \lbrace dp[i][k] + dp[k + 1][j] + cost[i][j] \rbrace,\quad i < k \le j$。 + +1. 其中状态 $dp[i][j]$ 表示为:区间 $[i, j]$ (即下标位置 $i$ 到下标位置 $j$ 上所有元素)上的最大价值。 +2. $cost[i][j]$ 表示为:将两个区间 $[i, k]$ 与 $[k + 1, j]$ 中的元素合并为区间 $[i, j]$ 中的元素的代价。 +3. 这里的 $max / min$ 取决于题目是求最大值还是求最小值。 + +多个小区间转移到大区间的区间 DP 问题的基本解题思路如下: 1. 枚举区间长度; 2. 枚举区间的起点,根据区间起点和区间长度得出区间终点; @@ -23,7 +55,7 @@ 对应代码如下: ```Python -for l in range(2, n): # 枚举区间长度 +for l in range(1, n): # 枚举区间长度 for i in range(n): # 枚举区间起点 j = i + l - 1 # 根据起点和长度得到终点 if j>= n: @@ -38,32 +70,311 @@ for l in range(2, n): # 枚举区间长度 下面我们根据几个例子来讲解一下区间 DP 问题的具体解题思路。 -### 2.1 戳气球 +### 2.1 最长回文子序列 #### 2.1.1 题目链接 -- [312. 戳气球 - 力扣](https://leetcode.cn/problems/burst-balloons/) +- [516. 最长回文子序列 - 力扣](https://leetcode.cn/problems/longest-palindromic-subsequence/) #### 2.1.2 题目大意 +**描述**:给定一个字符串 $s$。 + +**要求**:找出其中最长的回文子序列,并返回该序列的长度。 + +**说明**: + +- **子序列**:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 +- 1ドル \le s.length \le 1000$。 +- $s$ 仅由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```Python +输入:s = "bbbab" +输出:4 +解释:一个可能的最长回文子序列为 "bbbb"。 +``` + +- 示例 2: + +```Python +输入:s = "cbbd" +输出:2 +解释:一个可能的最长回文子序列为 "bb"。 +``` + #### 2.1.3 解题思路 -### 2.2 合并石头的最低成本 +##### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。 + +###### 3. 状态转移方程 + +我们对区间 $[i, j]$ 边界位置上的字符 $s[i]$ 与 $s[j]$ 进行分类讨论: + +1. 如果 $s[i] = s[j],ドル则 $dp[i][j]$ 为区间 $[i + 1, j - 1]$ 范围内最长回文子序列长度 + 2ドル,ドル即 $dp[i][j] = dp[i + 1][j - 1] + 2$。 +2. 如果 $s[i] \ne s[j],ドル则 $dp[i][j]$ 取决于以下两种情况,取其最大的一种: + 1. 加入 $s[i]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i][j - 1]$。 + 2. 加入 $s[j]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i - 1][j]$。 + +则状态转移方程为: + +$dp[i][j] = \begin{cases} max \lbrace dp[i + 1][j - 1] + 2 \rbrace & s[i] = s[j] \cr max \lbrace dp[i][j - 1], dp[i - 1][j] \rbrace & s[i] \ne s[j] \end{cases}$ + +###### 4. 初始条件 + +- 单个字符的最长回文序列是 1ドル,ドル即 $dp[i][i] = 1$。 + +###### 5. 最终结果 + +由于 $dp[i][j]$ 依赖于 $dp[i + 1][j - 1]$、$dp[i + 1][j]$、$dp[i][j - 1],ドル所以我们应该按照从下到上、从左到右的顺序进行遍历。 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。所以最终结果为 $dp[0][size - 1]$。 + +##### 思路 1:代码 + +```Python +class Solution: + def longestPalindromeSubseq(self, s: str) -> int: + size = len(s) + dp = [[0 for _ in range(size)] for _ in range(size)] + for i in range(size): + dp[i][i] = 1 + + for i in range(size - 1, -1, -1): + for j in range(i + 1, size): + if s[i] == s[j]: + dp[i][j] = dp[i + 1][j - 1] + 2 + else: + dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) + + return dp[0][size - 1] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2),ドル其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(n^2)$。 + +### 2.2 戳气球 #### 2.2.1 题目链接 -- [1000. 合并石头的最低成本 - 力扣](https://leetcode.cn/problems/minimum-cost-to-merge-stones/) +- [312. 戳气球 - 力扣](https://leetcode.cn/problems/burst-balloons/) #### 2.2.2 题目大意 +**描述**:有 $n$ 个气球,编号为 0ドル \sim n - 1,ドル每个气球上都有一个数字,这些数字存在数组 $nums$ 中。现在开始戳破气球。其中戳破第 $i$ 个气球,可以获得 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币,这里的 $i - 1$ 和 $i + 1$ 代表和 $i$ 相邻的两个气球的编号。如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 1ドル$ 的气球。 + +**要求**:求出能获得硬币的最大数量。 + +**说明**: + +- $n == nums.length$。 +- 1ドル \le n \le 300$。 +- 0ドル \le nums[i] \le 100$。 + +**示例**: + +- 示例 1: + +```Python +输入:nums = [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 +``` + +- 示例 2: + +```Python +输入:nums = [1,5] +输出:10 +解释: +nums = [1,5] --> [5] --> [] +coins = 1*1*5 + 1*5*1 = 10 +``` + #### 2.2.3 解题思路 -### 2.3 最长回文子串 +##### 思路 1:动态规划 + +根据题意,如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 1ドル$ 的气球。我们可以预先在 $nums$ 的首尾位置,添加两个数字为 1ドル$ 的虚拟气球,这样变成了 $n + 2$ 个气球,气球对应编号也变为了 0ドル \sim n + 1$。 + +对应问题也变成了:给定 $n + 2$ 个气球,每个气球上有 1ドル$ 个数字,代表气球上的硬币数量,当我们戳破气球 $nums[i]$ 时,就能得到对应 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币。现在要戳破 0ドル \sim n + 1$ 之间的所有气球(不包括编号 0ドル$ 和编号 $n + 1$ 的气球),请问最多能获得多少枚硬币? + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。 + +###### 3. 状态转移方程 + +假设气球 $i$ 与气球 $j$ 之间最后一个被戳破的气球编号为 $k$。则 $dp[i][j]$ 取决于由 $k$ 作为分割点分割出的两个区间 $(i, k)$ 与 + +$(k, j)$ 上所能获取的最多硬币数 + 戳破气球 $k$ 所能获得的硬币数,即状态转移方程为: + +$dp[i][j] = max \lbrace dp[i][k] + dp[k][j] + nums[i] \times nums[k] \times nums[j] \rbrace, \quad i < k < j$ + +###### 4. 初始条件 + +- $dp[i][j]$ 表示的是开区间,则 $i < j - 1$。而当 $i \ge j - 1$ 时,所能获得的硬币数为 0ドル,ドル即 $dp[i][j] = 0,\quad i \ge j - 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。所以最终结果为 $dp[0][n + 1]$。 + +##### 思路 1:代码 + +```Python +class Solution: + def maxCoins(self, nums: List[int]) -> int: + size = len(nums) + arr = [0 for _ in range(size + 2)] + arr[0] = arr[size + 1] = 1 + for i in range(1, size + 1): + arr[i] = nums[i - 1] + + dp = [[0 for _ in range(size + 2)] for _ in range(size + 2)] + + for l in range(3, size + 3): + for i in range(0, size + 2): + j = i + l - 1 + if j>= size + 2: + break + for k in range(i + 1, j): + dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + arr[i] * arr[j] * arr[k]) + + return dp[0][size + 1] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^3),ドル其中 $n$ 为气球数量。 +- **空间复杂度**:$O(n^2)$。 + +### 2.3 切棍子的最小成本 #### 2.3.1 题目链接 -- [5. 最长回文子串 - 力扣](https://leetcode.cn/problems/longest-palindromic-substring/) +- [1547. 切棍子的最小成本 - 力扣](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) #### 2.3.2 题目大意 -#### 2.3.3 解题思路 \ No newline at end of file +**描述**:给定一个整数 $n,ドル代表一根长度为 $n$ 个单位的木根,木棍从 0ドル \sim n$ 标记了若干位置。例如,长度为 6ドル$ 的棍子可以标记如下: + + + +再给定一个整数数组 $cuts,ドル其中 $cuts[i]$ 表示需要将棍子切开的位置。 + +我们可以按照顺序完成切割,也可以根据需要更改切割顺序。 + +每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是所有次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根小木棍的长度和就是切割前木棍的长度)。 + +**要求**:返回切棍子的最小总成本。 + +**说明**: + +- 2ドル \le n \le 10^6$。 +- 1ドル \le cuts.length \le min(n - 1, 100)$。 +- 1ドル \le cuts[i] \le n - 1$。 +- $cuts$ 数组中的所有整数都互不相同。 + +**示例**: + +- 示例 1: + + + +```Python +输入:n = 7, cuts = [1,3,4,5] +输出:16 +解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示。 +第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4 的棍子,最后切割长度为 3 的棍子。总成本为 7 +たす 6 +たす 4 +たす 3 =わ 20 。而将切割顺序重新排列为 [3, 5, 1, 4] 后,总成本 = 16(如示例图中 7 +たす 4 +たす 3 +たす 2 =わ 16)。 +``` + + + +- 示例 2: + +```Python +输入:n = 9, cuts = [5,6,1,4,2] +输出:22 +解释:如果按给定的顺序切割,则总成本为 25。总成本 <= 25 的切割顺序很多,例如,[4, 6, 5, 2, 1] 的总成本 = 22,是所有可能方案中成本最小的。 +``` + +#### 2.3.3 解题思路 + +##### 思路 1:动态规划 + +我们可以预先在数组 $cuts$ 种添加位置 0ドル$ 和位置 $n,ドル然后对数组 $cuts$ 进行排序。这样待切割的木棍就对应了数组中连续元素构成的「区间」。 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 + +###### 3. 状态转移方程 + +假设位置 $i$ 与位置 $j$ 之间最后一个切割的位置为 $k,ドル则 $dp[i][j]$ 取决与由 $k$ 作为切割点分割出的两个区间 $[i, k]$ 与 $[k, j]$ 上的最小成本 + 切割位置 $k$ 所带来的成本。 + +而切割位置 $k$ 所带来的成本是这段区间所代表的小木棍的长度,即 $cuts[j] - cuts[i]$。 + +则状态转移方程为:$dp[i][j] = min \lbrace dp[i][k] + dp[k][j] + cuts[j] - cuts[i] \rbrace, \quad i < k < j$ + +###### 4. 初始条件 + +- 相邻位置之间没有切割点,不需要切割,最小成本为 0ドル,ドル即 $dp[i - 1][i] = 0$。 +- 其余位置默认为最小成本为一个极大值,即 $dp[i][j] = \infty, \quad i + 1 \ne j$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 所以最终结果为 $dp[0][size - 1]$。 + +##### 思路 1:代码 + +```Python +class Solution: + def minCost(self, n: int, cuts: List[int]) -> int: + cuts.append(0) + cuts.append(n) + cuts.sort() + + size = len(cuts) + dp = [[float('inf') for _ in range(size)] for _ in range(size)] + for i in range(1, size): + dp[i - 1][i] = 0 + + for l in range(3, size + 1): # 枚举区间长度 + for i in range(size): # 枚举区间起点 + j = i + l - 1 # 根据起点和长度得到终点 + if j>= size: + continue + dp[i][j] = float('inf') + for k in range(i + 1, j): # 枚举区间分割点 + # 状态转移方程,计算合并区间后的最优值 + dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i]) + return dp[0][size - 1] +``` + +##### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m^3),ドル其中 $m$ 为数组 $cuts$ 的元素个数。 +- **空间复杂度**:$O(m^2)$。 \ No newline at end of file diff --git a/Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md b/Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md index 2c72f553..a2f419bb 100644 --- a/Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md +++ b/Contents/10.Dynamic-Programming/05.Interval-DP/02.Interval-DP-List.md @@ -2,14 +2,19 @@ | 题号 | 标题 | 题解 | 标签 | 难度 | | :------ | :------ | :------ | :------ | :------ | -| 0678 | [有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0678.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、贪心、字符串、动态规划 | 中等 | +| 0486 | 预测赢家 | | | | +| 0312 | [戳气球](https://leetcode.cn/problems/burst-balloons/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0312.%20%E6%88%B3%E6%B0%94%E7%90%83.md) | 数组、动态规划 | 困难 | +| 0877 | [石子游戏](https://leetcode.cn/problems/stone-game) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0877.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F.md) | 数组、数学、动态规划、博弈 | 中等 | +| 1000 | 合并石头的最低成本 | | | | +| 1547 | [切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1547.%20%E5%88%87%E6%A3%8D%E5%AD%90%E7%9A%84%E6%9C%80%E5%B0%8F%E6%88%90%E6%9C%AC.md) | 数组、动态规划 | 困难 | +| 0664 | 奇怪的打印机 | | | | +| 1039 | 多边形三角剖分的最低得分 | | | | +| 0546 | 移除盒子 | | | | | 1995 | 统计特殊四元组 | | | | | 0375 | 猜数字大小 II | | | | +| 0678 | [有效的括号字符串](https://leetcode.cn/problems/valid-parenthesis-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0678.%20%E6%9C%89%E6%95%88%E7%9A%84%E6%8B%AC%E5%8F%B7%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 栈、贪心、字符串、动态规划 | 中等 | +| 0005 | [最长回文子串](https://leetcode.cn/problems/longest-palindromic-substring/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0005.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E4%B8%B2.md) | 字符串、动态规划 | 中等 | | 0516 | [最长回文子序列](https://leetcode.cn/problems/longest-palindromic-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0516.%20%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | | 0730 | 统计不同回文子序列 | | | | -| 1039 | 多边形三角剖分的最低得分 | | | | -| 0664 | 奇怪的打印机 | | | | -| 0877 | [石子游戏](https://leetcode.cn/problems/stone-game) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0877.%20%E7%9F%B3%E5%AD%90%E6%B8%B8%E6%88%8F.md) | 数组、数学、动态规划、博弈 | 中等 | | 2104 | 子数组范围和 | | | | -| 0312 | 戳气球 | | | | diff --git a/README.md b/README.md index f9e42e97..852be2eb 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ 我是一名 iOS / macOS 的开发程序员,另外也是北航软院的一名非全硕士(在读)。曾在大学期间学习过算法知识,并参加过 3 年的 ACM 比赛, 但水平有限,未能取得理想成绩。但是这 3 年的 ACM 经历,给我最大的收获是锻炼了自己的逻辑思维和解决实际问题的能力,这种能力为我今后的工作、学习打下了坚实的基础。 -我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 700+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 +我从 2021 年 03 月 30 日开始每日在 LeetCode 刷题,到 2022 年 06 月 08 日已经刷了 1000+ 道题目,并且完成了 750+ 道题解。努力向着 1000+、1500+、2000+ 道题解前进。 在公众号 **「程序员充电站」** 里回复 "**算法打卡**",拉你进 LeetCode 算法打卡计划群一起组队打卡。 @@ -259,4 +259,4 @@ - [动态规划优化题目](./Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md) ## 11. 附加内容 -## [12. LeetCode 题解(已完成 757 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file +## [12. LeetCode 题解(已完成 759 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file diff --git "a/Solutions/0312. 346円210円263円346円260円224円347円220円203円.md" "b/Solutions/0312. 346円210円263円346円260円224円347円220円203円.md" index 1c8582d1..d132c3cc 100644 --- "a/Solutions/0312. 346円210円263円346円260円224円347円220円203円.md" +++ "b/Solutions/0312. 346円210円263円346円260円224円347円220円203円.md" @@ -5,7 +5,7 @@ ## 题目大意 -**描述**:有 $n$ 个气球,编号为 0ドル \sim n - 1,ドル每个气球上都有一个数字,这些数字存在数组 $nums$ 中。现在开始戳破气球。其中戳破第 $i$ 个气球,可以获得 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚迎宾,这里的 $i - 1$ 和 $i + 1$ 代表和 $i$ 相邻的两个气球的编号。如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 1ドル$ 的气球。 +**描述**:有 $n$ 个气球,编号为 0ドル \sim n - 1,ドル每个气球上都有一个数字,这些数字存在数组 $nums$ 中。现在开始戳破气球。其中戳破第 $i$ 个气球,可以获得 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币,这里的 $i - 1$ 和 $i + 1$ 代表和 $i$ 相邻的两个气球的编号。如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 1ドル$ 的气球。 **要求**:求出能获得硬币的最大数量。 @@ -41,33 +41,59 @@ coins = 1*1*5 + 1*5*1 = 10 ### 思路 1:动态规划 +根据题意,如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 1ドル$ 的气球。我们可以预先在 $nums$ 的首尾位置,添加两个数字为 1ドル$ 的虚拟气球,这样变成了 $n + 2$ 个气球,气球对应编号也变为了 0ドル \sim n + 1$。 + +对应问题也变成了:给定 $n + 2$ 个气球,每个气球上有 1ドル$ 个数字,代表气球上的硬币数量,当我们戳破气球 $nums[i]$ 时,就能得到对应 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币。现在要戳破 0ドル \sim n + 1$ 之间的所有气球(不包括编号 0ドル$ 和编号 $n + 1$ 的气球),请问最多能获得多少枚硬币? + ###### 1. 划分阶段 -按照 进行阶段划分。 +按照区间长度进行阶段划分。 ###### 2. 定义状态 -定义状态 $dp[i]$ 表示为:。 +定义状态 $dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。 ###### 3. 状态转移方程 +假设气球 $i$ 与气球 $j$ 之间最后一个被戳破的气球编号为 $k$。则 $dp[i][j]$ 取决于由 $k$ 作为分割点分割出的两个区间 $(i, k)$ 与 +$(k, j)$ 上所能获取的最多硬币数 + 戳破气球 $k$ 所能获得的硬币数,即状态转移方程为: -###### 4. 初始条件 +$dp[i][j] = max \lbrace dp[i][k] + dp[k][j] + nums[i] \times nums[k] \times nums[j] \rbrace, \quad i < k < j$ +###### 4. 初始条件 +- $dp[i][j]$ 表示的是开区间,则 $i < j - 1$。而当 $i \ge j - 1$ 时,所能获得的硬币数为 0ドル,ドル即 $dp[i][j] = 0,\quad i \ge$ ###### 5. 最终结果 -根据我们之前定义的状态,$dp[i]$ 表示为:。 所以最终结果为 $dp[size]$。 +根据我们之前定义的状态,$dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。。所以最终结果为 $dp[0][n + 1]$。 ### 思路 1:代码 ```Python - +class Solution: + def maxCoins(self, nums: List[int]) -> int: + size = len(nums) + arr = [0 for _ in range(size + 2)] + arr[0] = arr[size + 1] = 1 + for i in range(1, size + 1): + arr[i] = nums[i - 1] + + dp = [[0 for _ in range(size + 2)] for _ in range(size + 2)] + + for l in range(3, size + 3): + for i in range(0, size + 2): + j = i + l - 1 + if j>= size + 2: + break + for k in range(i + 1, j): + dp[i][j] = max(dp[i][j], dp[i][k] + dp[k][j] + arr[i] * arr[j] * arr[k]) + + return dp[0][size + 1] ``` ### 思路 1:复杂度分析 -- **时间复杂度**: -- **空间复杂度**: +- **时间复杂度**:$O(n^3),ドル其中 $n$ 为气球数量。 +- **空间复杂度**:$O(n^2)$。 diff --git "a/Solutions/0516. 346円234円200円351円225円277円345円233円236円346円226円207円345円255円220円345円272円217円345円210円227円.md" "b/Solutions/0516. 346円234円200円351円225円277円345円233円236円346円226円207円345円255円220円345円272円217円345円210円227円.md" index 7ceaf242..9dc0da8d 100644 --- "a/Solutions/0516. 346円234円200円351円225円277円345円233円236円346円226円207円345円255円220円345円272円217円345円210円227円.md" +++ "b/Solutions/0516. 346円234円200円351円225円277円345円233円236円346円226円207円345円255円220円345円272円217円345円210円227円.md" @@ -5,30 +5,70 @@ ## 题目大意 -给定一个字符串 `s`,找出其中最长的回文子序列,并返回该序列的长度。 +**描述**:给定一个字符串 $s$。 + +**要求**:找出其中最长的回文子序列,并返回该序列的长度。 + +**说明**: + +- **子序列**:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 +- 1ドル \le s.length \le 1000$。 +- $s$ 仅由小写英文字母组成。 + +**示例**: + +- 示例 1: + +```Python +输入:s = "bbbab" +输出:4 +解释:一个可能的最长回文子序列为 "bbbb"。 +``` + +- 示例 2: + +```Python +输入:s = "cbbd" +输出:2 +解释:一个可能的最长回文子序列为 "bb"。 +``` ## 解题思路 -动态规划求解。 +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 -定义状态 `dp[i][j]` 表示为:字符串 `s` 在 `[i, j]` 范围内的最长回文子序列长度。 +定义状态 $dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。 -则状态转移公式为: +###### 3. 状态转移方程 -- 如果 `s[i] == s[j]`,则 `dp[i][j]` 为 `[i + 1, j - 1]` 范围内最长回文子序列长度 + 2,即 `dp[i][j] = dp[i + 1][j - 1] + 2`。 +我们对区间 $[i, j]$ 边界位置上的字符 $s[i]$ 与 $s[j]$ 进行分类讨论: -- 如果 `s[i] != s[j]`,则 `dp[i][j]` 取决于以下两种情况,取其最大的一种: +1. 如果 $s[i] = s[j],ドル则 $dp[i][j]$ 为区间 $[i + 1, j - 1]$ 范围内最长回文子序列长度 + 2ドル,ドル即 $dp[i][j] = dp[i + 1][j - 1] + 2$。 +2. 如果 $s[i] \ne s[j],ドル则 $dp[i][j]$ 取决于以下两种情况,取其最大的一种: + 1. 加入 $s[i]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i][j - 1]$。 + 2. 加入 $s[j]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i - 1][j]$。 - - 加入 `s[i]` 所能组成的最长回文子序列长度,即:`dp[i][j] = dp[i][j - 1]`。 - - 加入 `s[j]` 所能组成的最长回文子序列长度,即:`dp[i][j] = dp[i - 1][j]`。 +则状态转移方程为: - 下一步确定遍历方向。 +$dp[i][j] = \begin{cases} max \lbrace dp[i + 1][j - 1] + 2 \rbrace & s[i] = s[j] \cr max \lbrace dp[i][j - 1], dp[i - 1][j] \rbrace & s[i] \ne s[j] \end{cases}$ -由于 `dp[i][j]` 依赖于 `dp[i + 1][j - 1]`、`dp[i + 1][j]`、`dp[i][j - 1]`,所以我们应该按照从下到上、从左到右的顺序进行遍历。 +###### 4. 初始条件 -最后输出 `[0, size - 1]` 范围内最长回文子序列长度,即 `dp[0][size - 1]` 为最终答案。 +- 单个字符的最长回文序列是 1ドル,ドル即 $dp[i][i] = 1$。 -## 代码 +###### 5. 最终结果 + +由于 $dp[i][j]$ 依赖于 $dp[i + 1][j - 1]$、$dp[i + 1][j]$、$dp[i][j - 1],ドル所以我们应该按照从下到上、从左到右的顺序进行遍历。 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。所以最终结果为 $dp[0][size - 1]$。 + +### 思路 1:代码 ```Python class Solution: @@ -48,3 +88,8 @@ class Solution: return dp[0][size - 1] ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n^2),ドル其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(n^2)$。 + diff --git "a/Solutions/1547. 345円210円207円346円243円215円345円255円220円347円232円204円346円234円200円345円260円217円346円210円220円346円234円254円.md" "b/Solutions/1547. 345円210円207円346円243円215円345円255円220円347円232円204円346円234円200円345円260円217円346円210円220円346円234円254円.md" new file mode 100644 index 00000000..c81900dd --- /dev/null +++ "b/Solutions/1547. 345円210円207円346円243円215円345円255円220円347円232円204円346円234円200円345円260円217円346円210円220円346円234円254円.md" @@ -0,0 +1,110 @@ +# [1547. 切棍子的最小成本](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/) + +- 标签:数组、动态规划 +- 难度:困难 + +## 题目大意 + +**描述**:给定一个整数 $n,ドル代表一根长度为 $n$ 个单位的木根,木棍从 0ドル \sim n$ 标记了若干位置。例如,长度为 6ドル$ 的棍子可以标记如下: + + + +再给定一个整数数组 $cuts,ドル其中 $cuts[i]$ 表示需要将棍子切开的位置。 + +我们可以按照顺序完成切割,也可以根据需要更改切割顺序。 + +每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是所有次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根小木棍的长度和就是切割前木棍的长度)。 + +**要求**:返回切棍子的最小总成本。 + +**说明**: + +- 2ドル \le n \le 10^6$。 +- 1ドル \le cuts.length \le min(n - 1, 100)$。 +- 1ドル \le cuts[i] \le n - 1$。 +- $cuts$ 数组中的所有整数都互不相同。 + +**示例**: + +- 示例 1: + + + +```Python +输入:n = 7, cuts = [1,3,4,5] +输出:16 +解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示。 +第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4 的棍子,最后切割长度为 3 的棍子。总成本为 7 +たす 6 +たす 4 +たす 3 =わ 20 。而将切割顺序重新排列为 [3, 5, 1, 4] 后,总成本 = 16(如示例图中 7 +たす 4 +たす 3 +たす 2 =わ 16)。 +``` + + + +- 示例 2: + +```Python +输入:n = 9, cuts = [5,6,1,4,2] +输出:22 +解释:如果按给定的顺序切割,则总成本为 25。总成本 <= 25 的切割顺序很多,例如,[4, 6, 5, 2, 1] 的总成本 = 22,是所有可能方案中成本最小的。 +``` + +## 解题思路 + +### 思路 1:动态规划 + +我们可以预先在数组 $cuts$ 种添加位置 0ドル$ 和位置 $n,ドル然后对数组 $cuts$ 进行排序。这样待切割的木棍就对应了数组中连续元素构成的「区间」。 + +###### 1. 划分阶段 + +按照区间长度进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 + +###### 3. 状态转移方程 + +假设位置 $i$ 与位置 $j$ 之间最后一个切割的位置为 $k,ドル则 $dp[i][j]$ 取决与由 $k$ 作为切割点分割出的两个区间 $[i, k]$ 与 $[k, j]$ 上的最小成本 + 切割位置 $k$ 所带来的成本。 + +而切割位置 $k$ 所带来的成本是这段区间所代表的小木棍的长度,即 $cuts[j] - cuts[i]$。 + +则状态转移方程为:$dp[i][j] = min \lbrace dp[i][k] + dp[k][j] + cuts[j] - cuts[i] \rbrace, \quad i < k < j$ + +###### 4. 初始条件 + +- 相邻位置之间没有切割点,不需要切割,最小成本为 0ドル,ドル即 $dp[i - 1][i] = 0$。 +- 其余位置默认为最小成本为一个极大值,即 $dp[i][j] = \infty, \quad i + 1 \ne j$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 所以最终结果为 $dp[0][size - 1]$。 + +### 思路 1:代码 + +```Python +class Solution: + def minCost(self, n: int, cuts: List[int]) -> int: + cuts.append(0) + cuts.append(n) + cuts.sort() + + size = len(cuts) + dp = [[float('inf') for _ in range(size)] for _ in range(size)] + for i in range(1, size): + dp[i - 1][i] = 0 + + for l in range(3, size + 1): # 枚举区间长度 + for i in range(size): # 枚举区间起点 + j = i + l - 1 # 根据起点和长度得到终点 + if j>= size: + continue + dp[i][j] = float('inf') + for k in range(i + 1, j): # 枚举区间分割点 + # 状态转移方程,计算合并区间后的最优值 + dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i]) + return dp[0][size - 1] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m^3),ドル其中 $m$ 为数组 $cuts$ 的元素个数。 +- **空间复杂度**:$O(m^2)$。