← 返回博客列表 TikTok OA CodeSignal 四道真题复盘与解法
TikTok

TikTok OA CodeSignal 四道真题复盘与解法

2026-08-13

前段时间投了 TikTok 的后端岗,简历过筛之后收到一封 CodeSignal 的测评邀请。这篇复盘记录我在这场 OA 里碰到的四道题、平台形式,以及每一题的完整解法。需要提一句:CodeSignal 最近把 General Coding Assessment 的界面刷新了一版,也轮换了一部分题目,但共享题库里的老题仍然会反复出现——我这次四道里就有三道是能在往期讨论里对上号的。如果你也在准备 TikTok 的 OA,或者想找 OA辅助 / OA代面 把节奏和思路一起打磨到位,希望这份笔记能帮到你。

面试环境与形式概览

整场测评在 CodeSignal General Coding Assessment(共享题库)上完成,全程限时 70 分钟,一共四道题,难度从前往后递进:前两道是热身性质的 Easy,第三题是矩阵/数组模拟,第四题是偏数据结构的压轴题。CodeSignal 会全程录屏、监控切屏,所以中途尽量别离开答题页。

项目 详情
平台 CodeSignal General Coding Assessment(共享题库)
时长 70 分钟
题量 4 道
难度 递进(Easy → 模拟 → 数据结构)
考察重点 基础实现速度、边界处理、模拟与查询效率

节奏建议:前两道 Easy 尽量在 20 分钟内拿下,把时间留给第三、四题。CodeSignal 的题目是要真正跑通全部测试用例的,所以写完记得自己先在脑子里 dry run 几个边界例子再提交。


Problem 1:数字积减和

题目背景

给定一个正整数 n,计算它十进制各位数字的乘积与各位数字之和,返回「乘积 − 和」。例如 123456:乘积是 1·2·3·4·5·6 = 720,和是 1+2+3+4+5+6 = 21,结果 720 − 21 = 699。再看 1010:因为包含数字 0,乘积直接变成 0,和是 0+1+0+1 = 2,结果 0 − 2 = -2

思路

把整数转成字符串再逐位转回整数:list(map(int, str(n)))。一次遍历同时累乘和累加即可。乘积的初值设为 1,只要遇到任意一位是 0,乘积自然就变成 0,不需要单独判断。

Python 解法

def digit_product_minus_sum(n: int) -> int:
    """返回 n 各位数字的乘积减去各位数字之和。

    只要有任意一位是 0,乘积会自然变为 0,无需特判。
    """
    digits = list(map(int, str(n)))  # 拆出每一位

    product = 1
    total = 0
    for d in digits:
        product *= d  # 累乘,遇到 0 会自然归零
        total += d    # 累加

    return product - total

Dry run 演示

n = 123456 走一遍:

步骤 当前位 product total 说明
初始 1 0 product 初值为 1
读 1 1 1 1
读 2 2 2 3
读 3 3 6 6
读 4 4 24 10
读 5 5 120 15
读 6 6 720 21

最终 720 − 21 = 699

再验证含 0 的边界 n = 1010:遍历到第二位 0product 归零,之后无论怎么乘都还是 0,最终 total = 2,返回 0 − 2 = -2,符合预期。

时间复杂度:O(log n),位数与 n 的对数成正比。 空间复杂度:O(log n),存放各位数字。


Problem 2:最长连续字符

题目背景

给定一个只含小写字母的字符串,找出被单个字符连续重复的最长一段;如果有多段并列最长,取最右边的那一段;返回「字符 + 长度」的拼接,例如连续三个 c 返回 "c3"

思路

线性扫描,维护当前段的字符 cur_char 和长度 cur_len:遇到和上一位相同就 cur_len += 1,不同就把 cur_len 重置为 1 并更新 cur_char。同时用 best_len / best_char 记录全局最优。并列时取最右的关键是比较用 >=:只要出现同样长的段,就用后来的覆盖前面的。

