Problem
In the Dota2 senate, senators from two parties โ Radiant ('R') and Dire ('D') โ take turns in the given order, and each may permanently revoke any one opposing senator's voting rights. A party wins when only its members retain the right to vote. Given the initial seating order as a string, determine which party wins when every senator plays optimally.
- Input:
senate = "RDD" - Output:
"Dire" - Explanation: R(0) bans D(1), but D(2) then bans R(0), leaving only Dire senators.
Counter-example: "RD" returns "Radiant" โ R acts first and immediately bans the only D senator before D gets a turn.
Intuition
The optimal move is always to ban the nearest upcoming opponent โ eliminating a future threat is strictly better than eliminating someone who already voted, since that delays harm for zero extra benefit. Two queues naturally track each party's "next available" senator in order, and a simple comparison of their indices decides who acts first each step.
Solution โ Two-Queue Greedy
Pop the front senator from each party's queue. The one with the smaller index acts earlier in the current cycle and eliminates the other. The winner re-enters their queue at index + n, representing their slot in the next cycle.
- Build two queues โ
radiantanddireโ holding each party's senate indices in order. - While both queues are non-empty, pop the front index from each.
- If the Radiant index is smaller, R acts first and eliminates D โ re-enqueue R at
r_index + n. - Otherwise, D acts first and eliminates R โ re-enqueue D at
d_index + n. - The party with a non-empty queue when the loop ends declares victory.
1from collections import deque
2
3def predictPartyVictory(senate: str) -> str:
4 n = len(senate)
5 radiant = deque(i for i, c in enumerate(senate) if c == 'R')
6 dire = deque(i for i, c in enumerate(senate) if c == 'D')
7
8 while radiant and dire:
9 r_index = radiant.popleft()
10 d_index = dire.popleft()
11 if r_index < d_index:
12 radiant.append(r_index + n) # R acts first; survives to next cycle
13 else:
14 dire.append(d_index + n) # D acts first; survives to next cycle
15
16 return "Radiant" if radiant else "Dire"Time: O(n) โ each senator is eliminated exactly once; total loop iterations equal the number of senators eliminated, which is at most n โ 1.
Space: O(n) โ two queues together hold at most n entries at any time.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Two-Queue Greedy | O(n) | O(n) | Simulating ordered elimination between two opposing groups where each side acts greedily |
Common Mistakes
- Forgetting
+ nwhen re-enqueuing the winner โ without addingn, the winning senator gets an index smaller than any remaining senator, making them always act first in subsequent comparisons and breaking cycle ordering entirely. - Comparing party sizes instead of indices โ a party with more senators does not automatically win; the voting order determines outcomes, not headcount.
- Re-initializing queues at the start of each round โ the
+ ntrick handles multi-round cycling implicitly; there is no need to restart between rounds. - Swapping the return values โ after the loop, the non-empty queue is the winner; when
direis empty, return"Radiant", not the reverse. - Using a Python list with
pop(0)instead of a deque โlist.pop(0)is O(n) per call, turning the whole algorithm O(nยฒ); always usecollections.dequefor queue operations in Python.
Related Problems
Asteroid Collisionโ two groups of opposing elements eliminate each other in sequence using greedy stack logicGas Stationโ greedy circular simulation to find a starting point where resources never run dryReveal Cards In Increasing Orderโ queue simulation to reconstruct a specific card-reveal orderingRemove K Digitsโ greedy elimination using a monotonic structure to minimize the resulting number