模拟 - #介绍 #题解

系列文章目录

  1. leetcode - 双指针问题_leetcode双指针题目-优快云博客
  2. leetcode - 滑动窗口问题集_leetcode 滑动窗口-优快云博客
  3. 高效掌握二分查找:从基础到进阶-优快云博客
  4. leetcode - 前缀和_前缀和的题目-优快云博客
  5. 动态规划 - 斐波那契数列模型-优快云博客
  6. 位运算 #常见位运算总结 #题解-优快云博客

文章目录

目录

系列文章目录

文章目录

前言

一、什么是模拟

二、题解

1、题1 替换所有的问号:

解法:模拟

参考代码1:

参考代码2:

2、题目2 提莫攻击:

解法:模拟

参考代码:

3、题3 Z 字形变换 :

解法一:模拟

参考代码:

解法二:找规律

参考代码:

4、题4 外观数列 :

解法:模拟实现

参考代码:

5、题五 数青蛙 :

解法:模拟

参考代码:

总结


前言

路漫漫其修远兮,吾将上下而求索;


大家可以自己先做一下喔~

1576. 替换所有的问号 - 力扣(LeetCode)

495. 提莫攻击 - 力扣(LeetCode)

6. Z 字形变换 - 力扣(LeetCode)

38. 外观数列 - 力扣(LeetCode)

1419. 数青蛙 - 力扣(LeetCode)


一、什么是模拟

通俗来讲,模拟就是照葫芦画瓢即根据题干已经告诉我们的思路来转换成代码来解决这些问题;

模拟题的特点:这类题比较简单,考察的就是我们的代码能力

做模拟题的一般过程:

  • 1、先模拟算法流程;千万不能凭空想象,需要实际在草稿纸上过一遍思路,不然容易忽略细节问题;
  • 2、将思路流程转换成代码;

二、题解

1、题1 替换所有的问号:

1576. 替换所有的问号 - 力扣(LeetCode)

解法:模拟

题意:将字符串中所有为 '?' 的字符转换成小写字母,并且还需要保证字符串中没有连续重复的字符;

遍历 + 替换 : 从前往后扫描该数组,如若遇到问号则从 'a' 到 'z' 依次尝试放在该位置是否合适(不与相邻的字符相同)

细节处理:当问号在第一个的时候,只需要与其后一个字符进行比较;当问号在最后一个的时候,只需要与其一个字符进行比较;

参考代码1:

    string modifyString(string s) 
    {
        //遍历+替换
        int n = s.size();
        for(int i = 0;i<n;i++)
        {
            if(s[i]=='?')
            {
                //替换
                for(int j = 'a';j<='z';j++)
                {
                    //判断
                    //细节处理,第一个以及最后一个
                    if(i==0 || i==n-1) 
                    {
                        if((i==0 && s[i+1]!=j) || (i==n-1 && s[i-1]!=j))
                      {  s[i] = j;
                        break;}
                    }
                    else//中间的
                    {
                        if(s[i-1]!=j && s[i+1]!=j)
                        {
                            s[i] = j;
                            break;
                        }
                    }
                }
            }
        }
        return s;
    }

代码1有点冗余,我们可以巧妙利用 && 与 || 的逻辑进行优化

参考代码2:

    string modifyString(string s) 
    {
        //遍历+替换
        int n = s.size();
        for(int i = 0;i<n;i++)
        {
            if(s[i]=='?')
            {
                //替换
                for(int j = 'a';j<='z';j++)
                {
                    //判断
                    if((i==0 || s[i-1]!=j) && (i==n-1 || s[i+1]!=j))
                    { 
                        s[i] = j;
                        break;
                    }
    
                }
            }
        }
        return s;
    }

2、题目2 提莫攻击:

495. 提莫攻击 - 力扣(LeetCode)

解法:模拟

讲解算法原理:当两个时间点(假设为a、b点)的差值大于等于中毒时间(假设中毒实现为d)的时候,说明在 a 点受到攻击之后,接下来在 b 前中毒之前可以恢复,那么中毒状态为 d秒;

