← 返回面经列表

微软OA真题复盘:最大累积得分游戏 + 最少操作次数

4 分钟

Microsoft OA Question 1

题目一:最大累积得分游戏

问题描述

一个游戏规则如下:

  • 玩家从第0个格子开始,初始得分为0
  • 有n个格子,编号从0到n-1,每个格子有一个分值
  • 第0个格子的分值始终为0
  • 每次移动可以:
    • 向右移动1步,或
    • 向右移动p步,其中p是个位数为3的素数(如3、13、23、43等)
  • 玩家不能超过最后一个格子
  • 当玩家落在某个格子时,该格子的分值会加到总分中
  • 游戏在玩家到达第n-1个格子时结束

求:到达终点的最大可能得分

解题思路

第一步:预处理素数

首先需要找出所有个位数为3的素数。这些素数是:3、13、23、43、53、73、83等。

def get_primes_ending_with_3(max_n):
    """获取所有不超过max_n且个位数为3的素数"""
    primes = []
    
    def is_prime(num):
        if num < 2:
            return False
        if num == 2:
            return True
        if num % 2 == 0:
            return False
        for i in range(3, int(num**0.5) + 1, 2):
            if num % i == 0:
                return False
        return True
    
    for num in range(3, max_n, 10):  # 个位数为3的数:3, 13, 23, 33...
        if is_prime(num):
            primes.append(num)
    
    return primes

第二步:动态规划求解

使用DP数组,其中dp[i]表示到达第i个格子的最大得分。

关键思路

  • 初始化:dp[0] = 0(起点),其他位置初始化为负无穷(表示不可达)
  • 状态转移:对于每个可达的位置i,尝试所有可能的移动方式
    • 移动1步到i+1
    • 移动p步到i+p(p为个位数为3的素数)
  • 取较大值更新目标位置
def max_score_game(cells, n):
    """
    求最大累积得分
    
    Args:
        cells: 格子分值数组
        n: 格子总数
    
    Returns:
        到达终点的最大得分
    """
    # 预处理素数
    primes = get_primes_ending_with_3(n)
    
    # DP初始化
    dp = [-float('inf')] * n
    dp[0] = cells[0]  # 起点分值为0
    
    # 状态转移
    for i in range(n):
        if dp[i] == -float('inf'):
            continue  # 不可达的位置跳过
        
        # 尝试移动1步
        if i + 1 < n:
            dp[i + 1] = max(dp[i + 1], dp[i] + cells[i + 1])
        
        # 尝试移动p步(p为个位数为3的素数)
        for p in primes:
            if i + p < n:
                dp[i + p] = max(dp[i + p], dp[i] + cells[i + p])
            else:
                break  # 后续素数更大,不需要继续
    
    return dp[n - 1]

第三步:示例演算

以题目给定的例子:cells = [0, -10, -20, -30, 50],n = 5

  • 起点:dp[0] = 0
  • 从0出发:
    • 移动1步到1:dp[1] = 0 + (-10) = -10
    • 移动3步到3:dp[3] = 0 + (-30) = -30
  • 从1出发(dp[1] = -10):
    • 移动1步到2:dp[2] = -10 + (-20) = -30
    • 移动3步到4:dp[4] = -10 + 50 = 40 ✓
  • 从2出发(dp[2] = -30):
    • 移动1步到3:dp[3] = max(-30, -30 + (-30)) = -30
    • 移动3步到5:超出范围
  • 从3出发(dp[3] = -30):
    • 移动1步到4:dp[4] = max(40, -30 + 50) = 40

最终答案:40

时间复杂度分析

  • 预处理素数:O(n log log n)(筛法)
  • DP遍历:O(n × k),其中k是个位数为3的素数个数(通常很小)
  • 总体:O(n log log n)

常见陷阱

陷阱1:忘记初始化为负无穷,导致不可达位置被错误计算 ✅ 解决:明确区分可达和不可达状态

