A clear explanation of finding the last remaining number after alternating left-to-right and right-to-left eliminations.
Problem Restatement
We start with a sorted list of integers:
[1, 2, 3, ..., n]Then we repeatedly remove numbers until only one number remains.
The removal process alternates direction:
- From left to right, remove the first number and every other number after it.
- From right to left, remove the rightmost number and every other number before it.
- Keep alternating directions.
Return the last remaining number.
For example, when n = 9:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[2, 4, 6, 8]
[2, 6]
[6]So the answer is 6.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer n |
| Output | Last remaining number |
| Constraint | 1 <= n <= 10^9 |
Example function shape:
def lastRemaining(n: int) -> int:
...Examples
Example 1:
n = 9Start with:
[1, 2, 3, 4, 5, 6, 7, 8, 9]Remove from left to right:
[2, 4, 6, 8]Remove from right to left:
[2, 6]Remove from left to right:
[6]So the answer is:
6Example 2:
n = 1Only one number exists.
So the answer is:
1First Thought: Simulate the List
The direct idea is to build the full list:
arr = [1, 2, 3, ..., n]Then repeatedly keep every other number.
For n = 9:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[2, 4, 6, 8]
[2, 6]
[6]This is easy to understand, but n can be as large as 10^9.
We cannot build a list with one billion integers.
We need to track the pattern without storing the list.
Key Insight
After each elimination round, the remaining numbers still form an arithmetic sequence.
For example:
[1, 2, 3, 4, 5, 6, 7, 8, 9]After removing from left to right:
[2, 4, 6, 8]This sequence has:
| Property | Value |
|---|---|
| First number | 2 |
| Step between numbers | 2 |
| Count | 4 |
After removing from right to left:
[2, 6]This sequence has:
| Property | Value |
|---|---|
| First number | 2 |
| Step between numbers | 4 |
| Count | 2 |
So we do not need the whole list.
We only track:
| Variable | Meaning |
|---|---|
head | First remaining number |
step | Distance between adjacent remaining numbers |
remaining | Number of remaining values |
left_to_right | Current removal direction |
The answer is head when only one number remains.
When Does head Move?
The first remaining number changes in two cases.
Case 1: removing from left to right.
The first number is always removed, so head moves forward by step.
Case 2: removing from right to left with an odd number of elements.
Example:
[2, 4, 6]Remove from right:
remove 6, then 2The remaining list is:
[4]The head changes.
But with an even number of elements:
[2, 4, 6, 8]Remove from right:
remove 8, then 4The remaining list is:
[2, 6]The head stays the same.
So the rule is:
if left_to_right or remaining % 2 == 1:
head += stepAfter each round:
remaining //= 2
step *= 2
left_to_right = not left_to_rightAlgorithm
Initialize:
head = 1
step = 1
remaining = n
left_to_right = TrueWhile more than one number remains:
- If we remove from left to right, move
head. - If we remove from right to left and
remainingis odd, also movehead. - Halve
remaining. - Double
step. - Flip direction.
Return head.
Correctness
At the start of every round, the remaining numbers form an arithmetic sequence:
head, head + step, head + 2 * step, ...with remaining elements.
This is true initially, where head = 1, step = 1, and remaining = n.
During one elimination round, every other element is removed. The remaining elements therefore still form an arithmetic sequence, but the distance between adjacent remaining elements doubles. So step *= 2 is correct.
The number of remaining elements is halved, so remaining //= 2 is correct.
The only question is whether the first remaining value changes.
When removing from left to right, the first element is always removed, so the new first element is head + step.
When removing from right to left, the first element is removed only if the current count is odd. Therefore head moves by step exactly when remaining is odd.
Thus the update rule:
if left_to_right or remaining % 2 == 1:
head += stepkeeps head equal to the first element of the remaining sequence after each round.
When only one element remains, the first element is also the last remaining element. Therefore returning head is correct.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(log n) | Each round halves the number of remaining elements |
| Space | O(1) | Only a few integer variables are used |
Implementation
class Solution:
def lastRemaining(self, n: int) -> int:
head = 1
step = 1
remaining = n
left_to_right = True
while remaining > 1:
if left_to_right or remaining % 2 == 1:
head += step
remaining //= 2
step *= 2
left_to_right = not left_to_right
return headCode Explanation
We begin with the full sequence:
head = 1
step = 1
remaining = n
left_to_right = TrueThe loop continues until one number remains:
while remaining > 1:Update the first remaining number when the current round removes it:
if left_to_right or remaining % 2 == 1:
head += stepThen update the sequence shape:
remaining //= 2
step *= 2Every round removes half the values, and the spacing between surviving values doubles.
Finally, alternate the direction:
left_to_right = not left_to_rightWhen the loop ends, head is the only remaining number.
Testing
def test_solution():
s = Solution()
assert s.lastRemaining(1) == 1
assert s.lastRemaining(2) == 2
assert s.lastRemaining(3) == 2
assert s.lastRemaining(4) == 2
assert s.lastRemaining(5) == 2
assert s.lastRemaining(6) == 4
assert s.lastRemaining(7) == 4
assert s.lastRemaining(8) == 6
assert s.lastRemaining(9) == 6
assert s.lastRemaining(10) == 8
print("all tests passed")
test_solution()Test meaning:
| Test | Why |
|---|---|
n = 1 | Minimum input |
n = 2 | One left-to-right elimination |
n = 3 | Odd count affects head |
n = 4 | Even count right-to-left keeps head |
n = 9 | Main example |
n = 10 | Checks behavior just after the main example |