Round table serving problem
Problem Statement: Consider a round table with people sitting at fixed positions. Food is distributed starting from a marked position, the “head” of the table. At each step, the person currently serving chooses to give the food to either the person on their immediate left or right, with equal probability. This process continues: any person who receives the food then chooses left or right with equal probability, and so on. A person may be served multiple times during this process.
Your task is to determine at which position to sit in order to maximize the probability of being served last.
Solution
We will formulate the following idea: regardless where we sit, the only way we can be served last if both of ours neighbours are served before us. Once one of our neighbour is served (which side is served first with higher chance depends on our location, but doesn’t matter), then what is the probability that the other side is served before us? This 2nd part doesn’t depend on our location.
Notation and Observations: Let us fix a position at the round table and define the following events:
- $L$: the person to the left of us is served before we are.
- $R$: the person to the right of us is served before we are.
- $\mathcal{L}$: the left side of the table is served before the right side.
- $\mathcal{R}$: the right side of the table is served before the left side.
Note that:
-
Eventually, one side must be served before us:
\[\mathbb{P}(L \cup R) = 1\] -
$\mathcal{L}$ and $\mathcal{R}$ are disjoint and cover the whole probability space:
\(\mathcal{L} \cap \mathcal{R} = \varnothing, \quad \mathcal{L} \cup \mathcal{R} = \Omega\) \(\Rightarrow \mathbb{P}(\mathcal{L}) + \mathbb{P}(\mathcal{R}) = 1\)
-
If a side is served before the other side, then the person on that side is served before us:
\[\mathcal{L} \subseteq L, \quad \mathcal{R} \subseteq R\]
We want to compute the probability that we are served last — that is, both neighbors are served before us:
\[\mathcal{P} := \mathbb{P}(L \cap R)\]Note that $L \cap R = (L \cap \mathcal{R}) \cup (R \cap \mathcal{L})$.
We could reason with a Venn diagram, but let’s show the previous statement formally. We decompose both $L$ and $R$ using the partition $\mathcal{L} \cup \mathcal{R} = \Omega$:
\[\begin{aligned} L &= (L \cap \mathcal{L}) \cup (L \cap \mathcal{R}) \\ R &= (R \cap \mathcal{L}) \cup (R \cap \mathcal{R}) \end{aligned}\]Then:
\[\begin{aligned} L \cap R &= \left[(L \cap \mathcal{R}) \cup (L \cap \mathcal{L})\right] \cap \left[(R \cap \mathcal{R}) \cup (R \cap \mathcal{L})\right] \\ &= \left[ (L \cap \mathcal{R}) \cup \mathcal{L} \right] \cap \left[ \mathcal{R} \cup (R \cap \mathcal{L}) \right] \\ &= \underbrace{(L \cap \mathcal{R} \cap \mathcal{R})}_{=L \cap \mathcal{R}} \cup \underbrace{(L \cap \mathcal{R} \cap R \cap \mathcal{L})}_{=\varnothing} \cup \underbrace{(\mathcal{L} \cap \mathcal{R})}_{=\varnothing} \cup \underbrace{(\mathcal{L} \cap R \cap \mathcal{L})}_{=R \cap \mathcal{L}} \\ &= (L \cap \mathcal{R}) \cup (R \cap \mathcal{L}) \end{aligned}\]$\mathcal{L}$ and $\mathcal{R}$ are disjoint events, so:
\[\mathbb{P}(L \cap R) = \mathbb{P}(L \cap \mathcal{R}) + \mathbb{P}(R \cap \mathcal{L})\]Now apply the definition of conditional probability (if $\mathbb{P}(\mathcal{R}) > 0$ and $\mathbb{P}(\mathcal{L}) > 0$):
\[\begin{aligned} \mathbb{P}(L \cap \mathcal{R}) =& \mathbb{P}(L \mid \mathcal{R}) \cdot \mathbb{P}(\mathcal{R}) \\ \mathbb{P}(R \cap \mathcal{L}) =& \mathbb{P}(R \mid \mathcal{L}) \cdot \mathbb{P}(\mathcal{L}) \end{aligned}\]Note that for symmetry, $\mathbb{P}(L \mid \mathcal{R}) = \mathbb{P}(R \mid \mathcal{L})$, so we can write:
\[p := \mathbb{P}(L \mid \mathcal{R}) = \mathbb{P}(R \mid \mathcal{L})\]Then:
\[\mathbb{P}(L \cap R) = p \cdot \left( \mathbb{P}(\mathcal{R}) + \mathbb{P}(\mathcal{L}) \right) = p\]Note how $p$ does not depend on the position we choose at the round table: it is the probability that once the person on the one side is served, the person on the other side is served before us.
Why the logic applies with the conditional probability even when $\mathbb{P}(\mathcal{R}) > 0$ and $\mathbb{P}(\mathcal{L}) > 0$? If the first person served sit on our right, then $\mathbb{P}(R) = \mathbb{P}(\mathcal{R}) = 1$, and with $p := \mathbb{P}(L \mid \mathcal{R}) = \mathbb{P}(L)$ the equation $\mathbb{P}(L \cap R) = p$ still holds. So we can conclude that the probability of being served last is independent of the position we choose at the round table.
Python coding challenges
Coin exchange I
Source code
from typing import List
from functools import lru_cache
class Solution:
def __init__(self):
self.memo = {}
def coinChange2(self, coins: List[int], amount: int) -> int:
# bottom up
# coins 1, 2, 7
# 1 2 3 4 7 8 9 ... 40
# 1 1 2 2 # not this
# [1] [2] [1,2] [2,2] [] # but this
if amount == 0:
return 0
coins = sorted(coins)
largest = coins[-1]
exchngs = [[] for i in range(amount+1)] # at i stores the smallest list of coins summing up to i
for i in range(1,amount+1):
for coin in coins:
last_amount = i-coin
if last_amount < 0:
continue
if last_amount == 0:
exchngs[i] = [coin]
elif len(exchngs[last_amount]) == 0:
continue
if len(exchngs[i]) == 0:
exchngs[i] = exchngs[last_amount] + [coin]
elif len(exchngs[i]) > len(exchngs[last_amount]) + 1:
exchngs[i] = exchngs[last_amount] + [coin]
length = len(exchngs[amount])
if length == 0:
return -1
return length
def coinChange(self, coins: List[int], amount: int) -> int:
# top down
if amount == 0:
return 0
res = self.coinChgLst(coins, amount)
return len(res) if res is not None else -1
def coinChgLst(self, coins: List[int], amount: int) -> List[int]:
if amount in self.memo:
return self.memo[amount]
best = None # shortest list of coins summing up to amount
for coin in coins:
rem = amount-coin
if rem == 0:
self.memo[amount] = [coin]
return [coin]
if rem < 0:
continue
cand = self.coinChgLst(coins, rem)
if cand is None:
continue # no solution for this amount
if best is None or len(best) > len(cand):
best = cand + [coin]
self.memo[amount] = best
return best
Coin exchange II
Souce code
class Solution:
def change(self, amount: int, coins: List[int]) -> int:
if amount == 0:
return 1
coins = sorted(coins, reverse=True)
exchngs = [0 for i in range(amount+1)] # at i stores the number of ways to exchange i amount
for coin in coins:
for i in range(1,amount+1):
last_amount = i-coin
if last_amount < 0:
continue
if last_amount == 0:
exchngs[i] += 1
elif exchngs[last_amount] == 0:
continue
exchngs[i] += exchngs[last_amount]
return exchngs[amount]
Source code
class Twitter:
def __init__(self):
self.tweetsByUsers = defaultdict(deque) # userID, tweetID
self.subscripts = defaultdict(set) # who they are subscribed to
self.timer = 0
self.increment = -1 # choose 1 or -1 depending on whether you want to use be memory efficient or time efficient
def postTweet(self, userId: int, tweetId: int) -> None:
self.tweetsByUsers[userId].appendleft(tuple((self.timer, tweetId)))
self.timer += self.increment
if len(self.tweetsByUsers[userId]) > 10:
self.tweetsByUsers[userId].pop()
def getNewsFeed(self, userId: int) -> List[int]:
if self.increment == 1:
ret = [] # heapq with maxsize 10, (time, tweetId)
users = list(self.subscripts[userId]) + [userId]
for user in users:
tweets = self.tweetsByUsers[user]
for tweet in tweets:
if len(ret) >= 10:
heapq.heappushpop(ret, tweet)
else:
heapq.heappush(ret, tweet)
ret = sorted(ret, reverse=True)
ret = [val[1] for val in ret]
return ret
else:
ret = [] # list with maxsize 10, (time, tweetId)
users = list(self.subscripts[userId]) + [userId]
potentialTweets = defaultdict(deque) # copy of tweetsByUsers, but only for the users we are interested in
nominees = [] # heapq with the next most recent tweet from each of the users, storing (time, tweetId, userId)
for user in users:
potentialTweets[user] = self.tweetsByUsers[user].copy()
if potentialTweets[user]:
heapq.heappush(nominees, potentialTweets[user].popleft() + (user,))
while nominees:
_, tweetId, user = heapq.heappop(nominees)
ret.append(tweetId)
if potentialTweets[user]:
heapq.heappush(nominees, potentialTweets[user].popleft() + (user,))
if len(ret) == 10:
break
return ret
def follow(self, followerId: int, followeeId: int) -> None:
self.subscripts[followerId].add(followeeId)
def unfollow(self, followerId: int, followeeId: int) -> None:
self.subscripts[followerId].discard(followeeId)
