当前位置: 首页 > news >正文

LeetCode算法日记 - Day 23: 外观数列、数青蛙

目录

1. 外观数列

1.1 题目解析

1.2 解法

1.3 代码实现

2. 数青蛙

2.1 题目解析

2.2 解法

2.3 代码实现


1. 外观数列

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

「外观数列」是一个数位字符串序列,由递归公式定义:

  • countAndSay(1) = "1"
  • countAndSay(n) 是 countAndSay(n-1) 的行程长度编码。

行程长度编码(RLE)是一种字符串压缩方法,其工作原理是通过将连续相同字符(重复两次或更多次)替换为字符重复次数(运行长度)和字符的串联。例如,要压缩字符串 "3322251" ,我们将 "33" 用 "23" 替换,将 "222" 用 "32" 替换,将 "5" 用 "15" 替换并将 "1" 用 "11" 替换。因此压缩后字符串变为 "23321511"

给定一个整数 n ,返回 外观数列 的第 n 个元素。

示例 1:

输入:n = 4

输出:"1211"

解释:

countAndSay(1) = "1"

countAndSay(2) = "1" 的行程长度编码 = "11"

countAndSay(3) = "11" 的行程长度编码 = "21"

countAndSay(4) = "21" 的行程长度编码 = "1211"

示例 2:

输入:n = 1

输出:"1"

解释:

这是基本情况。

提示:

  • 1 <= n <= 30

1.1 题目解析

题目本质
这题是在做“从 1 开始,不断对上一次字符串做行程长度编码 (Run-Length Encoding, RLE)”,得到第 n 项。RLE 的规则就是:把连续相同字符的一段,用“这段的长度 + 该字符”来表示。

常规解法
递归:countAndSay(n) = RLE(countAndSay(n-1)),直到 n=1 返回 "1"。

问题分析
递归每次都要建立新字符串,函数栈也会递归到深度 n;虽然也能过,但实现上不如迭代直观。核心成本其实来自给字符串做 RLE,一轮扫描就够了,所以完全可以从 1 迭代到 n,每次把当前串编码出下一串。

思路转折
要高效 ⇒ 每一轮都对“上一个字符串”线性扫描一遍,把连续段压缩即可。实现上用双指针/游标(或单指针+计数)最自然:

  • 指定一段的起点,向右扩展到这段结束;

  • 把 (长度)(字符) 追加到 StringBuilder;

  • 继续处理下一段。
    这样总复杂度是所有中间串长度之和,对 n ≤ 30 十分可控。

1.2 解法

算法思想

迭代从 ret = "1" 开始做 n-1 次编码,RLE 用连续段的“长度 + 字符”替换该段。每一轮只需 O(当前串长)。

i)初始化 ret = "1";若 n==1 直接返回。

ii)循环 i=2..n:ret = encode(ret)。

iii)encode(s):

  • 用指针 i 从左到右扫描;

  • 记当前字符 ch = s.charAt(i),用 j 往右走到 s[j] != ch;

  • 追加 (j-i) 和 ch 到 StringBuilder;令 i = j 继续;

  • 返回构建好的字符串。

iiii)最终返回 ret。

1.3 代码实现

class Solution {public String countAndSay(int n) {String ret = "1";for (int i = 2; i <= n; i++) {ret = encode(ret); // 对上一次结果做 RLE,得到下一次}return ret;}// 对字符串做一次行程长度编码:连续段 -> (长度)(字符)private String encode(String s) {StringBuilder sb = new StringBuilder();int i = 0, n = s.length();while (i < n) {char ch = s.charAt(i);int j = i;while (j < n && s.charAt(j) == ch) j++; // 扩展到该连续段的末尾sb.append(j - i).append(ch);            // 追加“长度+字符”i = j;                                  // 下一段}return sb.toString();}
}

复杂度分析

  • 时间:每一轮 O(当前串长),总计为所有中间结果长度之和。