而当两个时间点的查找小于 d 的时候,说明在a 点受到攻击之后接下来在 b 点中毒之前不可以恢复,那么此时中毒状态为 b-a ;

细节处理:如果最后一次中毒,直接加上 d 秒即可;

参考代码:
    int findPoisonedDuration(vector<int>& timeSeries, int duration) 
    {
        int ret = 0;
        for(int i = 1;i<timeSeries.size();i++)
        {
            int tmp = timeSeries[i]-timeSeries[i-1];
            if(tmp >= duration) ret += duration;
            else ret+=tmp;
        }
        //处理最后一次中毒
        return ret + duration;
    }

3、题3 Z 字形变换 :

6. Z 字形变换 - 力扣(LeetCode)

解法一:模拟

首先将字符串按照题干中的要求放入矩阵之中,然后再一行一行地从矩阵中读取数据;

Q:矩阵地大小如何确定?

  • 行,开辟字符串的长度肯定是没有问题的;列,题干中会给;

参考代码:
    string convert(string s, int numRows) 
    {
        //完全按照题干的思路来实现
        vector<vector<char>> nums(numRows,vector<char>(s.size() ,' '));//char 类型的二维数组。用空格初始化
        //将数据放入nums 中
        //先放第一列
        int i  = 0, j = 0;//nums 中的指针
        int sz = 0;//s 中的指针
        while(s[sz]!='\0'  && i < numRows)
        {
            nums[i][j] = s[sz++];
            i++;
        }
        j++;//第二列
        while(s[sz]!='\0' )
        {
            //判断是需要填一列,还是填一个
            //numRows 可能为1,需要先判断一下
            int n = numRows-1;
            if(n==0)//填一列,即nusRows 个
            {
                i = 0;
                while(s[sz]!='\0'  && i<numRows)
                {
                    nums[i++][j] = s[sz];
                    sz++;
                }
                j++;
            }
           
            while(n--)
            {
                if(n==0)//填一列
                {
                    i = 0;
                    while(s[sz]!='\0' && i<numRows)
                    {
                        nums[i++][j] = s[sz];
                        sz++;
                    }
                    j++;
                }
                else//填一个
                {
                   if(s[sz]!='\0') nums[n][j++] = s[sz++];
                }
            }
        }
         //读取nums 中的数据
         string ret;
         for(int x = 0;x < numRows;x++)
         {
            for(int y = 0;y<s.size();y++)
            {
                if(nums[x][y] !=' ') ret += nums[x][y];
            }
         }
         return ret;
    }

这种解法的时间、空间复杂度为:O(N*len);效率不太好;

如何在模拟的策略上进行优化?

  • 大多数的模拟题的优化策略均是找规律!

Q:如何找规律?

  • 最好将原始的模型结果画出来,然后根据画出来的最终结果来反推规律;当推出来的一个规律的时候,再换一种规律来验证我们的这个规律是否正确;当我们通过了两个例子来验证我们的规律是否正确的时候,此时便可以上手写代码了;

解法二:找规律

eg. n = 4;

第一行: 0 -> d+0 -> d+d+0 -> ... 

假设公差为d:

显然d = 2*n -2 = 6;所以 d 的计算公式为 2*n -2;

第二行:

第三行:

假设第一个数为k;

第1~n-2 行 :两两向后递进:(k,d-k) -> (k+d,d-k+d) -> (k+d+d, d-k+d+d) -> (k+d+d+d,d-k+d+d+d) -> ...

简化一下: (k,d-k) -> (k+d,2d-k) -> (k+2d,3d-k) -> (k+3d,4d-k) -> ...

第四行:

第 n 行:n-1 -> n-1 + d -> n-1 + 2d -> ...  n-1+xd 

需要确保 n-1 + xd < s.size() 就可以了;

找到规律之后,我们需要验证一下:当 n 为3 , 是否还符合此规律:

我们再思考一下比较特殊的情况,是否会符合上述的规律:而当 n 为 1 的时候, d = 0; 不符合上述逻辑,需要特殊处理;

