代码随想录训练营day38| 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯...

@TOC


前言

代码随想录算法训练营day38


一、Leetcode 509. 斐波那契数

1.题目

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n) 。

示例 1:

输入:n = 2 输出:1 解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:

输入:n = 3 输出:2 解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:

输入:n = 4 输出:3 解释:F(4) = F(3) + F(2) = 2 + 1 = 3

提示:

0 <= n <= 30

来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/fibonacci-number

2.解题思路

方法一:动态规划

斐波那契数的边界条件是 F(0)=0F(0)=0 和 F(1)=1F(1)=1。当 n>1n>1 时,每一项的和都等于前两项的和,因此有如下递推关系:

F(n)=F(n−1)+F(n−2)F(n)=F(n−1)+F(n−2)

由于斐波那契数存在递推关系,因此可以使用动态规划求解。动态规划的状态转移方程即为上述递推关系,边界条件为 F(0)F(0) 和 F(1)F(1)。

根据状态转移方程和边界条件,可以得到时间复杂度和空间复杂度都是 O(n)O(n) 的实现。由于 F(n)F(n) 只和 F(n−1)F(n−1) 与 F(n−2)F(n−2) 有关,因此可以使用「滚动数组思想」把空间复杂度优化成 O(1)O(1)。如下的代码中给出的就是这种实现。

3.代码实现

```java class Solution { public int fib(int n) { if (n < 2) { return n; } int p = 0, q = 0, r = 1; for (int i = 2; i <= n; ++i) { p = q; q = r; r = p + q; } return r; } }

```

二、Leetcode 70. 爬楼梯

1.题目

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例 1:

输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶

示例 2:

输入:n = 3 输出:3 解释:有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶

提示:

1 <= n <= 45

来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/climbing-stairs

2.解题思路

方法一:动态规划

思路和算法

我们用 f(x)f(x) 表示爬到第 xx 级台阶的方案数,考虑最后一步可能跨了一级台阶,也可能跨了两级台阶,所以我们可以列出如下式子:

f(x)=f(x−1)+f(x−2)f(x)=f(x−1)+f(x−2)

它意味着爬到第 xx 级台阶的方案数是爬到第 x−1x−1 级台阶的方案数和爬到第 x−2x−2 级台阶的方案数的和。很好理解,因为每次只能爬 11 级或 22 级,所以 f(x)f(x) 只能从 f(x−1)f(x−1) 和 f(x−2)f(x−2) 转移过来,而这里要统计方案总数,我们就需要对这两项的贡献求和。

以上是动态规划的转移方程,下面我们来讨论边界条件。我们是从第 00 级开始爬的,所以从第 00 级爬到第 00 级我们可以看作只有一种方案,即 f(0)=1f(0)=1;从第 00 级到第 11 级也只有一种方案,即爬一级,f(1)=1f(1)=1。这两个作为边界条件就可以继续向后推导出第 nn 级的正确结果。我们不妨写几项来验证一下,根据转移方程得到 f(2)=2f(2)=2,f(3)=3f(3)=3,f(4)=5f(4)=5,……,我们把这些情况都枚举出来,发现计算的结果是正确的。

我们不难通过转移方程和边界条件给出一个时间复杂度和空间复杂度都是 O(n)O(n) 的实现,但是由于这里的 f(x)f(x) 只和 f(x−1)f(x−1) 与 f(x−2)f(x−2) 有关,所以我们可以用「滚动数组思想」把空间复杂度优化成 O(1)O(1)。下面的代码中给出的就是这种实现

3.代码实现

```java class Solution { public int climbStairs(int n) { int p = 0, q = 0, r = 1; for (int i = 1; i <= n; ++i) { p = q; q = r; r = p + q; } return r; } }

```

三、452. 用最少数量的箭引爆气球

1.题目

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。

一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。

给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。

示例 1:

输入:points = [[10,16],[2,8],[1,6],[7,12]] 输出:2 解释:气球可以用2支箭来爆破: -在x = 6处射出箭,击破气球[2,8]和[1,6]。 -在x = 11处发射箭,击破气球[10,16]和[7,12]。

示例 2:

输入:points = [[1,2],[3,4],[5,6],[7,8]] 输出:4 解释:每个气球需要射出一支箭,总共需要4支箭。

示例 3:

输入:points = [[1,2],[2,3],[3,4],[4,5]] 输出:2 解释:气球可以用2支箭来爆破: - 在x = 2处发射箭,击破气球[1,2]和[2,3]。 - 在x = 4处射出箭,击破气球[3,4]和[4,5]。

提示:

