程序员老鬼

晋升答辩那天,我准备了20页PPT,把过去一年的成就讲得天花乱坠,结果..后来才发现,晋升名单其实早就在答辩前定好了

刚看到个贴子,说一位网友晋升答辩,整了20页PPT把一年业绩夸夸其谈,结果评委只问了一句:“你在这个项目里的不可替代性在哪?”他当场愣住,后来才发现晋升名单早就定好了,答辩纯属走过场。

Image

我觉得这事吧,有两层。第一,很多公司晋升确实有“预设答案”,别把一场答辩当成命运大转盘,心态要现实点。第二,就算是走过场,那句“不可替代性”也问得挺扎心:你到底凭什么站在那张台子上?

从我的角度看,“不可替代性”,就是你能解决别人解决不了的问题,能扛别人不愿扛的事。晋升不一定公平,但想清楚这一点,对谁都是刚需。剩下的,就当练本事,别把希望全押在一场PPT上。

面试题:拆分二叉搜索树

昨天晚上十一点多吧,我在公司楼下抽烟呢,手机群里有人突然甩了个算法题,说“拆分二叉搜索树”,我第一反应还以为是那种把树砍两半的暴力活……后来一想不对,这玩意其实特别像线上做灰度:一棵 BST,给你个阈值 V,你得把它拆成两棵树,一棵全是 <= V,另一棵全是 > V,而且还得保持 BST 结构不乱,节点也不能丢。

你们知道 BST 那个味儿吧,左边都小,右边都大,所以拆的时候千万别去全树扫描,那就跟线上全量扫库一样,迟早出事。正确姿势是顺着“阈值”这条线走:

  • 如果当前节点 root.val <= V,那它肯定应该留在“小树”里;但它的右子树里可能有一部分跑到“大树”去,所以只需要去拆右子树。拆完以后,小树这边的 root.right 接回“右子树拆出来的小半边”,大树那半边单独返回。
  • 反过来如果 root.val > V,这个节点就得留在“大树”里;但它左子树里可能有一些其实 <= V,所以去拆左子树。拆完以后,大树这边的 root.left 接回“左子树拆出来的大半边”,小树那半边返回。

说白了就是递归一路下去,每次只动一条边,复杂度基本就是树高,平衡树接近 O(log n),最坏退化成链表才 O(n),但也比你傻乎乎全遍历强太多了。

代码我就按工程里最常见的写法来,返回一个长度为 2 的数组:res[0] 是小树根,res[1] 是大树根。对了,别用什么 Pair 啊三方包啊,面试官看了容易皱眉,直接数组最省事。

publicclassSplitBST{

staticclassTreeNode{
int val;
        TreeNode left, right;
        TreeNode(int v) { this.val = v; }
    }

// 返回结果:res[0] = <= V 的BST根,res[1] = > V 的BST根
publicstatic TreeNode[] splitBST(TreeNode root, int V) {
if (root == null) returnnew TreeNode[]{null, null};

if (root.val <= V) {
// root 属于小树,去拆右子树
            TreeNode[] rightSplit = splitBST(root.right, V);
            root.right = rightSplit[0];      // 右子树里 <=V 的部分接回小树
returnnew TreeNode[]{root, rightSplit[1]}; // >V 的部分就是大树
        } else {
// root 属于大树,去拆左子树
            TreeNode[] leftSplit = splitBST(root.left, V);
            root.left = leftSplit[1];        // 左子树里 >V 的部分接回大树
returnnew TreeNode[]{leftSplit[0], root};  // <=V 的部分就是小树
        }
    }

// 随手写个小测试,别太在意输出格式,主要看结构不乱、节点不丢
publicstaticvoidmain(String[] args){
/*
                4
              /   \
             2     6
            / \   / \
           1  3  5  7
         */

        TreeNode root = new TreeNode(4);
        root.left = new TreeNode(2);
        root.right = new TreeNode(6);
        root.left.left = new TreeNode(1);
        root.left.right = new TreeNode(3);
        root.right.left = new TreeNode(5);
        root.right.right = new TreeNode(7);

        TreeNode[] res = splitBST(root, 2);
// res[0] 应该包含 1,2;res[1] 应该包含 3,4,5,6,7(并保持BST)
        System.out.println(res[0].val); // 大概率是2
        System.out.println(res[1].val); // 大概率是4
    }
}

你要是把这个思路带到日常写业务,其实挺像“按条件把一坨数据拆成两条链路”,关键点就是:别乱动不该动的部分,只沿着可能跨界的那条边去处理。昨天群里还有人问“能不能迭代写”,能写啊,但递归其实更像你脑子里那棵树的形状,写错概率更低……哎我先不说了,刚有人 @我 说线上又有个慢 SQL,要去看下日志了。

-END-