diff --git a/Contents/00.Introduction/04.Solutions-List.md b/Contents/00.Introduction/04.Solutions-List.md index 8b77334e..5612c812 100644 --- a/Contents/00.Introduction/04.Solutions-List.md +++ b/Contents/00.Introduction/04.Solutions-List.md @@ -1,4 +1,4 @@ -# LeetCode 题解(已完成 748 道) +# LeetCode 题解(已完成 751 道) | 题号 | 标题 | 题解 | 标签 | 难度 | | :------ | :------ | :------ | :------ | :------ | @@ -315,7 +315,7 @@ | 0513 | [找树左下角的值](https://leetcode.cn/problems/find-bottom-left-tree-value/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0513.%20%E6%89%BE%E6%A0%91%E5%B7%A6%E4%B8%8B%E8%A7%92%E7%9A%84%E5%80%BC.md) | 树、深度优先搜索、广度优先搜索、二叉树 | 中等 | | 0515 | [在每个树行中找最大值](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0515.%20%E5%9C%A8%E6%AF%8F%E4%B8%AA%E6%A0%91%E8%A1%8C%E4%B8%AD%E6%89%BE%E6%9C%80%E5%A4%A7%E5%80%BC.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) | 字符串、动态规划 | 中等 | -| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-2/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | +| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | | 0525 | [连续数组](https://leetcode.cn/problems/contiguous-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0525.%20%E8%BF%9E%E7%BB%AD%E6%95%B0%E7%BB%84.md) | 数组、哈希表、前缀和 | 中等 | | 0530 | [二叉搜索树的最小绝对差](https://leetcode.cn/problems/minimum-absolute-difference-in-bst/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0530.%20%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E7%BB%9D%E5%AF%B9%E5%B7%AE.md) | 树、深度优先搜索、广度优先搜索、二叉搜索树、二叉树 | 简单 | | 0538 | [把二叉搜索树转换为累加树](https://leetcode.cn/problems/convert-bst-to-greater-tree/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0538.%20%E6%8A%8A%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E8%BD%AC%E6%8D%A2%E4%B8%BA%E7%B4%AF%E5%8A%A0%E6%A0%91.md) | 树、深度优先搜索、二叉搜索树、二叉树 | 中等 | @@ -395,6 +395,7 @@ | 0758 | [字符串中的加粗单词](https://leetcode.cn/problems/bold-words-in-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0758.%20%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E5%8A%A0%E7%B2%97%E5%8D%95%E8%AF%8D.md) | 字典树、数组、哈希表、字符串、字符串匹配 | 中等 | | 0763 | [划分字母区间](https://leetcode.cn/problems/partition-labels/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0763.%20%E5%88%92%E5%88%86%E5%AD%97%E6%AF%8D%E5%8C%BA%E9%97%B4.md) | 贪心、哈希表、双指针、字符串 | 中等 | | 0765 | [情侣牵手](https://leetcode.cn/problems/couples-holding-hands/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0765.%20%E6%83%85%E4%BE%A3%E7%89%B5%E6%89%8B.md) | 贪心、深度优先搜索、广度优先搜索、并查集、图 | 困难 | +| 0766 | [托普利茨矩阵](https://leetcode.cn/problems/toeplitz-matrix/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0766.%20%E6%89%98%E6%99%AE%E5%88%A9%E8%8C%A8%E7%9F%A9%E9%98%B5.md) | 数组、矩阵 | 简单 | | 0771 | [宝石与石头](https://leetcode.cn/problems/jewels-and-stones/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0771.%20%E5%AE%9D%E7%9F%B3%E4%B8%8E%E7%9F%B3%E5%A4%B4.md) | 哈希表、字符串 | 简单 | | 0778 | [水位上升的泳池中游泳](https://leetcode.cn/problems/swim-in-rising-water/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0778.%20%E6%B0%B4%E4%BD%8D%E4%B8%8A%E5%8D%87%E7%9A%84%E6%B3%B3%E6%B1%A0%E4%B8%AD%E6%B8%B8%E6%B3%B3.md) | 深度优先搜索、广度优先搜索、并查集、数组、二分查找、矩阵、堆(优先队列) | 困难 | | 0779 | [第K个语法符号](https://leetcode.cn/problems/k-th-symbol-in-grammar/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0779.%20%E7%AC%ACK%E4%B8%AA%E8%AF%AD%E6%B3%95%E7%AC%A6%E5%8F%B7.md) | 递归 | 中等 | @@ -511,9 +512,11 @@ | 1358 | [包含所有三种字符的子字符串数目](https://leetcode.cn/problems/number-of-substrings-containing-all-three-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1358.%20%E5%8C%85%E5%90%AB%E6%89%80%E6%9C%89%E4%B8%89%E7%A7%8D%E5%AD%97%E7%AC%A6%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%95%B0%E7%9B%AE.md) | 哈希表、字符串、滑动数组 | 中等 | | 1400 | [构造 K 个回文字符串](https://leetcode.cn/problems/construct-k-palindrome-strings/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1400.%20%E6%9E%84%E9%80%A0%20K%20%E4%B8%AA%E5%9B%9E%E6%96%87%E5%AD%97%E7%AC%A6%E4%B8%B2.md) | 贪心、哈希表、字符串、计数 | 中等 | | 1408 | [数组中的字符串匹配](https://leetcode.cn/problems/string-matching-in-an-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1408.%20%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%8C%B9%E9%85%8D.md) | 字符串、字符串匹配 | 简单 | +| 1422 | [分割字符串的最大得分](https://leetcode.cn/problems/maximum-score-after-splitting-a-string/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1422.%20%E5%88%86%E5%89%B2%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E6%9C%80%E5%A4%A7%E5%BE%97%E5%88%86.md) | 字符串 | 简单 | | 1423 | [可获得的最大点数](https://leetcode.cn/problems/maximum-points-you-can-obtain-from-cards/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1423.%20%E5%8F%AF%E8%8E%B7%E5%BE%97%E7%9A%84%E6%9C%80%E5%A4%A7%E7%82%B9%E6%95%B0.md) | 数组、前缀和、滑动窗口 | 中等 | | 1438 | [绝对差不超过限制的最长连续子数组](https://leetcode.cn/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1438.%20%E7%BB%9D%E5%AF%B9%E5%B7%AE%E4%B8%8D%E8%B6%85%E8%BF%87%E9%99%90%E5%88%B6%E7%9A%84%E6%9C%80%E9%95%BF%E8%BF%9E%E7%BB%AD%E5%AD%90%E6%95%B0%E7%BB%84.md) | 队列、数组、有序集合、滑动窗口、单调队列、堆(优先队列) | 中等 | | 1446 | [连续字符](https://leetcode.cn/problems/consecutive-characters/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1446.%20%E8%BF%9E%E7%BB%AD%E5%AD%97%E7%AC%A6.md) | 字符串 | 简单 | +| 1449 | [数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1449.%20%E6%95%B0%E4%BD%8D%E6%88%90%E6%9C%AC%E5%92%8C%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AD%97.md) | 数组、动态规划 | 困难 | | 1450 | [在既定时间做作业的学生人数](https://leetcode.cn/problems/number-of-students-doing-homework-at-a-given-time/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1450.%20%E5%9C%A8%E6%97%A2%E5%AE%9A%E6%97%B6%E9%97%B4%E5%81%9A%E4%BD%9C%E4%B8%9A%E7%9A%84%E5%AD%A6%E7%94%9F%E4%BA%BA%E6%95%B0.md) | 数组 | 简单 | | 1456 | [定长子串中元音的最大数目](https://leetcode.cn/problems/maximum-number-of-vowels-in-a-substring-of-given-length/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1456.%20%E5%AE%9A%E9%95%BF%E5%AD%90%E4%B8%B2%E4%B8%AD%E5%85%83%E9%9F%B3%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E7%9B%AE.md) | 字符串、滑动窗口 | 中等 | | 1480 | [一维数组的动态和](https://leetcode.cn/problems/running-sum-of-1d-array/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1480.%20%E4%B8%80%E7%BB%B4%E6%95%B0%E7%BB%84%E7%9A%84%E5%8A%A8%E6%80%81%E5%92%8C.md) | 数组 | 简单 | diff --git a/Contents/00.Introduction/05.Categories-List.md b/Contents/00.Introduction/05.Categories-List.md index 8d1a031b..e4e75cdf 100644 --- a/Contents/00.Introduction/05.Categories-List.md +++ b/Contents/00.Introduction/05.Categories-List.md @@ -951,11 +951,11 @@ | :------ | :------ | :------ | :------ | :------ | | 0279 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0279.%20%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 广度优先搜索、数学、动态规划 | 中等 | | 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 动态规划 | 中等 | -| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-2/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | +| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | | 0139 | [单词拆分](https://leetcode.cn/problems/word-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0139.%20%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86.md) | 字典树、记忆化搜索、哈希表、字符串、动态规划 | 中等 | | 0377 | [组合总和 Ⅳ](https://leetcode.cn/problems/combination-sum-iv/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0377.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20%E2%85%A3.md) | 数组、动态规划 | 中等 | | 0638 | 大礼包 | | | | -| 1449 | 数位成本和为目标值的最大数字 | | | | +| 1449 | [数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1449.%20%E6%95%B0%E4%BD%8D%E6%88%90%E6%9C%AC%E5%92%8C%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AD%97.md) | 数组、动态规划 | 困难 | #### 多重背包问题 diff --git a/Contents/00.Introduction/07.Interview-200-List.md b/Contents/00.Introduction/07.Interview-200-List.md index affc9dd5..a419e22a 100644 --- a/Contents/00.Introduction/07.Interview-200-List.md +++ b/Contents/00.Introduction/07.Interview-200-List.md @@ -472,7 +472,7 @@ | 0509 | [斐波那契数](https://leetcode.cn/problems/fibonacci-number/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0509.%20%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.md) | 数组 | 简单 | | 0121 | [买卖股票的最佳时机](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0121.%20%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BA.md) | 数组、动态规划 | 简单 | | 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 动态规划 | 中等 | -| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-2/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | +| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | | 0300 | [最长递增子序列](https://leetcode.cn/problems/longest-increasing-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0300.%20%E6%9C%80%E9%95%BF%E9%80%92%E5%A2%9E%E5%AD%90%E5%BA%8F%E5%88%97.md) | 二分查找、动态规划 | 中等 | | 1143 | [最长公共子序列](https://leetcode.cn/problems/longest-common-subsequence/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1143.%20%E6%9C%80%E9%95%BF%E5%85%AC%E5%85%B1%E5%AD%90%E5%BA%8F%E5%88%97.md) | 字符串、动态规划 | 中等 | | 0718 | [最长重复子数组](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0718.%20%E6%9C%80%E9%95%BF%E9%87%8D%E5%A4%8D%E5%AD%90%E6%95%B0%E7%BB%84.md) | 数组、二分查找、动态规划、滑动窗口、哈希函数、滚动哈希 | 中等 | diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md index 879a96b6..498d02da 100644 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md +++ b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/02.Knapsack-Problem-02.md @@ -236,104 +236,6 @@ class Solution: - **时间复杂度**:$O(n \times W)$,其中 $n$ 为物品种类数量,$W$ 为背包的载重上限。 - **空间复杂度**:$O(W)$。 -### 3.4 完全背包问题的应用 - -#### 3.4.1 题目链接 - -- [322. 零钱兑换 - 力扣](https://leetcode.cn/problems/coin-change/) - -#### 3.4.2 题目大意 - -**描述**:给定代表不同面额的硬币数组 `coins` 和一个总金额 `amount`。 - -**要求**:求出凑成总金额所需的最少的硬币个数。如果无法凑出,则返回 -1。 - -**说明**: - -- $1 \le coins.length \le 12$。 -- $1 \le coins[i] \le 2^{31} - 1$。 -- $0 \le amount \le 10^4$。 - -**示例**: - -- 示例 1: - -```Python -输入:coins = [1, 2, 5], amount = 11 -输出:3 -解释:11 = 5 + 5 + 1 -``` - -- 示例 2: - -```Python -输入:coins = [2], amount = 3 -输出:-1 -``` - -#### 3.4.3 解题思路 - -##### 思路 1:完全背包问题 - -这道题可以转换为:有 $n$ 种不同的硬币,$coins[i]$ 表示第 $i$ 种硬币的面额,每种硬币可以无限次使用。请问凑成总金额为 $amount$ 的背包,最少需要多少硬币? - -与普通完全背包问题不同的是,这里求解的是最少硬币数量。我们可以改变一下「状态定义」和「状态转移方程」。 - -###### 1. 划分阶段 - -按照当前背包的载重上限进行阶段划分。 - -###### 2. 定义状态 - -定义状态 $dp[c]$ 表示为:凑成总金额为 $c$ 的最少硬币数量。 - -###### 3. 状态转移方程 - -$dp[c] = \begin{cases} dp[c] & c < coins[i - 1] \cr min \lbrace dp[c], dp[c - coins[i - 1]] + 1 \rbrace & c \ge coins[i - 1] \end{cases}$ - -1. 当 $c < coins[i - 1]$ 时: - 1. 不使用第 $i - 1$ 枚硬币,只使用前 $i - 1$ 枚硬币凑成金额 $w$ 的最少硬币数量,即 $dp[c]$。 -2. 当 $c \ge coins[i - 1]$ 时,取下面两种情况中的较小值: - 1. 不使用第 $i - 1$ 枚硬币,只使用前 $i - 1$ 枚硬币凑成金额 $w$ 的最少硬币数量,即 $dp[c]$。 - 2. 凑成金额 $c - coins[i - 1]$ 的最少硬币数量,再加上当前硬币的数量 $1$,即 $dp[c - coins[i - 1]] + 1$。 - -###### 4. 初始条件 - -- 凑成总金额为 $0$ 的最少硬币数量为 $0$,即 $dp[0] = 0$。 -- 默认凑成总金额为 $w$ 的最少硬币数量为一个极大值(比如 $amount + 1$),表示无法凑成。 - -###### 5. 最终结果 - -根据我们之前定义的状态,$dp[c]$ 表示为:凑成总金额为 $c$ 的最少硬币数量。则最终结果为 $dp[amount]$。 - -1. 如果 $dp[amount] \ne amount + 1$,则说明: $dp[amount]$ 为凑成金额 $amount$ 的最少硬币数量,则返回 $dp[amount]$。 -2. 如果 $dp[amount] = amount + 1$,则说明:无法凑成金额 $amount$,则返回 $-1$。 - -##### 思路 1:代码 - -```Python -class Solution: - def coinChange(self, coins: List[int], amount: int) -> int: - size = len(coins) - dp = [(amount + 1) for _ in range(amount + 1)] - dp[0] = 0 - - # 枚举前 i 种物品 - for i in range(1, size + 1): - # 正序枚举背包装载重量 - for c in range(coins[i - 1], amount + 1): - dp[c] = min(dp[c], dp[c - coins[i - 1]] + 1) - - if dp[amount] != amount + 1: - return dp[amount] - return -1 -``` - -##### 思路 1:复杂度分析 - -- **时间复杂度**:$O(amount \times size)$。其中 $amount$ 表示总金额,$size$ 表示硬币的种类数。 -- **空间复杂度**:$O(amount)$。 - ## 参考资料 - 【资料】[背包九讲 - 崔添翼](https://github.com/tianyicui/pack) diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md index d49531e8..98151488 100644 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md +++ b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/04.Knapsack-Problem-04.md @@ -10,6 +10,8 @@ > > 请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? +![](https://qcdn.itcharge.cn/images/20230329095653.png) + #### 思路 1:动态规划 混合背包问题其实就是将「0-1 背包问题」、「完全背包问题」和「多重背包问题」这 $3$ 种背包问题综合起来,有的是能取 $1$ 件,有的能取无数件,有的只能取 $count[i]$ 件。 @@ -82,6 +84,8 @@ class Solution: > **分组背包问题**:有 $n$ 组物品和一个最多能装重量为 $W$ 的背包,第 $i$ 组物品的件数为 $group\underline{}count[i]$,第 $i$ 组的第 $j$ 个物品重量为 $weight[i][j]$,价值为 $value[i][j]$。每组物品中最多只能选择 $1$ 件物品装入背包。请问在总重量不超过背包载重上限的情况下,能装入背包的最大价值是多少? +![](https://qcdn.itcharge.cn/images/20230329095729.png) + ### 6.1 分组背包问题基本思路 #### 思路 1:动态规划 + 二维基本思路 @@ -200,6 +204,8 @@ class Solution: > **二维费用背包问题**:有 $n$ 件物品和有一个最多能装重量为 $W$、容量为 $V$ 的背包。第 $i$ 件物品的重量为 $weight[i]$,体积为 $volume[i]$,价值为 $value[i]$,每件物品有且只有 $1$ 件。请问在总重量不超过背包载重上限、容量上限的情况下,能装入背包的最大价值是多少? +![](https://qcdn.itcharge.cn/images/20230329095857.png) + ### 7.1 二维费用背包问题基本思路 我们可以参考「0-1 背包问题」的状态定义和基本思路,在「0-1 背包问题」基本思路的基础上,增加一个维度用于表示物品的容量。 diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md index 274e5e1b..d18b5b4b 100644 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md +++ b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/05.Knapsack-Problem-05.md @@ -27,7 +27,7 @@ #### 思路 1:动态规划 + 一维状态 -1. **划分阶段**:按照物品种类的序号、当前背包的载重上限进行阶段划分。 +1. **划分阶段**:按照当前背包的载重上限进行阶段划分。 2. **定义状态**:定义状态 $dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中,恰好装满背包的情况下,能装入背包的最大价值总和。 3. **状态转移方程**:$dp[w] = dp[w] + dp[w - weight[i - 1]]$ 4. **初始条件**: diff --git a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md index 3d2f7f83..e9e07d02 100644 --- a/Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md +++ b/Contents/10.Dynamic-Programming/04.Knapsack-Problem/06.Knapsack-Problem-List.md @@ -14,11 +14,11 @@ | :------ | :------ | :------ | :------ | :------ | | 0279 | [完全平方数](https://leetcode.cn/problems/perfect-squares/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0279.%20%E5%AE%8C%E5%85%A8%E5%B9%B3%E6%96%B9%E6%95%B0.md) | 广度优先搜索、数学、动态规划 | 中等 | | 0322 | [零钱兑换](https://leetcode.cn/problems/coin-change/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0322.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2.md) | 动态规划 | 中等 | -| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-2/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | +| 0518 | [零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0518.%20%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2%20II.md) | 数组、动态规划 | 中等 | | 0139 | [单词拆分](https://leetcode.cn/problems/word-break/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0139.%20%E5%8D%95%E8%AF%8D%E6%8B%86%E5%88%86.md) | 字典树、记忆化搜索、哈希表、字符串、动态规划 | 中等 | | 0377 | [组合总和 Ⅳ](https://leetcode.cn/problems/combination-sum-iv/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/0377.%20%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C%20%E2%85%A3.md) | 数组、动态规划 | 中等 | | 0638 | 大礼包 | | | | -| 1449 | 数位成本和为目标值的最大数字 | | | | +| 1449 | [数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) | [Python](https://github.com/itcharge/LeetCode-Py/blob/main/Solutions/1449.%20%E6%95%B0%E4%BD%8D%E6%88%90%E6%9C%AC%E5%92%8C%E4%B8%BA%E7%9B%AE%E6%A0%87%E5%80%BC%E7%9A%84%E6%9C%80%E5%A4%A7%E6%95%B0%E5%AD%97.md) | 数组、动态规划 | 困难 | #### 多重背包问题 diff --git a/README.md b/README.md index 6a8618cb..ba26c09c 100644 --- a/README.md +++ b/README.md @@ -259,4 +259,4 @@ - [动态规划优化题目](./Contents/10.Dynamic-Programming/11.DP-Optimization/04.DP-Optimization-List.md) ## 11. 附加内容 -## [12. LeetCode 题解(已完成 748 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file +## [12. LeetCode 题解(已完成 751 道)](./Contents/00.Introduction/04.Solutions-List.md) \ No newline at end of file diff --git "a/Solutions/0279. \345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" "b/Solutions/0279. \345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" index 0638cde3..3d8f8393 100644 --- "a/Solutions/0279. \345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" +++ "b/Solutions/0279. \345\256\214\345\205\250\345\271\263\346\226\271\346\225\260.md" @@ -5,9 +5,9 @@ ## 题目大意 -**描述**:给定一个正整数 `n`。从中找到若干个完全平方数(比如 `1`、`4`、`1`、`16` ...),使得它们的和等于 `n`。 +**描述**:给定一个正整数 $n$。从中找到若干个完全平方数(比如 $1、4、9、16…$),使得它们的和等于 $n$。 -**要求**:返回和为 `n` 的完全平方数的最小数量。 +**要求**:返回和为 $n$ 的完全平方数的最小数量。 **说明**: @@ -33,7 +33,7 @@ ## 解题思路 -暴力枚举思路:对于小于 `n` 的完全平方数,直接暴力枚举所有可能的组合,并且找到平方数个数最小的一个。 +暴力枚举思路:对于小于 $n$ 的完全平方数,直接暴力枚举所有可能的组合,并且找到平方数个数最小的一个。 并且对于所有小于 $n$ 的完全平方数($k = 1, 4, 9, 16, ...$),存在公式:$ans(n) = min(ans(n - k) + 1),k = 1,4,9,16,...$ @@ -45,22 +45,22 @@ 我们可以转换一下思维。 -1. 将 `n` 作为根节点,构建一棵多叉数。 -2. 从 `n` 节点出发,如果一个小于 `n` 的数刚好与 `n` 相差一个平方数,则以该数为值构造一个节点,与 `n` 相连。 +1. 将 $n$ 作为根节点,构建一棵多叉数。 +2. 从 $n$ 节点出发,如果一个小于 $n$ 的数刚好与 $n$ 相差一个平方数,则以该数为值构造一个节点,与 $n$ 相连。 -那么求解和为 `n` 的完全平方数的最小数量就变成了求解这棵树从根节点 `n` 到节点 `0` 的最短路径,或者说树的最小深度。 +那么求解和为 $n$ 的完全平方数的最小数量就变成了求解这棵树从根节点 $n$ 到节点 $0$ 的最短路径,或者说树的最小深度。 这个过程可以通过广度优先搜索来做。 ### 思路 1:广度优先搜索 -1. 定义 `visited` 为标记访问节点的 set 集合变量,避免重复计算。定义 `queue` 为存放节点的队列。使用 `count` 表示为树的最小深度,也就是和为 `n` 的完全平方数的最小数量。 -2. 首先,我们将 `n` 标记为已访问,即 `visited.add(n)`。并将其加入队列 `queue` 中,即 `queue.append(n)`。 -3. 令 `count` 加 `1`,表示最小深度加 `1`。然后依次将队列中的节点值取出。 -4. 对于取出的节点值 `value`,遍历可能出现的平方数(即遍历 $[1, \sqrt{value} + 1]$ 中的数)。 +1. 定义 $visited$ 为标记访问节点的 set 集合变量,避免重复计算。定义 $queue$ 为存放节点的队列。使用 $count$ 表示为树的最小深度,也就是和为 $n$ 的完全平方数的最小数量。 +2. 首先,我们将 $n$ 标记为已访问,即 `visited.add(n)`。并将其加入队列 $queue$ 中,即 `queue.append(n)`。 +3. 令 $count$ 加 $1$,表示最小深度加 $1$。然后依次将队列中的节点值取出。 +4. 对于取出的节点值 $value$,遍历可能出现的平方数(即遍历 $[1, \sqrt{value} + 1]$ 中的数)。 5. 每次从当前节点值减去一个平方数,并将减完的数加入队列。 - 1. 如果此时的数等于 `0`,则满足题意,返回当前树的最小深度。 - 2. 如果此时的数不等于 `0`,则将其加入队列,继续查找。 + 1. 如果此时的数等于 $0$,则满足题意,返回当前树的最小深度。 + 2. 如果此时的数不等于 $0$,则将其加入队列,继续查找。 ### 思路 1:代码 @@ -97,3 +97,57 @@ class Solution: - **时间复杂度**:$O(n \times \sqrt{n})$。 - **空间复杂度**:$O(n)$。 +### 思路 2:动态规划 + +我们可以将这道题转换为「完全背包问题」中恰好装满背包的方案数问题。 + +1. 将 $k = 1, 4, 9, 16, ...$ 看做是 $k$ 种物品,每种物品都可以无限次使用。 +2. 将 $n$ 看做是背包的装载上限。 +3. 这道题就变成了,从 $k$ 种物品中选择一些物品,装入装载上限为 $n$ 的背包中,恰好装满背包最少需要多少件物品。 + +###### 1. 划分阶段 + +按照当前背包的载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:从完全平方数中挑选一些数,使其和恰好凑成 $w$ ,最少需要多少个完全平方数。 + +###### 3. 状态转移方程 + +$dp[w] = min \lbrace dp[w], dp[w - num] + 1$ + +###### 4. 初始条件 + +- 恰好凑成和为 $0$,最少需要 $0$ 个完全平方数。 +- 默认情况下,在不使用完全平方数时,都不能恰好凑成和为 $w$ ,此时将状态值设置为一个极大值(比如 $n + 1$),表示无法凑成。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[w]$ 表示为:将物品装入装载上限为 $w$ 的背包中,恰好装满背包,最少需要多少件物品。 所以最终结果为 $dp[n]$。 + +1. 如果 $dp[n] \ne n + 1$,则说明:$dp[n]$ 为装入装载上限为 $n$ 的背包,恰好装满背包,最少需要的物品数量,则返回 $dp[n]$。 +2. 如果 $dp[n] = n + 1$,则说明:无法恰好装满背包,则返回 $-1$。因为 $n$ 肯定能由 $n$ 个 $1$ 组成,所以这种情况并不会出现。 + +### 思路 2:代码 + +```Python +class Solution: + def numSquares(self, n: int) -> int: + dp = [n + 1 for _ in range(n + 1)] + dp[0] = 0 + + for i in range(1, int(sqrt(n)) + 1): + num = i * i + for w in range(num, n + 1): + dp[w] = min(dp[w], dp[w - num] + 1) + + if dp[n] != n + 1: + return dp[n] + return -1 +``` + +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n \times \sqrt{n})$。 +- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0322. \351\233\266\351\222\261\345\205\221\346\215\242.md" "b/Solutions/0322. \351\233\266\351\222\261\345\205\221\346\215\242.md" index e7f873e5..75e0c6c9 100644 --- "a/Solutions/0322. \351\233\266\351\222\261\345\205\221\346\215\242.md" +++ "b/Solutions/0322. \351\233\266\351\222\261\345\205\221\346\215\242.md" @@ -82,7 +82,7 @@ class Solution: ### 思路 2:完全背包问题 -这道题可以转换为:有 $n$ 种不同的硬币,$coins[i]$ 表示第 $i$ 种硬币的面额,每种硬币可以无限次使用。请问凑成总金额为 $amount$ 的背包,最少需要多少硬币? +这道题可以转换为:有 $n$ 种不同的硬币,$coins[i]$ 表示第 $i$ 种硬币的面额,每种硬币可以无限次使用。请问恰好凑成总金额为 $amount$ 的背包,最少需要多少硬币? 与普通完全背包问题不同的是,这里求解的是最少硬币数量。我们可以改变一下「状态定义」和「状态转移方程」。 @@ -107,11 +107,15 @@ $dp[c] = \begin{cases} dp[c] & c < coins[i - 1] \cr min \lbrace dp[c], dp[c - co ###### 4. 初始条件 - 凑成总金额为 $0$ 的最少硬币数量为 $0$,即 $dp[0] = 0$。 +- 默认情况下,在不使用硬币时,都不能恰好凑成总金额为 $w$ ,此时将状态值设置为一个极大值(比如 $n + 1$),表示无法凑成。 ###### 5. 最终结果 根据我们之前定义的状态,$dp[c]$ 表示为:凑成总金额为 $c$ 的最少硬币数量。则最终结果为 $dp[amount]$。 +1. 如果 $dp[amount] \ne amount + 1$,则说明: $dp[amount]$ 为凑成金额 $amount$ 的最少硬币数量,则返回 $dp[amount]$。 +2. 如果 $dp[amount] = amount + 1$,则说明:无法凑成金额 $amount$,则返回 $-1$。 + ### 思路 2:代码 ```Python diff --git "a/Solutions/0377. \347\273\204\345\220\210\346\200\273\345\222\214 \342\205\243.md" "b/Solutions/0377. \347\273\204\345\220\210\346\200\273\345\222\214 \342\205\243.md" index c9747d6e..59e996b1 100644 --- "a/Solutions/0377. \347\273\204\345\220\210\346\200\273\345\222\214 \342\205\243.md" +++ "b/Solutions/0377. \347\273\204\345\220\210\346\200\273\345\222\214 \342\205\243.md" @@ -5,30 +5,99 @@ ## 题目大意 -给定一个由不同整数组成的数组 `nums` 和一个目标整数 `target`。要求:从 `nums` 中找出并返回总和为 `target` 的元素组合个数。 +**描述**:给定一个由不同整数组成的数组 $nums$ 和一个目标整数 $target$。 + +**要求**:从 $nums$ 中找出并返回总和为 $target$ 的元素组合个数。 + +**说明**: + +- 题目数据保证答案符合 32 位整数范围。 +- $1 \le nums.length \le 200$。 +- $1 \le nums[i] \le 1000$。 +- $nums$ 中的所有元素互不相同。 +- $1 \le target \le 1000$。 + +**示例**: + +- 示例 1: + +```Python +输入:nums = [1,2,3], target = 4 +输出:7 +解释: +所有可能的组合为: +(1, 1, 1, 1) +(1, 1, 2) +(1, 2, 1) +(1, 3) +(2, 1, 1) +(2, 2) +(3, 1) +请注意,顺序不同的序列被视作不同的组合。 +``` + +- 示例 2: + +```Python +输入:nums = [9], target = 3 +输出:0 +``` ## 解题思路 -完全背包问题。题目求解的是组合数。 +### 思路 1:动态规划 + +「完全背包问题求方案数」的变形。本题与「完全背包问题求方案数」不同点在于:方案中不同的物品顺序代表不同方案。 + +比如「完全背包问题求方案数」中,凑成总和为 $4$ 的方案 $[1, 3]$ 算 $1$ 种方案,但是在本题中 $[1, 3]$、$[3, 1]$ 算 $2$ 种方案数。 + +我们需要在考虑某一总和 $w$ 时,需要将 $nums$ 中所有元素都考虑到。对应到循环关系时,即将总和 $w$ 的遍历放到外侧循环,将 $nums$ 数组元素的遍历放到内侧循环,即: -动态规划的状态 `dp[i]` 可以表示为:凑成总和 `i` 的组合数。 +```Python +for w in range(target + 1): + for i in range(1, len(nums) + 1): + xxxx +``` + +###### 1. 划分阶段 + +按照总和进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:凑成总和 $w$ 的组合数。 + +###### 3. 状态转移方程 + +凑成总和为 $w$ 的组合数 = 「不使用当前 $nums[i - 1]$,只使用之前整数凑成和为 $w$ 的组合数」+「使用当前 $nums[i - 1]$ 凑成和为 $w - nums[i - 1]$ 的方案数」。即状态转移方程为:$dp[w] = dp[w] + dp[w - nums[i - 1]]$。 -动态规划的状态转移方程为:`dp[i] = dp[i] + dp[i - nums[j]]`,意思为凑成总和为 `i` 的组合数 = 「不使用当前 `nums[j]`,只使用之前整数凑成和为 `i` 的组合数」+「使用当前 `nums[j]` 凑成金额 `i - nums[j]` 的方案数」。 +###### 4. 初始条件 -最终输出 `dp[target]`。 +- 凑成总和 $0$ 的组合数为 $1$,即 $dp[0] = 1$。 -## 代码 +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[w]$ 表示为:凑成总和 $w$ 的组合数。 所以最终结果为 $dp[target]$。 + +### 思路 1:代码 ```Python class Solution: def combinationSum4(self, nums: List[int], target: int) -> int: + size = len(nums) dp = [0 for _ in range(target + 1)] dp[0] = 1 - size = len(nums) - for i in range(target + 1): - for j in range(size): - if i - nums[j] >= 0: - dp[i] += dp[i - nums[j]] + + for w in range(target + 1): + for i in range(1, size + 1): + if w >= nums[i - 1]: + dp[w] = dp[w] + dp[w - nums[i - 1]] + return dp[target] ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times target)$,其中 $n$ 为数组 $nums$ 的元素个数,$target$ 为目标整数。 +- **空间复杂度**:$O(target)$。 + diff --git "a/Solutions/0416. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" "b/Solutions/0416. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" index efe957f2..ded2f59d 100644 --- "a/Solutions/0416. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" +++ "b/Solutions/0416. \345\210\206\345\211\262\347\255\211\345\222\214\345\255\220\351\233\206.md" @@ -49,7 +49,7 @@ ###### 1. 划分阶段 -当前背包的装载重量上限进行阶段划分。 +当前背包的载重上限进行阶段划分。 ###### 2. 定义状态 @@ -57,11 +57,11 @@ ###### 3. 状态转移方程 -$dp[w] = \begin{cases} dp[w] & w < nums[i - 1] \cr max \lbrace dp[w], dp[w - nums[i - 1]] \rbrace & w \ge nums[i - 1] \end{cases}$ +$dp[w] = \begin{cases} dp[w] & w < nums[i - 1] \cr max \lbrace dp[w], \quad dp[w - nums[i - 1]] + nums[i - 1] \rbrace & w \ge nums[i - 1] \end{cases}$ ###### 4. 初始条件 -- 如果背包容量为 $0$,则无论选取什么元素,可以获得的元素和一定是 $0$,即 $dp[0] = 0$。 +- 无论背包载重上限为多少,只要不选择物品,可以获得的最大价值一定是 $0$,即 $dp[w] = 0,0 \le w \le W$。 ###### 5. 最终结果 @@ -73,7 +73,8 @@ $dp[w] = \begin{cases} dp[w] & w < nums[i - 1] \cr max \lbrace dp[w], dp[w - num ```Python class Solution: - def zeroOnePack(self, weight: [int], value: [int], W: int): + # 思路 2:动态规划 + 滚动数组优化 + def zeroOnePackMethod2(self, weight: [int], value: [int], W: int): size = len(weight) dp = [0 for _ in range(W + 1)] @@ -92,7 +93,7 @@ class Solution: return False target = sum_nums // 2 - return self.zeroOnePack(nums, nums, target) == target + return self.zeroOnePackMethod2(nums, nums, target) == target ``` ### 思路 1:复杂度分析 diff --git "a/Solutions/0494. \347\233\256\346\240\207\345\222\214.md" "b/Solutions/0494. \347\233\256\346\240\207\345\222\214.md" index e41bfc37..0a0c9fcb 100644 --- "a/Solutions/0494. \347\233\256\346\240\207\345\222\214.md" +++ "b/Solutions/0494. \347\233\256\346\240\207\345\222\214.md" @@ -5,9 +5,9 @@ ## 题目大意 -**描述**:给定一个整数数组 `nums` 和一个整数 `target`。数组长度不超过 `20`。向数组中每个整数前加 `+` 或 `-`。然后串联起来构造成一个表达式。 +**描述**:给定一个整数数组 $nums$ 和一个整数 $target$。数组长度不超过 $20$。向数组中每个整数前加 `+` 或 `-`。然后串联起来构造成一个表达式。 -**要求**:返回通过上述方法构造的、运算结果等于 `target` 的不同表达式数目。 +**要求**:返回通过上述方法构造的、运算结果等于 $target$ 的不同表达式数目。 **说明**: @@ -44,14 +44,14 @@ 使用深度优先搜索对每位数字进行 `+` 或者 `-`,具体步骤如下: -1. 定义从位置 `0`、和为 `0` 开始,到达数组尾部位置为止,和为 `target` 的方案数为 `dfs(0, 0)`,`size`。 -2. 下面从位置 `0`、和为 `0` 开始,以深度优先搜索遍历每个位置。 -3. 如果当前位置 `i` 到达最后一个位置 `size`: - 1. 如果和 `cur_sum` 等于目标和 `target`,则返回方案数 `1`。 - 2. 如果和 `cur_sum` 不等于目标和 `target`,则返回方案数 `0`。 -4. 递归搜索 `i + 1` 位置,和为 `cur_sum - nums[i]` 的方案数。 -5. 递归搜索 `i + 1` 位置,和为 `cur_sum + nums[i]` 的方案数。 -6. 将 4 ~ 5 两个方案数加起来就是当前位置 `i`、和为 `cur_sum` 的方案数,返回该方案数。 +1. 定义从位置 $0$、和为 $0$ 开始,到达数组尾部位置为止,和为 $target$ 的方案数为 `dfs(0, 0)`。 +2. 下面从位置 $0$、和为 $0$ 开始,以深度优先搜索遍历每个位置。 +3. 如果当前位置 $i$ 到达最后一个位置 $size$: + 1. 如果和 $cur\underline{}sum$ 等于目标和 $target$,则返回方案数 $1$。 + 2. 如果和 $cur\underline{}sum$ 不等于目标和 $target$,则返回方案数 $0$。 +4. 递归搜索 $i + 1$ 位置,和为 $cur\underline{}sum - nums[i]$ 的方案数。 +5. 递归搜索 $i + 1$ 位置,和为 $cur\underline{}sum + nums[i]$ 的方案数。 +6. 将 4 ~ 5 两个方案数加起来就是当前位置 $i$、和为 $cur\underline{}sum$ 的方案数,返回该方案数。 7. 最终方案数为 `dfs(0, 0)`,将其作为答案返回即可。 ### 思路 1:代码 @@ -82,18 +82,18 @@ class Solution: 在思路 1 中我们单独使用深度优先搜索对每位数字进行 `+` 或者 `-` 的方法超时了。所以我们考虑使用记忆化搜索的方式,避免进行重复搜索。 -这里我们使用哈希表 `table` 记录遍历过的位置 `i` 及所得到的的当前和`cur_sum` 下的方案数,来避免重复搜索。具体步骤如下: - -1. 定义从位置 `0`、和为 `0` 开始,到达数组尾部位置为止,和为 `target` 的方案数为 `dfs(0, 0)`。 -2. 下面从位置 `0`、和为 `0` 开始,以深度优先搜索遍历每个位置。 -3. 如果当前位置 `i` 遍历完所有位置: - 1. 如果和 `cur_sum` 等于目标和 `target`,则返回方案数 `1`。 - 2. 如果和 `cur_sum` 不等于目标和 `target`,则返回方案数 `0`。 -4. 如果当前位置 `i`、和为 `cur_sum` 之前记录过(即使用 `table` 记录过对应方案数),则返回该方案数。 -5. 如果当前位置 `i`、和为 `cur_sum` 之前没有记录过,则: - 1. 递归搜索 `i + 1` 位置,和为 `cur_sum - nums[i]` 的方案数。 - 2. 递归搜索 `i + 1` 位置,和为 `cur_sum + nums[i]` 的方案数。 - 3. 将上述两个方案数加起来就是当前位置 `i`、和为 `cur_sum` 的方案数,将其记录到哈希表 `table` 中,并返回该方案数。 +这里我们使用哈希表 $$table$$ 记录遍历过的位置 $i$ 及所得到的的当前和 $cur\underline{}sum$ 下的方案数,来避免重复搜索。具体步骤如下: + +1. 定义从位置 $0$、和为 $0$ 开始,到达数组尾部位置为止,和为 $target$ 的方案数为 `dfs(0, 0)`。 +2. 下面从位置 $0$、和为 $0$ 开始,以深度优先搜索遍历每个位置。 +3. 如果当前位置 $i$ 遍历完所有位置: + 1. 如果和 $cur\underline{}sum$ 等于目标和 $target$,则返回方案数 $1$。 + 2. 如果和 $cur\underline{}sum$ 不等于目标和 $target$,则返回方案数 $0$。 +4. 如果当前位置 $i$、和为 $cur\underline{}sum$ 之前记录过(即使用 $table$ 记录过对应方案数),则返回该方案数。 +5. 如果当前位置 $i$、和为 $cur\underline{}sum$ 之前没有记录过,则: + 1. 递归搜索 $i + 1$ 位置,和为 $cur\underline{}sum - nums[i]$ 的方案数。 + 2. 递归搜索 $i + 1$ 位置,和为 $cur\underline{}sum + nums[i]$ 的方案数。 + 3. 将上述两个方案数加起来就是当前位置 $i$、和为 $cur\underline{}sum$ 的方案数,将其记录到哈希表 $table$ 中,并返回该方案数。 6. 最终方案数为 `dfs(0, 0)`,将其作为答案返回即可。 ### 思路 2:代码 @@ -128,32 +128,32 @@ class Solution: ### 思路 3:动态规划 -假设数组中所有元素和为 `sum`,数组中所有符号为 `+` 的元素为 `sum_x`,符号为 `-` 的元素和为 `sum_y`。则 `target = sum_x - sum_y`。 +假设数组中所有元素和为 $sum$,数组中所有符号为 `+` 的元素为 $sum\underline{}x$,符号为 `-` 的元素和为 $sum\underline{}y$。则 $target = sum\underline{}x - sum\underline{}y$。 -而 `sum_x + sum_y = sum`。根据两个式子可以求出 `2 * sum_x = target + sum `,即 `sum_x = (target + sum) / 2`。 +而 $sum\underline{}x + sum\underline{}y = sum$。根据两个式子可以求出 $2 \times sum\underline{}x = target + sum$,即 $sum\underline{}x = (target + sum) / 2$。 -那么这道题就变成了,如何在数组中找到一个集合,使集合中元素和为 `(target + sum) / 2`。这就变为了求容量为 `(target + sum) / 2` 的 01 背包问题。 +那么这道题就变成了,如何在数组中找到一个集合,使集合中元素和为 $(target + sum) / 2$。这就变为了「0-1 背包问题」中求装满背包的方案数问题。 ###### 1. 定义状态 -定义状态 `dp[i]` 表示为:填满容量为 `i` 的背包,有 `dp[i]` 种方法。 +定义状态 $dp[i]$ 表示为:填满容量为 $i$ 的背包,有 $dp[i]$ 种方法。 ###### 2. 状态转移方程 -填满容量为 `i` 的背包的方法数来源于: +填满容量为 $i$ 的背包的方法数来源于: -1. 不使用当前 `num`:只使用之前元素填满容量为 `i` 的背包的方法数。 -2. 使用当前 `num`:填满容量 `i - num` 的包的方法数,再填入 `num` 的方法数。 +1. 不使用当前 $num$:只使用之前元素填满容量为 $i$ 的背包的方法数。 +2. 使用当前 $num$:填满容量 $i - num$ 的包的方法数,再填入 $num$ 的方法数。 -则动态规划的状态转移方程为:`dp[i] = dp[i] + dp[i - num]`。 +则动态规划的状态转移方程为:$dp[i] = dp[i] + dp[i - num]$。 ###### 3. 初始化 -初始状态下,默认填满容量为 `0` 的背包有 `1` 种办法。即 `dp[i] = 1`。 +初始状态下,默认填满容量为 $0$ 的背包有 $1$ 种办法(什么也不装)。即 $dp[i] = 1$。 ###### 4. 最终结果 -根据状态定义,最后输出 `dp[sise]`(即填满容量为 `size` 的背包,有 `dp[size]` 种方法)即可,其中 `size` 为数组 `nums` 的长度。 +根据状态定义,最后输出 $dp[sise]$(即填满容量为 $size$ 的背包,有 $dp[size]$ 种方法)即可,其中 $size$ 为数组 $nums$ 的长度。 ### 思路 3:代码 @@ -174,6 +174,5 @@ class Solution: ### 思路 3:复杂度分析 -- **时间复杂度**:$O(n)$。$n$ 为数组 $nums$ 的长度。 +- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $nums$ 的长度。 - **空间复杂度**:$O(n)$。 - diff --git "a/Solutions/0518. \351\233\266\351\222\261\345\205\221\346\215\242 II.md" "b/Solutions/0518. \351\233\266\351\222\261\345\205\221\346\215\242 II.md" index 4d8b6f33..1ca54d2a 100644 --- "a/Solutions/0518. \351\233\266\351\222\261\345\205\221\346\215\242 II.md" +++ "b/Solutions/0518. \351\233\266\351\222\261\345\205\221\346\215\242 II.md" @@ -1,27 +1,73 @@ -# [0518. 零钱兑换 II](https://leetcode.cn/problems/coin-change-2/) +# [0518. 零钱兑换 II](https://leetcode.cn/problems/coin-change-ii/) - 标签:数组、动态规划 - 难度:中等 ## 题目大意 -给定一个整数数组 `coins` 表示不同面额的硬币,另给一个整数 `amount` 表示总金额。 +**描述**:给定一个整数数组 $coins$ 表示不同面额的硬币,另给一个整数 $amount$ 表示总金额。 -要求:计算并返回可以凑成总金额的硬币组合数。如果无法凑出总金额,则返回`0`。 +**要求**:计算并返回可以凑成总金额的硬币方案数。如果无法凑出总金额,则返回 $0$。 -假定:每一种面额的硬币枚数为无限个。 +**说明**: + +- 每一种面额的硬币枚数为无限个。 +- $1 \le coins.length \le 300$。 +- $1 \le coins[i] \le 5000$。 +- $coins$ 中的所有值互不相同。 +- $0 \le amount \le 5000$。 + +**示例**: + +- 示例 1: + +```Python +输入:amount = 5, coins = [1, 2, 5] +输出:4 +解释:有四种方式可以凑成总金额: +5=5 +5=2+2+1 +5=2+1+1+1 +5=1+1+1+1+1 +``` + +- 示例 2: + +```Python +输入:amount = 3, coins = [2] +输出:0 +解释:只用面额 2 的硬币不能凑成总金额 3。 +``` ## 解题思路 -完全背包问题。「[322. 零钱兑换](https://leetcode.cn/problems/coin-change/)」中计算的是凑成总金额的最少硬币个数,而这道题计算的是凑成总金额的硬币组合数。 +### 思路 1:动态规划 + +这道题可以转换为:有 $n$ 种不同的硬币,$coins[i]$ 表示第 $i$ 种硬币的面额,每种硬币可以无限次使用。请问凑成总金额为 $amount$ 的背包,一共有多少种方案? -可以转换为有 n 枚不同的硬币,每种硬币可以无限次使用。凑成总金额为 `amount` 的背包,总共有多少种组合方式。 +这就变成了完全背包问题。「[322. 零钱兑换](https://leetcode.cn/problems/coin-change/)」中计算的是凑成总金额的最少硬币个数,而这道题计算的是凑成总金额的方案数。 -动态规划的状态 `dp[i]` 可以表示为:凑成总金额为 `i` 的组合数。 +###### 1. 划分阶段 -动态规划的状态转移方程为:`dp[i] = dp[i] + dp[i - coin]`,意思为凑成总金额为 `i` 的组合数 = 「不使用当前 `coin`,只使用之前硬币凑成金额 `i` 的组合数」+「使用当前 `coin` 凑成金额 `i - coin` 的方案数」。 +按照当前背包的载重上限进行阶段划分。 -## 代码 +###### 2. 定义状态 + +定义状态 $dp[i]$ 表示为:凑成总金额为 $i$ 的方案总数。 + +###### 3. 状态转移方程 + +凑成总金额为 $i$ 的方案数 = 「不使用当前 $coin$,只使用之前硬币凑成金额 $i$ 的方案数」+「使用当前 $coin$ 凑成金额 $i - coin$ 的方案数」。即状态转移方程为:$dp[i] = dp[i] + dp[i - coin]$。 + +###### 4. 初始条件 + +- 凑成总金额为 $0$ 的方案数为 $1$,即 $dp[0] = 1$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[i]$ 表示为:凑成总金额为 $i$ 的方案总数。 所以最终结果为 $dp[amount]$。 + +### 思路 1:代码 ```Python class Solution: @@ -36,3 +82,8 @@ class Solution: return dp[amount] ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times amount)$,其中 $n$ 为数组 $coins$ 的元素个数,$amount$ 为总金额。 +- **空间复杂度**:$O(amount)$。 + diff --git "a/Solutions/0766. \346\211\230\346\231\256\345\210\251\350\214\250\347\237\251\351\230\265.md" "b/Solutions/0766. \346\211\230\346\231\256\345\210\251\350\214\250\347\237\251\351\230\265.md" new file mode 100644 index 00000000..fb51e7b7 --- /dev/null +++ "b/Solutions/0766. \346\211\230\346\231\256\345\210\251\350\214\250\347\237\251\351\230\265.md" @@ -0,0 +1,69 @@ +# [0766. 托普利茨矩阵](https://leetcode.cn/problems/toeplitz-matrix/) + +- 标签:数组、矩阵 +- 难度:简单 + +## 题目大意 + +**描述**:给定一个 $m \times n$ 大小的矩阵 $matrix$。 + +**要求**:如果 $matrix$ 是托普利茨矩阵,则返回 `True`;否则返回 `False`。 + +**说明**: + +- **托普利茨矩阵**:矩阵上每一条由左上到右下的对角线上的元素都相同。 +- $m == matrix.length$。 +- $n == matrix[i].length$。 +- $1 \le m, n \le 20$。 +- $0 \le matrix[i][j] \le 99$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode.com/uploads/2020/11/04/ex1.jpg) + +```Python +输入:matrix = [[1,2,3,4],[5,1,2,3],[9,5,1,2]] +输出:true +解释: +在上述矩阵中, 其对角线为: +"[9]", "[5, 5]", "[1, 1, 1]", "[2, 2, 2]", "[3, 3]", "[4]"。 +各条对角线上的所有元素均相同, 因此答案是 True。 +``` + +- 示例 2: + +![](https://assets.leetcode.com/uploads/2020/11/04/ex2.jpg) + +```Python +输入:matrix = [[1,2],[2,2]] +输出:false +解释: +对角线 "[1, 2]" 上的元素不同。 +``` + +## 解题思路 + +### 思路 1:简单模拟 + +1. 两层循环遍历矩阵,依次判断矩阵当前位置 $(i, j)$ 上的值 $matrix[i][j]$ 与其左上角位置 $(i - 1, j - 1)$ 位置上的值 $matrix[i - 1][j - 1]$ 是否相等。 +2. 如果不相等,则返回 `False`。 +3. 遍历完,则返回 `True`。 + +### 思路 1:代码 + +```Python +class Solution: + def isToeplitzMatrix(self, matrix: List[List[int]]) -> bool: + for i in range(1, len(matrix)): + for j in range(1, len(matrix[0])): + if matrix[i][j] != matrix[i - 1][j - 1]: + return False + return True +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(m \times n)$,其中 $m$、$n$ 分别是矩阵 $matrix$ 的行数、列数。 +- **空间复杂度**:$O(m \times n)$。 diff --git "a/Solutions/1009. \345\215\201\350\277\233\345\210\266\346\225\264\346\225\260\347\232\204\345\217\215\347\240\201.md" "b/Solutions/1009. \345\215\201\350\277\233\345\210\266\346\225\264\346\225\260\347\232\204\345\217\215\347\240\201.md" index eba2b93e..929d2f12 100644 --- "a/Solutions/1009. \345\215\201\350\277\233\345\210\266\346\225\264\346\225\260\347\232\204\345\217\215\347\240\201.md" +++ "b/Solutions/1009. \345\215\201\350\277\233\345\210\266\346\225\264\346\225\260\347\232\204\345\217\215\347\240\201.md" @@ -5,7 +5,7 @@ ## 题目大意 -**描述**:给定一个十进制数 `n`。 +**描述**:给定一个十进制数 $n$。 **要求**:返回其二进制表示的反码对应的十进制整数。 @@ -35,11 +35,11 @@ ### 思路 1:模拟 -1. 将十进制数 `n` 转为二进制 `binary`。 -2. 遍历二进制 `binary` 的每一个数位 `digit`。 - 1. 如果 `digit` 为 `0`,则将其转为 `1`,存入答案 `res` 中。 - 2. 如果 `digit` 为 `1`,则将其转为 `0`,存入答案 `res` 中。 -3. 返回答案 `res`。 +1. 将十进制数 $n$ 转为二进制 $binary$。 +2. 遍历二进制 $binary$ 的每一个数位 $digit$。 + 1. 如果 $digit$ 为 $0$,则将其转为 $1$,存入答案 $res$ 中。 + 2. 如果 $digit$ 为 $1$,则将其转为 $0$,存入答案 $res$ 中。 +3. 返回答案 $res$。 ### 思路 1:代码 diff --git "a/Solutions/1037. \346\234\211\346\225\210\347\232\204\345\233\236\346\227\213\351\225\226.md" "b/Solutions/1037. \346\234\211\346\225\210\347\232\204\345\233\236\346\227\213\351\225\226.md" index f2d85de2..d79ff69d 100644 --- "a/Solutions/1037. \346\234\211\346\225\210\347\232\204\345\233\236\346\227\213\351\225\226.md" +++ "b/Solutions/1037. \346\234\211\346\225\210\347\232\204\345\233\236\346\227\213\351\225\226.md" @@ -5,14 +5,14 @@ ## 题目大意 -**描述**:给定一个数组 `points` ,其中 `points[i] = [xi, yi]` 表示平面上的一个点。 +**描述**:给定一个数组 $points$,其中 $points[i] = [xi, yi]$ 表示平面上的一个点。 **要求**:如果这些点构成一个回旋镖,则返回 `True`,否则,则返回 `False`。 **说明**: - **回旋镖**:定义为一组三个点,这些点各不相同且不在一条直线上。 -- `points.length == 3` +- $points.length == 3$。 - $points[i].length == 2$。 - $0 \le xi, yi \le 100$。 @@ -21,41 +21,41 @@ - 示例 1: ```Python +输入:points = [[1,1],[2,3],[3,2]] +输出:True ``` - 示例 2: ```Python +输入:points = [[1,1],[2,2],[3,3]] +输出:False ``` ## 解题思路 ### 思路 1: - 当三个点满足: (x2−x1)×(y3−y1)=(x3−x1)×(y2−y1)(x_2 - x_1) \times (y_3 - y_1) = (x_3 - x_1) \times (y_2 - y_1)(*x*2−*x*1)×(*y*3−*y*1)=(*x*3−*x*1)×(*y*2−*y*1) 时共线,否则为回旋镖。 +设三点坐标为 $A = (x1, y1)$,$B = (x2, y2)$,$C = (x3, y3)$,则向量 $\overrightarrow{AB} = (x2 - x1, y2 - y1)$,$\overrightarrow{BC} = (x3 - x2, y3 - y2)$。 -### 思路 1:代码 - -```Python - -``` - -### 思路 1:复杂度分析 +如果三点共线,则应满足:$\overrightarrow{AB} \times \overrightarrow{BC} = (x2 − x1) \times (y3 − y2) - (x3 − x2) \times (y2 − y1) = 0$。 -- **时间复杂度**: -- **空间复杂度**: +如果三点不共线,则应满足:$\overrightarrow{AB} \times \overrightarrow{BC} = (x2 − x1) \times (y3 − y2) - (x3 − x2) \times (y2 − y1) \ne 0$。 -### 思路 2: - - - -### 思路 2:代码 +### 思路 1:代码 ```Python - +class Solution: + def isBoomerang(self, points: List[List[int]]) -> bool: + x1, y1 = points[0] + x2, y2 = points[1] + x3, y3 = points[2] + cross1 = (x2 - x1) * (y3 - y2) + cross2 = (x3 - x2) * (y2 - y1) + return cross1 - cross2 != 0 ``` -### 思路 2:复杂度分析 +### 思路 1:复杂度分析 -- **时间复杂度**: -- **空间复杂度**: \ No newline at end of file +- **时间复杂度**:$O(1)$。 +- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1049. \346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217 II.md" "b/Solutions/1049. \346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217 II.md" index 36c7c8a8..6fe9423e 100644 --- "a/Solutions/1049. \346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217 II.md" +++ "b/Solutions/1049. \346\234\200\345\220\216\344\270\200\345\235\227\347\237\263\345\244\264\347\232\204\351\207\215\351\207\217 II.md" @@ -5,38 +5,91 @@ ## 题目大意 -有一堆石头,用整数数组 `stones` 表示,其中 `stones[i]` 表示第 `i` 块石头的重量。 +**描述**:有一堆石头,用整数数组 $stones$ 表示,其中 $stones[i]$ 表示第 $i$​ 块石头的重量。每一回合,从石头中选出任意两块石头,将这两块石头一起粉碎。假设石头的重量分别为 $x$ 和 $y$。且 $x \le y$,则结果如下: -每一回合,从石头中选出任意两块石头,将这两块石头一起粉碎。假设石头的重量分别为 `x` 和 `y`。且 `x ≤ y`,则结果如下: +- 如果 $x = y$,则两块石头都会被完全粉碎; +- 如果 $x < y$,则重量为 $x$ 的石头被完全粉碎,而重量为 $y$ 的石头新重量为 $y - x$。 -- 如果 `x == y`,则两块石头都会被完全粉碎; -- 如果 `x < y`,则重量为 `x` 的石头被完全粉碎,而重量为 `y` 的石头新重量为 `y - x`。 +**要求**:最后,最多只会剩下一块石头,返回此石头的最小可能重量。如果没有石头剩下,则返回 $0$。 -最后,最多只会剩下一块石头,返回此石头的最小可能重量。如果没有石头剩下,则返回 0。 +**说明**: + +- $1 \le stones.length \le 30$。 +- $1 \le stones[i] \le 100$。 + +**示例**: + +- 示例 1: + +```Python +输入:stones = [2,7,4,1,8,1] +输出:1 +解释: +组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1], +组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1], +组合 2 和 1,得到 1,所以数组转化为 [1,1,1], +组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。 +``` + +- 示例 2: + +```Python +输入:stones = [31,26,33,21,40] +输出:5 +``` ## 解题思路 +### 思路 1:动态规划 + 选取两块石头,重新放回去的重量是两块石头的差值绝对值。重新放回去的石头还会进行选取,然后进行粉碎,直到最后只剩一块或者不剩石头。 这个问题其实可以转化为:把一堆石头尽量平均的分成两对,求两堆石头重量差的最小值。 这就和「[0416. 分割等和子集](https://leetcode.cn/problems/partition-equal-subset-sum/)」有点相似。两堆石头的重量要尽可能的接近数组总数量和的一半。 -进一步可以变为:假设石头总重量和为 `sum`,则问题为将一堆石头放进容量最多为 `sum / 2` 的背包中,获得的最大价值为 `max_weight`(即其中一堆石子的重量),则另一堆石子的重量为 `sum - max_weight`。则两者的差值为 `sum - 2 * max_weight`,即为答案。 +进一步可以变为:「0-1 背包问题」。 + +1. 假设石头总重量和为 $sum$,将一堆石头放进载重上限为 $sum / 2$ 的背包中,获得的最大价值为 $max\underline{}weight$(即其中一堆石子的重量)。另一堆石子的重量为 $sum - max\underline{}weight$。 +2. 则两者的差值为 $sum - 2 \times max\underline{}weight$,即为答案。 -## 代码 +###### 1. 划分阶段 + +按照石头的序号进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:将石头放入载重上限为 $w$ 的背包中可以获得的最大价值。 + +###### 3. 状态转移方程 + +$dp[w] = max \lbrace dp[w], dp[w - stones[i - 1]] + stones[i - 1] \rbrace$。 + +###### 4. 初始条件 + +- 无论背包载重上限为多少,只要不选择石头,可以获得的最大价值一定是 $0$,即 $dp[w] = 0,0 \le w \le W$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[w]$ 表示为:将石头放入载重上限为 $w$ 的背包中可以获得的最大价值,即第一堆石头的价值为 $dp[size]$,第二堆石头的价值为 $sum - dp[size]$,最终答案为两者的差值,即 $sum - dp[size] \times 2$。 + +### 思路 1:代码 ```Python class Solution: def lastStoneWeightII(self, stones: List[int]) -> int: - size = 15010 - dp = [0 for _ in range(size)] - sum_stones = sum(stones) - target = sum_stones // 2 - for i in range(len(stones)): - for j in range(target, stones[i]-1, -1): - dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]) - - return sum_stones - dp[target] * 2 + W = 1500 + size = len(stones) + dp = [0 for _ in range(W + 1)] + target = sum(stones) // 2 + for i in range(1, size + 1): + for w in range(target, stones[i - 1] - 1, -1): + dp[w] = max(dp[w], dp[w - stones[i - 1]] + stones[i - 1]) + + return sum(stones) - dp[target] * 2 ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times W)$,其中 $n$ 为数组 $stones$ 的元素个数,$W$ 为数组 $stones$ 中元素和的一半。 +- **空间复杂度**:$O(W)$。 diff --git "a/Solutions/1184. \345\205\254\344\272\244\347\253\231\351\227\264\347\232\204\350\267\235\347\246\273.md" "b/Solutions/1184. \345\205\254\344\272\244\347\253\231\351\227\264\347\232\204\350\267\235\347\246\273.md" new file mode 100644 index 00000000..a8e8f46a --- /dev/null +++ "b/Solutions/1184. \345\205\254\344\272\244\347\253\231\351\227\264\347\232\204\350\267\235\347\246\273.md" @@ -0,0 +1,67 @@ +# 题目相关 + +- 标签:数组 +- 难度:简单 + +## 题目大意 + +**描述**:环形公交路线上有 $n$ 个站,序号为 $0 \sim n - 1$。给定一个数组 $distance$ 表示每一对相邻公交站之间的距离,其中 $distance[i]$ 表示编号为 $i$ 的车站与编号为 $(i + 1) \mod n$ 的车站之间的距离。再给定乘客的出发点编号 $start$ 和目的地编号 $destination$。 + +**要求**:返回乘客从出发点 $start$ 到目的地 $destination$ 之间的最短距离。 + +**说明**: + +- $1 \le n \le 10^4$。 +- $distance.length == n$。 +- $0 \le start, destination < n$。 +- $0 \le distance[i] \le 10^4$。 + +**示例**: + +- 示例 1: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/08/untitled-diagram-1.jpg) + +```Python +输入:distance = [1,2,3,4], start = 0, destination = 1 +输出:1 +解释:公交站 0 和 1 之间的距离是 1 或 9,最小值是 1。 +``` + +- 示例 2: + +![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2019/09/08/untitled-diagram-1-1.jpg) + +```Python +输入:distance = [1,2,3,4], start = 0, destination = 2 +输出:3 +解释:公交站 0 和 2 之间的距离是 3 或 7,最小值是 3。 +``` + +## 解题思路 + +### 思路 1:简单模拟 + +1. 因为 $start$ 和 $destination$ 的先后顺序不影响结果,为了方便计算,我们先令 $start \le destination$。 +2. 遍历数组 $distance$,计算出 $[start, destination]$ 之间的距离和 $dist$。 +3. 计算出环形路线中 $[destination, start]$ 之间的距离和为 $sum(distance) - dist$。 +4. 比较 $2 \sim 3$ 中两个距离的大小,将距离最小值作为答案返回。 + +### 思路 1:代码 + +```Python +class Solution: + def distanceBetweenBusStops(self, distance: List[int], start: int, destination: int) -> int: + start, destination = min(start, destination), max(start, destination) + dist = 0 + for i in range(len(distance)): + if start <= i < destination: + dist += distance[i] + + return min(dist, sum(distance) - dist) +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(1)$。 diff --git "a/Solutions/1422. \345\210\206\345\211\262\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206.md" "b/Solutions/1422. \345\210\206\345\211\262\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206.md" new file mode 100644 index 00000000..1f76bd12 --- /dev/null +++ "b/Solutions/1422. \345\210\206\345\211\262\345\255\227\347\254\246\344\270\262\347\232\204\346\234\200\345\244\247\345\276\227\345\210\206.md" @@ -0,0 +1,75 @@ +# [1422. 分割字符串的最大得分](https://leetcode.cn/problems/maximum-score-after-splitting-a-string/) + +- 标签:字符串 +- 难度:简单 + +## 题目大意 + +**描述**:给定一个由若干 $0$ 和 $1$ 组成的字符串。将字符串分割成两个非空子字符串的得分为:左子字符串中 $0$ 的数量 + 右子字符串中 $1$ 的数量。 + +**要求**:计算并返回该字符串分割成两个非空子字符串(即左子字符串和右子字符串)所能获得的最大得分。 + +**说明**: + +- $2 \le s.length \le 500$。 +- 字符串 $s$ 仅由字符 $0$ 和 $1$ 组成。 + +**示例**: + +- 示例 1: + +```Python +输入:s = "011101" +输出:5 +解释: +将字符串 s 划分为两个非空子字符串的可行方案有: +左子字符串 = "0" 且 右子字符串 = "11101",得分 = 1 + 4 = 5 +左子字符串 = "01" 且 右子字符串 = "1101",得分 = 1 + 3 = 4 +左子字符串 = "011" 且 右子字符串 = "101",得分 = 1 + 2 = 3 +左子字符串 = "0111" 且 右子字符串 = "01",得分 = 1 + 1 = 2 +左子字符串 = "01110" 且 右子字符串 = "1",得分 = 2 + 1 = 3 +``` + +- 示例 2: + +```Python +输入:s = "00111" +输出:5 +解释:当 左子字符串 = "00" 且 右子字符串 = "111" 时,我们得到最大得分 = 2 + 3 = 5 +``` + +## 解题思路 + +### 思路 1:前缀和 + +1. 遍历字符串 $s$,使用前缀和数组来记录每个前缀子字符串中 $1$ 的个数。 +2. 再次遍历字符串 $s$,枚举每个分割点,利用前缀和数组计算出当前分割出的左子字符串中 $1$ 的个数与右子字符串中 $0$ 的个数,并计算当前得分,然后更新最大得分。 +3. 返回最大得分作为答案。 + +### 思路 1:代码 + +```Python +class Solution: + def maxScore(self, s: str) -> int: + size = len(s) + one_cnts = [0 for _ in range(size + 1)] + + for i in range(1, size + 1): + if s[i - 1] == '1': + one_cnts[i] = one_cnts[i - 1] + 1 + else: + one_cnts[i] = one_cnts[i - 1] + + ans = 0 + for i in range(1, size): + left_score = i - one_cnts[i] + right_score = one_cnts[size] - one_cnts[i] + ans = max(ans, left_score + right_score) + + return ans +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$,其中 $n$ 为字符串 $s$ 的长度。 +- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/1449. \346\225\260\344\275\215\346\210\220\346\234\254\345\222\214\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\346\234\200\345\244\247\346\225\260\345\255\227.md" "b/Solutions/1449. \346\225\260\344\275\215\346\210\220\346\234\254\345\222\214\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\346\234\200\345\244\247\346\225\260\345\255\227.md" new file mode 100644 index 00000000..0adcd960 --- /dev/null +++ "b/Solutions/1449. \346\225\260\344\275\215\346\210\220\346\234\254\345\222\214\344\270\272\347\233\256\346\240\207\345\200\274\347\232\204\346\234\200\345\244\247\346\225\260\345\255\227.md" @@ -0,0 +1,119 @@ +# [1449. 数位成本和为目标值的最大数字](https://leetcode.cn/problems/form-largest-integer-with-digits-that-add-up-to-target/) + +- 标签:数组、动态规划 +- 难度:困难 + +## 题目大意 + +**描述**:给定一个整数数组 $cost$ 和一个整数 $target$。现在从 `""` 开始,不断通过以下规则得到一个新的整数: + +1. 给当前结果添加一个数位($i + 1$)的成本为 $cost[i]$($cost$ 数组下标从 $0$ 开始)。 +2. 总成本必须恰好等于 $target$。 +3. 添加的数位中没有数字 $0$。 + +**要求**:找到按照上述规则可以得到的最大整数。 + +**说明**: + +- 由于答案可能会很大,请你以字符串形式返回。 +- 如果按照上述要求无法得到任何整数,请你返回 `"0"`。 +- $cost.length == 9$。 +- $1 \le cost[i] \le 5000$。 +- $1 \le target \le 5000$。 + +**示例**: + +- 示例 1: + +```Python +输入:cost = [4,3,2,5,6,7,2,5,5], target = 9 +输出:"7772" +解释:添加数位 '7' 的成本为 2 ,添加数位 '2' 的成本为 3 。所以 "7772" 的代价为 2*3+ 3*1 = 9 。 "977" 也是满足要求的数字,但 "7772" 是较大的数字。 + 数字 成本 + 1 -> 4 + 2 -> 3 + 3 -> 2 + 4 -> 5 + 5 -> 6 + 6 -> 7 + 7 -> 2 + 8 -> 5 + 9 -> 5 +``` + +- 示例 2: + +```Python +输入:cost = [7,6,5,5,5,6,8,7,8], target = 12 +输出:"85" +解释:添加数位 '8' 的成本是 7 ,添加数位 '5' 的成本是 5 。"85" 的成本为 7 + 5 = 12。 + 数字 成本 + 1 -> 7 + 2 -> 6 + 3 -> 5 + 4 -> 5 + 5 -> 5 + 6 -> 6 + 7 -> 8 + 8 -> 7 + 9 -> 8 +``` + +## 解题思路 + +把每个数位($1 \sim 9$)看做是一件物品,$cost[i]$ 看做是物品的重量,一共有无数件物品可以使用,$target$ 看做是背包的载重上限,得到的最大整数可以看做是背包的最大价值。那么问题就变为了「完全背包问题」中的「恰好装满背包的最大价值问题」。 + +因为答案可能会很大,要求以字符串形式返回。这里我们可以直接令 $dp[w]$ 为字符串形式,然后定义一个 `def maxInt(a, b):` 方法用于判断两个字符串代表的数字大小。 + +### 思路 1:动态规划 + +###### 1. 划分阶段 + +按照背包载重上限进行阶段划分。 + +###### 2. 定义状态 + +定义状态 $dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中,恰好装满背包的情况下,能装入背包的最大整数。 + +###### 3. 状态转移方程 + +$dp[w] = maxInt(dp[w], str(i) + dp[w - cost[i - 1]])$ + +###### 4. 初始条件 + +1. 只有载重上限为 $0$ 的背包,在不放入物品时,能够恰好装满背包(有合法解),此时背包所含物品的最大价值为空字符串,即 `dp[0] = ""`。 +2. 其他载重上限下的背包,在放入物品的时,都不能恰好装满背包(都没有合法解),此时背包所含物品的最大价值属于未定义状态,值为自定义字符 `"#"`,即 ,`dp[w] = "#"`,$0 \le w \le target$。 + +###### 5. 最终结果 + +根据我们之前定义的状态,$dp[w]$ 表示为:将物品装入一个最多能装重量为 $w$ 的背包中,恰好装满背包的情况下,能装入背包的最大价值总和。 所以最终结果为 $dp[target]$。 + +### 思路 1:代码 + +```Python +class Solution: + def largestNumber(self, cost: List[int], target: int) -> str: + def maxInt(a, b): + if len(a) == len(b): + return max(a, b) + if len(a) > len(b): + return a + return b + + size = len(cost) + dp = ["#" for _ in range(target + 1)] + dp[0] = "" + + for i in range(1, size + 1): + for w in range(cost[i - 1], target + 1): + if dp[w - cost[i - 1]] != "#": + dp[w] = maxInt(dp[w], str(i) + dp[w - cost[i - 1]]) + if dp[target] == "#": + return "0" + return dp[target] +``` + +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n \times target)$,其中 $n$ 为数组 $cost$ 的元素个数,$target$ 为所给整数。 +- **空间复杂度**:$O(target)$。 diff --git "a/Solutions/1876. \351\225\277\345\272\246\344\270\272\344\270\211\344\270\224\345\220\204\345\255\227\347\254\246\344\270\215\345\220\214\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" "b/Solutions/1876. \351\225\277\345\272\246\344\270\272\344\270\211\344\270\224\345\220\204\345\255\227\347\254\246\344\270\215\345\220\214\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" index 12b5e584..bdf74c79 100644 --- "a/Solutions/1876. \351\225\277\345\272\246\344\270\272\344\270\211\344\270\224\345\220\204\345\255\227\347\254\246\344\270\215\345\220\214\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" +++ "b/Solutions/1876. \351\225\277\345\272\246\344\270\272\344\270\211\344\270\224\345\220\204\345\255\227\347\254\246\344\270\215\345\220\214\347\232\204\345\255\220\345\255\227\347\254\246\344\270\262.md" @@ -5,16 +5,16 @@ ## 题目大意 -**描述**:给定搞一个字符串 `s`。 +**描述**:给定搞一个字符串 $s$。 -**要求**:返回 `s` 中长度为 $3$ 的好子字符串的数量。如果相同的好子字符串出现多次,则每一次都应该被记入答案之中。 +**要求**:返回 $s$ 中长度为 $3$ 的好子字符串的数量。如果相同的好子字符串出现多次,则每一次都应该被记入答案之中。 **说明**: - **子字符串**:指的是一个字符串中连续的字符序列。 - **好子字符串**:如果一个字符串中不含有任何重复字符,则称这个字符串为好子字符串。 - $1 \le s.length \le 100$。 -- `s` 只包含小写英文字母。 +- $s$ 只包含小写英文字母。 **示例**: @@ -40,7 +40,7 @@ ### 思路 1:模拟 -1. 遍历长度为 3 的子字符串。 +1. 遍历字符串 $s$ 中长度为 3 的子字符串。 2. 判断子字符串中的字符是否有重复。如果没有重复,则答案进行计数。 3. 遍历完输出答案。 diff --git "a/Solutions/1903. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\346\234\200\345\244\247\345\245\207\346\225\260.md" "b/Solutions/1903. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\346\234\200\345\244\247\345\245\207\346\225\260.md" index c90e0d59..790dadd3 100644 --- "a/Solutions/1903. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\346\234\200\345\244\247\345\245\207\346\225\260.md" +++ "b/Solutions/1903. \345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\346\234\200\345\244\247\345\245\207\346\225\260.md" @@ -5,15 +5,15 @@ ## 题目大意 -**描述**:给定一个字符串 `num`,表示一个大整数。 +**描述**:给定一个字符串 $num$,表示一个大整数。 -**要求**:在字符串 `num` 的所有非空子字符串中找出值最大的奇数,并以字符串形式返回。如果不存在奇数,则返回一个空字符串 `""`。 +**要求**:在字符串 $num$ 的所有非空子字符串中找出值最大的奇数,并以字符串形式返回。如果不存在奇数,则返回一个空字符串 `""`。 **说明**: - **子字符串**:指的是字符串中一个连续的字符序列。 - $1 \le num.length \le 10^5$ -- `num` 仅由数字组成且不含前导零。 +- $num$ 仅由数字组成且不含前导零。 **示例**: @@ -37,7 +37,10 @@ ### 思路 1:贪心算法 -1. +如果某个数 $x$ 为奇数,则 $x$ 末尾位上的数字一定为奇数。那么我们只需要在末尾为奇数的字符串中考虑最大的奇数即可。显而易见的是,最大的奇数一定是长度最长的那个。所以我们只需要逆序遍历字符串,找到第一个奇数,从整个字符串开始位置到该奇数位置所代表的整数,就是最大的奇数。具体步骤如下: + +1. 逆序遍历字符串 $s$。 +2. 找到第一个奇数位置 $i$,则 $num[0: i + 1]$ 为最大的奇数,将其作为答案返回。 ### 思路 1:代码 diff --git "a/Solutions/1941. \346\243\200\346\237\245\346\230\257\345\220\246\346\211\200\346\234\211\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\347\233\270\345\220\214.md" "b/Solutions/1941. \346\243\200\346\237\245\346\230\257\345\220\246\346\211\200\346\234\211\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\347\233\270\345\220\214.md" index 8564dd20..64af0ae6 100644 --- "a/Solutions/1941. \346\243\200\346\237\245\346\230\257\345\220\246\346\211\200\346\234\211\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\347\233\270\345\220\214.md" +++ "b/Solutions/1941. \346\243\200\346\237\245\346\230\257\345\220\246\346\211\200\346\234\211\345\255\227\347\254\246\345\207\272\347\216\260\346\254\241\346\225\260\347\233\270\345\220\214.md" @@ -5,14 +5,14 @@ ## 题目大意 -**描述**:给定一个字符串 `s`。如果 `s` 中出现过的所有字符的出现次数相同,那么我们称字符串 `s` 是「好字符串」。 +**描述**:给定一个字符串 $s$。如果 $s$ 中出现过的所有字符的出现次数相同,那么我们称字符串 $s$ 是「好字符串」。 -**要求**:如果 `s` 是一个好字符串,则返回 `True`,否则返回 `False`。 +**要求**:如果 $s$ 是一个好字符串,则返回 `True`,否则返回 `False`。 **说明**: - $1 \le s.length \le 1000$。 -- `s` 只包含小写英文字母。 +- $s$ 只包含小写英文字母。 **示例**: @@ -37,7 +37,7 @@ ### 思路 1:哈希表 -1. 使用哈希表记录每个字符的频数。 +1. 使用哈希表记录字符串 $s$ 中每个字符的频数。 2. 然后遍历哈希表中的键值对,检测每个字符的频数是否相等。 3. 如果发现频数不相等,则直接返回 `False`。 4. 如果检查完发现所有频数都相等,则返回 `True`。