题解:54.最少步数归零问题

221 阅读4分钟

解题思路:最少步数归零问题

解决该问题的核心在于如何通过最少的删除操作,将数组中每个数字逐步归零。以下是具体解题思路的逐步推导:


一、问题理解

首先需要理解题目中的关键点:

  1. 删除操作:每次可以从一个数字中删除一位。
  2. 目标:通过最少的删除操作,使数组中的所有数字最终变为 0
  3. 优化目标:尽可能减少删除次数。

示例分析

  • 数字 103 可以通过以下删除操作逐步归零:
    1. 删除第1位:103 -> 03 -> 3 -> 0,需要 3 次。
    2. 删除第2位:103 -> 13 -> 3 -> 0,需要 3 次。
    3. 删除第3位:103 -> 10 -> 1 -> 0,需要 3 次。

无论删除顺序如何,一个数字的位数决定了归零所需的最少步数。


二、问题分解与思路推导

1. 核心目标

将数字逐步归零的核心问题是:如何通过最优次序的删除操作,减少整体的删除步数。

  • 对每个数字,删除的顺序会影响中间状态,但最终需要归零的总步数与每次删除后的数字“剩余难度”密切相关。
  • 使用数字的各位和(digit sum)来衡量数字的“剩余难度”。
  • 优先处理当前“最容易归零”的数字,逐步消耗删除次数。

2. 贪心策略

为了减少总步数,我们采用如下贪心策略:

  1. 按难度优先级处理数字
    • 每次优先处理 digit sum 最小的数字,即最容易归零的数字。
  2. 逐步归零
    • 每次删除一位数字后,重新评估剩余数字的难度。
  3. 动态调整优先级
    • 使用最小堆维护数字的优先级,确保每次从当前最容易归零的数字开始操作。

三、贪心法与堆的结合

1. 为什么选择贪心法?

  • 每次优先处理 digit sum 最小的数字,能够快速减少总步数。
  • 每步的局部最优解(删除最容易的数字)最终能够带来全局最优解(总步数最少)。

2. 为什么用最小堆?

  • 最小堆动态维护当前数组中数字的“最小难度”,保证每次操作选取最优对象。
  • 每次删除一位后,将更新后的数字重新加入堆。

四、解题方法总结

核心思路

  1. 计算每个数字的初始难度(digit sum),并加入最小堆。
  2. 每次从堆中取出难度最小的数字,执行删除操作。
  3. 删除后更新数字的难度,并将其重新加入堆(如果未归零)。
  4. 重复操作直至所有数字归零。

五、算法设计与伪代码

算法设计

  1. 将所有数字及其初始难度(digit sum)存入最小堆。
  2. 每次取出堆顶数字,删除其中一位。
  3. 若数字未归零,则更新其难度并重新放回堆中。
  4. 记录操作次数,直到堆为空。

伪代码

  1. 初始化最小堆 heap,将所有数字及其 digit sum 加入堆。
  2. 初始化操作计数器 steps = 0。
  3. 当堆不为空时: a. 从堆中取出当前难度最小的数字 num。 b. 如果 num == 0,跳过。 c. 删除 num 的某一位,使其 digit sum 最小化。 d. 更新操作次数 steps += 1。 e. 如果 num 未归零,将更新后的数字重新加入堆。
  4. 返回操作计数器 steps。

代码

def solution(n: int, a: list) -> int:
    def digit_sum(x):
        """Calculate the sum of digits of a number."""
        return sum(int(d) for d in str(x))

    steps = 0
    
    # Use a min-heap to always process the smallest digit-sum first.
    import heapq
    heap = []

    # Add each number and its digit sum to the heap.
    for num in a:
        heapq.heappush(heap, (digit_sum(num), num))

    while heap:
        # Extract the smallest digit-sum number.
        _, num = heapq.heappop(heap)
        if num == 0:
            continue

        # Find the digit that gives the largest reduction in digit sum and remove it.
        str_num = str(num)
        min_digit_sum = float('inf')
        best_num = None

        for i in range(len(str_num)):
            # Remove the i-th digit and calculate the new number.
            new_num = int(str_num[:i] + str_num[i + 1:]) if str_num[:i] + str_num[i + 1:] else 0
            new_digit_sum = digit_sum(new_num)
            if new_digit_sum < min_digit_sum:
                min_digit_sum = new_digit_sum
                best_num = new_num

        # Update the number to the best reduced version and push it back to the heap.
        steps += 1
        if best_num > 0:
            heapq.heappush(heap, (digit_sum(best_num), best_num))

    return steps