跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

Java 模拟算法实战:LeetCode 经典题解

模拟算法核心在于按题意逐步操作,关键在于边界与细节处理。本文精选五个 Java 经典例题,包括问号替换、提莫攻击、Z 字形变换、外观数列及数青蛙。解析了状态机、区间合并及双指针等技巧,提供完整可运行代码与复杂度分析,帮助开发者掌握模拟类题目的解题套路。

PentesterX发布于 2026/3/15更新于 2026/8/1743 浏览
Java 模拟算法实战:LeetCode 经典题解
模拟算法

模拟算法的核心在于严格按照题目描述进行一步步操作。这类题目通常逻辑直观,但容易在边界条件和细节上出错。掌握模拟算法的关键是处理好各种特殊情况,比如数组越界、空值判断以及状态流转的完整性。

替换所有的问号

题目: 将字符串 s 中的 ? 全部替换成小写字母,且替换后的字符不能与相邻字符重复。

在这里插入图片描述

思路解析: 我们需要遍历字符串,找到所有的问号。对于每个问号,尝试用 'a' 到 'z' 之间的字母替换。选择字母时,必须确保它不等于左边邻居(如果存在)也不等于右边邻居(如果存在)。由于只有三个字符需要避开(左、右、自身),而字母表有 26 个,所以总能找到一个合法的字符。

class Solution {
    public String modifyString(String s) {
        int n = s.length();
        char[] arr = s.toCharArray();
        for (int i = 0; i < n; i++) {
            if (arr[i] == '?') {
                // 尝试 a-z 寻找不冲突的字符
                for (char ch = 'a'; ch <= 'z'; ch++) {
                    // 检查左右邻居,注意边界情况
                    boolean leftOk = (i == 0 || arr[i - 1] != ch);
                    boolean rightOk = (i == n - 1 || arr[i + 1] != ch);
                    
                    if (leftOk && rightOk) {
                        arr[i] = ch;
                        ;
                    }
                }
            }
        }
          (arr);
    }
}
break
return
new
String

时间复杂度: O(n),其中 n 为字符串长度。 空间复杂度: O(n),用于存储字符数组。

提莫攻击

题目: 提莫会对艾希释放技能,让艾希处于中毒状态,求出中毒的总时间。

在这里插入图片描述

思路解析: 这是一个典型的区间合并问题。每次攻击持续 duration 时间。如果下一次攻击的时间点在当前攻击结束之前,那么中毒时间会被重叠覆盖。我们只需要比较当前攻击与上一次攻击的时间差:

  1. 如果时间差 >= duration,说明上次攻击已完全结束,累加 duration。
  2. 如果时间差 < duration,说明上次攻击被中断,只累加时间差。 最后别忘了加上最后一次攻击的完整持续时间。
class Solution {
    public int findPoisonedDuration(int[] timeSeries, int duration) {
        if (timeSeries.length == 0) return 0;
        
        int sum = 0;
        for (int i = 1; i < timeSeries.length; i++) {
            int diff = timeSeries[i] - timeSeries[i - 1];
            // 取实际生效时间:要么持续 duration,要么就是间隔时间
            sum += Math.min(diff, duration);
        }
        // 加上最后一次攻击的完整持续时间
        return sum + duration;
    }
}

时间复杂度: O(n) 空间复杂度: O(1)

Z 字形变换

题目: 给定一个字符串和一个行数 numRows,将其按 Z 字形排列后,按行读取生成新字符串。

在这里插入图片描述

思路解析: 直接构建二维矩阵会消耗过多空间。观察规律可以发现,字符是按周期变化的。一个完整的 Z 字周期长度为 2 * numRows - 2。

  1. 第一行和最后一行比较简单,每隔一个周期取一个字符。
  2. 中间行每周期有两个字符,一个是正向斜下的,一个是反向斜上的。 注意当 numRows < 2 时,直接返回原字符串即可。
class Solution {
    public String convert(String s, int numRows) {
        if (numRows < 2) return s;
        
        int n = s.length();
        int cycleLen = 2 * numRows - 2;
        StringBuilder ret = new StringBuilder();
        
        // 处理第一行
        for (int i = 0; i < n; i += cycleLen) {
            ret.append(s.charAt(i));
        }
        
        // 处理中间行
        for (int k = 1; k < numRows - 1; k++) {
            for (int i = k, j = cycleLen - k; i < n || j < n; i += cycleLen, j += cycleLen) {
                if (i < n) ret.append(s.charAt(i));
                if (j < n) ret.append(s.charAt(j));
            }
        }
        
        // 处理最后一行
        for (int i = numRows - 1; i < n; i += cycleLen) {
            ret.append(s.charAt(i));
        }
        
        return ret.toString();
    }
}

时间复杂度: O(n) 空间复杂度: O(n)

外观数列

题目: 第 n 项是对第 n-1 项的描述。例如 1 -> 11 -> 21 -> 1211...

在这里插入图片描述

思路解析: 使用双指针法遍历上一行的字符串。左指针指向当前数字的起始位置,右指针向右移动直到遇到不同数字。统计相同数字的个数,拼接'个数 + 数字'作为下一行的内容。