Python 解法

def longest_run(s: str) -> str:
    """返回最长连续重复字符段,并列时取最右边一段。

    用 >= 保证相同长度时后出现的段覆盖先出现的段。
    """
    if not s:
        return ""  # 空串直接返回空

    cur_char = s[0]
    cur_len = 1
    best_char = s[0]
    best_len = 1

    for ch in s[1:]:
        if ch == cur_char:
            cur_len += 1  # 延续当前段
        else:
            cur_char = ch  # 开启新段
            cur_len = 1
        if cur_len >= best_len:  # >= 让最右的并列段胜出
            best_len = cur_len
            best_char = cur_char

    return f"{best_char}{best_len}"

Dry run 演示

s = "aabbbxxccc"bbbccc 长度并列为 3)走一遍关键节点:

位置 字符 cur_char / cur_len best_char / best_len 说明
0 a a / 1 a / 1 初始
1 a a / 2 a / 2 延续
4 b b / 3 b / 3 第一段最长 3
7 c c / 1 b / 3 新开 c 段
9 c c / 3 c / 3 并列 3,>= 覆盖为 c

最终返回 "c3"bbbccc 都是长度 3,取最右的 ccc

时间复杂度:O(n),单次遍历。 空间复杂度:O(1),只用了常数个变量。


Problem 3:内存分配模拟

题目背景

模拟一段内存的分配与释放。给定一系列操作:

思路

用一个布尔数组表示每个单元是否被占用。alloc 时从左到右扫描,统计当前连续空闲长度,遇到被占用的单元就把计数清零;一旦计数达到 x,就把这段区间标记为占用,并在哈希表里存下 id -> (start, length)erase 时用 ID 在哈希表里查到区间,把这些单元清空,然后删除映射。

Python 解法

class MemoryRegion:
    """连续内存的分配 / 释放模拟。

    used[i] 表示单元 i 是否被占用;blocks 记录 id -> (start, length)。
    """

    def __init__(self, size: int):
        self.used = [False] * size
        self.blocks = {}       # id -> (start, length)
        self.next_id = 0       # 自增分配 ID

    def alloc(self, x: int) -> int:
        """占用最左边 x 个连续空闲单元,返回起始下标;失败返回 -1。"""
        run = 0
        for i in range(len(self.used)):
            if self.used[i]:
                run = 0            # 碰到占用,连续长度清零
            else:
                run += 1
                if run == x:       # 凑够 x 个连续空闲
                    start = i - x + 1
                    for j in range(start, i + 1):
                        self.used[j] = True
                    self.blocks[self.next_id] = (start, x)
                    self.next_id += 1
                    return start
        return -1                  # 没有足够的连续空闲

    def erase(self, block_id: int) -> int:
        """释放该 ID 对应的整块内存,返回长度;无效 ID 返回 -1。"""
        if block_id not in self.blocks:
            return -1
        start, length = self.blocks.pop(block_id)
        for j in range(start, start + length):
            self.used[j] = False
        return length

Dry run 演示

假设内存大小为 8,依次执行操作:

操作 内存状态(1=占用) 返回 说明
初始 00000000 全空闲
alloc 3 11100000 0 最左 3 连空闲,分配 ID 0
alloc 2 11111000 3 接着往右,分配 ID 1
erase 0 00011000 3 释放 ID 0 的 3 格
alloc 4 00011111 4 左侧只有 3 空格不够,落到下标 4
erase 5 00011111 -1 ID 5 不存在

时间复杂度alloc 为 O(n)(最坏扫描整段内存),erase 为 O(len)(清空对应区间)。 空间复杂度:O(n),用于占用数组与分配映射。


Problem 4:障碍区间查询

题目背景

这一题在共享题库里的题面被贴错了——原文把 Q4 配成了 Q1 的描述,真正的 Q4 要靠它的解法来还原。它实际考的是一个障碍放置 / 区间查询问题:你维护一个有序的障碍位置数组,支持两种操作:

