Skip to content

feat: add solutions to lc problem: No.2151 #3987

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ tags:
- <strong>在认为 0 是坏人但说真话的情况下,这组玩家中没有一个好人。</strong>
- 说假话。在这种情况下,1 是好人。
- <strong>在认为 0 是坏人且说假话的情况下,这组玩家中只有一个好人。</strong>
在最佳情况下,至多有一个好人,所以返回 1 。
在最佳情况下,至多有一个好人,所以返回 1 。
注意,能得到此结论的方法不止一种。
</pre>

Expand Down Expand Up @@ -120,17 +120,17 @@ tags:
```python
class Solution:
def maximumGood(self, statements: List[List[int]]) -> int:
def check(mask):
def check(mask: int) -> int:
cnt = 0
for i, s in enumerate(statements):
if (mask >> i) & 1:
for j, v in enumerate(s):
if v < 2 and ((mask >> j) & 1) != v:
for i, row in enumerate(statements):
if mask >> i & 1:
for j, x in enumerate(row):
if x < 2 and (mask >> j & 1) != x:
return 0
cnt += 1
return cnt

return max(check(mask) for mask in range(1, 1 << len(statements)))
return max(check(i) for i in range(1, 1 << len(statements)))
```

#### Java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,17 @@ Note that there is more than one way to arrive at this conclusion.
```python
class Solution:
def maximumGood(self, statements: List[List[int]]) -> int:
def check(mask):
def check(mask: int) -> int:
cnt = 0
for i, s in enumerate(statements):
if (mask >> i) & 1:
for j, v in enumerate(s):
if v < 2 and ((mask >> j) & 1) != v:
for i, row in enumerate(statements):
if mask >> i & 1:
for j, x in enumerate(row):
if x < 2 and (mask >> j & 1) != x:
return 0
cnt += 1
return cnt

return max(check(mask) for mask in range(1, 1 << len(statements)))
return max(check(i) for i in range(1, 1 << len(statements)))
```

#### Java
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
class Solution:
def maximumGood(self, statements: List[List[int]]) -> int:
def check(mask):
def check(mask: int) -> int:
cnt = 0
for i, s in enumerate(statements):
if (mask >> i) & 1:
for j, v in enumerate(s):
if v < 2 and ((mask >> j) & 1) != v:
for i, row in enumerate(statements):
if mask >> i & 1:
for j, x in enumerate(row):
if x < 2 and (mask >> j & 1) != x:
return 0
cnt += 1
return cnt

return max(check(mask) for mask in range(1, 1 << len(statements)))
return max(check(i) for i in range(1, 1 << len(statements)))
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,16 @@ tags:

### 方法一:记忆化搜索

我们定义一个函数 $dfs(i)$,表示从第 $i$ 个水果开始购买所有水果所需要的最少金币数。那么答案就是 $dfs(1)$。
我们定义一个函数 $\textit{dfs}(i)$,表示从第 $i$ 个水果开始购买所有水果所需要的最少金币数。那么答案就是 $\textit{dfs}(1)$。

函数 $dfs(i)$ 的执行逻辑如下:
函数 $\textit{dfs}(i)$ 的执行逻辑如下:

- 如果 $i \times 2 \geq n$,说明只要买第 $i - 1$ 个水果即可,剩余的水果都可以免费获得,所以返回 $prices[i - 1]$。
- 否则,我们可以购买水果 $i$,然后在接下来的 $i + 1$ 到 $2i + 1$ 个水果中选择一个水果 $j$ 开始购买,那么 $dfs(i) = prices[i - 1] + \min_{i + 1 \le j \le 2i + 1} dfs(j)$。
- 如果 $i \times 2 \geq n$,说明只要买第 $i - 1$ 个水果即可,剩余的水果都可以免费获得,所以返回 $\textit{prices}[i - 1]$。
- 否则,我们可以购买水果 $i$,然后在接下来的 $i + 1$ 到 $2i + 1$ 个水果中选择一个水果 $j$ 开始购买,那么 $\textit{dfs}(i) = \textit{prices}[i - 1] + \min_{i + 1 \le j \le 2i + 1} \textit{dfs}(j)$。

为了避免重复计算,我们使用记忆化搜索的方法,将已经计算过的结果保存起来,下次遇到相同的情况时,直接返回结果即可。

时间复杂度 $O(n^2)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $prices$ 的长度。
时间复杂度 $O(n^2)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $\textit{prices}$ 的长度。

<!-- tabs:start -->