1 <= points.length <= 105
points[i].length == 2
-231 <= xstart < xend <= 231 - 1

来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons

2.解题思路

方法一:排序 + 贪心

思路与算法

我们首先随机地射出一支箭,再看一看是否能够调整这支箭地射出位置,使得我们可以引爆更多数目的气球。

fig1

如图 1-1 所示,我们随机射出一支箭,引爆了除红色气球以外的所有气球。我们称所有引爆的气球为「原本引爆的气球」,其余的气球为「原本完好的气球」。可以发现,如果我们将这支箭的射出位置稍微往右移动一点,那么我们就有机会引爆红色气球,如图 1-2 所示。

那么我们最远可以将这支箭往右移动多远呢?我们唯一的要求就是:原本引爆的气球只要仍然被引爆就行了。这样一来,我们找出原本引爆的气球中右边界位置最靠左的那一个,将这支箭的射出位置移动到这个右边界位置,这也是最远可以往右移动到的位置:如图 1-3 所示,只要我们再往右移动一点点,这个气球就无法被引爆了。

为什么「原本引爆的气球仍然被引爆」是唯一的要求?别急,往下看就能看到其精妙所在。

因此,我们可以断定:

一定存在一种最优(射出的箭数最小)的方法,使得每一支箭的射出位置都恰好对应着某一个气球的右边界。

这是为什么?我们考虑任意一种最优的方法,对于其中的任意一支箭,我们都通过上面描述的方法,将这支箭的位置移动到它对应的「原本引爆的气球中最靠左的右边界位置」,那么这些原本引爆的气球仍然被引爆。这样一来,所有的气球仍然都会被引爆,并且每一支箭的射出位置都恰好位于某一个气球的右边界了。

有了这样一个有用的断定,我们就可以快速得到一种最优的方法了。考虑所有气球中右边界位置最靠左的那一个,那么一定有一支箭的射出位置就是它的右边界(否则就没有箭可以将其引爆了)。当我们确定了一支箭之后,我们就可以将这支箭引爆的所有气球移除,并从剩下未被引爆的气球中,再选择右边界位置最靠左的那一个,确定下一支箭,直到所有的气球都被引爆。

我们可以写出如下的伪代码:

let points := [[x(0), y(0)], [x(1), y(1)], ... [x(n-1), y(n-1)]],表示 n 个气球 let burst := [false] * n,表示每个气球是否被引爆 let ans := 1,表示射出的箭数

将 points 按照 y 值(右边界)进行升序排序

while burst 中还有 false 值 do let i := 最小的满足 burst[i] = false 的索引 i for j := i to n-1 do if x(j) <= y(i) then burst[j] := true end if end for end while

return ans

这样的做法在最坏情况下时间复杂度是 O(n2)O(n2),即这 nn 个气球对应的区间互不重叠,whilewhile 循环需要执行 nn 次。那么我们如何继续进行优化呢?

事实上,在内层的 jj 循环中,当我们遇到第一个不满足 x(j)≤y(i)x(j)≤y(i) 的 jj 值,就可以直接跳出循环,并且这个 y(j)y(j) 就是下一支箭的射出位置。为什么这样做是对的呢?我们考虑某一支箭的索引 itit​ 以及它的下一支箭的索引 jtjt​,对于索引在 jtjt​ 之后的任意一个可以被 itit​ 引爆的气球,记索引为 j0j0​,有:

x(j0)≤y(it)x(j0​)≤y(it​)

由于 y(it)≤y(jt)y(it​)≤y(jt​) 显然成立,那么

x(j0)≤y(jt)x(j0​)≤y(jt​)

也成立,也就是说:当前这支箭在索引 jtjt​(第一个无法引爆的气球)之后所有可以引爆的气球,下一支箭也都可以引爆。因此我们就证明了其正确性,也就可以写出如下的伪代码:

let points := [[x(0), y(0)], [x(1), y(1)], ... [x(n-1), y(n-1)]],表示 n 个气球 let pos := y(0),表示当前箭的射出位置 let ans := 1,表示射出的箭数

将 points 按照 y 值(右边界)进行升序排序

for i := 1 to n-1 do if x(i) > pos then ans := ans + 1 pos := y(i) end if end for

return ans

这样就可以将计算答案的时间从 O(n2)O(n2) 降低至 O(n)O(n)。

3.代码实现

```java class Solution { public int findMinArrowShots(int[][] points) { if (points.length == 0) { return 0; } Arrays.sort(points, new Comparator

```

猜你喜欢

转载自blog.csdn.net/HHX_01/article/details/131285433