老公是java开发 今年 35岁了娃刚满1岁,没有本科学历 buff叠满了间间,我的工资不足以养一大家子。。
刚看到个贴子,说一位网友老公做Java开发,今年35岁、没本科学历,娃才1岁,结果现在中年焦虑直接拉满,担心失业、担心房贷、担心孩子未来,整个人都被压得喘不过气。我看着都替她难受。
在我看来,目前焦点不是“怎么办”,而是先稳住情绪。家庭像船,风浪再大,先别让船自己散架。老公这年纪呢,可以考虑转管理、做外包、转测试、学点运维自动化,甚至找更稳定的行业,都不是死路。
你现在工资压力大,那就更得两口子齐心,别互相怪罪,不然雪上加霜。【备注:文末可领最新资料】
面试题:随机数索引
我们先把题目说清楚,再聊怎么写代码。
“随机数索引”这道题,大致意思是这样的:
给你一个整数数组 nums,里面可能有很多重复的数字。你要设计一个类:
Solution solution = new Solution(nums);
int index = solution.pick(target);
要求:
pick(target)要返回一个 下标,使得nums[index] == target如果有多个满足条件的下标,每个下标被返回的 概率要相同 会被调用很多次,所以不能每次都干特别蠢的事(比如每次都复制一份数组)
这就是典型的面试题:简单一句话,里面藏着几个考点——随机性、公平性、时间复杂度和空间复杂度。
最直观的解法:用 Map 把位置记下来
如果数组不大,最容易想到的就是先“建个索引表”:
遍历一遍
nums用
Map<Integer, List<Integer>>记录:每个值出现在哪些下标上pick(target)的时候:先拿到那一串下标列表,比如 [2, 5, 9]再在 0 ~ 列表长度-1 之间随机一个位置,返回对应下标即可
伪代码像这样:
classSolution{
private Map<Integer, List<Integer>> map = new HashMap<>();
private Random random = new Random();
publicSolution(int[] nums){
for (int i = 0; i < nums.length; i++) {
map.computeIfAbsent(nums[i], k -> new ArrayList<>()).add(i);
}
}
publicintpick(int target){
List<Integer> list = map.get(target);
int r = random.nextInt(list.size());
return list.get(r);
}
}
这个方案的特点:
构造函数:O(n) 每次 pick:O(1)但空间:O(n),数组如果很大、值又特别分散,就会吃不少内存
有些题目会默认你可以这样写,有些会暗示“数组可能很大”,那就得想更省空间的办法。
进阶思路:水塘抽样
如果我们 不想额外开 Map,只保留原始数组,还能不能做到“每个满足条件的下标被选中的概率一样”?
答案是可以的,用一个叫 水塘抽样(Reservoir Sampling) 的经典思路。
核心想法是:
每次调用 pick(target)时,我们从头到尾再扫一遍数组对于所有满足 nums[i] == target的位置,按照一定概率更新当前答案最后留下来的那个下标,就是“等概率抽出来”的
具体做法可以这样理解:
准备两个变量:
int count = 0;统计目前遇到的target出现了多少次int ans = -1;当前选中的下标
从左到右遍历数组:
令 count++以 1/count的概率,把ans更新成当前的i
每当遇到一个
nums[i] == target:
遍历结束,返回 ans
为什么这个是“平均的”?直观上:
第一次遇到目标时, count = 1,一定会选它,暂时只有它一个候选第二次遇到目标时,以 1/2 的概率用新的替换旧的,这时候两个位置被留下来的概率都是 1/2 第三次时,以 1/3 的概率选新的,那前两个被留下来的概率会再乘上 2/3 一路推下去你会发现:第 k 次出现的位置被选中的最终概率 = 1/k * (k/(总次数)) = 1/(总次数),也就是平均分
这样我们就做到:
额外空间:O(1) 每次 pick:O(n)只用原始数组,不用开大 Map
在面试里,如果题目强调“数组很大、内存有限、数据可能是流式的”,多半就是想让你说出这个算法。
下面给一个完整可用的 Java 写法,用的就是上面这套逻辑:
import java.util.Random;
classSolution{
privatefinalint[] nums;
privatefinal Random random;
publicSolution(int[] nums){
this.nums = nums;
this.random = new Random();
}
publicintpick(int target){
int count = 0;
int ans = -1;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != target) {
continue;
}
count++;
// 生成 [0, count) 之间的随机数,等于 0 的概率就是 1/count
if (random.nextInt(count) == 0) {
ans = i;
}
}
return ans;
}
}
这个类的使用方式还是:
Solution solution = new Solution(nums);
int index = solution.pick(target);
如果同一个 target 调用很多次,返回的下标会在所有满足条件的位置之间“均匀摇号”,这正是题目要的效果。
想要 快:用 Map<值, 下标列表>,时间 O(1),空间 O(n)想要 省内存:用水塘抽样,每次扫一遍数组,时间 O(n),空间 O(1) 题目名字叫“随机数索引”,本质上考的是“如何实现等概率随机 + 时间空间权衡”
你在刷题的时候,两种方案都写一遍,既能练代码,也能在面试里根据场景自由切换。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html