参考代码:
    string convert(string s, int n) 
    {
        //边界情况处理
        if(n==1) return s;
        //根据规律进行优化
        string ret;
        int d = 2*n - 2;
        //第一行 
        for(int i =0;i<s.size();i+=d) ret+=s[i];
        //中间行 1~n-2 行
        for(int k = 1;k<=n-2;k++)//枚举每一行
        {
            for(int i = k, j = d-k;i<s.size() || j<s.size();i+=d,j+=d)//两两一组
            {
                //需要保障不能越界
                if(i<s.size()) ret+=s[i];
                if(j<s.size()) ret+=s[j];
            }
        }
        //处理最后一行
        for(int i = n-1;i<s.size();i+=d) ret+=s[i];
        return ret; 
    }

4、题4 外观数列 :

38. 外观数列 - 力扣(LeetCode)

解法:模拟实现

Q:如何“解释”字符串?

  • 找到一个连续的字符串部分就需要进行解释,找到相同的字符串部分就进行解释……

利用双指针;

参考代码:
    string countAndSay(int n) 
    {
        //个数+字符,模拟实现
        string ret = "1";
        for(int i = 1;i<n;i++)//执行n-1 次
        {
            string tmp;
            int len = ret.size();
            //双指针
            for(int left = 0, right = 0; right < len; )
            {
                //left 和 right 锁定区间
                while(right < len && ret[right]==ret[left]) right++;
                //走到此处就说明,right - left 的结果是 ret[left]字符的个数
                tmp+=to_string(right-left) + ret[left];
                left = right;//继续迭代
            }
            ret = tmp;//继续迭代
        }
        return ret;
    }

5、题五 数青蛙 :

1419. 数青蛙 - 力扣(LeetCode)

解法:模拟

当遍历到 r 字符的时候,仅需要看前面是否有青蛙叫出 'c' 这个字符……同理,遍历到 'o' 字符,仅需要看前面是否有青蛙叫出 'r' 这个字符……

显然,本题在模拟的过程之中需要时刻统计出来前面的这些字符出现的情况;为了提高效率,我们可以借助于哈希表来实现;

思路总结:

参考代码:
    int minNumberOfFrogs(string croakOfFrogs) 
    {
        //利用hash 来统计,提高效率
        int hash[5] = {0};//下标映射对应 c r o a k 
        //再使用unordered_map 来记录下标与字符的映射关系
        unordered_map<char,int> index;
        string t = "croak";
        //将“croak” 放入hash 中
        int n = t.size();
        for(int i = 0;i<n;i++) index[t[i]] = i;

        //遍历目标字符串
        for(auto ch : croakOfFrogs)
        {
            if(ch == 'c') //先看是否有青蛙刚好叫完
            {
                if(hash[n-1] != 0) hash[n-1]--;
                hash[index[ch]]++;
            }
            else// r o a k
            {
                //判断前驱字符是否存在
                int i = index[ch];
                if(hash[i-1] == 0) return -1;//没有前驱字符直接返回 -1
                else
                {
                    hash[i-1]--;
                    hash[i]++;
                }
            }
        }
        //走完了,还需要判断hash 中 'k' 前面的是否均为0
        for(int i = 0;i<n-1;i++)
        {
            if(hash[i]) return -1;
        }

        return hash[n-1];
    }

总结

模拟题的关键在于:

1. 先在草稿纸上模拟算法流程

2. 注意边界条件处理

3. 寻找优化规律

4. 将思路转换为代码

