牛牛的快递、最小花费爬楼梯、数组中两个字符串的最小距离
文章目录
- 牛牛的快递、最小花费爬楼梯、数组中两个字符串的最小距离
-
- [1. 牛牛的快递(BC64)](#1. 牛牛的快递(BC64))
- [2. 最小花费爬楼梯](#2. 最小花费爬楼梯)
-
- 问题描述
- 题目解法
-
- 解法一:动态规划(空间优化)
- [解法二:标准 DP 数组(更直观)](#解法二:标准 DP 数组(更直观))
- 错误记录
- 要点总结
- [3. 数组中两个字符串的最小距离](#3. 数组中两个字符串的最小距离)
-
- 问题描述
- 题目解法
-
- 解法一:一次遍历,分情况更新
- [解法二:一次遍历 + 大值初始化(简化版)](#解法二:一次遍历 + 大值初始化(简化版))
- 要点总结
1. 牛牛的快递(BC64)
🔗 题目链接 | 难度:🟢 Easy
问题描述
text
牛牛正在寄快递。快递在 1kg 以内的按起步价 20 元计算,超出部分按每 kg 1 元计算,
不足 1kg 部分按 1kg 计算。如果加急的话要额外付 5 元。请问牛牛总共要支付多少快递费。
输入描述:
第一行输入一个单精度浮点数 a 和一个字符 b:
- a 表示快递的重量
- b 表示是否加急,'y' 表示加急,'n' 表示不加急
输出描述:
输出牛牛总共要支付的快递费用
示例 1:
输入:1.5 y
输出:26
示例 2:
输入:0.7 n
输出:20
题目解法
解法一:模拟
思路:
模拟就行,没有复杂的思路。关键点:超出1kg的部分向上取整。
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
double w;
char ch;
cin >> w >> ch;
int res = 20;
// 超出1kg的部分,向上取整(不足1kg按1kg算)
if (w > 1) {
int extra = (int)ceil(w - 1);
res += extra;
}
if (ch == 'y')
res += 5;
cout << res;
return 0;
}
简化写法 :直接用 (int)(w - 0.999999...) 或 (int)(w - 1 + 1e-6) + 1 实现向上取整。
Info: 代码审查
- 代码逻辑正确,无明显错误。
- 复杂度:时间 O(1),空间 O(1)。
- 建议 :
float精度有限,可改用double,对 w 接近 1.0 的边界情况更可靠。
要点总结
- 模拟题,关键在超出 1kg 部分向上取整 (
ceil或整数运算) - 注意
float精度问题,推荐用double或(int)((w - 1) + 0.999999) - 加急费单独判断
ch == 'y'即可
常用数学函数速查(<cmath>)
| 函数 | 头文件 | 作用 | 示例(x=2.7, y=-2.7) |
|---|---|---|---|
ceil(x) |
<cmath> |
向 +∞ 取整 | ceil(2.7)=3.0, ceil(-2.7)=-2.0 |
floor(x) |
<cmath> |
向 -∞ 取整 | floor(2.7)=2.0, floor(-2.7)=-3.0 |
trunc(x) |
<cmath> |
向 0 取整(截断小数) | trunc(2.7)=2.0, trunc(-2.7)=-2.0 |
round(x) |
<cmath> |
四舍五入 | round(2.7)=3.0, round(-2.7)=-3.0 |
abs(x) |
<cstdlib> |
整数绝对值 | abs(-3)=3 |
fabs(x) |
<cmath> |
浮点绝对值 | fabs(-2.7)=2.7 |
fmod(x, y) |
<cmath> |
浮点数取余(符号同 x) | fmod(5.3, 2.0)=1.3 |
2. 最小花费爬楼梯
🔗 题目链接 | 难度:🟢 Easy
问题描述
text
给定一个整数数组 cost,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用,
下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请计算并返回达到楼梯顶部(即下标 n,超过数组长度)的最低花费。
示例 1:
输入:3
10 2 20
输出:6
解释:从索引1出发,支付2到索引2,再支付4到顶部(或直接支付4从索引1跳到顶部)
示例 2:
输入:10 15 20
输出:15
解释:从索引0出发,支付10到达索引1,再支付15到达顶部,总花费25;或从索引1出发支付15直接到顶部
提示:可以将问题理解为:从第i个台阶出发所需的最小花费 = cost[i] + min(从i+1出发的最小花费, 从i+2出发的最小花费)
Warning: 示例说明
本笔记中示例的数字(
10 2 20→6)与 NowCoder 官方示例不一致。官方示例为[2, 5, 20]→5和[1, 100, 1, 1, 1, 90, 1, 1, 80, 1]→6。代码逻辑正确,不影响实际通过。
题目解法
解法一:动态规划(空间优化)
思路:
最小花费爬楼梯是一个典型的动态规划 问题。核心思想:
- 定义 dp[i] = 到达第 i 个台阶的最小花费
- 状态转移:dp[i] = cost[i] + min(dp[i-1], dp[i-2])
- 答案:min(dp[n-1], dp[n-2])(可从索引0或1出发)
由于只需前两个状态,可用三个变量滚动更新,避免使用数组。
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
if (n == 1) {
int num;
cin >> num;
cout << num;
return 0;
}
int prev2 = 0; // dp[i-2],到达i-2台阶的最小花费
int prev1 = 0; // dp[i-1],到达i-1台阶的最小花费
int cur = 0; // dp[i],当前台阶的最小花费
while (n--) {
int cost;
cin >> cost;
// dp[i] = cost[i] + min(dp[i-1], dp[i-2])
cur = cost + min(prev1, prev2);
// 滚动更新:prev2 <- prev1, prev1 <- cur
prev2 = prev1;
prev1 = cur;
}
cout << min(prev1, prev2);
return 0;
}
解法二:标准 DP 数组(更直观)
cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> dp(n + 1, 0);
for (int i = 0; i < n; ++i) {
int cost;
cin >> cost;
if (i == 0) dp[i] = cost;
else if (i == 1) dp[i] = min(cost, dp[0]);
else dp[i] = cost + min(dp[i-1], dp[i-2]);
}
cout << min(dp[n-1], dp[n-2]);
}
错误记录
错误一:变量滚动逻辑混乱
需要注意的是要想清楚移动之前和移动之后的数据分别是什么,不然会乱了套。记住:prev2 是更早的,prev1 是较新的。
要点总结
- 状态定义 :
dp[i]= 到达第 i 个台阶并支付 costi 的最小花费 - 状态转移 :
dp[i] = cost[i] + min(dp[i-1], dp[i-2]) - 起点选择:可从下标 0 或 1 开始(对应初始 prev 值为 0)
- 空间优化:三个变量滚动更新,O(1) 空间
- 答案取法 :
min(dp[n-1], dp[n-2])表示从最后一级或倒数第二级跳到顶部
3. 数组中两个字符串的最小距离
🔗 题目链接 | 难度:🟡 Medium
问题描述
text
给定一个字符串数组 strs,再给定两个字符串 str1 和 str2,返回在 strs 中 str1 和 str2
的最小距离。如果 str1 或 str2 为 null,或不在 strs 中,返回 -1。
输入描述:
第一行输入一个整数 n(1 ≤ n ≤ 10^5),代表数组 strs 的长度。
第二行有两个字符串,分别代表 str1 和 str2。
接下来 n 行,每行一个字符串,代表数组 strs(保证所有字符串长度 ≤ 10)。
输出描述:
输出一行,包含一个整数,代表返回的值。
示例 1:
输入:
1
CD AB
CD
输出:-1
示例 2:
输入:
5
QWER 666
QWER
1234
qwe
666
QWER
输出:1
题目解法
解法一:一次遍历,分情况更新
思路:
也是模拟,想清楚每一步做什么,分清楚不同的情况:我们以str1为例
- 当找到
str1时,之前有没有找到过str1,因为str1可能会出现多次;- 如果之前出现了,就要判断之前之前是否出现过
str2- 如果之前出现过
str2,就要判断当前str1与str2的距离与上一个str1与str2的距离哪个更小,取最小的值 - 没有的话直接更新
str1的最新位置即可,因为新位置一定比原来的位置与st2的距离更小
- 如果之前出现过
- 如果没有出现,也要判断之前之前是否出现过
str2- 如果之前出现过
str2,就要更新距离 - 没有的话直接更新
str1的最新位置即可
- 如果之前出现过
- 如果之前出现了,就要判断之前之前是否出现过
这里是否可以再简化一点,不判断str1和str2第一次出现的情况(取消finds1 != -1的判断),把这种情况也用最短距离判断res = min(res, finds1 - finds2),此时就需要注意finds1和finds2初始化的大小就不能为0了,题目中说明了n有最大值,所以这个方法是可行的:将finds1和finds2初始化为大于n的值,最后在结果输出时判断一次
cpp
int main() {
int arrsize;
cin >> arrsize;
if(arrsize <= 1)
{
cout << -1;
return 0;
}
string str1, str2;
cin >> str1 >> str2;
int finds1 = -1, finds2 = -1;
int res = -1;
for(int i = 0; i < arrsize; ++i)
{
string tmp;
cin >> tmp;
if(tmp == str1)
{
if(finds1 != -1)//已经有str1
{
finds1 = i;
if(finds2 != -1)//如果也有str2
//判断最短距离
res = min(res, finds1 - finds2);
}
else
{
finds1 = i;
if(finds2 != -1)
res = finds1 - finds2;
}
}
else if(tmp == str2)
{
if(finds2 != -1)
{
finds2 = i;
if(finds1 != -1)
res = min(res, finds2 - finds1);
}
else
{
finds2 = i;
if(finds1 != -1)
res = finds2 - finds1;
}
}
}
cout << res;
}
Bug: 代码审查
- 边界情况 :当
str1 == str2时,else if导致第二个字符串永远无法匹配,结果始终为 -1。牛客网测试用例可能不包含此情况。
解法二:一次遍历 + 大值初始化(简化版)
思路:
将 finds1 和 finds2 初始化为大于 n 的值(如 n + 1),res 也初始化为 n + 1。这样遍历时统一用 min(res, i - finds2) 或 min(res, i - finds1),无需区分第一次出现的情况。最后判断 res 是否仍为初始值即可。
时间复杂度:O(n),空间复杂度:O(1)
cpp
int main() {
int n;
string str1, str2;
cin >> n;
cin >> str1 >> str2;
int finds1 = n + 1, finds2 = n + 1;
int res = n + 1;
for (int i = 0; i < n; ++i) {
string tmp;
cin >> tmp;
if (tmp == str1) {
if (finds2 != n + 1)
res = min(res, i - finds2);
finds1 = i;
} else if (tmp == str2) {
if (finds1 != n + 1)
res = min(res, i - finds1);
finds2 = i;
}
}
cout << (res == n + 1 ? -1 : res);
return 0;
}
要点总结
- 核心思想:一次遍历,贪心记录最近出现的 str1 和 str2 下标
- 每遇到 str1 或 str2,检查另一个是否出现过,若出现过则更新最小距离
- 不需要数组或哈希表,只需两个变量记录最新位置即可
- 用大值初始化可简化代码(避免第一次出现的特殊判断)