This problem asks to find the number of distinct ways to build a house of cards using a given number n
of cards. A valid house of cards follows specific rules regarding the arrangement of triangles (two cards leaning against each other) and horizontal cards.
The key observation is that each layer of a house of cards requires 3k + 2
cards, where k
is the number of triangles in that layer (and thus the layer index, starting from 0). We can solve this problem using dynamic programming (specifically, memoization) because subproblems (building houses with fewer cards) are reused.
The core function dfs(n, k)
recursively determines the number of ways to build a house of cards:
Base Cases:
3k + 2 > n
, there aren't enough cards to add another layer, so return 0.3k + 2 == n
, there are exactly enough cards for a single layer, so return 1.Recursive Step:
dfs(n - (3k + 2), k + 1)
to explore building a house with a new layer (using 3k + 2
cards).dfs(n, k + 1)
to explore building a house without adding another layer (leaving n
cards).Memoization: To avoid redundant calculations, a memoization table (f
in the code) stores the results of dfs(n, k)
. If a result is already computed, it's retrieved from the table; otherwise, it's computed and stored.
n x n/3
, leading to a time complexity of O(n²). Each state is visited at most once.The following code demonstrates the solution in several programming languages:
Python:
class Solution:
def houseOfCards(self, n: int) -> int:
@cache
def dfs(n: int, k: int) -> int:
x = 3 * k + 2
if x > n:
return 0
if x == n:
return 1
return dfs(n - x, k + 1) + dfs(n, k + 1)
return dfs(n, 0)
Java:
class Solution {
private Integer[][] f;
public int houseOfCards(int n) {
f = new Integer[n + 1][n / 3 + 1]; //Memoization table
return dfs(n, 0);
}
private int dfs(int n, int k) {
int x = 3 * k + 2;
if (x > n) return 0;
if (x == n) return 1;
if (f[n][k] != null) return f[n][k];
return f[n][k] = dfs(n - x, k + 1) + dfs(n, k + 1);
}
}
C++:
class Solution {
public:
int houseOfCards(int n) {
vector<vector<int>> f(n + 1, vector<int>(n / 3 + 1, -1)); //Memoization
function<int(int, int)> dfs = [&](int n, int k) {
int x = 3 * k + 2;
if (x > n) return 0;
if (x == n) return 1;
if (f[n][k] != -1) return f[n][k];
return f[n][k] = dfs(n - x, k + 1) + dfs(n, k + 1);
};
return dfs(n, 0);
}
};
Go:
func houseOfCards(n int) int {
f := make([][]int, n+1)
for i := range f {
f[i] = make([]int, n/3+1)
for j := range f[i] {
f[i][j] = -1
}
}
var dfs func(n, k int) int
dfs = func(n, k int) int {
x := 3*k + 2
if x > n {
return 0
}
if x == n {
return 1
}
if f[n][k] == -1 {
f[n][k] = dfs(n-x, k+1) + dfs(n, k+1)
}
return f[n][k]
}
return dfs(n, 0)
}
TypeScript:
function houseOfCards(n: number): number {
const f: number[][] = Array(n + 1).fill(null).map(() => Array(Math.floor(n / 3) + 1).fill(-1));
const dfs = (n: number, k: number): number => {
const x = 3 * k + 2;
if (x > n) return 0;
if (x === n) return 1;
if (f[n][k] !== -1) return f[n][k];
return f[n][k] = dfs(n - x, k + 1) + dfs(n, k + 1);
};
return dfs(n, 0);
}
These codes all implement the same dynamic programming (memoization) solution, varying only in syntax and specific data structure usage for memoization. They all achieve the same time and space complexity.