米哈游(原神)首超腾讯,登顶榜首(内含算法原题)

米哈游首超腾讯

近日,第三方机构 data.ai 公布 2023 年中国游戏厂商及应用出海收入 30 强。

其中米哈游超越腾讯,首次登顶年度出海收入榜榜首。

国内共有 27 家手游发行商海外营收超 1 亿美元,米哈游和腾讯则是仅有的「海外营收超 10 亿美元」的两家。

米哈游在 2023 大获成功,主要是依靠于其 2023 年 4 月份推出的《崩坏:星穹铁道》。

该游戏位于 2023 年度手游榜单中的第三名,属于米哈游登顶收入总榜的核心因素。

...

回归主线。

来做一道「米哈游」相关的面试原题。

题目描述

平台:LeetCode

题号:481

神奇字符串 s 仅由 '1''2' 组成,并需要遵守下面的规则:

  • 神奇字符串 s 的神奇之处在于,串联字符串中 '1''2' 的连续出现次数可以生成该字符串。

s 的前几个元素是 s = "1221121221221121122......" 。如果将 s 中连续的若干 12 进行分组,可以得到 "1 22 11 2 1 22 1 22 11 2 11 22 ......"

每组中 1 或者 2 的出现次数分别是 "1 2 2 1 1 2 1 2 2 1 2 2 ......" 。上面的出现次数正是 s 自身。

给你一个整数 n ,返回在神奇字符串 s 的前 n 个数字中 1 的数目。

示例 1:

ini 复制代码
输入:n = 6

输出:3

解释:神奇字符串 s 的前 6 个元素是 "122112",它包含三个 1,因此返回 3 。 

示例 2:

ini 复制代码
输入:n = 1

输出:1

提示:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 < = n < = 1 0 5 1 <= n <= 10^5 </math>1<=n<=105

双指针 + 构造 + 打表

我们将相关的字符串分为三类:题目描述的神奇字符串 s 称为"原串",对 s 进行连续段划分所得的串叫"划分串",对划分串进行计数的串叫"计数串"

解题的核心思路:由于划分串是对原串的划分,同时计数串又与原串相同,因此可得三类串均只有 12 两种数值。即可知划分串的每段长度只能是「长度为 1」或「长度为 2」,利用划分串的每段构造长度有限,我们可以通过「简单分情况讨论」的方式进行构造

具体的,我们需要利用「原串和计数串的相同的性质」对 s 进行构造:不难发现计数串总是不长于原串,因此我们可以使用变量 i 来记录当前构造到原串位置,使用变量 j 来记录计数串对应到的实际位置。

不失一般性假设当前构造到 s 中的某一位为 last,而计数串对应的实际位置为 t,由于两者均只有 12 两种可能,我们可以对其进行简单的分情况讨论(可见代码注释)。

