程序员老鬼

面完十五分钟就接 offer了,hr还打电话给我读了很长一段用心的面试评价,很幸运

十五分钟面完就接 offer,这事听着都像职场爽文。更离谱的是,HR 还专门打电话过来,读了一大段面试评价,那种认真劲儿,打工人听完都得愣两秒:现在招聘市场还有这种待遇?
现在太多人找工作,简历投出去像扔进海里,能收到个“不合适”都算讲武德,更别提面完马上给结果,还附赠一段走心反馈。 这种体验最稀缺的,根本不是 offer 本身,是那种“我被认真对待了”的感觉。人一找工作久了,最先磨没的就是这点体面。碰上这种公司,哪怕后面不一定十全十美,起码开头没把人当流水线零件。打工人有时候图的,也就是这口气。 面 试 题 : 单调递增的数字 一眼看这个题,很多人会顺手上回溯,甚至想把所有小于等于 n 的数字都扫一遍。这个路子能做,但我一般不太信,数据一大就显得笨。 题目要找的是:给定一个非负整数 n ,返回小于等于它、并且各位数字单调递增的最大数字。像 1234 这种就算, 332 这种不算,因为 3 > 2 ,后面掉下去了。 这题真正麻烦的地方,不在“怎么判断递增”,而在“发现某一位破坏递增后,前面到底该怎么改”。拿 332 举例,很多人第一反应是把最后那个 2 改大,或者把中间那个 3 改小一点就完事。其实都不对。这个题要的是 不超过 n 的最大值 ,所以一旦某一位出现下降,正确动作不是修后面,而是先把前一位减 1,再把后面全部变成 9 。 比如:
  • 332 -> 329 还不行,因为 3 > 2
  • 再往前退一位 -> 299
  • 这时候就对了,而且还是最大
这个思路有点像线上修数据:不是头痛医头,得看影响是不是会往前传。 直接上代码,写法我习惯这样,够短,也不绕:
class Solution {
    public int monotoneIncreasingDigits(int n) {
        char[] arr = String.valueOf(n).toCharArray();
        int mark = arr.length;

        for (int i = arr.length - 1; i > 0; i--) {
            if (arr[i - 1] > arr[i]) {
                arr[i - 1]--;
                mark = i;
            }
        }

        for (int i = mark; i < arr.length; i++) {
            arr[i] = '9';
        }

        return Integer.parseInt(new String(arr));
    }
}
这段代码重点就两步。 第一步,从右往左扫。为什么不是从左往右?因为你一旦发现 arr[i - 1] > arr[i] ,说明前一位大了,得减 1。但前一位减完,未必还能和更前面保持递增,所以要继续往左检查。这个地方如果从左往右写,后面还得反复回退,代码会别扭很多。 第二步,记录一个 mark 。从这个位置开始,后面的数全部改成 9 。原因很直接:前面既然已经退了一步,后面当然要尽可能大,才能保证结果最大。 拿 1234 跑一下,没有任何一位出问题, mark 不变,结果还是它自己。 拿 10 跑一下:
  • 1 > 0 ,前面减一,变成 0
  • 后面补 9
  • 得到 09 ,转成整数就是 9
拿 120 跑一下:
  • 2 > 0 ,变成 110 ,并记录位置
  • 继续看 1 和 1 ,没问题
  • 后面补 9
  • 结果 119
这题不难,难的是第一次容易在“局部修补”里绕住。真正顺手的解法,往往都是先找到那一位开始坏掉,再把后面的路一次性铺平。算法题里这种味道挺常见:局部出错,修正却不一定发生在局部。