Нужна функция биективной (1:1) проекции упорядоченных чисел от 1 до N на такой же диапазон.
Самый простой пример, чтобы понятно представить идею, это зеркалирование порядка битов в числе:
0 000 -> 000 0
1 001 -> 100 4
2 010 -> 010 2
3 011 -> 110 6
4 100 -> 001 1
5 101 -> 101 5
6 110 -> 011 3
7 111 -> 111 7
Теперь, чтобы получить очередное случайное, просто берите подряд следующее.
В варианте с битовыми операциями диапазон должен бысть степенью двойки, если у вас он иной, нужна другая функция. Лишь бы она однозначно ставила в соответсвие любому целому из исходного диапазона, единственное ответное из другого, и наоборот.
Так не придётся хранить, перемешивать, генерировать случайные. Для "псевдослучайности" можно варьировать функцию. Например, не зеркалить порядок битов, а перемешивать биты как-то иначе. Каждый вариант перемешки битов создаёт новый «перемешанный массив» всех возможных значений.
Главное, это быстро вычисляется и требует минимальных ресурсов.