一些细节:由于神奇字符串起始字符固定,构造逻辑固定,因此神奇字符串唯一固定。 我们可以采取 static 代码块的方式进行打表预处理(Java 中的 static 代码块只会在类加载的过程执行一次,而 LC 的测评机制是实例化多个 Solution 对象来跑多个样例,但 Solution 类仍只会被加载一次,即 static 在多个样例测评中只会被执行一次。

Java 代码:

Java 复制代码
class Solution {
    static int N = 100010;
    static int[] f = new int[N];
    static {
        StringBuilder sb = new StringBuilder();
        sb.append("01"); // 首位多加一个 0 作为哨兵
        for (int i = 1, j = 1, cnt = 0; i < N; j++) {
            int last = sb.charAt(sb.length() - 1) - '0', t = sb.charAt(j) - '0';
            if (last == 1) {
                if (t == 1) {
                    // 当原串当前字符是 1,而计数串当前字符为 1 
                    // 往后构造形成的原串只能是 12,原串指针后移一位
                    sb.append("2");
                    f[i] = ++cnt; i++;
                } else {
                    // 当原串当前字符是 1,而计数串当前字符为 2
                    // 往后构造形成的原串只能是 112,此时同步更新 f[i + 1],原串指针后移两位
                    sb.append("12");
                    f[i] = ++cnt; f[i + 1] = ++cnt; i += 2;
                }
            } else {
                if (t == 1) {
                    // 当原串当前字符是 2,而计数串当前字符为 1 
                    // 往后构造形成的原串只能是 21,原串指针后移一位
                    sb.append("1");
                    f[i] = cnt; i++;
                } else {
                    // 当原串当前字符是 2,而计数串当前字符为 2
                    // 往后构造形成的原串只能是 221,原串指针后移两位
                    sb.append("21");
                    f[i] = f[i + 1] = cnt; i += 2;
                }
            }
        }
    }
    public int magicalString(int n) {
        return f[n];
    }
}

C++ 代码:

C++ 复制代码
class Solution {
public:
    static const int N = 100010;
    static vector<int> f;
    Solution() {
        if(!f.empty()) return;
        f.resize(N);
        string sb = "01"; // 首位多加一个 0 作为哨兵
        for (int i = 1, j = 1, cnt = 0; i < N; j++) {
            int last = sb[sb.size() - 1] - '0', t = sb[j] - '0';
            if (last == 1) {
                if (t == 1) {
                    sb += '2';
                    f[i++] = ++cnt;
                } else {
                    sb += "12";
                    f[i++] = ++cnt; f[i++] = ++cnt;
                }
            } else {
                if (t == 1) {
                    sb += '1';
                    f[i++] = cnt;
                } else {
                    sb += "21";
                    f[i++] = f[i++] = cnt;
                }
            }
        }
    }
    int magicalString(int n) {
        return f[n];
    }
};
vector<int> Solution::f = {};

Python 代码:

Python 复制代码
class Solution:
    def magicalString(self, n: int) -> int:
        ss = '01' # 首位多加一个 0 作为哨兵
        i, j, cnt = 1, 1, 0
        f = [0] * (n + 10)
        while i <= n:
            last, t = ss[i], ss[j]
            if last == '1':
                if t == '1':
                    # 当原串当前字符是 1,而计数串当前字符为 1 
                    # 往后构造形成的原串只能是 12,原串指针后移一位
                    ss += '2'
                    f[i], cnt, i = cnt + 1, cnt + 1, i + 1
                else:
                    # 当原串当前字符是 1,而计数串当前字符为 2
                    # 往后构造形成的原串只能是 112,此时同步更新 f[i + 1],原串指针后移两位
                    ss += '12'
                    f[i], f[i + 1], cnt, i = cnt + 1, cnt + 2, cnt + 2, i + 2
            else:
                if t == '1':
                    # 当原串当前字符是 2,而计数串当前字符为 1 
                    # 往后构造形成的原串只能是 21,原串指针后移一位
                    ss += '1'
                    f[i], i = cnt, i + 1
                else:
                    # 当原串当前字符是 2,而计数串当前字符为 2
                    # 往后构造形成的原串只能是 221,原串指针后移两位
                    ss += '21'
                    f[i], f[i + 1], i = cnt, cnt, i + 2
            j += 1
        return f[n]

TypeScript 代码:

TypeScript 复制代码
function magicalString(n: number): number {
    let str = '01' // 首位多加一个 0 作为哨兵
    const f = new Array<number>(n + 10).fill(0)
    for (let i = 1, j = 1, cnt = 0; i <= n; j++) {
        const last = str[str.length - 1], t = str[j]
        if (last == '1') {
            if (t == '1') {
                // 当原串当前字符是 1,而计数串当前字符为 1 
                // 往后构造形成的原串只能是 12,原串指针后移一位
                str += '2'
                f[i] = ++cnt; i++
            } else {
                // 当原串当前字符是 1,而计数串当前字符为 2
                // 往后构造形成的原串只能是 112,此时同步更新 f[i + 1],原串指针后移两位
                str += '12'
                f[i] = ++cnt; f[i + 1] = ++cnt; i += 2
            }
        } else {
            if (t == '1') {
                // 当原串当前字符是 2,而计数串当前字符为 1 
                // 往后构造形成的原串只能是 21,原串指针后移一位
                str += '1'
                f[i] = cnt; i++
            } else {
                // 当原串当前字符是 2,而计数串当前字符为 2
                // 往后构造形成的原串只能是 221,原串指针后移两位
                str += '21'
                f[i] = f[i + 1] = cnt; i += 2
            }
        }
    }
    return f[n]
}
  • 时间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n),若将 static 打表逻辑放到本地进行,能够减少构造的计算量,但仍会有创建答案数组的 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n) 开销,因此为均摊 <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( n ) O(n) </math>O(n)

我是宫水三叶,每天都会分享算法知识,并和大家聊聊近期的所见所闻。

欢迎关注,明天见。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 合集新基地 🎉🎉

相关推荐
dr李四维13 分钟前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
ifanatic14 分钟前
[面试]-golang基础面试题总结
面试·职场和发展·golang
雯0609~34 分钟前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ38 分钟前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z43 分钟前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
XINGTECODE1 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
程序猿进阶1 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺1 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
彭世瑜1 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4041 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html