{x}
blog image

Handshakes That Don't Cross

Solution Explanation: Handshakes That Don't Cross

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 kth person. This divides the remaining people into two groups: those between the first and kth person, and those between the kth 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.