  • 空间:每轮一个新的 StringBuilder,峰值为当前结果长度的 O(L)。

2. 数青蛙

1419. 数青蛙 - 力扣(LeetCode)

给你一个字符串 croakOfFrogs,它表示不同青蛙发出的蛙鸣声(字符串 "croak" )的组合。由于同一时间可以有多只青蛙呱呱作响,所以 croakOfFrogs 中会混合多个 “croak” 

请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。

要想发出蛙鸣 "croak",青蛙必须 依序 输出 ‘c’, ’r’, ’o’, ’a’, ’k’ 这 5 个字母。如果没有输出全部五个字母,那么它就不会发出声音。如果字符串 croakOfFrogs 不是由若干有效的 "croak" 字符混合而成,请返回 -1 。

示例 1:

输入:croakOfFrogs = "croakcroak"
输出:1 
解释:一只青蛙 “呱呱” 两次

示例 2:

输入:croakOfFrogs = "crcoakroak"
输出:2 
解释:最少需要两只青蛙,“呱呱” 声用黑体标注
第一只青蛙 "crcoakroak"
第二只青蛙 "crcoakroak"

示例 3:

输入:croakOfFrogs = "croakcrook"
输出:-1
解释:给出的字符串不是 "croak" 的有效组合。

提示:

  • 1 <= croakOfFrogs.length <= 105
  • 字符串中的字符只有 'c''r''o''a' 或者 'k'

2.1 题目解析

题目本质
把一串混合的 “croak” 拆分为若干只按顺序发音的青蛙序列,问最少需要多少只青蛙才能完成这些发声。等价于:在扫描过程中,最多同时处在进行中(没有到 k)的青蛙数量是多少;若出现顺序非法或有青蛙未叫完,则不合法返回 -1

常规解法(直观想法)
开 5 计数器分别统计当前有多少只停在 c/r/o/a/k 阶段;字符到来就把青蛙从前一阶段“搬”到后一阶段;遇到 c 时若无空闲可复用青蛙,则新增一只;一路维护“在场(未到 k)的数量”的最大值为答案。

问题分析
只要线性扫描一遍字符串即可,每个字符只做 O(1) 的阶段迁移,时间 O(|S|),空间 O(1)。难点是如何优雅地表达“阶段迁移 + 复用已完成的青蛙”,并在结尾校验没有半途而废

思路转折
将 "croak" 的 5 个字母视作 5 个阶段(0..4):

  • 维护一个长度为 5 的数组 hash[i],表示当前停在阶段 i 的青蛙数

  • 用 Map<Character,Integer> 把 'c','r','o','a','k' 映射到 0..4;

  • 扫描:

    • 若是 'c':优先复用已完成(阶段 4)的青蛙 hash[4]--,否则相当于新增一只;然后让它进入阶段 0(hash[0]++)

    • 若是其它字母 x:找到其阶段 i=index.get(x),要求 hash[i-1]>0,然后从前一阶段转到当前阶段(hash[i-1]--, hash[i]++)

  • 结束后必须保证 hash[0..3] 都为 0(没有未完成者),答案即为 hash[4](完成且可复用的总只数 = 全程最少青蛙数)。

为何返回 hash[4]?因为每只合法青蛙最终一定停在 'k' 阶段(完成一次 croak)。若字符串整体合法,所有青蛙都会“收尾”到阶段 4,数量正是最少需要的不同青蛙数。 映射的关键在 t="croak" 与 index.put(t.charAt(i), i):因此 'k' 的阶段编号是 4,也就是 n-1,所以 hash[n-1] 自然对应 'k' 阶段。

2.2 解法

算法思想

把 "croak" 看成 5 个有序阶段,数组 hash[0..4] 表示每个阶段的在途青蛙数;字符到来即做阶段迁移

  • 'c':若有 hash[4]>0 先复用(表示上一轮刚结束的一只),否则等价于新增一只;然后 hash[0]++

