【算法题】模拟

"模拟"是算法中最直观的解题思路之一------按照题目描述的规则/过程,一步步还原操作逻辑,不需要复杂的算法设计,但需要理清细节,处理好边界条件。本文将通过5道经典模拟题,拆解不同场景下的模拟思路与代码实现。

一、替换所有的问号

题目描述:

给定仅含小写字母和 '?' 的字符串 s,将所有 '?' 替换为小写字母,使最终字符串无连续重复字符(不能修改非 '?' 字符,题目保证非 '?' 字符无连续重复)。

示例

  • 输入:s = "?zs",输出:"azs"'z' 是无效替换,会导致连续重复)
  • 输入:s = "ubv?w",输出:"ubvaw"(不能替换为 'v''w'

解题思路:

遍历每个 '?',尝试从 'a''z' 的字符,找到与左右相邻字符都不重复的字符进行替换:

  • 若当前是字符串开头,只需检查右侧字符;
  • 若当前是字符串结尾,只需检查左侧字符;
  • 否则同时检查左右字符。

完整代码:

cpp 复制代码
class Solution {
public:
    string modifyString(string s) {
        for(int i = 0; i < s.size(); i++)
        {
            if(s[i] == '?')
                for(char c = 'a'; c <= 'z'; c++)
                {
                    if((i == 0 || s[i - 1] != c) && (i == s.size() - 1 || s[i + 1] != c))
                    {
                        s[i] = c;
                        break;
                    }
                }
        }
        return s;
    }
};

复杂度分析:

  • 时间复杂度: O ( n × 26 ) O(n×26) O(n×26),遍历字符串( O ( n ) O(n) O(n)),每个问号最多尝试26个字符(常数级),总复杂度 O ( n ) O(n) O(n)。
  • 空间复杂度: O ( 1 ) O(1) O(1),仅修改原字符串(题目允许原地修改)。

二、提莫攻击

题目描述:

提莫攻击会让艾希中毒 duration 秒,若在中毒期间再次攻击,中毒计时器重置。给定非递减的攻击时间数组 timeSeriesduration,返回艾希的总中毒秒数。

示例

  • 输入:timeSeries = [1,4], duration = 2,输出:4(中毒时间:1-2秒、4-5秒,共4秒)

解题思路:

遍历攻击时间,计算相邻两次攻击的间隔duration 的较小值(间隔≥duration 则中毒满 duration 秒,否则中毒间隔秒),最后加上最后一次攻击的 duration 秒。

完整代码:

cpp 复制代码
class Solution {
public:
    int findPoisonedDuration(vector<int>& timeSeries, int duration) {
        if(timeSeries.empty()) return 0; // 处理空数组边界
        int ret = 0;
        for(int i = 1; i < timeSeries.size(); i++)
        {
            int interval = timeSeries[i] - timeSeries[i - 1];
            ret += min(interval, duration);
        }
        return ret + duration;
    }
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),遍历攻击时间数组一次。
  • 空间复杂度: O ( 1 ) O(1) O(1),仅用常数级额外变量。

三、Z字形变换

题目描述:

将字符串 s 按指定行数 numRows 以"Z形"排列,之后逐行读取得到新字符串。

示例

  • 输入:s = "PAYPALISHIRING", numRows = 3,输出:"PAHNAPLSIIGYIR"(Z形排列后逐行读取)

解题思路:

找Z形排列的字符下标规律

  1. 主间隔:d = 2 * numRows - 2(每一行的垂直列字符间隔)。
  2. 首行/尾行:仅取间隔为 d 的字符(下标 i, i+d, i+2d...)。
  3. 中间行(第 k 行):除主间隔字符外,还需取斜列字符(下标 d-k, d-k+d, ...)。
  4. 边界条件:numRows = 1 时直接返回原字符串。

完整代码:

cpp 复制代码
class Solution {
public:
    string convert(string s, int numRows) {
        if(numRows == 1) return s;

        string ret;
        int n = s.size(), d = 2 * numRows - 2;
        // 首行
        for(int i = 0; i < n; i += d)
            ret += s[i];
        // 中间行
        for(int k = 1; k < numRows - 1; k++)
        {
            for(int i = k, j = d - k; i < n || j < n; i += d, j += d)
            {
                if(i < n) ret += s[i];
                if(j < n) ret += s[j];
            }
        }
        // 尾行
        for(int i = numRows - 1; i < n; i += d)
            ret += s[i];

        return ret;
    }
};

复杂度分析:

  • 时间复杂度: O ( n ) O(n) O(n),每个字符仅被访问一次。
  • 空间复杂度: O ( n ) O(n) O(n),存储结果字符串(必要输出)。

四、外观数列

题目描述:

"外观数列"是递归定义的数字字符串:countAndSay(1) = "1"countAndSay(n) 是对 countAndSay(n-1) 的"行程长度编码"(统计连续相同字符的数量+字符)。

示例

  • 输入:n = 4,输出:"1211"1→11→21→1211

解题思路:

countAndSay(1) = "1" 开始,循环模拟n-1次编码过程

  1. 遍历当前字符串,用双指针统计连续相同字符的长度(right - left)。
  2. 拼接"长度+字符"到新字符串,更新 left = right 继续统计。

完整代码:

cpp 复制代码
class Solution {
public:
    string countAndSay(int n) {
        string ret = "1";
        for(int i = 1; i < n; i++)
        {
            string tmp;
            int len = ret.size();
            for(int left = 0, right = 0; right < len;)
            {
                // 统计连续相同字符
                while(right < len && ret[left] == ret[right]) right++;
                tmp += to_string(right - left) + ret[left];
                left = right;
            }
            ret = tmp;
        }
        return ret;
    }
};

复杂度分析:

  • 时间复杂度: O ( m ) O(m) O(m),m 是第 n 个外观数列的长度(随n指数增长,但每个字符仅被遍历一次)。
  • 空间复杂度: O ( m ) O(m) O(m),存储当前轮次的外观数列。

五、数青蛙

题目描述:

字符串 croakOfFrogs 是多个"croak"(青蛙鸣叫)的混合,每个青蛙需按 'c'→'r'→'o'→'a'→'k' 的顺序鸣叫。返回最少需要多少只青蛙,若字符串无效则返回 -1

示例

  • 输入:croakOfFrogs = "croakcroak",输出:1(一只青蛙叫两次)
  • 输入:croakOfFrogs = "crcoakroak",输出:2(需两只青蛙)

解题思路:

用数组统计每个鸣叫阶段的青蛙数量,模拟青蛙的鸣叫流程:

  1. 建立映射:'c'→0, 'r'→1, 'o'→2, 'a'→3, 'k'→4
  2. 遍历字符:
    • 若为 'c':优先复用完成鸣叫(处于 k 阶段)的青蛙,否则新增一只。
    • 若为其他字符:需检查前一个阶段是否有青蛙,有则转移到当前阶段,否则字符串无效。
  3. 最终检查:若中间阶段(非 k)有剩余青蛙,说明字符串无效;否则 k 阶段的数量即为最少青蛙数。

完整代码:

cpp 复制代码
class Solution {
public:
    int minNumberOfFrogs(string croakOfFrogs) {
        string t = "croak";
        int n = t.size();
        vector<int> stage(n, 0); // 统计每个阶段的青蛙数量
        unordered_map<char, int> idxMap;
        for(int i = 0; i < n; i++) idxMap[t[i]] = i;

        for(char ch : croakOfFrogs) {
            if(ch == 'c') {
                // 复用完成鸣叫的青蛙(k阶段),否则新增
                if(stage[n-1] > 0) stage[n-1]--;
                stage[0]++;
            } else {
                int currIdx = idxMap[ch];
                int prevIdx = currIdx - 1;
                // 前一个阶段无青蛙,字符串无效
                if(stage[prevIdx] == 0) return -1;
                stage[prevIdx]--;
                stage[currIdx]++;
            }
        }

        // 中间阶段有剩余,字符串无效
        for(int i = 0; i < n-1; i++) {
            if(stage[i] > 0) return -1;
        }
        return stage[n-1];
    }
};

复杂度分析:

  • 时间复杂度: O ( m ) O(m) O(m),m 是字符串长度,遍历一次字符串。
  • 空间复杂度: O ( 1 ) O(1) O(1),仅用固定大小的数组和哈希表(字符数固定为5)。
相关推荐
s09071361 天前
FPGA加速:Harris角点检测全解析
图像处理·算法·fpga开发·角点检测
前端程序猿之路1 天前
30天大模型学习之Day 2:Prompt 工程基础系统
大数据·人工智能·学习·算法·语言模型·prompt·ai编程
星火开发设计1 天前
堆排序原理与C++实现详解
java·数据结构·c++·学习·算法·排序算法
2501_941803621 天前
在柏林智能城市照明场景中构建实时调控与高并发能耗数据分析平台的工程设计实践经验分享
算法
福楠1 天前
C++ STL | list
c语言·开发语言·数据结构·c++·算法·list
努力学算法的蒟蒻1 天前
day55(1.6)——leetcode面试经典150
算法·leetcode·面试
s砚山s1 天前
代码随想录刷题——二叉树篇(十)
算法
2301_764441331 天前
基于HVNS算法和分类装载策略的仓储系统仿真平台
人工智能·算法·分类
AI科技星1 天前
统一场论变化的引力场产生电磁场推导与物理诠释
服务器·人工智能·科技·线性代数·算法·重构·生活