mirror of
https://github.com/krahets/hello-algo.git
synced 2026-05-11 11:07:14 +08:00
build
This commit is contained in:
@@ -7,7 +7,7 @@ status: new
|
||||
|
||||
!!! question
|
||||
|
||||
给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$ 、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。每个物品只能选择一次,**但可以选择物品的一部分,价值根据选择的重量比例计算**,问在不超过背包容量下背包中物品的最大价值。
|
||||
给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。每个物品只能选择一次,**但可以选择物品的一部分,价值根据选择的重量比例计算**,问在不超过背包容量下背包中物品的最大价值。
|
||||
|
||||

|
||||
|
||||
@@ -26,7 +26,7 @@ status: new
|
||||
|
||||
### 1. 贪心策略确定
|
||||
|
||||
最大化背包内物品总价值,**本质上是要最大化单位重量下的物品价值**。由此便可推出图 15-5 所示的贪心策略:
|
||||
最大化背包内物品总价值,**本质上是要最大化单位重量下的物品价值**。由此便可推出图 15-5 所示的贪心策略。
|
||||
|
||||
1. 将物品按照单位价值从高到低进行排序。
|
||||
2. 遍历所有物品,**每轮贪心地选择单位价值最高的物品**。
|
||||
|
||||
@@ -7,7 +7,7 @@ status: new
|
||||
|
||||
贪心算法是一种常见的解决优化问题的算法,其基本思想是在问题的每个决策阶段,都选择当前看起来最优的选择,即贪心地做出局部最优的决策,以期望获得全局最优解。贪心算法简洁且高效,在许多实际问题中都有着广泛的应用。
|
||||
|
||||
贪心算法和动态规划都常用于解决优化问题。它们有一些相似之处,比如都依赖最优子结构性质。两者的不同点在于:
|
||||
贪心算法和动态规划都常用于解决优化问题。它们之间存在一些相似之处,比如都依赖最优子结构性质,但工作原理是不同的。
|
||||
|
||||
- 动态规划会根据之前阶段的所有决策来考虑当前决策,并使用过去子问题的解来构建当前子问题的解。
|
||||
- 贪心算法不会重新考虑过去的决策,而是一路向前地进行贪心选择,不断缩小问题范围,直至问题被解决。
|
||||
@@ -237,7 +237,7 @@ status: new
|
||||
|
||||
也就是说,对于零钱兑换问题,贪心算法无法保证找到全局最优解,并且有可能找到非常差的解。它更适合用动态规划解决。
|
||||
|
||||
一般情况下,贪心算法适用于以下两类问题:
|
||||
一般情况下,贪心算法适用于以下两类问题。
|
||||
|
||||
1. **可以保证找到最优解**:贪心算法在这种情况下往往是最优选择,因为它往往比回溯、动态规划更高效。
|
||||
2. **可以找到近似最优解**:贪心算法在这种情况下也是可用的。对于很多复杂问题来说,寻找全局最优解是非常困难的,能以较高效率找到次优解也是非常不错的。
|
||||
@@ -246,7 +246,7 @@ status: new
|
||||
|
||||
那么问题来了,什么样的问题适合用贪心算法求解呢?或者说,贪心算法在什么情况下可以保证找到最优解?
|
||||
|
||||
相较于动态规划,贪心算法的使用条件更加苛刻,其主要关注问题的两个性质:
|
||||
相较于动态规划,贪心算法的使用条件更加苛刻,其主要关注问题的两个性质。
|
||||
|
||||
- **贪心选择性质**:只有当局部最优选择始终可以导致全局最优解时,贪心算法才能保证得到最优解。
|
||||
- **最优子结构**:原问题的最优解包含子问题的最优解。
|
||||
@@ -265,13 +265,13 @@ status: new
|
||||
|
||||
## 15.1.3 贪心解题步骤
|
||||
|
||||
贪心问题的解决流程大体可分为三步:
|
||||
贪心问题的解决流程大体可分为以下三步。
|
||||
|
||||
1. **问题分析**:梳理与理解问题特性,包括状态定义、优化目标和约束条件等。这一步在回溯和动态规划中都有涉及。
|
||||
2. **确定贪心策略**:确定如何在每一步中做出贪心选择。这个策略能够在每一步减小问题的规模,并最终能解决整个问题。
|
||||
3. **正确性证明**:通常需要证明问题具有贪心选择性质和最优子结构。这个步骤可能需要使用到数学证明,例如归纳法或反证法等。
|
||||
|
||||
确定贪心策略是求解问题的核心步骤,但实施起来可能并不容易,原因包括:
|
||||
确定贪心策略是求解问题的核心步骤,但实施起来可能并不容易,主要包含以下原因。
|
||||
|
||||
- **不同问题的贪心策略的差异较大**。对于许多问题来说,贪心策略都比较浅显,我们通过一些大概的思考与尝试就能得出。而对于一些复杂问题,贪心策略可能非常隐蔽,这种情况就非常考验个人的解题经验与算法能力了。
|
||||
- **某些贪心策略具有较强的迷惑性**。当我们满怀信心设计好贪心策略,写出解题代码并提交运行,很可能发现部分测试样例无法通过。这是因为设计的贪心策略只是“部分正确”的,上文介绍的零钱兑换就是个典型案例。
|
||||
@@ -282,7 +282,7 @@ status: new
|
||||
|
||||
## 15.1.4 贪心典型例题
|
||||
|
||||
贪心算法常常应用在满足贪心选择性质和最优子结构的优化问题中,以下是一些典型的贪心算法问题:
|
||||
贪心算法常常应用在满足贪心选择性质和最优子结构的优化问题中,以下列举了一些典型的贪心算法问题。
|
||||
|
||||
1. **硬币找零问题**:在某些硬币组合下,贪心算法总是可以得到最优解。
|
||||
2. **区间调度问题**:假设你有一些任务,每个任务在一段时间内进行,你的目标是完成尽可能多的任务。如果每次都选择结束时间最早的任务,那么贪心算法就可以得到最优解。
|
||||
|
||||
@@ -29,16 +29,15 @@ $$
|
||||
|
||||
### 1. 贪心策略确定
|
||||
|
||||
这道题还有更高效率的解法。如图 15-8 所示,现选取一个状态 $[i, j]$ ,其满足索引 $i < j$ 且高度 $ht[i] < ht[j]$ ,即 $i$ 为短板、 $j$ 为长板。
|
||||
这道题还有更高效率的解法。如图 15-8 所示,现选取一个状态 $[i, j]$ ,其满足索引 $i < j$ 且高度 $ht[i] < ht[j]$ ,即 $i$ 为短板、$j$ 为长板。
|
||||
|
||||

