问题描述
给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。
示例:
// 初始化一个单链表 [1,2,3].
ListNode head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
Solution solution = new Solution(head);
// getRandom()方法应随机返回1,2,3中的一个,保证每个元素被返回的概率相等。
solution.getRandom();
思路分析
先说结论,当你遇到第 i
个元素时,应该有 1/i
的概率选择该元素,1 - 1/i
的概率保持原有的选择。
举例说明: 1 - 10
遇到1,概率为1,保留第一个数。
遇到2,概率为1/2,这个时候,1和2各1/2的概率被保留
遇到3,3被保留的概率为1/3,(之前剩下的数假设1被保留),2/3的概率 1 被保留,(此时1被保留的总概率为 2/3 * 1/2 = 1/3)
遇到4,4被保留的概率为1/4,(之前剩下的数假设1被保留),3/4的概率 1 被保留,(此时1被保留的总概率为 3/4 * 2/3 * 1/2 = 1/4)
以此类推,每个数被保留的概率都是1/N。
下面是官话证明(个人觉得不好理解)
代码实现
用i来记录节点的个数,每次执行if(r.nextInt(i)==0)的判断就可以做到第i个节点被选中额概率为1/i
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
private ListNode head;
public Solution(ListNode head) {
this.head = head ;
}
/** Returns a random node's value. */
public int getRandom() {
int res=0,i=1;
Random r = new Random();
ListNode p = head;
// while 循环遍历链表
while (p != null) {
// 生成一个 [0, i) 之间的整数
// 这个整数等于 0 的概率就是 1/i
if(r.nextInt(i)==0){
res =p.val;
}
i++;
p=p.next;
}
return res;
}
}
问题拓展
给定一个单链表,随机选择链表的k个节点,并返回相应的节点值。保证每个节点被选的概率一样。
结论:如果要随机选择 k
个数,只要在第 i
个元素处以 k/i
的概率选择该元素,以 1 - k/i
的概率保持原有选择即可。
代码:
/* 返回链表中 k 个随机节点的值 */
int[] getRandom(ListNode head, int k) {
Random r = new Random();
int[] res = new int[k];
ListNode p = head;
// 前 k 个元素先默认选上
for (int j = 0; j < k && p != null; j++) {
res[j] = p.val;
p = p.next;
}
int i = k;
// while 循环遍历链表
while (p != null) {
// 生成一个 [0, i) 之间的整数
int j = r.nextInt(i);
i++;
// 这个整数小于 k 的概率就是 k/i
if (j < k) {
res[j] = p.val;
}
p = p.next;
}
return res;
}
博主点评
上述都是在已知结论的情况下推导证明,不知道结论的情况下如何写出这样的题解呢。对于拓展的情况考场上又有几个人能及时用数学归纳法推导出来呢。所以这道题从面试角度来看意义不大,可以当成思维练习题培养逻辑的严谨性,要有钻研难题的决心。思考时所积累的解题路径是有用的。
同类题目
https://leetcode-cn.com/problems/random-pick-index/submissions/