2

We have to generate Array {1,2,3,..,n} in O(1) space.
I am able to do it in O(n) space.

I did O(n) space solution by first storing the array and then randomizing it in place. But how to do it without storing array in O(1) space.

I m just generating random number and instead of storing them I need to print them as storing would require O(n) space but I need to do it in O(1) space and what my doubt is if we go on generating random number and print them there may be some numbers between 1 to n which may be generated more than once and some may not be generated. So How do I manage to print all numbers exactly once in O(1) space?

P.S.- I am not given any array. Input is just 'n' and I have to print the permutation of array {1,2,3,...,n} in O(n) time and in O(1) space.

  • 3
    Provably impossible in a fairly natural model. – David Eisenstat Aug 24 '15 at 12:19
  • 1
    If you are given an array and you need to shuffle it, it's doable using [fisher-yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle). To generate an array from scratch without additional space is impossible by definition, since the output itself is `O(n)`, and you need to generate it. – amit Aug 24 '15 at 12:21
  • 2
    The O(1) space requirement is surely regarding *extra* storage. There is no solution with O(1) space complexity that yields a >=polynomial output (considering the space of the output). – Rubens Aug 24 '15 at 12:22
  • If you are using c++, you can use random_shuffle() or next_permutation() function, as you need. – vish4071 Aug 24 '15 at 12:34
  • 1
    I suspect you've misunderstood the assignment. There's a difference between O(1) space and O(1) *additional* space. – pjs Aug 24 '15 at 12:54
  • 1
    I'm unclear on whether you're generating random elements and storing them in an array, or shuffling an existing array. Which is it? – Erick G. Hagstrom Aug 24 '15 at 13:25
  • @DavidEisenstat So Fisher-Yates doesn't work because the PRNG period causes only a fraction of permutations to be possible to reach? – Niklas B. Aug 24 '15 at 14:11
  • 1
    @NiklasB. It won't work because, as the update from the Abhishek says, there's no array. Conceivably if permutations that were only computationally indistinguishable from true randomness were OK (making the standard crypto assumptions), then there might be some solution involving crypto primitives. – David Eisenstat Aug 24 '15 at 15:05
  • @DavidEisenstat I couldnt get what u said. Please can u elaborate. Does there exist some solution? Or this turns out to be a problem whose solution we are yet to find. – Abhishek Yadav Aug 24 '15 at 15:33
  • Oh! So OP really means O(1) space. As in, generate one element of the array, print it and drop it. Generate another element, etc. So really there's no array at all, it's just a random permutation of the sequence 1, 2, 3, ... , n. Random draws, no replacement, in O(n) time and O(1) space. – Erick G. Hagstrom Aug 24 '15 at 16:59
  • So, not an answer, but IF there were a PRNG such that a) you get to specify the exact period, and b) you can map the output one-to-one to the integers 1..n, then you're golden. Just do getNext() n times. I am not hopeful regarding the existence of such a creature. @DavidEisenstat, you're the pro. Is that as hopeless a quest as it sounds? – Erick G. Hagstrom Aug 24 '15 at 17:09
  • Whoops, @amit, didn't mean any slight. You're obviously well respected around here as well. – Erick G. Hagstrom Aug 24 '15 at 17:12
  • @ErickG.Hagstrom If one had a pseudorandom permutation on a set of size Theta(n), then that would do the trick. I'm not a crypto expert, so I don't know what specifically would fit the bill. – David Eisenstat Aug 24 '15 at 17:22
  • A possibility would be a linear feedback shift register (or perhaps an xorshift random number generator) with a period of n. Although I don't know how to construct one that would work if n is not a power of 2. – Jim Mischel Aug 24 '15 at 18:42
  • 1
    Clever idea. Nitpick: A k-bit LFSR with maximum cycle length would generate a sequence of n=2**k-1 values since it cannot generate 0. – njuffa Aug 24 '15 at 19:40
  • A symmetric Cipher is actually PRNG with a one-to-one mapping. If you craft it correctly, you can [Randomly iterate through sequence of integers, 1 to n](http://stackoverflow.com/questions/30062435/randomly-iterate-through-sequence-of-integers-1-to-n/30097202#30097202). mcdowella's answer outlined the same idea: Format-Preserving-Encryption, Feistel Cipher, AES-FFX, counter-mode. – Thomas B. Aug 26 '15 at 15:50

4 Answers4

2

I've built a linear-feedback-shift-register generator solution which I think meets your requirements. The implementation is based on Fibonacci LFSRs, so it achieves full cycle for the given number of bits. I went ahead and put in the polynomial coefficients for up to 19 bits and select the appropriate coefficient set based on the specified value of N. Generated values greater than N get chucked overboard, but the total number of values in the full cycle is less than 2N so it will produce your N values in O(N) time. The LFSR preserves one word of state, so it's O(1) space.

Here is the implementation in Ruby:

#!/usr/bin/env ruby -w

# Linear Feedback Shift Register generator which operates on smallest bit
# range possible for a specified integer range, and skips over values outside
# the specified range. Since this attains full cycle length for the specified
# number of bits, and the number of bits is minimized relative to the specified
# N, the total number of iterations is bounded by 2N and is therefore O(N).
class LFSR
  # Polynomials for maximal LFSRs determine shift amounts for up to 19 bits.
  # See https://en.wikipedia.org/wiki/Linear_feedback_shift_register for
  # details. Add more entries if you need more than 19 bits.
  SHIFT_AMT = [
    [], [], [1], [1], [1], [2], [1], [1], [2, 3, 4], [4], [3], [2],
    [1, 2, 8], [1, 2, 5], [1, 2, 12], [1], [2, 3, 5], [3], [7], [1, 2, 5]
  ]

  # Constructor for the LFSR.  Specify the N and seed value.
  def initialize(n, seed)
    @n = n
    @state =  (seed % n) + 1
    @num_bits = Math.log2(n).floor + 1
  end

  # Generate the next iterate of the LFSR.  If it's above the specified N,
  # keep trying until you're done.
  def next_value
    loop do
      bit = @state
      SHIFT_AMT[@num_bits].each { |amt| bit ^= (@state >> amt) }
      @state = ((@state >> 1) | ((bit & 1) << (@num_bits - 1)))
      return @state if @state <= @n
    end
  end
end

N = (ARGV.shift || 100).to_i # Specify the 'N' value on cmd line. Default = 100
SEED = (ARGV.shift || 0x7FFF).to_i # Optionally specify a "seed" for the LFSR
my_lfsr = LFSR.new(N, SEED)  # Instantiate an LFSR object
N.times { p my_lfsr.next_value }   # Invoke it N times, print the results
pjs
  • 18,696
  • 4
  • 27
  • 56
  • How dependent on `n` is the amount of space needed for the `SHIFT_AMT` array? – גלעד ברקן Aug 25 '15 at 01:01
  • @גלעדברקן As you can see, hardly at all. The provided `SHIFT_AMT` sets are for up to 19 bits, and the largest subarray is 3 elements. As far as I'm concerned, if we restrict `N` to 32 or 64 bit integers it's still a fixed size table, it would just have 45 more entries to go 64 bit. – pjs Aug 25 '15 at 01:21
  • How dependent on `n` is the average number of iterations needed for `next_value` to find an iterate in range? What is that average? – גלעד ברקן Aug 25 '15 at 02:37
  • @גלעדברקן You will enumerate the full cycle of all integers from 1 to the smallest power of 2 containing `N`, which is less than `2N` and clearly contains all of the values from 1 to `N`. Hence the number of iterations is less than two per invocation of `next_value` on average. That's the basis of my claim that it's `O(N)` to generate them all, since it's bounded by `2N`. – pjs Aug 25 '15 at 02:50
  • Oh, right, you did mention that, sounds like an interesting thing to learn more about. – גלעד ברקן Aug 25 '15 at 02:55
1

If n is a power of 2, a block cipher with a block size of n bits could be used to generate a permutation on n elements - just write out Encrypt(0), Encrypt(1)... Encrypt(n-1).

If n is not a power of 2, let m be the first power of 2 above n. Encrypt 0..n-1, and if the result is >= n, Encrypt it again, until you get a value within range. This amounts to writing out the permutation on m elements as a cycle, and then deleting the elements >= n.

If you don't have a standard block cipher of the required size lying around, you could use the Luby-Rackoff aka Feistel construction to create one using a hash function as the F operation in https://en.wikipedia.org/wiki/Feistel_cipher. A peculiarity of Feistel networks where F() produces more than one bit, treated as permutations, is that they never produce odd permutations: if the Feistel output is k bits wide each round produces some multiple of 2^(k-1) 2-cycles, which produces an even permutation for k > 1, so you may want to think about this a bit and/or use multiple Feistel rounds with different styles of feedback to get reasonably random permutations out of this. A suitably elaborate system of Fiestel rounds with 1 bit of Feistel output can be viewed as an implicit construction of an exchange network as might be used to implement arbitrary permutations in networks.

mcdowella
  • 19,301
  • 2
  • 19
  • 25
-1

Strictly speaking an O(1) solution is impossible, because a number n itself takes log(n) bits to store.

On the other hand, if the goal of exercise is to avoid an array of n integers, and you are willing to sacrifice some rigor - namely assume that n! can be represented in O(1) memory - the solution is to generate a random number k in range [0, n!), and compute the k-th permutation, printing numbers as you calculate them.

Community
  • 1
  • 1
user58697
  • 7,808
  • 1
  • 14
  • 28
-2

In general, this is an impossible problem. Although it is possible to reorganize a list without any memory beyond the list and be left with a statistically random list, a true random list is impossible.

If the problem is interpreted as a number stream filter, at any point in time we can see only one element of the stream. It helps that we can see the part of the stream that has yet to be processed, but if we cannot alter that portion, we are stuck.

The two basic ways to perfectly mix a list (given a perfect random number generator):

for each element in the list:
  select a random element later in the list.
  swap.

for each element in the list:
  select a random element earlier in the list.
  swap.

A truly random resort of a list will be reducible to one of these two methods. Unfortunately, both of these methods cannot work with a one pass filter that has no random write permission. The first one needs to modify part of the list that has not been created yet. The second one needs to modify part of the list already printed out.

warren
  • 563
  • 2
  • 13