|
||||
|
||||
<p align="center"> 图 15-8 初始状态 </p>
|
||||
|
||||
如图 15-9 所示,**若此时将长板 $j$ 向短板 $i$ 靠近,则容量一定变小**。这是因为在移动长板 $j$ 后:
|
||||
如图 15-9 所示,**若此时将长板 $j$ 向短板 $i$ 靠近,则容量一定变小**。
|
||||
|
||||
- 宽度 $j-i$ 肯定变小。
|
||||
- 高度由短板决定,因此高度只可能不变( $i$ 仍为短板)或变小(移动后的 $j$ 成为短板)。
|
||||
这是因为在移动长板 $j$ 后,宽度 $j-i$ 肯定变小;而高度由短板决定,因此高度只可能不变( $i$ 仍为短板)或变小(移动后的 $j$ 成为短板)。
|
||||
|
||||

|
||||
|
||||
@@ -54,10 +53,10 @@ $$
|
||||
|
||||
图 15-11 展示了贪心策略的执行过程。
|
||||
|
||||
1. 初始状态下,指针 $i$ , $j$ 分列与数组两端。
|
||||
1. 初始状态下,指针 $i$ 和 $j$ 分列与数组两端。
|
||||
2. 计算当前状态的容量 $cap[i, j]$ ,并更新最大容量。
|
||||
3. 比较板 $i$ 和 板 $j$ 的高度,并将短板向内移动一格。
|
||||
4. 循环执行第 `2.` , `3.` 步,直至 $i$ 和 $j$ 相遇时结束。
|
||||
4. 循环执行第 `2.` 和 `3.` 步,直至 $i$ 和 $j$ 相遇时结束。
|
||||
|
||||
=== "<1>"
|
||||

