程序员老鬼

老公是java开发 今年 35岁了娃刚满1岁,没有本科学历 buff叠满了间间,我的工资不足以养一大家子。。

刚看到个贴子,说一位网友老公做Java开发,今年35岁、没本科学历,娃才1岁,结果现在中年焦虑直接拉满,担心失业、担心房贷、担心孩子未来,整个人都被压得喘不过气。我看着都替她难受。

Image

在我看来,目前焦点不是“怎么办”,而是先稳住情绪。家庭像船,风浪再大,先别让船自己散架。老公这年纪呢,可以考虑转管理、做外包、转测试、学点运维自动化,甚至找更稳定的行业,都不是死路。

你现在工资压力大,那就更得两口子齐心,别互相怪罪,不然雪上加霜。【备注:文末可领最新资料】

面试题:随机数索引

我们先把题目说清楚,再聊怎么写代码。

“随机数索引”这道题,大致意思是这样的:

给你一个整数数组 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 的位置,按照一定概率更新当前答案
  • 最后留下来的那个下标,就是“等概率抽出来”的

具体做法可以这样理解:

  1. 准备两个变量:

  • 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

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