陷阱2:素数预处理不完整,遗漏某些素数 ✅ 解决:使用筛法或逐一检验,确保所有个位数为3的素数都被找到

陷阱3:没有考虑负数分值的情况 ✅ 解决:DP初始化为负无穷,正确处理所有分值


题目二:最少操作次数

Microsoft OA Question 2

问题描述

给定一个正整数n,每次操作可以:

  • 加上2^i(i ≥ 0),或
  • 减去2^i(i ≥ 0)

求:将n变为0的最少操作次数

解题思路

核心观察:二进制贪心

这道题的关键在于观察n的二进制表示,并利用二进制进位的性质。

关键规律

  • 如果当前位是0,直接右移(相当于除以2)
  • 如果当前位是1,需要通过加或减来消除这个1

详细算法

def min_operations(n):
    """
    求将n变为0的最少操作次数
    
    Args:
        n: 正整数
    
    Returns:
        最少操作次数
    """
    operations = 0
    
    while n > 0:
        # 观察n的最后两位二进制
        last_two_bits = n & 3  # 取最后两位:n % 4
        
        if last_two_bits == 0:
            # 00:直接右移
            n >>= 1
        elif last_two_bits == 1:
            # 01:减1(变成00),然后右移
            n -= 1
            operations += 1
        elif last_two_bits == 2:
            # 10:直接右移
            n >>= 1
        else:  # last_two_bits == 3
            # 11:加1(进位变成100),然后右移
            n += 1
            operations += 1
        
        operations += 1  # 右移操作计数
    
    return operations

算法解释

为什么这样做?

观察二进制的最后两位:

  1. 00:最后一位是0,直接右移即可
  2. 01:最后一位是1,需要减1变成00,然后右移
  3. 10:最后一位是0,直接右移即可
  4. 11:最后两位都是1,加1会产生进位(变成100),然后右移

这样做的好处是:

  • 每次操作都能消除至少一位
  • 通过进位可以合并多个1
  • 贪心策略保证最优性

示例演算

以n = 5为例:

n = 5 = 101₂

第1步:最后两位是01
  - 减1:5 - 1 = 4 = 100₂(操作1)
  - 右移:4 >> 1 = 2 = 10₂(操作2)

第2步:最后两位是10
  - 右移:2 >> 1 = 1 = 1₂(操作3)

第3步:最后两位是01
  - 减1:1 - 1 = 0(操作4)
  - 右移:0 >> 1 = 0(操作5)

总操作数:5

验证:5 → 4 → 2 → 1 → 0
     (减1) (÷2) (÷2) (减1)

等等,让我重新计算。题目说的是"操作",每次操作是加或减2^i。

重新理解题意

实际上,我们需要重新理解这个问题:

  • 每次操作是加或减一个2的幂次
  • 目标是用最少的操作次数将n变为0

更优的思路:这等价于用最少个数的2的幂次来表示n。

例如:

  • n = 5 = 4 + 1 = 2² + 2⁰(2个操作)
  • n = 5 = 8 - 3 = 8 - 2 - 1 = 2³ - 2¹ - 2⁰(3个操作)

所以最优是2个操作。

改进的算法

def min_operations_v2(n):
    """
    求将n变为0的最少操作次数(改进版)
    
    使用二进制表示的1的个数作为基础
    """
    operations = 0
    
    while n > 0:
        last_two_bits = n & 3
        
        if last_two_bits == 0:
            # 00:直接右移
            n >>= 1
        elif last_two_bits == 1:
            # 01:减1
            n -= 1
            operations += 1
        elif last_two_bits == 2:
            # 10:直接右移
            n >>= 1
        else:  # last_two_bits == 3
            # 11:加1(进位)
            n += 1
            operations += 1
    
    return operations

对于n = 5:

5 = 101₂
最后两位是01 → 减1 → 4 = 100₂(操作1)
最后两位是00 → 右移 → 2 = 10₂
最后两位是10 → 右移 → 1 = 1₂
最后两位是01 → 减1 → 0(操作2)

