This problem asks to find the number of ways to pair up numPeople
individuals in a circle such that no handshakes cross. The solution leverages dynamic programming to efficiently solve this.
Core Idea:
The problem can be broken down recursively. Consider the first person. They must shake hands with someone. Let's say they shake hands with the k
th person. This divides the remaining people into two groups: those between the first and k
th person, and those between the k
th person and the first person. Crucially, no handshake can cross the initial handshake. Therefore, we can recursively solve the problem for these two smaller groups independently.
Dynamic Programming Approach:
We use a function dfs(i)
to represent the number of ways to arrange handshakes for i
people. The base cases are:
dfs(0) = 1
: No people, no handshakes.dfs(1) = 1
: One person, no handshakes.dfs(2) = 1
: Two people, one handshake.For i > 2
, we iterate through all possible handshake partners for the first person (0 to i - 1
, but incrementing only by 2 to avoid the crossing problem). Let l
be the number of people to the left of the handshake, and r
be the number of people to the right. Then:
dfs(i) = Σ (dfs(l) * dfs(r))
where the summation is over all possible l
values (0, 2, 4,...).
Memoization:
To avoid recalculating dfs(i)
for the same i
multiple times, we use memoization (caching the results). This dramatically improves the efficiency.
Time and Space Complexity:
Time Complexity: O(n2). The dfs
function's execution time grows roughly quadratically with the input size (numPeople
). The outer loop runs up to n/2
times (in the worst case), and each iteration involves a recursive call that itself can be O(n) in the worst case. Memoization significantly reduces this complexity from an exponential solution, but the quadratic relationship remains.
Space Complexity: O(n). The space used is dominated by the memoization table (f
in the code examples), which stores the results for up to n
people. The recursive call stack also contributes to space complexity, but it remains linear due to the memoization.
Code Examples (with detailed comments):
The provided code in Python, Java, C++, Go, and TypeScript all implement this dynamic programming solution with memoization. The structure remains largely consistent across all languages. The mod
operation (taking the remainder when dividing by 109 + 7) is included to handle potentially large results, preventing integer overflow.
The memoization (using @cache
in Python, f
array in other languages) is the key to making this solution efficient. Without memoization, the time complexity would become exponential.