程序员老鬼

疯了,英伟达北京员工,年薪1688万,光个税就扣了687万

刚看到个贴子,说英伟达北京有员工一年总包1688万,个税直接扣了687万,我是真被震住了。😵‍💫 这数字放在普通人眼里,基本相当于“别人交税=我一辈子”。

Image

我觉得这事吧,最震撼的不是工资条,而是——高收入的世界跟我们完全不是一个维度的事。

网友回帖有人酸、有羡慕,我看着也理解,但说到底,人家那种薪资,本质上是全球顶尖竞争、技术壁垒、行业红利叠加出来的。

换个角度想,这种贴子最大的意义是让人看到现实的社会差距,同时也提醒我们:普通人的路,还是得扎扎实实提升自己的价值。羡慕可以,但焦虑没必要,每个人的赛道不同。

见识丰富心态稳了,看山还是山,过好自己的日子,比啥都实在。【备注:文末可领最新资料】

面试题:最优账单平衡

那天晚上在工位啃外卖,隔壁小伙儿突然冒一句:“东哥,你做过那个最优账单平衡没?就室友AA那种。”我一听这场景就熟:几个人一起吃饭、打车、买电影票,最后一堆乱七八糟账单,问题就变成一句话——怎么用“最少的转账次数”把钱结清。

注意哈,这题不是算谁该给谁多少钱,这个大家都能算出来;难点在于:在所有“能结清”的方案里,找一条“最少转账次数”的方案,这才叫最优账单平衡(LeetCode 465 就是这玩意儿)。

先把题目脑补清楚。一般给你的输入长这样:

transactions = [
  [0, 1, 10], // 0 给 1 付了 10
  [2, 0,  5], // 2 给 0 付了 5
  ...
]

最终目标:返回一个整数,表示大家再互相转几笔就能完全结清,且这几笔数要最少。

那咋下手?直接在 transactions 上转来转去很难想,所以第一步先把问题“压缩”一下。

第一步:算每个人的净资产变化

就当大家最终只有一个“系统账本”: 正数表示这个人整体上“付多了,要收钱”, 负数表示“付少了,欠别人钱”。

比如上面那个例子,粗算一下:

  • 0:收 5 付 10,净 -5
  • 1:收 10,净 +10
  • 2:付 5,净 -5

我们就可以得到一个数组 balances,忽略那些刚好为 0 的人:

[-5, -5, 10]

接下来问题就变成:给你一个数组,和为 0,每个位置代表一个人(正负表示收/付),你可以让下标 i 和 j 之间做一次转账,把他们的绝对值相互抵消一部分,问最少要多少次才能全部变成 0。

第二步:暴力其实就那一条路——回溯搜索

这个问题贪心很难一眼搞定,因为你一开始怎么配对,后面会被影响,所以常规做法就是回溯 + 剪枝。

核心思路长这样:

1)从头找第一个不是 0 的人 idx,说明他还没结清。 2)然后让他去找后面任何一个“符号相反”的人 j,和对方结一笔:

  • 比如 balances[idx] = -5,balances[j] = 10, 那我可以假装 idx 给 j 转 5,结完之后就是 0 和 5。 3)这一次转账就算了一笔,把数组状态改完,递归往后算,看看后面还需要多少笔。 4)回溯回来再恢复现场,换一个 j 再试。 5)在所有尝试里取最小值,就是答案。

听起来有点暴力对吧,但人数一般不大(LeetCode 上最多 8~12 个人有欠款),配合剪枝是能跑得动的。

几个重要剪枝,不然会超时:

  • 跳过已经结清的:一开始就把净值为 0 的人去掉,递归里也始终往后找第一个非 0 的人,从那儿开始配对。
  • 跳过“数值相同”的重复尝试:比如 balances[j] 跟前面某个试过的值一样,那这一步会产生完全一样的状态,没必要再来一遍。
  • 如果某次配对刚好把一个人的账结到 0 了,通常可以提前 break,这个配对已经是当前这一步最好的效果了,再试其他人收益不大。

第三步:用 Java 把这个逻辑写下来

直接上代码,你可以当成一个工具方法丢到项目里:

classSolution{

publicintminTransfers(int[][] transactions){
// 1. 先算每个人的净资产变化
        Map<Integer, Integer> map = new HashMap<>();
for (int[] t : transactions) {
int from = t[0], to = t[1], amount = t[2];
            map.put(from, map.getOrDefault(from, 0) - amount);
            map.put(to,   map.getOrDefault(to,   0) + amount);
        }

// 2. 只保留非 0 的人,压成一个数组
        List<Integer> list = new ArrayList<>();
for (int v : map.values()) {
if (v != 0) {
                list.add(v);
            }
        }
int n = list.size();
if (n == 0) return0; // 本来就结清了

int[] balances = newint[n];
for (int i = 0; i < n; i++) {
            balances[i] = list.get(i);
        }

// 3. 回溯搜索
return dfs(balances, 0);
    }

// 从 start 这个位置开始,最少还需要多少笔转账
privateintdfs(int[] balances, int start){
int n = balances.length;
// 找到第一个还没结清的人
while (start < n && balances[start] == 0) {
            start++;
        }
if (start == n) return0; // 全部清零

int res = Integer.MAX_VALUE;
int cur = balances[start];

// 用一个变量记住本层已经尝试过的数值,防止重复
int prev = 0;
for (int i = start + 1; i < n; i++) {
// 只和符号相反的结算,且避免同样数值的重复搜索
if (balances[i] * cur < 0 && balances[i] != prev) {
                prev = balances[i];

// 尝试让 start 和 i 结一笔
                balances[i] += cur;
                res = Math.min(res, 1 + dfs(balances, start + 1));
// 回溯恢复
                balances[i] -= cur;

// 小剪枝:如果这一笔直接把对方结成 0,效果已经很好了
if (balances[i] + cur == 0) {
break;
                }
            }
        }
return res == Integer.MAX_VALUE ? 0 : res;
    }
}

你可以简单过一下逻辑:上面这个 dfs 每一层固定一个“还没结账的人”,然后枚举他可以跟谁结一笔,往下递归找最优,最后拿最小值。因为把很多重复状态都剪掉了,实际跑起来比想象中快。

最后顺带提一句,这题在面试里挺常见的,尤其是那种“几个人一起AA怎么设计”的开放题,先用这个算法保证转账次数最少,再考虑下真实支付里的约束(比如平台抽佣、转账上限之类的),会显得比较有思考深度。剩下的你可以自己拿几个小例子跑一跑,感受一下搜索过程怎么走的,就更好记了。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

最后给大家分享一份不错的副业资料,点击下方公众号,回复关键字: 副业 领,也可以链接我微信:hls404