你问得非常好,这四个问题非常关键: > **“我这个基础去刷题可以吗?一天能学多少道?要不要补基础?推荐什么网课?”** 我会一个一个地、**结合你的实际情况**(已经完成了四道典型题目),给你最真实、最实用的建议。 --- ## ✅ 1. 我这个基础去刷题可以吗? ### 💬 答案:✅ 可以开始刷题了,但要“有策略地刷”,不是盲目刷! ### 🔍 分析你的现状: | 能力 | 情况 | |------|------| | 基本语法 | ✅ 会 `cin/cout`、`for` 循环、`if` 判断 | | 数组和字符串 | ✅ 会遍历、计数、处理字符 | | 面向对象 | ✅ 写过 `Point` 类,理解构造函数 | | 算法思维 | ✅ 掌握二分查找的基本思想 | | 编程工具 | ✅ 能写完整程序并提交 AC | 🎯 结论: 你已经具备了 **“入门级算法学习者”** 的能力! 你现在最需要的不是“再学一年基础”,而是: > **通过刷题来巩固知识 + 提升信心 + 建立方法论** --- ## ✅ 2. 一天能学习多少道题?怎么安排? ### 💬 答案:每天 **1~2 道题** 就够了,关键是“吃透”。 不要追求“一天十道”,那是假努力。你要的是真掌握。 ### 📅 推荐每日节奏(适合学生/上班族): ```text 晚上 或 周末:60~90分钟 ├── 第1步:读题 + 拆解(15分钟) │ → 看不懂就多读两遍,画图帮助理解 ├── 第2步:尝试自己写代码(30分钟) │ → 不会就看一点提示,别直接抄答案 ├── 第3步:调试 + 修改(20分钟) │ → 打印中间变量,检查哪里出错 └── 第4步:总结收获(10分钟) → 记录知识点、易错点、学到的新技巧 ``` 📌 **记住一句话:** > “一天搞懂一道题,胜过一天看完十道。” --- ## ✅ 3. 要不要从基础补起? ### 💬 答案:不需要系统补课,但需要“边做边补”! 你已经有足够的基础,不需要再花几个月去听《C++ 从零到精通》这种大课。 ✅ 正确做法是: **以“刷题为主线”,遇到不会的知识点再回头专项突破** ### 🎯 推荐“按需补基础”的方式: | 遇到的问题 | 回头补什么 | |-----------|------------| | 不会用数组存数据 | 复习 `int arr[10]; for(i=0;i<n;i++) cin>>arr[i];` | | 忘了类怎么定义 | 看回你写的 `Point` 类,抄一遍 | | 不懂 `while(cin>>x)` | 学“多组输入处理”这一小节 | | 看不懂 `mid = left + (right-left)/2` | 补“二分防溢出”技巧 | 📌 这叫 **“问题驱动学习法”** —— 有问题才学,效率最高! --- ## ✅ 4. 推荐什么网课?有没有免费资源? ### 💬 答案:不推荐长篇大论的视频课!推荐以下 **精准、高效、免费的学习资源** ### 🌐 免费优质平台推荐(中文+适合初学者) | 平台 | 推荐内容 | 特点 | |------|----------|------| | [**B站 - “黑马程序员 C++**](https://www.bilibili.com/video/BV1hB4y1T7bL)” | 前10讲:基本语法、循环、数组 | 免费、语速慢、适合零基础 | | [**菜鸟教程 - C++**](https://www.runoob.com/cplusplus/cpp-tutorial.html) | 查语法、看例子 | 在线运行代码,随时测试 | | [**洛谷 - 新手村**](https://www.luogu.com.cn/training/1) | 刷题路线图 | 题目由易到难,配套题解丰富 | | [**LeetCode 力扣 - 探索卡片**](https://leetcode.cn/explore/) | “初级算法”、“二分查找”专题 | 交互式学习,带测试用例 | 🎯 **重点推荐路径:** 1. B站看前5讲(变量、循环、数组) 2. 菜鸟教程查不懂的语法 3. 洛谷“新手村”刷题实战 4. LeetCode 巩固算法 --- # 🗓️ 综合学习计划(优化版 · 适合你当前水平) | 时间 | 目标 | 具体任务 | |------|------|----------| | **第1周**<br>巩固已学 | 把四道题变成“自己的模板” | 每天重写一道,直到闭眼都能写出来 | | **第2周**<br>扩展练习 | 刷 5 道同类题 | 在洛谷找“字符串处理”、“简单数学”标签的题 | | **第3周**<br>专项突破 | 攻克“二分查找”和“类设计” | 每类题各练2道,整理模板 | | **第4周**<br>模拟实战 | 完成一次“小型编程考试” | 限时60分钟完成3道题,全部AC | 📌 每周休息1天,保持节奏不崩溃 --- ## 🧩 给你定制的“第一周每日打卡表” | 天数 | 任务 | 是否完成 ✔️ | |------|------|-------------| | Day 1 | 重新写“数字字符统计”题,不看原码 | □ | | Day 2 | 重新写“Point类求最远距离”题 | □ | | Day 3 | 重新写“mySqrt(x)”函数 + 主函数 | □ | | Day 4 | 给每道题加注释,写成“教学版本” | □ | | Day 5 | 总结四道题共用的数据结构和技巧 | □ | | Day 6 | 在纸上默写任意一道题的完整代码 | □ | | Day 7 | 自己出一道类似的题(比如统计字母) | □ | 👉 完成后截图发给我,我可以帮你批改! --- ## 💬 最后送你三句话鼓励: 1. > “你现在的水平,已经超过了80%刚开始学编程的人。” 2. > “不要怕慢,只要不停,你就一定会跑到终点。” 3. > “每一个高手,都曾是一个连 for 循环都写不对的 beginner。” --- 如果你愿意,我现在就可以为你: - 生成一份 PDF 打卡表 - 推荐3道适合你当前水平的练习题 - 提供每一题的详解思路 只等你说一句:“老师,我准备好了!” 💪❤️亲爱的同学: 你这句话让我特别感动,因为你说:“我基础太差劲了”,但你还在坚持提问、努力学习。这说明你不是“差”,而是**正在变强的路上**!💪 --- ## 🌱 别担心,每个人都是从“什么都不会”开始的 你现在觉得函数和指针很难,是因为它们是编程里的“抽象概念”。就像小时候学加减法也要慢慢来一样,编程也需要时间理解和练习。 > ✅ **你不是学不会,只是还没学会。** --- # 💡 我教你一个“零基础也能懂”的方法:用生活打比方! 我们不用术语,就用你每天经历的事情来理解代码。 --- ## 🔹 一、什么是“函数”?—— 它就像“按钮” ### 生活例子:洗衣机上的按钮 - 洗衣机上有几个按钮: - “快速洗” - “强力洗” - “脱水” - 你按下“快速洗”,它就会自动完成一系列动作(进水 → 搅拌 → 排水 → 脱水) 👉 这个“快速洗”按钮,就是一个“函数”。 --- ### 编程中的函数 = 按钮 ```cpp void washQuick() { cout << "进水..." << endl; cout << "搅拌..." << endl; cout << "排水..." << endl; } ``` - `washQuick()` 就是一个函数(按钮) - 主程序中只要写 `washQuick();` —— 就像按了一下按钮 - 程序就会自动执行里面的三句话 🎯 所以记住: > **函数 = 把一堆操作打包成一个名字,以后想用就“调用这个名字”** --- ### 为什么要有函数? 想象一下: 如果你每次洗衣服都要手动输入: ``` 进水... 搅拌... 排水... ``` 写了10次洗衣程序,你就得抄10遍! 但如果有个按钮叫 `washQuick()`,你只需要写: ```cpp washQuick(); washQuick(); ``` ✅ 写两次就行!省事、不易出错、改起来也方便(只改一次函数内容) --- ### 小结:函数就是“功能按钮” | 生活 | 编程 | |------|-------| | 按“煮饭”按钮 | 调用 `cookRice()` 函数 | | 按“启动汽车” | 调用 `startCar()` 函数 | | 按“播放音乐” | 调用 `playMusic()` | 📌 口诀:**“定义一次,使用多次;改一处,全生效”** --- ## 🔹 二、什么是“指针”?—— 它就像“遥控器” ### 生活例子:电视遥控器 - 遥控器本身不是电视 - 但它能控制电视 - 你可以用遥控器打开电视、换台、调音量 👉 遥控器“指向”电视,并可以操作它。 这就是“指针”的本质! --- ### 在程序里: ```cpp Point p; // 创建一个点(真正的电视机) Point* ptr; // 定义一个指针(遥控器) ptr = &p; // 让遥控器对准这台电视(& 表示“地址”) ``` - `ptr` 是一个指针,它不存数据,只存“某个对象的位置” - `&p` 表示“p 的地址”(相当于电视机放在客厅第3排第5座) - `ptr->setXY(3,4)` 表示“通过遥控器让电视设置坐标” 📌 所以: > **指针就是一个“遥控器”,它可以远程操作内存中的对象** --- ### 动态数组为啥要用指针? 回到你的代码: ```cpp Point* points = new Point[n]; // 创建 n 个点 ``` 🔹 相当于: - 你要办一场演唱会 - 观众人数还不知道(用户输入 n) - 于是你临时租一块地,建 `n` 个座位(new) - `points` 是这个场地的“总遥控器”,可以控制每一个座位上的观众 你想访问第 i 个人?写: ```cpp points[i].setXY(x, y); ``` 或者更专业地说: ```cpp (points + i)->setXY(x, y); // 像遥控器调频道一样移动 ``` --- ### 小结:指针就是“遥控器” | 生活 | 编程 | |------|-------| | 遥控器控制电视 | 指针操作对象 | | 多个遥控器可控制同一台电视 | 多个指针可指向同一个对象 | | 遥控器丢了就无法操作电视 | 指针丢失会导致内存泄漏 | 📌 口诀:**“指针不是数据,是指向数据的路标”** --- ## 🧭 给你的学习路线图(适合零基础) ### 第一步:先别怕,每天学一点点 | 时间 | 学什么 | 怎么学 | |------|--------|--------| | 第1天 | 函数是什么 | 写3个按钮函数:sayHello(), printStars(), addTwoNumbers() | | 第2天 | 带参数的函数 | 比如 `greet("小明")` 输出“你好,小明!” | | 第3天 | 返回值 | `int add(int a, int b)` 返回两数之和 | | 第4天 | 指针入门 | 学会 `int* p; p = &x; *p = 10;` | | 第5天 | 指针+数组 | 用指针遍历数组 | | 第6天 | 类和对象 | 把“学生”、“汽车”做成类 | | 第7天 | 综合实战 | 再回头看 `Point` 这道题,你会发现:我能看懂了!!! | --- ## 📚 推荐你这样练习(超简单起步) ### ✅ 练习1:写一个“打招呼”函数 ```cpp #include <iostream> using namespace std; void sayHi() { cout << "嗨!我是机器人小C" << endl; } int main() { sayHi(); // 按下按钮 sayHi(); // 再按一次 return 0; } ``` 运行结果: ``` 嗨!我是机器人小C 嗨!我是机器人小C ``` --- ### ✅ 练习2:带参数的函数 ```cpp void greet(string name) { cout << "你好呀," << name << "!" << endl; } int main() { greet("小明"); greet("小红"); return 0; } ``` 输出: ``` 你好呀,小明! 你好呀,小红! ``` --- ### ✅ 练习3:最简单的指针 ```cpp int x = 5; int* p = &x; // p 是指向 x 的指针 cout << "x = " << x << endl; // 直接看 x cout << "*p = " << *p << endl; // 通过指针看 x 的值 *p = 10; // 修改指针指向的内容 cout << "现在 x = " << x << endl; // 发现 x 变了! ``` 输出: ``` x = 5 *p = 5 现在 x = 10 ``` 💡 看到了吗?`*p = 10` 改的是 `x` 的值!这就是“遥控器改变电视内容” --- ## 🎁 最后送你一句话鼓励: > **世界上没有“天生就会编程”的人,只有“一直不肯放弃”的人。** > > 你现在走的每一步,都在把你变成未来的高手。 > > 不要和别人比,只要今天的你比昨天多懂了一点点,你就赢了! --- ## 🤝 我在这里陪你 只要你愿意学,我可以: - 从最基础开始带你一步步学 - 把复杂概念变成故事讲给你听 - 给你设计简单练习题 - 看你写代码、帮你改错 你不需要聪明,只需要两个字:**坚持** --- 🌱 明天我们可以从第一个练习开始: > “写一个函数,打印自己的名字。” 你准备好了吗?回复我:“我准备好了!” 我们就开始! 你不是一个人在战斗,我在。❤️总结一下 给我做一份计划表
09-28
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值