|
||||
@@ -92,7 +91,7 @@ $$
|
||||
|
||||
代码循环最多 $n$ 轮,**因此时间复杂度为 $O(n)$** 。
|
||||
|
||||
变量 $i$ , $j$ , $res$ 使用常数大小额外空间,**因此空间复杂度为 $O(1)$** 。
|
||||
变量 $i$、$j$、$res$ 使用常数大小额外空间,**因此空间复杂度为 $O(1)$** 。
|
||||
|
||||
=== "Java"
|
||||
|
||||
|
||||
@@ -41,13 +41,13 @@ $$
|
||||
|
||||
如图 15-14 所示,当 $n \geq 4$ 时,切分出一个 $2$ 后乘积会变大,**这说明大于等于 $4$ 的整数都应该被切分**。
|
||||
|
||||
**贪心策略一**:如果切分方案中包含 $\geq 4$ 的因子,那么它就应该被继续切分。最终的切分方案只应出现 $1$ , $2$ , $3$ 这三种因子。
|
||||
**贪心策略一**:如果切分方案中包含 $\geq 4$ 的因子,那么它就应该被继续切分。最终的切分方案只应出现 $1$、$2$、$3$ 这三种因子。
|
||||
|
||||

|
||||
|
||||
<p align="center"> 图 15-14 切分导致乘积变大 </p>
|
||||
|
||||
接下来思考哪个因子是最优的。在 $1$ , $2$ , $3$ 这三个因子中,显然 $1$ 是最差的,因为 $1 \times (n-1) < n$ 恒成立,即切分出 $1$ 反而会导致乘积减小。
|
||||
接下来思考哪个因子是最优的。在 $1$、$2$、$3$ 这三个因子中,显然 $1$ 是最差的,因为 $1 \times (n-1) < n$ 恒成立,即切分出 $1$ 反而会导致乘积减小。
|
||||
|
||||
如图 15-15 所示,当 $n = 6$ 时,有 $3 \times 3 > 2 \times 2 \times 2$ 。**这意味着切分出 $3$ 比切分出 $2$ 更优**。
|
||||
|
||||
@@ -57,9 +57,9 @@ $$
|
||||
|
||||
<p align="center"> 图 15-15 最优切分因子 </p>
|
||||
|
||||
总结以上,可推出贪心策略:
|
||||
总结以上,可推出以下贪心策略。
|
||||
|
||||
1. 输入整数 $n$ ,从其不断地切分出因子 $3$ ,直至余数为 $0$ , $1$ , $2$ 。
|
||||
1. 输入整数 $n$ ,从其不断地切分出因子 $3$ ,直至余数为 $0$、$1$、$2$ 。
|
||||
2. 当余数为 $0$ 时,代表 $n$ 是 $3$ 的倍数,因此不做任何处理。
|
||||
3. 当余数为 $2$ 时,不继续划分,保留之。
|
||||
4. 当余数为 $1$ 时,由于 $2 \times 2 > 1 \times 3$ ,因此应将最后一个 $3$ 替换为 $2$ 。
|
||||
@@ -278,12 +278,12 @@ $$
|
||||
|
||||
<p align="center"> 图 15-16 最大切分乘积的计算方法 </p>
|
||||
|
||||
**时间复杂度取决于编程语言的幂运算的实现方法**。以 Python 为例,常用的幂计算函数有:
|
||||
**时间复杂度取决于编程语言的幂运算的实现方法**。以 Python 为例,常用的幂计算函数有三种。
|
||||
|
||||
- 运算符 `**` 和函数 `pow()` 的时间复杂度均为 $O(\log a)$ 。
|
||||
- 函数 `math.pow()` 内部调用 C 语言库的 `pow()` 函数,其执行浮点取幂,时间复杂度为 $O(1)$ 。
|
||||
|
||||
变量 $a$ , $b$ 使用常数大小的额外空间,**因此空间复杂度为 $O(1)$** 。
|
||||
变量 $a$ 和 $b$ 使用常数大小的额外空间,**因此空间复杂度为 $O(1)$** 。
|
||||
|
||||
### 3. 正确性证明
|
||||
|
||||
|
||||
Reference in New Issue
Block a user