Expand Down Expand Up @@ -250,11 +250,11 @@ function minimumCoins(prices: number[]): number {

与方法一类似,我们定义 $f[i]$ 表示从第 $i$ 个水果开始购买所有水果所需要的最少金币数。那么答案就是 $f[1]$。

状态转移方程为 $f[i] = \min_{i + 1 \le j \le 2i + 1} f[j] + prices[i - 1]$。
状态转移方程为 $f[i] = \min_{i + 1 \le j \le 2i + 1} f[j] + \textit{prices}[i - 1]$。

在实现上,我们从后往前计算,并且可以直接在数组 $prices$ 上进行状态转移,这样可以节省空间
时间复杂度 $O(n^2)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $\textit{prices}$ 的长度

时间复杂度 $O(n^2)$,其中 $n$ 为数组 $prices$ 的长度。空间复杂度 $O(1)$。
在代码实现上,我们可以直接使用 $\textit{prices}$ 数组来存储 $f$ 数组,那么空间复杂度可以优化到 $O(1)$。

<!-- tabs:start -->

Expand Down Expand Up @@ -334,9 +334,9 @@ function minimumCoins(prices: number[]): number {

我们观察方法二中的状态转移方程,可以发现,对于每个 $i$,我们需要求出 $f[i + 1], f[i + 2], \cdots, f[2i + 1]$ 的最小值,并且随着 $i$ 的减小,这些值的范围也在减小。这实际上是求一个单调收窄的滑动窗口的最小值,我们可以使用单调队列来优化。

我们从后往前计算,维护一个单调递增的队列 $q$,队列中存储的是下标。如果 $q$ 的队首元素大于 $i \times 2 + 1$,说明 $i$ 之后的元素都不会被用到,所以我们将队首元素出队。如果 $i$ 不大于 $(n - 1) / 2$,那么我们可以将 $prices[q[0] - 1]$ 加到 $prices[i - 1]$ 上,然后将 $i$ 加入队尾。如果 $q$ 的队尾元素对应的水果价格大于等于 $prices[i - 1]$,那么我们将队尾元素出队,直到队尾元素对应的水果价格小于 $prices[i - 1]$ 或者队列为空,然后将 $i$ 加入队尾。
我们从后往前计算,维护一个单调递增的队列 $q$,队列中存储的是下标。如果 $q$ 的队首元素大于 $i \times 2 + 1$,说明 $i$ 之后的元素都不会被用到,所以我们将队首元素出队。如果 $i$ 不大于 $(n - 1) / 2$,那么我们可以将 $\textit{prices}[q[0] - 1]$ 加到 $\textit{prices}[i - 1]$ 上,然后将 $i$ 加入队尾。如果 $q$ 的队尾元素对应的水果价格大于等于 $\textit{prices}[i - 1]$,那么我们将队尾元素出队,直到队尾元素对应的水果价格小于 $\textit{prices}[i - 1]$ 或者队列为空,然后将 $i$ 加入队尾。

时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $prices$ 的长度。
时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $\textit{prices}$ 的长度。

<!-- tabs:start -->

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,18 @@ tags:

<!-- solution:start -->

### Solution 1
### Solution 1: Memoization Search

We define a function $\textit{dfs}(i)$ to represent the minimum number of coins needed to buy all the fruits starting from the $i$-th fruit. The answer is $\textit{dfs}(1)$.

The execution logic of the function $\textit{dfs}(i)$ is as follows:

- If $i \times 2 \geq n$, it means that buying the $(i - 1)$-th fruit is sufficient, and the remaining fruits can be obtained for free, so return $\textit{prices}[i - 1]$.
- Otherwise, we can buy fruit $i$, and then choose a fruit $j$ to start buying from the next $i + 1$ to $2i + 1$ fruits. Thus, $\textit{dfs}(i) = \textit{prices}[i - 1] + \min_{i + 1 \le j \le 2i + 1} \textit{dfs}(j)$.

To avoid redundant calculations, we use memoization to store the results that have already been computed. When encountering the same situation again, we directly return the result.

The time complexity is $O(n^2)$, and the space complexity is $O(n)$. Here, $n$ is the length of the array $\textit{prices}$.

<!-- tabs:start -->

Expand Down Expand Up @@ -231,7 +242,17 @@ function minimumCoins(prices: number[]): number {

<!-- solution:start -->

### Solution 2
### Solution 2: Dynamic Programming

We can rewrite the memoization search in Solution 1 into a dynamic programming form.

Similar to Solution 1, we define $f[i]$ to represent the minimum number of coins needed to buy all the fruits starting from the $i$-th fruit. The answer is $f[1]$.

The state transition equation is $f[i] = \min_{i + 1 \le j \le 2i + 1} f[j] + \textit{prices}[i - 1]$.

The time complexity is $O(n^2)$, and the space complexity is $O(n)$. Here, $n$ is the length of the array $\textit{prices}$.

In the code implementation, we can directly use the $\textit{prices}$ array to store the $f$ array, thus optimizing the space complexity to $O(1)$.

<!-- tabs:start -->

Expand Down Expand Up @@ -307,7 +328,13 @@ function minimumCoins(prices: number[]): number {

<!-- solution:start -->

### Solution 3
### Solution 3: Dynamic Programming + Monotonic Queue Optimization

Observing the state transition equation in Solution 2, we can see that for each $i$, we need to find the minimum value of $f[i + 1], f[i + 2], \cdots, f[2i + 1]$. As $i$ decreases, the range of these values also decreases. This is essentially finding the minimum value in a sliding window with a narrowing range, which can be optimized using a monotonic queue.

We calculate from back to front, maintaining a monotonically increasing queue $q$, where the queue stores indices. If the front element of $q$ is greater than $i \times 2 + 1$, it means that the elements after $i$ will not be used, so we dequeue the front element. If $i$ is not greater than $(n - 1) / 2$, we can add $\textit{prices}[q[0] - 1]$ to $\textit{prices}[i - 1]$, and then add $i$ to the back of the queue. If the fruit price corresponding to the back element of $q$ is greater than or equal to $\textit{prices}[i - 1]$, we dequeue the back element until the fruit price corresponding to the back element is less than $\textit{prices}[i - 1]$ or the queue is empty, then add $i$ to the back of the queue.

The time complexity is $O(n)$, and the space complexity is $O(n)$. Here, $n$ is the length of the array $\textit{prices}$.

<!-- tabs:start -->

Expand Down
Loading