  • 其它字母:i=index.get(ch),要求 hash[i-1]>0,再做 hash[i-1]--, hash[i]++

i)预处理:t="croak",建立 index: 字符 -> 阶段编号(0..4);新建 int[5] hash。

ii)扫描字符串:

  • 若 ch=='c':if (hash[4]>0) hash[4]--; hash[0]++;

  • 否则:令 i=index.get(ch),若 hash[i-1]==0 返回 -1;否则 hash[i-1]--, hash[i]++。

iii)扫描完毕:若 hash[0]..hash[3] 有非零,返回 -1。

iiii)返回 hash[4] 作为最少青蛙数

2.3 代码实现

import java.util.*;class Solution {public int minNumberOfFrogs(String croakOfFrogs) {String t = "croak";int n = t.length();                // 5int[] hash = new int[n];           // hash[i]:处在第 i 阶段的青蛙数(i: c/r/o/a/k)Map<Character, Integer> index = new HashMap<>(5);for (int i = 0; i < n; i++) index.put(t.charAt(i), i);for (char ch : croakOfFrogs.toCharArray()) {if (ch == 'c') {// 先复用一只刚完成的青蛙(阶段 4),否则相当于新增一只if (hash[n - 1] > 0) hash[n - 1]--;hash[0]++; // 进入 'c' 阶段} else {Integer i = index.get(ch);if (i == null || i == 0) return -1; // 非法字符或顺序错误if (hash[i - 1] == 0) return -1;    // 前一阶段没人可转移hash[i - 1]--;                       // 从前一阶段拿一只hash[i]++;                           // 进入当前阶段}}// 不能有未完成的青蛙(停在 c/r/o/a),否则不合法for (int i = 0; i < n - 1; i++) {if (hash[i] != 0) return -1;}// 合法:所有青蛙都收尾在 'k' 阶段return hash[n - 1];}
}

复杂度分析

  • 时间:O(|S|),单次线性扫描;

  • 空间:O(1),仅 5 个阶段计数 + 一个小映射。

http://www.dtcms.com/a/352807.html

相关文章:

  • 欧洲数字化养殖平台 Herdwatch 借力 Iceberg + StarRocks 提升分析能力
  • 【Matplotlib学习】驾驭画布:Matplotlib 布局方式从入门到精通完全指南
  • 【RabbitWQ】基于 Java 实现轻量级消息队列(二)
  • 医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(一)
  • 【重学 MySQL】九十、Linux下MySQL的安装与卸载指南
  • 如何保证DDC楼宇自控系统与IBMS集成管理系统的稳定性和可靠性?
  • 深入解析 Flink Function
  • 【Datawhale之Happy-LLM】Encoder-only模型篇 task05精华~
  • 【雅思021】I’m sorry, I love you Ⅱ
  • 如何使用PyTorch搭建一个基础的神经网络并进行训练?
  • skywalking 原理
  • H20 性能表现之 gpt-oss-120b
  • 软考-系统架构设计师 管理信息系统(MIS)详细讲解
  • React内网开发代理配置详解
  • C++ 力扣 704.二分查找 基础二分查找 题解 每日一题
  • Https之(四)国密GMTLS
  • 【Redis#8】Redis 数据结构 -- Zset 类型
  • 改造thinkphp6的命令行工具和分批次导出大量数据
  • GTCB:引领金融革命,打造数字经济时代标杆
  • 【js】加密库sha.js 严重漏洞速查
  • UTXO 模型及扩展模型
  • 香港数字资产交易市场蓬勃发展,监管与创新并驾齐驱
  • 完整实验命令解析:从集群搭建到负载均衡配置(2)
  • 记录使用ruoyi-flowable开发部署中出现的问题以及解决方法(二)
  • 电脑开机显示器不亮
  • 智能安防:以AI重塑安全新边界
  • 欧盟《人工智能法案》生效一年主要实施进展概览(一)
  • docker-runc not installed on system
  • 【科研绘图系列】R语言在海洋生态学数据可视化中的应用:以浮游植物叶绿素和初级生产力为例
  • Kafka 4.0 兼容性矩阵解读、升级顺序与降级边界