题目描述

✅ 738. 单调递增的数字

image-20260929000134860

题意分析

求不超过 n、各位从左到右不下降的最大整数。相邻位可以相等,这里的“单调递增”指非严格递增。数字大小首先由高位决定,所以应尽量保留原数的高位,只在无法保持合法时降低某一位。

解法:从右向左修正

核心思路

[!blue]

发现 digits[i - 1] > digits[i] 时,若保持前面的高位不变,就不能直接增大右位来消除下降,否则可能超过原数。应让左位减 1;一旦某个高位变小,右侧就可以尽量取大,因此最终把它后面的所有位填成 9。

但左位减 1 后,可能又比它左边的数字小,产生新的下降。于是从右向左扫描,让这次修正继续向左传播;直到前缀重新满足不下降,或已经到达最高位。每次修正只减 1,保留了该位置在必须降低时能够取到的最大值。

用 mark 记录最终需要填 9 的后缀起点。发生下降时降低 digits[i - 1],并令 mark = i;如果后续修正传播到更左侧,mark 也随之左移。扫描期间暂不填 9,以便下一轮仍能看见刚减小的数位,判断是否还要向左修正。全部检查完后,再统一把 mark 及其右侧改为 9。

最终保留下来的前缀没有下降,后缀全部为 9,与前缀也能衔接。若发生修改,结果与原数最早不同的那一位恰好减 1,因此结果一定小于原数;更高位保持不动,降低的这一位取最大可行值,后缀也取最大值,得到的就是最大合法结果。若没有下降,mark 保持为长度,直接返回原数。

解题步骤

  1. 将 n 转为可修改的十进制位数组,令 mark 等于数组长度,表示暂时没有需要填 9 的后缀。
  2. 从最右侧相邻数位向左检查。若左位大于右位,将左位减 1,并将 mark 更新为右位下标。
  3. 扫描结束后,把下标从 mark 到末尾的数位全部设为 9。
  4. 将数位数组转回整数,转换会自然去掉可能出现的前导 0。n = 0 或仅有一位时,无需进行任何修正。

代码实现

class Solution {
    public int monotoneIncreasingDigits(int n) {
        char[] digits = String.valueOf(n).toCharArray();
        // 记录待填九的后缀起点,初始表示空后缀
        int mark = digits.length;

        for (int i = digits.length - 1; i > 0; i--) {
            if (digits[i - 1] > digits[i]) {
                // 减少左位后可能继续向左产生下降,交给下一轮检查
                digits[i - 1]--;
                mark = i;
            }
        }

        // 所有连锁修正完成后,再将后缀最大化
        for (int i = mark; i < digits.length; i++) {
            digits[i] = '9';
        }

        return Integer.parseInt(new String(digits));
    }
}
import "strconv"

func monotoneIncreasingDigits(n int) int {
    digits := []byte(strconv.Itoa(n))
    // 记录待填九的后缀起点,初始表示空后缀
    mark := len(digits)
    for i := len(digits) - 1; i > 0; i-- {
        if digits[i-1] > digits[i] {
            // 减少左位后可能继续向左产生下降,交给下一轮检查
            digits[i-1]--
            mark = i
        }
    }
    // 所有连锁修正完成后,再将后缀最大化
    for i := mark; i < len(digits); i++ {
        digits[i] = '9'
    }
    ans, _ := strconv.Atoi(string(digits))
    return ans
}

复杂度分析

  • 时间复杂度:$O(d)$,其中 $d$ 为 n 的十进制位数。转换、反向扫描和填充后缀都只需线性时间。
  • 空间复杂度:$O(d)$,保存可修改的数位数组和转换时的字符串。

关键点总结

[!green]

  • 修正向左传播,填九延迟到扫描完成。
  • mark=长度 表示没有待填后缀。

易错点总结

[!yellow]

  • 只处理第一次发现的下降就结束,会漏掉减 1 后向更高位产生的连锁下降。
  • 不填九虽可能合法,却不是最大答案。
  • 把相等位也当成下降,会排除合法的重复数字。

相似题目

题目 难度 关联与区别
402. 移掉 K 位数字 中等 同样在数位首次不满足字典序目标时调整前缀,本题降低数位并填充后缀,原题删除固定数量字符。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2025/61167177
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!