← 返回博客列表 IMC OA SWE 复盘:网格通行券 DP + 加权 LFU 缓存
IMC

IMC OA SWE 复盘:网格通行券 DP + 加权 LFU 缓存

2026-08-15

IMC 的 SWE OA 帮大家跑了一遍,题量不大,但难度在线,这里总结一份给准备中的同学做参考。一共 2 道 coding,120 分钟,HackerRank 自选语言,难度中等偏上,主要考察算法基础、工程思维以及高效实现能力。需要 OA辅助 或 OA代面 的同学,也可以照着这份复盘对齐节奏。

OA 概览与时间分配

维度 详情
平台 HackerRank,自选语言
时长 120 分钟
题量 2 道 coding
难度 中等偏上
考察 算法基础、工程思维、高效实现

两道题共 120 分钟,关键是避免前半小时在第一题里绕得太久,导致第二题没有留下读题和补边界的时间。建议按这个节奏推进:

  1. 先完整读两题,判断哪题状态更清晰。
  2. 第一题争取在 35 到 45 分钟内写出可过样例和主测试的版本。
  3. 第二题优先写出正确状态,再处理压缩空间、剪枝或复杂度优化。
  4. 最后预留 10 分钟,检查空输入、极值、重复元素、整型溢出与初始化。

Q1:带通行券的网格路径计数

题意

给定一个由 0 和 1 组成的网格,机器人从左上角 (0,0) 出发,只能向右或向下移动,目标是到达右下角。1 表示可以正常通过,0 表示障碍,但机器人最多可以使用 k 次通行券,每次通过一个障碍消耗一张,包括起点和终点。要求计算所有合法路径的数量,并对 10^9 + 7 取模。

思路

使用三维动态规划,定义 dp[i][j][t] 表示到达位置 (i,j) 且已经使用 t 张通行券的路径数量:

最后累加终点使用 0 到 k 张通行券的所有状态。时间复杂度 O(nmk),空间复杂度 O(nmk),也可以用滚动数组优化到 O(mk)

Python 实现

MOD = 10**9 + 7


def count_paths(grid, k):
    """grid: 0/1 二维网格;k: 通行券上限(障碍与起点终点都算消耗)。
    返回从左上到右下、向右/向下移动的合法路径数 % 1e9+7。"""
    n, m = len(grid), len(grid[0])
    # dp[j][t] 表示当前行到达列 j、已用 t 张券的路径数(滚动数组)
    dp = [[0] * (k + 1) for _ in range(m)]

    start_cost = 0 if grid[0][0] == 1 else 1
    if start_cost <= k:
        dp[0][start_cost] = 1

    for i in range(n):
        for j in range(m):
            if i == 0 and j == 0:
                continue
            cost = 0 if grid[i][j] == 1 else 1   # 障碍需消耗一张券
            ndp = [0] * (k + 1)
            for t in range(cost, k + 1):
                total = 0
                if j > 0:
                    total += dp[j - 1][t - cost]  # 从左侧来
                if i > 0:
                    total += dp[j][t - cost]      # 从上方来(滚动数组里 dp[j] 仍是上一行)
                ndp[t] = total % MOD
            dp[j] = ndp
        # 注意:上面对同一行内 dp[j-1] 的引用需保证已更新为本行值
    return sum(dp[m - 1]) % MOD

需要留意滚动数组里「左侧」与「上方」引用的时序:处理到 (i,j) 时,dp[j-1] 应已是本行更新后的值,dp[j] 仍是上一行的值。若担心时序绕晕,考场上直接用完整三维数组 dp[i][j][t] 写最稳妥,AC 后再压空间。

时间复杂度O(nmk)空间复杂度:滚动数组 O(mk),完整版 O(nmk)


Q2:按容量限制的加权 LFU 缓存

题意

实现一个按容量大小限制的加权 LFU 缓存。每条数据包含 keyvaluesize,所有数据的 size 总和不能超过 capacity

思路

用一个 HashMap 保存 key 到缓存节点的映射,再按访问频率维护多组双向链表,每条链表内部按最近使用顺序排列,同时记录 min_freq 和当前总大小:

借助哈希表与双向链表,单次访问、更新和淘汰都可以做到平均 O(1)。这里用 Python 的 OrderedDict 按频率分桶,天然维护每个频率桶内的 LRU 顺序,代码更短且等价于手写双向链表。

Python 实现

from collections import defaultdict, OrderedDict


