384. Shuffle an Array
On LeetCode ->Reformulated question¶
Design a class for an array that can:
- return the original array with
reset() - return a uniformly random permutation with
shuffle()
Compact example:
nums = [1,2,3]
ops = shuffle(), reset(), shuffle()
out = any permutation of [1,2,3], [1,2,3], any permutation of [1,2,3]
Key trick¶
Use Fisher-Yates shuffle:
- iterate every index
i - swap
a[i]with a random indexjin[i, n - 1]
This makes every permutation equally likely.
Trap¶
Common mistakes:
- only shuffling part of the array
- picking random indices from the full range every time
- mutating the stored original array
- returning the same list object from
reset()if later code may mutate it
Your current loop stops at n // 2, so it does not generate all permutations uniformly.
Why this question is interesting¶
It tests both:
- API/state design with
reset()vs current state - knowing the one correct uniform in-place shuffle algorithm
Pytest test¶
import pytest
@pytest.mark.parametrize(
"nums",
[
[1],
[1, 2],
[1, 2, 3],
[3, -1, 7, 9],
[10, 20, 30, 40, 50],
],
)
def test_shuffle_array(nums):
s = Solution(nums)
assert s.reset() == nums
assert s.reset() is not nums
shuffled = s.shuffle()
assert sorted(shuffled) == sorted(nums)
assert len(shuffled) == len(nums)
assert s.reset() == nums
assert s.shuffle() is not s.reset()
Solve the problem with idiomatic Python¶
from random import randrange
class Solution:
def __init__(self, nums: list[int]):
# Keep an immutable reference copy of the original configuration.
self.original = nums[:]
def reset(self) -> list[int]:
# Return a fresh copy so callers cannot mutate internal state.
return self.original[:]
def shuffle(self) -> list[int]:
# Fisher-Yates shuffle: uniform over all permutations.
arr = self.original[:]
n = len(arr)
for i in range(n):
j = randrange(i, n)
arr[i], arr[j] = arr[j], arr[i]
return arr
Library alternative:
- Python's
random.shufflesolves this exact problem in \(O(n)\) too.
import random
class Solution:
def __init__(self, nums: list[int]):
self.original = nums[:]
def reset(self) -> list[int]:
return self.original[:]
def shuffle(self) -> list[int]:
arr = self.original[:]
random.shuffle(arr)
return arr
Comment my solution¶
Issues:
self.nums = nums- stores the caller's list directly, so outside mutation can change your internal state
reset()returnsself.nums- returns the same object, not a safe copy
for i in range(nums_len // 2)- only shuffles half the positions, so distribution is wrong
- your comment about picked elements never moving back
- that is not enough; every index must be processed for uniformity
Minimal fix:
- store
nums[:] - return
self.original[:] - loop over all indices with random
jin[i, n - 1]
from random import randint
class Solution:
def __init__(self, nums: list[int]):
self.nums = nums
def reset(self) -> list[int]:
return self.nums
def shuffle(self) -> list[int]:
nums_len = len(self.nums)
nums_shuffle = self.nums[:]
for i in range(nums_len // 2):
# swap elements at i and j
# Since i is moving forward and we pick a element only
# at indice superior to i, nums_shuffle[i] is never put
# back to an earlier position
j = randint(i, nums_len - 1)
nums_shuffle[i], nums_shuffle[j] = nums_shuffle[j], nums_shuffle[i]
return nums_shuffle
obj = Solution([1, 2, 3, 4])
obj.reset() # [1, 2, 3, 4]
obj.shuffle() # [4, 1, 3, 2]
Code¶
from random import randrange
import random
import pytest
class Solution:
def __init__(self, nums: list[int]):
# Preserve the initial configuration.
self.original = nums[:]
def reset(self) -> list[int]:
# Return a copy so callers cannot mutate internal state.
return self.original[:]
def shuffle(self) -> list[int]:
# Fisher-Yates shuffle: each permutation is equally likely.
arr = self.original[:]
n = len(arr)
for i in range(n):
j = randrange(i, n)
arr[i], arr[j] = arr[j], arr[i]
return arr
class SolutionWithLibrary:
def __init__(self, nums: list[int]):
self.original = nums[:]
def reset(self) -> list[int]:
return self.original[:]
def shuffle(self) -> list[int]:
arr = self.original[:]
random.shuffle(arr)
return arr
@pytest.mark.parametrize(
"nums",
[
[1],
[1, 2],
[1, 2, 3],
[3, -1, 7, 9],
[10, 20, 30, 40, 50],
],
)
def test_shuffle_array(nums):
s = Solution(nums)
# reset returns original values
assert s.reset() == nums
# reset returns a new list, not the caller's exact object
assert s.reset() is not nums
# shuffle preserves elements
shuffled = s.shuffle()
assert sorted(shuffled) == sorted(nums)
assert len(shuffled) == len(nums)
# reset still restores original configuration
assert s.reset() == nums
# returned lists are fresh copies
assert s.shuffle() is not s.reset()
s = Solution([1, 2, 3, 4])
s.reset()
s.shuffle()
s.reset()
slib = SolutionWithLibrary([1, 2, 3, 4])
slib.shuffle()
slib.reset()