Skip to content

Commit

Permalink
feat: update solutions to lcci problems (#2088)
Browse files Browse the repository at this point in the history
  • Loading branch information
yanglbme authored Dec 12, 2023
1 parent 06fff4b commit f901522
Show file tree
Hide file tree
Showing 18 changed files with 264 additions and 7 deletions.
18 changes: 18 additions & 0 deletions lcci/04.12.Paths with Sum/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,24 @@ Given the following tree and &nbsp;<code>sum = 22,</code></p>

## Solutions

**Solution 1: Hash Table + Prefix Sum + Recursion**

We can use the idea of prefix sum to recursively traverse the binary tree, and use a hash table $cnt$ to count the occurrence of each prefix sum on the path from the root node to the current node.

We design a recursive function $dfs(node, s)$, where the current node being traversed is $node$, and the prefix sum on the path from the root node to the current node is $s$. The return value of the function is the number of paths with the path sum equal to $sum$ and the path ends at the $node$ node or its subtree nodes. Therefore, the answer is $dfs(root, 0)$.

The recursive process of the function $dfs(node, s)$ is as follows:

- If the current node $node$ is null, return $0$.
- Calculate the prefix sum $s$ on the path from the root node to the current node.
- Use $cnt[s - sum]$ to represent the number of paths with the path sum equal to $sum$ and the path ends at the current node, where $cnt[s - sum]$ is the count of the prefix sum equal to $s - sum$ in $cnt$.
- Add the count of the prefix sum $s$ by $1$, i.e., $cnt[s] = cnt[s] + 1$.
- Recursively traverse the left and right child nodes of the current node, i.e., call the functions $dfs(node.left, s)$ and $dfs(node.right, s)$, and add their return values.
- After the return value is calculated, subtract the count of the prefix sum $s$ of the current node by $1$, i.e., execute $cnt[s] = cnt[s] - 1$.
- Finally, return the answer.

The time complexity is $O(n)$, and the space complexity is $O(n)$. Here, $n$ is the number of nodes in the binary tree.

<!-- tabs:start -->

### **Python3**
Expand Down
6 changes: 6 additions & 0 deletions lcci/05.01.Insert Into Bits/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@

## Solutions

**Solution 1: Bit Manipulation**

First, we clear the bits from the $i$-th to the $j$-th in $N$, then we left shift $M$ by $i$ bits, and finally perform a bitwise OR operation on $M$ and $N$.

The time complexity is $O(\log n)$, where $n$ is the size of $N$. The space complexity is $O(1)$.

<!-- tabs:start -->

### **Python3**
Expand Down
25 changes: 24 additions & 1 deletion lcci/05.02.Binary Number to String/README_EN.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# [05.02. Binary Number to String](https://leetcode.cn/problems/bianry-number-to-string-lcci)
# [05.02. Binary Number to String](https://leetcode.cn/problems/binary-number-to-string-lcci)

[中文文档](/lcci/05.02.Binary%20Number%20to%20String/README.md)

Expand Down Expand Up @@ -30,6 +30,29 @@

## Solutions

**Solution 1: Decimal Fraction to Binary Fraction**

The method of converting a decimal fraction to a binary fraction is as follows: multiply the decimal part by $2$, take the integer part as the next digit of the binary fraction, and take the decimal part as the multiplicand for the next multiplication, until the decimal part is $0$ or the length of the binary fraction exceeds $32$ bits.

Let's take an example, suppose we want to convert $0.8125$ to a binary fraction, the process is as follows:

$$
\begin{aligned}
0.8125 \times 2 &= 1.625 \quad \text{take the integer part} \quad 1 \\
0.625 \times 2 &= 1.25 \quad \text{take the integer part} \quad 1 \\
0.25 \times 2 &= 0.5 \quad \text{take the integer part} \quad 0 \\
0.5 \times 2 &= 1 \quad \text{take the integer part} \quad 1 \\
\end{aligned}
$$

So the binary fraction representation of the decimal fraction $0.8125$ is $0.1101_{(2)}$.

For this problem, since the real number is between $0$ and $1$, its integer part must be $0$. We only need to convert the decimal part into a binary fraction according to the above method. Stop the conversion when the decimal part is $0$ or the length of the binary fraction is not less than $32$ bits.

Finally, if the decimal part is not $0$, it means that the real number cannot be represented in binary within $32$ bits, return the string `"ERROR"`. Otherwise, return the converted binary fraction.

The time complexity is $O(C)$, and the space complexity is $O(C)$. Here, $C$ is the length of the binary fraction, with a maximum of $32$.

<!-- tabs:start -->

### **Python3**
Expand Down
8 changes: 8 additions & 0 deletions lcci/05.03.Reverse Bits/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@

## Solutions

**Solution 1: Two Pointers**

We can use two pointers $i$ and $j$ to maintain a sliding window, where $i$ is the right pointer and $j$ is the left pointer. Each time the right pointer $i$ moves one bit to the right, if the number of $0$s in the window exceeds $1$, then the left pointer $j$ moves one bit to the right, until the number of $0$s in the window does not exceed $1$. Then calculate the length of the window at this time, compare it with the current maximum length, and take the larger value as the current maximum length.

Finally, return the maximum length.

The time complexity is $O(\log M)$, and the space complexity is $O(1)$. Here, $M$ is the maximum value of a 32-bit integer.

<!-- tabs:start -->

### **Python3**
Expand Down
14 changes: 14 additions & 0 deletions lcci/05.04.Closed Number/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@

## Solutions

**Solution 1: Bit Manipulation**

First, let's consider how to find the first number that is larger than $num$ and has the same number of $1$s in its binary representation.

We can traverse the adjacent two binary bits of $num$ from low to high. If the lower bit is $1$ and the adjacent higher bit is $0$, then we have found a position where we can change the $0$ at this position to $1$ and change the $1$ at this position to $0$. Then we move all the remaining lower bits of $1$ to the lowest bit, so we get a number that is larger than $num$ and has the same number of $1$s in its binary representation.

Similarly, we can find the first number that is smaller than $num$ and has the same number of $1$s in its binary representation.

We can traverse the adjacent two binary bits of $num$ from low to high. If the lower bit is $0$ and the adjacent higher bit is $1$, then we have found a position where we can change the $1$ at this position to $0$ and change the $0$ at this position to $1$. Then we move all the remaining lower bits of $0$ to the lowest bit, so we get a number that is smaller than $num$ and has the same number of $1$s in its binary representation.

In implementation, we can use a piece of code to handle the above two situations uniformly.

The time complexity is $O(\log n)$, where $n$ is the size of $num$. The space complexity is $O(1)$.

<!-- tabs:start -->

### **Python3**
Expand Down
6 changes: 6 additions & 0 deletions lcci/05.06.Convert Integer/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@

## Solutions

**Solution 1: Bit Manipulation**

We perform a bitwise XOR operation on A and B. The number of $1$s in the result is the number of bits that need to be changed.

The time complexity is $O(\log n)$, where $n$ is the maximum value of A and B. The space complexity is $O(1)$.

<!-- tabs:start -->

### **Python3**
Expand Down
36 changes: 36 additions & 0 deletions lcci/08.01.Three Steps Problem/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,42 @@

## Solutions

**Solution 1: Recursion**

We define $f[i]$ as the number of ways to reach the $i$-th step, initially $f[1]=1$, $f[2]=2$, $f[3]=4$. The answer is $f[n]$.

The recursion formula is $f[i] = f[i-1] + f[i-2] + f[i-3]$.

Since $f[i]$ is only related to $f[i-1]$, $f[i-2]$, $f[i-3]$, we can use three variables $a$, $b$, $c$ to store the values of $f[i-1]$, $f[i-2]$, $f[i-3]$, reducing the space complexity to $O(1)$.

The time complexity is $O(n)$, where $n$ is the given integer. The space complexity is $O(1)$.

**Solution 2: Matrix Quick Power to Accelerate Recursion**

We set $F(n)$ to represent a $1 \times 3$ matrix $\begin{bmatrix} F_{n - 1} & F_{n - 2} & F_{n - 3} \end{bmatrix}$, where $F_{n - 1}$, $F_{n - 2}$ and $F_{n - 3}$ respectively represent the number of ways to reach the $n - 1$-th, $n - 2$-th and $n - 3$-th steps.

We hope to derive $F(n)$ based on $F(n-1) = \begin{bmatrix} F_{n - 2} & F_{n - 3} & F_{n - 4} \end{bmatrix}$. That is to say, we need a matrix $base$, so that $F(n - 1) \times base = F(n)$, i.e.:

$$
\begin{bmatrix}
F_{n - 2} & F_{n - 3} & F_{n - 4}
\end{bmatrix} \times base = \begin{bmatrix} F_{n - 1} & F_{n - 2} & F_{n - 3} \end{bmatrix}
$$

Since $F_n = F_{n - 1} + F_{n - 2} + F_{n - 3}$, the matrix $base$ is:

$$
\begin{bmatrix}
1 & 1 & 0 \\
1 & 0 & 1 \\
1 & 0 & 0
\end{bmatrix}
$$

We define the initial matrix $res = \begin{bmatrix} 1 & 1 & 0 \end{bmatrix}$, then $F_n$ equals the sum of all elements in the result matrix of $res$ multiplied by $base^{n - 4}$. It can be solved using matrix quick power.

The time complexity is $O(\log n)$, and the space complexity is $O(1)$.

<!-- tabs:start -->

### **Python3**
Expand Down
8 changes: 8 additions & 0 deletions lcci/08.02.Robot in a Grid/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@

## Solutions

**Solution 1: DFS (Depth-First Search)**

We can use depth-first search to solve this problem. We start from the top left corner and move right or down until we reach the bottom right corner. If at some step, we find that the current position is an obstacle, or the current position is already in the path, then we return. Otherwise, we add the current position to the path and mark the current position as visited, then continue to move right or down.

If we can finally reach the bottom right corner, then we have found a feasible path, otherwise, it means there is no feasible path.

The time complexity is $O(m \times n)$, and the space complexity is $O(m \times n)$. Here, $m$ and $n$ are the number of rows and columns of the grid, respectively.

<!-- tabs:start -->

### **Python3**
Expand Down
4 changes: 2 additions & 2 deletions lcci/08.04.Power Set/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@

当前枚举到的元素下标为 $u$,我们可以选择将其加入子集 $t$ 中,也可以选择不加入子集 $t$ 中。递归这两种选择,即可得到所有的子集。

时间复杂度 $O(n\times 2^n)$,空间复杂度 $O(n)$。其中 $n$ 为数组的长度。数组中每个元素有两种状态,即选择或不选择,共 $2^n$ 种状态,每种状态需要 $O(n)$ 的时间来构造子集。
时间复杂度 $O(n \times 2^n)$,空间复杂度 $O(n)$。其中 $n$ 为数组的长度。数组中每个元素有两种状态,即选择或不选择,共 $2^n$ 种状态,每种状态需要 $O(n)$ 的时间来构造子集。

**方法二:二进制枚举**

我们可以将方法一中的递归过程改写成迭代的形式,即使用二进制枚举的方法来枚举所有的子集。

我们可以使用 $2^n$ 个二进制数来表示 $n$ 个元素的所有子集,若某个二进制数 `mask` 的第 $i$ 位为 $1$,表示子集中包含数组第 $i$ 个元素 $v$;若为 $0$,表示子集中不包含数组第 $i$ 个元素 $v$。

时间复杂度 $O(n\times 2^n)$,空间复杂度 $O(n)$。其中 $n$ 为数组的长度。一共有 $2^n$ 个子集,每个子集需要 $O(n)$ 的时间来构造。
时间复杂度 $O(n \times 2^n)$,空间复杂度 $O(n)$。其中 $n$ 为数组的长度。一共有 $2^n$ 个子集,每个子集需要 $O(n)$ 的时间来构造。

<!-- tabs:start -->

Expand Down
16 changes: 15 additions & 1 deletion lcci/08.04.Power Set/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,21 @@

## Solutions

Backtracking
**Solution 1: Recursive Enumeration**

We design a recursive function $dfs(u, t)$, where $u$ is the index of the current element being enumerated, and $t$ is the current subset.

For the current element with index $u$, we can choose to add it to the subset $t$, or we can choose not to add it to the subset $t$. Recursively making these two choices will yield all subsets.

The time complexity is $O(n \times 2^n)$, and the space complexity is $O(n)$. Here, $n$ is the length of the array. Each element in the array has two states, namely chosen or not chosen, for a total of $2^n$ states. Each state requires $O(n)$ time to construct the subset.

**Solution 2: Binary Enumeration**

We can rewrite the recursive process in Method 1 into an iterative form, that is, using binary enumeration to enumerate all subsets.

We can use $2^n$ binary numbers to represent all subsets of $n$ elements. If the $i$-th bit of a binary number `mask` is $1$, it means that the subset contains the $i$-th element $v$ of the array; if it is $0$, it means that the subset does not contain the $i$-th element $v$ of the array.

The time complexity is $O(n \times 2^n)$, and the space complexity is $O(n)$. Here, $n$ is the length of the array. There are a total of $2^n$ subsets, and each subset requires $O(n)$ time to construct.

<!-- tabs:start -->

Expand Down
8 changes: 8 additions & 0 deletions lcci/08.05.Recursive Mulitply/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@

## Solutions

**Solution 1: Recursion + Bit Manipulation**

First, we check if $B$ is $1$. If it is, we directly return $A$.

Otherwise, we check if $B$ is an odd number. If it is, we can right shift $B$ by one bit, then recursively call the function, and finally left shift the result by one bit and add $A$. If not, we can right shift $B$ by one bit, then recursively call the function, and finally left shift the result by one bit.

The time complexity is $O(\log n)$, and the space complexity is $O(\log n)$. Here, $n$ is the size of $B$.

<!-- tabs:start -->

### **Python3**
Expand Down
26 changes: 26 additions & 0 deletions lcci/08.06.Hanota/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,32 @@

## Solutions

**Solution 1: Recursion**

We design a function $dfs(n, a, b, c)$, which represents moving $n$ disks from $a$ to $c$, with $b$ as the auxiliary rod.

First, we move $n - 1$ disks from $a$ to $b$, then move the $n$-th disk from $a$ to $c$, and finally move $n - 1$ disks from $b$ to $c$.

The time complexity is $O(2^n)$, and the space complexity is $O(n)$. Here, $n$ is the number of disks.

**Solution 2: Iteration (Stack)**

We can use a stack to simulate the recursive process.

We define a struct $Task$, which represents a task, where $n$ represents the number of disks, and $a$, $b$, $c$ represent the three rods.

We push the initial task $Task(len(A), A, B, C)$ into the stack, and then continuously process the task at the top of the stack until the stack is empty.

If $n = 1$, then we directly move the disk from $a$ to $c$.

Otherwise, we push three subtasks into the stack, which are:

1. Move $n - 1$ disks from $b$ to $c$ with the help of $a$;
2. Move the $n$-th disk from $a$ to $c$;
3. Move $n - 1$ disks from $a$ to $b$ with the help of $c$.

The time complexity is $O(2^n)$, and the space complexity is $O(n)$. Here, $n$ is the number of disks.

<!-- tabs:start -->

### **Python3**
Expand Down
13 changes: 13 additions & 0 deletions lcci/08.08.Permutation II/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@

## Solutions

**Solution 1: Sorting + Backtracking**

We can first sort the string by characters, which allows us to put duplicate characters together and makes it easier for us to remove duplicates.

Then, we design a function $dfs(i)$, which means that we need to fill in the character at the $i$-th position. The specific implementation of the function is as follows:

- If $i = n$, it means that we have finished filling in, add the current permutation to the answer array, and then return.
- Otherwise, we enumerate the character $s[j]$ at the $i$-th position, where the range of $j$ is $[0, n - 1]$. We need to ensure that $s[j]$ has not been used and is different from the previously enumerated characters, so as to ensure that the current permutation is not repeated. If the conditions are met, we can fill in $s[j]$, and continue to recursively fill in the next position, that is, call $dfs(i + 1)$. After the recursive call ends, we need to mark $s[j]$ as unused for later enumeration.

In the main function, we first sort the string, then call $dfs(0)$, that is, start filling from the $0$-th position, and finally return the answer array.

The time complexity is $O(n \times n!)$, and the space complexity is $O(n)$. Here, $n$ is the length of the string $s$. $n!$ enumerations need to be performed, and each enumeration requires $O(n)$ time to determine whether it is repeated. In addition, we need a marker array to mark whether each position has been used, so the space complexity is $O(n)$.

<!-- tabs:start -->

### **Python3**
Expand Down
13 changes: 12 additions & 1 deletion lcci/08.09.Bracket/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,18 @@

## Solutions

DFS.
**Solution 1: DFS + Pruning**

The range of $n$ in the problem is $[1, 8]$, so we can directly solve this problem quickly through "brute force search + pruning".

We design a function `dfs(l, r, t)`, where $l$ and $r$ represent the number of left and right parentheses respectively, and $t$ represents the current parentheses sequence. Then we can get the following recursive structure:

- If $l > n$ or $r > n$ or $l < r$, then the current parentheses combination $t$ is illegal, return directly;
- If $l = n$ and $r = n$, then the current parentheses combination $t$ is legal, add it to the answer array `ans`, and return directly;
- We can choose to add a left parenthesis, and recursively execute `dfs(l + 1, r, t + "(")`;
- We can also choose to add a right parenthesis, and recursively execute `dfs(l, r + 1, t + ")")`.

The time complexity is $O(2^{n\times 2} \times n)$, and the space complexity is $O(n)$.

<!-- tabs:start -->

Expand Down
8 changes: 6 additions & 2 deletions lcci/08.10.Color Fill/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ to the starting pixel.</pre>

## Solutions

DFS or BFS.
**Solution 1: Flood Fill Algorithm**

> Flood fill, also called seed fill, is a flooding algorithm that determines and alters the area connected to a given node in a multi-dimensional array with some matching attribute. It is used in the "bucket" fill tool of paint programs to fill connected, similarly-colored areas with a different color.
The Flood Fill algorithm is a classic algorithm used to extract several connected points from a region and distinguish them from other adjacent regions (or color them differently). It is named for its strategy, which is similar to a flood spreading from one area to all reachable areas.

The simplest implementation method is to use the recursive method of DFS, or it can be implemented iteratively using BFS.

The time complexity is $O(m \times n)$, and the space complexity is $O(m \times n)$. Here, $m$ and $n$ are the number of rows and columns of the image, respectively.

<!-- tabs:start -->

Expand Down
32 changes: 32 additions & 0 deletions lcci/08.11.Coin/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,38 @@

## Solutions

**Solution 1: Dynamic Programming**

We define $f[i][j]$ as the number of ways to make up the total amount $j$ using only the first $i$ types of coins. Initially, $f[0][0]=1$, and the rest of the elements are $0$. The answer is $f[4][n]$.

Considering $f[i][j]$, we can enumerate the number of the $i$-th type of coin used, $k$, where $0 \leq k \leq j / c_i$, then $f[i][j]$ is equal to the sum of all $f[i−1][j−k \times c_i]$. Since the number of coins is infinite, $k$ can start from $0$. That is, the state transition equation is as follows:

$$
f[i][j] = f[i - 1][j] + f[i - 1][j - c_i] + \cdots + f[i - 1][j - k \times c_i]
$$

Let $j = j - c_i$, then the above state transition equation can be written as:

$$
f[i][j - c_i] = f[i - 1][j - c_i] + f[i - 1][j - 2 \times c_i] + \cdots + f[i - 1][j - k \times c_i]
$$

Substitute the second equation into the first equation to get:

$$
f[i][j]=
\begin{cases}
f[i - 1][j] + f[i][j - c_i], & j \geq c_i \\
f[i - 1][j], & j < c_i
\end{cases}
$$

The final answer is $f[4][n]$.

The time complexity is $O(C \times n)$, and the space complexity is $O(C \times n)$, where $C$ is the number of types of coins.

We notice that the calculation of $f[i][j]$ is only related to $f[i−1][..]$, so we can remove the first dimension and optimize the space complexity to $O(n)$.

<!-- tabs:start -->

### **Python3**
Expand Down
10 changes: 10 additions & 0 deletions lcci/08.13.Pile Box/README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@

## Solutions

**Solution 1: Sorting + Dynamic Programming**

First, we sort the boxes in ascending order by width and descending order by depth, then use dynamic programming to solve the problem.

We define $f[i]$ as the maximum height with the $i$-th box at the bottom. For $f[i]$, we enumerate $j \in [0, i)$, if $box[j][1] < box[i][1]$ and $box[j][2] < box[i][2]$, then we can put the $j$-th box on top of the $i$-th box, in which case $f[i] = \max\{f[i], f[j]\}$. Finally, we add the height of the $i$-th box to $f[i]$ to get the final value of $f[i]$.

The answer is the maximum value in $f$.

The time complexity is $O(n^2)$, and the space complexity is $O(n)$. Here, $n$ is the number of boxes.

<!-- tabs:start -->

### **Python3**
Expand Down
Loading

0 comments on commit f901522

Please sign in to comment.