【daily practice】三道经典题一次讲透:快递费模拟、爬楼梯 DP、字符串最小距离

牛牛的快递、最小花费爬楼梯、数组中两个字符串的最小距离

文章目录


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 206)与 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,就要判断当前str1str2的距离与上一个str1str2的距离哪个更小,取最小的值
      • 没有的话直接更新str1的最新位置即可,因为新位置一定比原来的位置与st2的距离更小
    • 如果没有出现,也要判断之前之前是否出现过str2
      • 如果之前出现过 str2,就要更新距离
      • 没有的话直接更新str1的最新位置即可

这里是否可以再简化一点,不判断str1str2第一次出现的情况(取消finds1 != -1的判断),把这种情况也用最短距离判断res = min(res, finds1 - finds2),此时就需要注意finds1finds2初始化的大小就不能为0了,题目中说明了n有最大值,所以这个方法是可行的:将finds1finds2初始化为大于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。牛客网测试用例可能不包含此情况。
解法二:一次遍历 + 大值初始化(简化版)

思路:

​ 将 finds1finds2 初始化为大于 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,检查另一个是否出现过,若出现过则更新最小距离
  • 不需要数组或哈希表,只需两个变量记录最新位置即可
  • 用大值初始化可简化代码(避免第一次出现的特殊判断)
相关推荐
Wang's Blog6 小时前
PostgreSQL笔记36:执行计划基础解读与优化器成本模型
数据库·笔记·postgresql
u0103055276 小时前
昇腾AI赋能安卓智能助手
人工智能·笔记
Wang's Blog6 小时前
PostgreSQL笔记35:索引常见问题诊断与解决方案全景解析
数据库·笔记·postgresql
_Narcissus_7 小时前
枚举和模拟算法笔记
c语言·数据结构·c++·笔记·算法·模拟·枚举
Fa_Mian_Tuan8 小时前
最小生成树之Kruskal算法(超详细原理+完整可运行C语言代码)
c语言·开发语言·数据结构·笔记·算法·图论
️学习的小王8 小时前
NumPy完整学习笔记:ndarray数组详解、创建、索引切片、常用函数与运算
笔记·学习·numpy
柳鲲鹏9 小时前
强烈反对买入机器人股票
笔记
老当益壮梁奶奶9 小时前
Linux软件编程学习笔记(二)Linux文件IO编程入门:从fopen到fgetc
linux·c语言·c++·笔记·学习
sunoo-2299 小时前
C 语言文件 IO 全攻略:从基础函数到实战踩坑(BMP 读取 + 词典查询)
linux·c语言·前端·笔记·vscode·学习
蒸蒸yyyyzwd9 小时前
cpp选手秋招学习笔记 day12
笔记·学习·oracle