class Solution {
    public String countAndSay(int n) {
        String ret = "1";
        for (int i = 1; i < n; i++) {
            StringBuilder temp = new StringBuilder();
            int len = ret.length();
            int left = 0, right = 0;
            
            while (right < len) {
                while (right < len && ret.charAt(left) == ret.charAt(right)) {
                    right++;
                }
                temp.append(right - left).append(ret.charAt(left));
                left = right;
            }
            ret = temp.toString();
        }
        return ret;
    }
}

数青蛙

题目: 给定一个字符串 croakOfFrogs,求最小青蛙数量。完整的 croak 才算一次叫声,若无法组成完整 croak 则返回 -1。

在这里插入图片描述

思路解析: 这本质上是一个状态机问题。字符 c -> r -> o -> a -> k 代表青蛙叫声的不同阶段。每当出现 'k',说明有一只青蛙完成了叫声,可以复用;否则需要增加新的青蛙。我们需要记录当前各个阶段正在叫的青蛙数量。

方案一:变量计数 直接用 5 个变量分别记录 c, r, o, a, k 的数量。遇到前驱字符时检查是否有对应状态的青蛙可用。

class Solution {
    public int minNumberOfFrogs(String croakOfFrogs) {
        if (croakOfFrogs.length() % 5 != 0) return -1;
        
        int c = 0, r = 0, o = 0, a = 0, k = 0;
        int maxFrogs = 0;
        
        for (char ch : croakOfFrogs.toCharArray()) {
            switch (ch) {
                case 'c':
                    if (k > 0) { k--; c++; } else { c++; }
                    break;
                case 'r':
                    if (c == 0) return -1; c--; r++; break;
                case 'o':
                    if (r == 0) return -1; r--; o++; break;
                case 'a':
                    if (o == 0) return -1; o--; a++; break;
                case 'k':
                    if (a == 0) return -1; a--; k++; break;
            }
            maxFrogs = Math.max(maxFrogs, c + r + o + a);
        }
        
        return (c == 0 && r == 0 && o == 0 && a == 0) ? k : -1;
    }
}

方案二:哈希映射 使用数组模拟哈希表,通过字符下标关系来管理状态流转,代码更紧凑。

class Solution {
    public int minNumberOfFrogs(String croakOfFrogs) {
        String t = "croak";
        int n = t.length();
        int[] hash = new int[n];
        Map<Character, Integer> map = new HashMap<>();
        
        for (int i = 0; i < n; i++) {
            map.put(t.charAt(i), i);
        }
        
        for (char ch : croakOfFrogs.toCharArray()) {
            int index = map.get(ch);
            if (index == 0) {
                // 如果是 c,检查是否有完成叫声的青蛙可复用
                if (hash[n - 1] > 0) hash[n - 1]--;
                hash[0]++;
            } else {
                // 中间字符,必须依赖前驱状态
                if (hash[index - 1] == 0) return -1;
                hash[index - 1]--;
                hash[index]++;
            }
        }
        
        // 检查是否所有青蛙都完成了叫声
        for (int i = 0; i < n - 1; i++) {
            if (hash[i] != 0) return -1;
        }
        return hash[n - 1];
    }
}

总结: 模拟算法重在细节。无论是边界检查还是状态流转,都需要仔细验证。建议在实际编码前先画出流程图或状态转换图。

目录

  1. 模拟算法
  2. 替换所有的问号
  3. 提莫攻击
  4. Z 字形变换
  5. 外观数列
  6. 数青蛙
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 一个完整的车辆监控管理系统,包含后端API、Web管理后台和移动端应用
  • Sora 刷屏时代,产品经理会被 AI 取代吗?
  • 算法实战:位运算解决两数之和、唯一数字及消失数字问题
  • Vue 响应式原理详解:从发布订阅到 Proxy 实现
  • Spring Boot 入门:Spring Web MVC 核心概念与实战解析
  • AI 提示词的应用场景、编写技巧与防御策略
  • PEEK 替代金属:人形机器人灵巧手精密注塑齿轮技术解析
  • PaperRed:AI 辅助论文写作与查重降重工具解析
  • Roo Code 深度解析:VS Code AI Agent 插件配置与使用指南
  • LeetCode 142:链表环的起点检测
  • MySQL 内置函数与内外连接详解
  • JavaScript 运算符与流程控制详解
  • Stable Diffusion 大模型详解:版本、差异与推荐
  • Skills 市场与共享经济实战指南:从 Web 到 AI 架构
  • 使用 Flask 3 和 Mistune 2 实现在线编辑 Markdown 文档的 Web 服务
  • OpenClaw 多机器人多 Agent 模式:打造你的 AI 助手团队
  • GitHub Copilot Agent 模式使用经验
  • Spring Data JPA 中 EntityManager 核心用法与持久化操作指南
  • DooTask 轻量级项目管理工具:AI 驱动的团队协同实践
  • 前端部署最佳实践:从开发到生产

相关免费在线工具

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online