Прежде всего, из объяснения и кода вполне понятно, что автор имел в виду, так же, как они написали. Множество может быть смоделировано как массив в реальной реализации, это ничего не значит. В задачах программирования очень часто люди используют довольно простые структуры - например, массив вместо java.util.Set
.
Итак, задача в основном:
Случайно выбирает набор M
элементов из массива размером N
.
Предположим, N >= M
.
Теперь самое сложное: почему этот алгоритм дает правильные результаты?
Просто глядя на алгоритм, трудно понять, как он работает и почему. Я думаю, это потому, что алгоритм на самом деле построен рекурсивно, с окончанием рекурсии, развернутым в итерации.
Давайте начнем с рекурсии.
Предположим, мы можем произвольно выбирать M
элементы из массива размером N - 1
. Как мы выбираем M
элементы из массива размером N
?
Поскольку в массиве есть «новый» элемент, мы можем либо заменить один из выбранных элементов им, либо оставить набор как есть. Но мы должны сохранять случайные свойства.
Набор M
элементов из N-1
может быть выбран (N-1)! / M!*(N-1 - M)!
способами.
Набор M
элементов из N
может быть выбран N! / M!*(N - M)!
способами.
Это означает, что мы должны сохранить набор с вероятностью (N-M)/N
и заменить один из элементов с вероятностью M/N
. Нам также нужно будет выбрать элемент для замены с вероятностью 1/M
.
Посмотрим, как это будет выглядеть в коде. Предположим, subset
- это наш случайно выбранный набор M
элементов из N-1
.
Сначала мы должны решить, заменить ли один из элементов или нет. Нам нужна (N-M)/N
вероятность. Для этого мы можем просто сгенерировать случайное число от 0
до N
. Если это число меньше M
, мы заменим.
boolean replace = rand(random, 0, N) < M;
if (replace) {
// then replace
}
Теперь нам нужно выбрать один из элементов для замены. Поскольку мы моделируем массив как набор, мы можем просто случайным образом выбрать индекс от 0
до M - 1
(включительно). Итак, мы получаем:
boolean replace = rand(random, 0, N) < M;
if (replace) {
subset[rand(random, 0, M - 1)] = original[N];
}
Здесь мы можем заметить, что если наше первое случайное значение (rand(random, 0, N)
) меньше M
, оно равно случайное значение между 0
и M-1
. Таким образом, нам не нужен второй rand
:
int r = rand(random, 0, N);
boolean replace = r < M;
if (replace) {
subset[r] = original[N];
}
Остальное должно быть довольно тривиально.
Базовый случай рекурсии M == N
. В этом случае мы ничего не заменяем, поэтому набор выделенных элементов является простым исходным массивом.
После этого рекурсию можно просто закодировать как цикл. i
представляет N
на каждом шаге - это дает ваш код.