总操作数:2 ✓

时间复杂度分析

  • 循环次数:O(log n)(每次至少右移一位)
  • 每次迭代:O(1)
  • 总体:O(log n)

常见陷阱

陷阱1:混淆"操作次数"和"右移次数" ✅ 解决:只有加或减才算操作,右移是免费的

陷阱2:没有利用进位的性质 ✅ 解决:当最后两位是11时,加1会产生进位,可以合并多个1

陷阱3:贪心策略不正确 ✅ 解决:观察最后两位,按规律处理


面试应对策略

时间管理

题目一(最大累积得分)

  • 理解题意:3-5分钟
  • 设计算法:5-7分钟
  • 编码实现:10-15分钟
  • 测试验证:5分钟
  • 总计:25-35分钟

题目二(最少操作次数)

  • 理解题意:2-3分钟
  • 分析二进制规律:5-8分钟
  • 编码实现:8-10分钟
  • 测试验证:3-5分钟
  • 总计:18-26分钟

临场技巧

  1. 题目一

    • 先确认素数的定义(个位数为3)
    • 明确边界条件(不能超过n-1)
    • 从简单例子开始推导
  2. 题目二

    • 先用几个例子找规律
    • 观察二进制表示
    • 解释为什么加1或减1是最优的

常见问题

Q:如果n很大(接近2^60),算法还能工作吗?

A:可以。题目二的时间复杂度是O(log n),即使n = 2^60也只需要约60次迭代。题目一需要预处理素数,但个位数为3的素数密度足够大,不会成为瓶颈。

Q:题目一中,如果所有格子分值都是负数怎么办?

A:仍然需要到达终点,所以选择分值最小的路径。DP算法会自动处理这种情况。

Q:能否用其他方法解题目二?

A:可以。题目二本质上是求用最少个数的2的幂次表示n。也可以用BFS或记忆化搜索,但二进制贪心是最优的。


完整代码实现

def get_primes_ending_with_3(max_n):
    """获取所有不超过max_n且个位数为3的素数"""
    primes = []
    
    def is_prime(num):
        if num < 2:
            return False
        if num == 2:
            return True
        if num % 2 == 0:
            return False
        for i in range(3, int(num**0.5) + 1, 2):
            if num % i == 0:
                return False
        return True
    
    for num in range(3, max_n, 10):
        if is_prime(num):
            primes.append(num)
    
    return primes


def max_score_game(cells):
    """求最大累积得分"""
    n = len(cells)
    primes = get_primes_ending_with_3(n)
    
    dp = [-float('inf')] * n
    dp[0] = cells[0]
    
    for i in range(n):
        if dp[i] == -float('inf'):
            continue
        
        # 移动1步
        if i + 1 < n:
            dp[i + 1] = max(dp[i + 1], dp[i] + cells[i + 1])
        
        # 移动p步
        for p in primes:
            if i + p < n:
                dp[i + p] = max(dp[i + p], dp[i] + cells[i + p])
            else:
                break
    
    return dp[n - 1]


def min_operations(n):
    """求最少操作次数"""
    operations = 0
    
    while n > 0:
        last_two_bits = n & 3
        
        if last_two_bits == 0:
            n >>= 1
        elif last_two_bits == 1:
            n -= 1
            operations += 1
        elif last_two_bits == 2:
            n >>= 1
        else:  # last_two_bits == 3
            n += 1
            operations += 1
    
    return operations


# 测试
if __name__ == "__main__":
    # 测试题目一
    cells = [0, -10, -20, -30, 50]
    print(f"最大得分: {max_score_game(cells)}")  # 输出: 40
    
    # 测试题目二
    print(f"最少操作次数: {min_operations(5)}")  # 输出: 2

🚀 需要面试辅助?

oavoservice 提供专业的 OA/VO 辅助服务,帮助你快速通过微软、亚马逊等大厂的技术面试。

👉 立即添加微信:Coding0201


联系方式

Email: [email protected] Telegram: @OAVOProxy