程序员老鬼

某大厂员工被裁赔偿56万被媳妇发网上炫耀,结果截图里公司名称没打码。 等发现删帖的时候,已经被网友截图传遍。

刚刷到这个,给我看得一愣一愣的。

北京一大厂员工被裁,赔了56万,本来这事已经算“失业里比较体面”的那种。结果他媳妇一激动,直接发网上晒,截图里公司名都没打码。

Image

等反应过来删帖,已经晚了。网友早就截图传开,公司、部门、姓名全给扒出来了。

这下好了,56万还没捂热,先全公司社死。同事估计表面不说,群里已经传八百遍了。最尴尬的是这钱本来挺让人羡慕,现在搞得像全网通报一样,HR看完都得沉默几秒。

今日面试题

一道“错误的集合”,别急着上 HashSet

数组里有一个数字重复了,另一个数字没了。

例如:

[1, 2, 2, 4]

数字范围是 1~4,其中 2 出现了两次,3 没出现,最后返回:

[2, 3]

这道题看着像查重,第一反应通常是塞进 HashSet。能做,但我一般不会先这么写。题目已经把数字范围限制在 1~n,数组下标正好是 0~n-1,这条件不用有点浪费。

可以直接拿数组本身做标记。

遍历到数字 x 时,把下标 x - 1 对应的位置改成负数,表示这个数字已经出现过。后面再次遇到 x,如果那个位置已经是负数,就说明 x 是重复数字。

拿 [1, 2, 2, 4] 跑一下:

遇到 1:标记下标 0
遇到 2:标记下标 1
再次遇到 2:下标 1 已经是负数,重复数字就是 2
遇到 4:标记下标 3

遍历结束后,再扫一遍数组。哪个位置还是正数,说明对应的数字从没出现过。下标 2 没被标记,所以缺失数字是 2 + 1 = 3。

Java 代码不用写得太绕:

classSolution{

publicint[] findErrorNums(int[] nums) {
int repeated = -1;
int missing = -1;

for (int i = 0; i < nums.length; i++) {
int value = Math.abs(nums[i]);
int markIndex = value - 1;

if (nums[markIndex] < 0) {
                repeated = value;
continue;
            }

            nums[markIndex] = -nums[markIndex];
        }

for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
                missing = i + 1;
break;
            }
        }

returnnewint[]{repeated, missing};
    }
}

这里有个细节不能漏:读取当前数字时必须用 Math.abs(nums[i])。

因为数组前面的元素可能已经被改成负数。如果直接拿负数计算下标,代码不是结果错,而是直接数组越界。这种问题我见得不少,思路没错,标记过程中把原数据改了,后面又忘了恢复数字含义。

这套写法只遍历两次数组,时间复杂度是 O(n),除了返回结果,没有额外申请与数组规模相关的空间。

当然,它会修改原数组。算法题里通常没问题,实际业务代码就得留意。调用方后面还要使用原始数据时,先复制一份:

int[] working = Arrays.copyOf(nums, nums.length);

不要为了省这一点空间,顺手把调用方的数据也改了。线上这种副作用,比多申请一个数组麻烦得多。

“错误的集合”不难,真正要看的是能不能注意到 1~n 和数组下标之间的关系。范围给得这么规整,通常不是摆设。