思路

bisect.insort 保持数组有序,插入是 O(n)(数组搬移导致)。查询时用二分找到「位置小于等于 x - 1 的最靠右的那个障碍」,再看它是否落在 [x - size, x - 1] 内:落在里面就说明放不下,返回 False;否则返回 True。查询是 O(log n)。

提示:如果面试进一步要求插入也做到 O(log n),就把有序数组换成平衡二叉搜索树 / 有序集合(例如 sortedcontainers.SortedList),插入和查询都能到 O(log n)。

Python 解法

import bisect


class ObstacleField:
    """维护有序障碍位置,支持新增与区间空闲查询。"""

    def __init__(self):
        self.obstacles = []  # 始终保持升序

    def add(self, pos: int) -> None:
        """插入一个障碍,保持数组有序(O(n) 的数组搬移)。"""
        bisect.insort(self.obstacles, pos)

    def query(self, x: int, size: int) -> bool:
        """判断 [x - size, x - 1] 区间内是否没有障碍。"""
        left = x - size
        right = x - 1
        # 找到第一个 > right 的位置,它左边一个就是 <= right 的最右障碍
        idx = bisect.bisect_right(self.obstacles, right) - 1
        if idx < 0:
            return True  # right 左侧没有任何障碍
        nearest = self.obstacles[idx]
        return nearest < left  # 落在 [left, right] 内则放不下

Dry run 演示

依次 ADD 2ADD 5ADD 9,数组变为 [2, 5, 9],再做几次查询:

操作 目标区间 [x-size, x-1] 最近障碍 返回 说明
QUERY(x=5, size=2) [3, 4] 2(<=4 最右) True 2 < 3,区间内无障碍
QUERY(x=6, size=3) [3, 5] 5 False 5 落在 [3,5]
QUERY(x=2, size=1) [1, 1] 无(idx<0 True 1 左侧无障碍
QUERY(x=10, size=1) [9, 9] 9 False 9 落在 [9,9]

时间复杂度query 为 O(log n)(二分);add 为 O(n)(数组搬移)。若改用有序集合,add 也可降到 O(log n)。 空间复杂度:O(n),存放障碍位置。


备考策略


FAQ

Q1:CodeSignal 的题目一定要跑通全部测试用例吗?

是的。和某些只看思路的现场轮不同,CodeSignal General Coding Assessment 是按通过的隐藏用例计分的,所以提交前一定要自己 dry run 几个边界例子,确认逻辑站得住再交。

Q2:共享题库的老题现在还会出现吗?

会。虽然界面刷新了一版、也轮换了部分题目,但共享题库里的经典题仍然高频复现。我这次四道里就有三道能在往期讨论里对上号,提前刷一遍高频题很值。

Q3:Q3 内存模拟里 alloc 找不到足够空间怎么处理?

扫描整段内存后连续空闲长度始终没达到 x,就返回 -1,且不改动任何状态。注意每碰到一个被占用的单元要立刻把连续计数清零。

Q4:Q4 的题面被贴错了,怎么判断真正要考什么?

共享题库偶尔会出现题面与测试用例不匹配的情况。遇到这种,以测试用例和函数签名为准去反推真实逻辑——这一题的 ADD / QUERY 语义就是从用例还原出来的障碍区间查询。

Q5:Q4 用有序数组还是有序集合更好?

如果查询远多于插入,bisect + 有序数组足够,查询 O(log n)、插入 O(n)。如果插入也很频繁,换成 sortedcontainers.SortedList,插入和查询都能到 O(log n)。


正在准备 TikTok 的 OA? 我们熟悉 CodeSignal General Coding Assessment 的题库与节奏,能提供全程 OA辅助 / OA代面 服务,陪你把高频题型和边界处理一次打磨到位。

立即添加微信 Coding0201获取一对一定制备考方案

联系方式