class WeightedLFU:
    def __init__(self, capacity):
        self.capacity = capacity
        self.size_used = 0
        self.node = {}                          # key -> [value, size, freq]
        self.freq = defaultdict(OrderedDict)    # freq -> OrderedDict[key],头部最久未用
        self.min_freq = 0

    def _remove_from_bucket(self, key, f):
        """把 key 从频率桶 f 摘出,桶空则清理并按需上移 min_freq。"""
        del self.freq[f][key]
        if not self.freq[f]:
            del self.freq[f]
            if self.min_freq == f:
                self.min_freq = f + 1           # 该桶已空,最低频率上移

    def _bump(self, key):
        """访问命中:把 key 从频率 f 桶移到 f+1 桶尾部(最近使用)。"""
        item = self.node[key]
        f = item[2]
        self._remove_from_bucket(key, f)
        item[2] = f + 1
        self.freq[f + 1][key] = None            # 尾部 = 最近使用

    def get(self, key):
        if key not in self.node:
            return -1
        self._bump(key)
        return self.node[key][0]

    def _evict(self, need):
        """淘汰直到能容纳 need:取 min_freq 桶头部(最久未用)删除。"""
        while self.size_used + need > self.capacity and self.node:
            bucket = self.freq[self.min_freq]
            old_key, _ = bucket.popitem(last=False)   # 头部 = 最久未使用
            self.size_used -= self.node[old_key][1]
            del self.node[old_key]
            if not bucket:
                del self.freq[self.min_freq]
                # 下一轮若还需淘汰,会从新的 min_freq 桶继续;这里无需精确上移

    def put(self, key, value, size):
        if size > self.capacity:
            return                              # 单条超容量,忽略
        if key in self.node:                    # 更新:值和大小变,频率不变,刷新顺序
            item = self.node[key]
            f = item[2]
            self._remove_from_bucket(key, f)    # 先摘出,避免被自身淘汰
            self.size_used += size - item[1]
            item[0], item[1] = value, size
            self._evict(0)                      # 变大后可能超容,先腾空间
            self.freq[f][key] = None            # 放回原频率桶尾部(最近使用)
            if f < self.min_freq or not self.freq.get(self.min_freq):
                self.min_freq = min(self.freq) if self.freq else f
            return
        self._evict(size)                       # 新增:腾出空间
        self.node[key] = [value, size, 1]
        self.freq[1][key] = None
        self.size_used += size
        self.min_freq = 1

更新已有键时的关键点:先把旧节点从其频率桶摘出再触发淘汰,避免刚更新的键被误删;淘汰完再放回原频率桶(频率不变)并刷新为最近使用,同时按剩余的最小键修正 min_freq

复杂度get / put 均摊 O(1)(哈希查找 + 频率桶内 OrderedDict 的头尾操作都是 O(1));淘汰单个节点 O(1),修正 min_freqmin(self.freq) 在桶数很少时可视为常数。


备考策略


FAQ

Q1:IMC 的 SWE OA 难度如何?和 LeetCode 相比是什么水平?

两道题、120 分钟,难度中等偏上。算法本身不算最难那档,但更看重工程实现的完整度和边界处理——DP 计数要把通行券消耗和取模处理干净,LFU 要把频率桶、LRU 和容量淘汰的状态维护对,比纯 LeetCode 中等题更考细致。

Q2:IMC SWE OA 用什么平台、多长时间、几道题?

HackerRank 平台,自选语言,120 分钟,2 道 coding。时间相对宽裕,但两题都需要一定的建模和边界处理时间,节奏管理很重要。

Q3:Q1 的通行券路径计数必须用三维 DP 吗?

是的,因为「已用几张券」是一个必须记录的状态维度。dp[i][j][t] 三维最直观;AC 后可用滚动数组把行维度压掉,降到 O(mk) 空间。注意障碍、起点、终点都算消耗券。

Q4:加权 LFU 和普通 LFU 有什么区别?

普通 LFU 按条数淘汰,加权 LFU 按每条数据的 size 累加,总和不能超过 capacity,所以一次 PUT 可能要淘汰多条才能腾出空间。此外「更新已有键不改变频率、单条 size 超容量直接忽略」是加权版本的两个特有规则。

Q5:如何准备 IMC 这类偏工程实现的 OA?

重点练两类题:一是带额外状态维度的 DP(如本题的通行券),二是 LRU/LFU 这类需要哈希表 + 链表组合的系统模拟题。练习时强制自己先写正确状态再优化,并养成最后过一遍边界清单的习惯。


正在准备 IMC 或其他量化 / 交易类公司的 SWE OA? 我们熟悉 HackerRank 自选语言、两题 120 分钟这类节奏,能陪你把带状态维度的 DP、LFU/LRU 系统模拟题的建模和边界一次打磨到位,提供全程 OA辅助 与 OA代面 支持。

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

联系方式