Хотя я считаю, что повторная выборка будет оптимальным решением в вашем случае (десятки повторных выборок - это очень дешевая цена), вот алгоритм, который я бы никогда не реализовал на практике (поскольку он использует очень сложные структуры данных и меньшеэффективнее, чем пересчет), но с доказуемыми границами.Для каждого запроса требуется O(n log n)
время предварительной обработки, O(n log n)
память и O(log n)
время, где n
- количество элементов, которые вы можете потенциально выбрать.
Все концы всех диапазонов хранятся в одноммассив (назовите его ends
).Например, в вашем случае у вас есть массив [-infty, 4, 37, 59, 79, +infty]
(может потребоваться некоторая настройка, например, добавление +1 к правым краям диапазонов; сейчас это не важно).Идея заключается в том, что для любого X
нам нужно только определить, между какими концами он находится.Например, если X=62
находится в диапазоне [59; 79]
(я назову такую пару интервал ).Затем для каждого интервала вы сохраняете набор всех возможных диапазонов.Для ввода X
вы просто находите интервал (используя бинарный поиск), а затем выводите случайный диапазон, соответствующий этому интервалу.
Как вычислить соответствующий набор диапазонов для каждого интервала?Идем слева направо в массиве ends
.Давайте предположим, что мы вычислим набор для текущего интервала и перейдем к следующему.Между этими интервалами есть какой-то конец.Если это левый конец некоторого интервала, мы добавляем соответствующий диапазон в новый набор (так как мы входим в этот диапазон).Если это правильный конец, мы удаляем диапазон.Как мы можем сделать это в O(log n)
времени вместо O(n)
?Это могут сделать неизменные сбалансированные наборы деревьев (по сути, они создают новые деревья вместо того, чтобы модифицировать старое).
Как вы возвращаете равномерно случайный диапазон из набора?Вы должны расширить наборы деревьев: каждый узел должен знать, сколько узлов содержит его поддерево.Сначала вы выбираете целое число в диапазоне [0; size(tree))
.Затем вы смотрите на свой корневой узел и его дочерние элементы.Например, предположим, что вы выбрали целое число 15, а поддерево вашего левого ребенка имеет размер 10, а у правого - 20. Затем вы переходите к правому дочернему элементу (начиная с 15 >= 10
) и обрабатываете его с целым числом 5 (начиная с 15 - 10 = 5
).).Вы в конечном итоге посетите лист, соответствующий одному диапазону.Верните этот диапазон.
Извините, если это трудно понять.Как я уже сказал, это не тривиальный подход, который вам понадобится для верхних границ в худшем случае (другие обсуждаемые ранее подходы требуют линейного времени в худшем случае; повторная выборка может выполняться неопределенное время, если нет элемента, удовлетворяющего ограничениям).Это также требует осторожного обращения (например, когда некоторые диапазоны имеют совпадающие конечные точки).