Skip to content

Commit

Permalink
更新题解列表
Browse files Browse the repository at this point in the history
  • Loading branch information
itcharge committed Jan 5, 2024
1 parent a9a7a98 commit ace6e17
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 54 deletions.
79 changes: 53 additions & 26 deletions Solutions/0480. 滑动窗口中位数.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,55 +9,77 @@

## 题目大意

给定一个数组 `nums`,有一个长度为 `k` 的窗口从最左端滑动到最右端。窗口中有 `k` 个数,每次窗口向右移动 `1` 位。
**描述**给定一个数组 $nums$,有一个长度为 $k$ 的窗口从最左端滑动到最右端。窗口中有 $k$ 个数,每次窗口向右移动 $1$ 位。

要求:找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。
**要求**:找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。

- 中位数:有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。
**说明**

例如:
- **中位数**:有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。
- 例如:
- $[2,3,4]$,中位数是 $3$
- $[2,3]$,中位数是 $(2 + 3) / 2 = 2.5$。
- 你可以假设 $k$ 始终有效,即:$k$ 始终小于等于输入的非空数组的元素个数。
- 与真实值误差在 $10 ^ {-5}$ 以内的答案将被视作正确答案。

- `[2, 3, 4]`,中位数是 `3`
- `[2, 3]`,中位数是 `(2 + 3) / 2 = 2.5`
**示例**

- 示例 1:

```python
给出 nums = [1,3,-1,-3,5,3,6,7],以及 k = 3

窗口位置 中位数
--------------- -----
[1 3 -1] -3 5 3 6 7 1
1 [3 -1 -3] 5 3 6 7 -1
1 3 [-1 -3 5] 3 6 7 -1
1 3 -1 [-3 5 3] 6 7 3
1 3 -1 -3 [5 3 6] 7 5
1 3 -1 -3 5 [3 6 7] 6
因此,返回该滑动窗口的中位数数组 [1,-1,-1,3,5,6]。
```

## 解题思路

题目要求动态维护长度为 `k` 的窗口中元素的中位数。如果对窗口元素进行排序,时间复杂度一般是 $O(k * log_2k)$。如果对每个区间都进行排序,那时间复杂度就更大了,肯定会超时。
### 思路 1:小顶堆 + 大顶堆

我们需要借助一个内部有序的数据结构,来降低取窗口中位数的时间复杂度。`Python` 可以借助 `heapq` 构建大顶堆和小顶堆。通过 `k` 的奇偶性和堆顶元素来获取中位数。
题目要求动态维护长度为 $k$ 的窗口中元素的中位数。如果对窗口元素进行排序,时间复杂度一般是 $O(k \times \log k)$。如果对每个区间都进行排序,那时间复杂度就更大了,肯定会超时。

我们需要借助一个内部有序的数据结构,来降低取窗口中位数的时间复杂度。Python 可以借助 `heapq` 构建大顶堆和小顶堆。通过 $k$ 的奇偶性和堆顶元素来获取中位数。

接下来还要考虑几个问题:初始化问题、取中位数问题、窗口滑动中元素的添加删除操作。接下来一一解决。

初始化问题:

我们将所有大于中位数的元素放到 `heap_max`(小顶堆)中,并且元素个数向上取整。然后再将所有小于等于中位数的元素放到 `heap_min`(大顶堆)中,并且元素个数向下取整。这样当 `k` 为奇数时,`heap_max``heap_min` 多一个元素,中位数就是 `heap_max` 堆顶元素。当 `k` 为偶数时,`heap_max``heap_min` 中的元素个数相同,中位数就是 `heap_min` 堆顶元素和 `heap_max` 堆顶元素的平均数。这个过程操作如下:
我们将所有大于中位数的元素放到 $heap\underline{}max$(小顶堆)中,并且元素个数向上取整。然后再将所有小于等于中位数的元素放到 $heap\underline{}min$(大顶堆)中,并且元素个数向下取整。这样当 $k$ 为奇数时,$heap\underline{}max$$heap\underline{}min$ 多一个元素,中位数就是 $heap\underline{}max$ 堆顶元素。当 $k$ 为偶数时,$heap\underline{}max$$heap\underline{}min$ 中的元素个数相同,中位数就是 $heap\underline{}min$ 堆顶元素和 $heap\underline{}max$ 堆顶元素的平均数。这个过程操作如下:

- 先将数组中前 `k` 个元素放到 `heap_max` 中。
- 再从 `heap_max` 中取出 `k // 2` 个堆顶元素放到 `heap_min` 中。
- 先将数组中前 $k$ 个元素放到 $heap\underline{}max$ 中。
- 再从 $heap\underline{}max$ 中取出 $k // 2$ 个堆顶元素放到 $heap\underline{}min$ 中。

取中位数问题(上边提到过):

-`k` 为奇数时,中位数就是 `heap_max` 堆顶元素。当 `k` 为偶数时,中位数就是 `heap_max` 堆顶元素和 `heap_min` 堆顶元素的平均数。
-$k$ 为奇数时,中位数就是 $heap\underline{}max$ 堆顶元素。当 $k$ 为偶数时,中位数就是 $heap\underline{}max$ 堆顶元素和 $heap\underline{}min$ 堆顶元素的平均数。

窗口滑动过程中元素的添加和删除问题:

- 删除:每次滑动将窗口左侧元素删除。由于 `heapq` 没有提供删除中间特定元素相对应的方法。所以我们使用「延迟删除」的方式先把待删除的元素标记上,等到待删除的元素出现在堆顶时,再将其移除。我们使用 `removes` (哈希表)来记录待删除元素个数。
- 删除:每次滑动将窗口左侧元素删除。由于 `heapq` 没有提供删除中间特定元素相对应的方法。所以我们使用「延迟删除」的方式先把待删除的元素标记上,等到待删除的元素出现在堆顶时,再将其移除。我们使用 $removes$ (哈希表)来记录待删除元素个数。
- 将窗口左侧元素删除的操作为:`removes[nums[left]] += 1`
- 添加:每次滑动在窗口右侧添加元素。需要根据上一步删除的结果来判断需要添加到哪一个堆上。我们用 `banlance` 记录 `heap_max``heap_min` 元素个数的差值。
- 如果窗口左边界 `nums[left]`小于等于 `heap_max` 堆顶元素 ,则说明上一步删除的元素在 `heap_min` 上,则让 `banlance -= 1`
- 如果窗口左边界 `nums[left]` 大于 `heap_max` 堆顶元素,则说明上一步删除的元素在 `heap_max` 上,则上 `banlance += 1`
- 如果窗口右边界 `nums[right]` 小于等于 `heap_max` 堆顶元素,则说明待添加元素需要添加到 `heap_min` 上,则让 `banlance += 1`
- 如果窗口右边界 `nums[right]` 大于 `heap_max` 堆顶元素,则说明待添加元素需要添加到 `heap_max` 上,则让 `banlance -= 1`
- 经过上述操作,`banlance` 的取值为 `0``-2``2` 中的一种。需要经过调整使得 `banlance == 0`
- 如果 `banlance == 0`,已经平衡,不需要再做操作。
- 如果 `banlance == -2`,则说明 `heap_min``heap_max` 的元素多了两个。则从 `heap_min` 中取出堆顶元素添加到 `heap_max` 中。
- 如果 `banlance == 2`,则说明 `heap_max``heap_min` 的元素多了两个。则从 `heap_max` 中取出堆顶元素添加到 `heap_min` 中。
- 调整完之后,分别检查 `heap_max``heap_min` 的堆顶元素。
- 如果 `heap_max` 堆顶元素恰好为待删除元素,即 `removes[-heap_max[0]] > 0`,则弹出 `heap_max` 堆顶元素。
- 如果 `heap_min` 堆顶元素恰好为待删除元素,即 `removes[heap_min[0]] > 0`,则弹出 `heap_min` 堆顶元素。
- 添加:每次滑动在窗口右侧添加元素。需要根据上一步删除的结果来判断需要添加到哪一个堆上。我们用 $banlance$ 记录 $heap\underline{}max$$heap\underline{}min$ 元素个数的差值。
- 如果窗口左边界 $nums[left]$小于等于 $heap\underline{}max$ 堆顶元素 ,则说明上一步删除的元素在 $heap\underline{}min$ 上,则让 `banlance -= 1`
- 如果窗口左边界 $nums[left]$ 大于 $heap\underline{}max$ 堆顶元素,则说明上一步删除的元素在 $heap\underline{}max$ 上,则上 `banlance += 1`
- 如果窗口右边界 $nums[right]$ 小于等于 $heap\underline{}max$ 堆顶元素,则说明待添加元素需要添加到 $heap\underline{}min$ 上,则让 `banlance += 1`
- 如果窗口右边界 $nums[right]$ 大于 $heap\underline{}max$ 堆顶元素,则说明待添加元素需要添加到 $heap\underline{}max$ 上,则让 `banlance -= 1`
- 经过上述操作,$banlance$ 的取值为 $0$、$-2$、$2$ 中的一种。需要经过调整使得 $banlance == 0$
- 如果 $banlance == 0$,已经平衡,不需要再做操作。
- 如果 $banlance == -2$,则说明 $heap\underline{}min$$heap\underline{}max$ 的元素多了两个。则从 $heap\underline{}min$ 中取出堆顶元素添加到 $heap\underline{}max$ 中。
- 如果 $banlance == 2$,则说明 $heap\underline{}max$$heap\underline{}min$ 的元素多了两个。则从 $heap\underline{}max$ 中取出堆顶元素添加到 $heap\underline{}min$ 中。
- 调整完之后,分别检查 $heap\underline{}max$$heap\underline{}min$ 的堆顶元素。
- 如果 $heap\underline{}max$ 堆顶元素恰好为待删除元素,即 $removes[-heap\underline{}max[0]] > 0$,则弹出 $heap\underline{}max$ 堆顶元素。
- 如果 $heap\underline{}min$ 堆顶元素恰好为待删除元素,即 $removes[heap\underline{}min[0]] > 0$,则弹出 $heap\underline{}min$ 堆顶元素。
- 最后取中位数放入答案数组中,然后继续滑动窗口。

## 代码
### 思路 1:代码

```python
import collections
Expand Down Expand Up @@ -111,6 +133,11 @@ class Solution:
return res
```

### 思路 1:复杂度分析

- **时间复杂度**:$O(n \times \log n)$。
- **空间复杂度**:$O(n)$。

## 参考资料

- 【题解】[《风 险 对 冲》:双堆对顶,大堆小堆同时维护,44ms - 滑动窗口中位数 - 力扣](https://leetcode.cn/problems/sliding-window-median/solution/feng-xian-dui-chong-shuang-dui-dui-ding-hq1dt/)
61 changes: 49 additions & 12 deletions Solutions/0683. K 个关闭的灯泡.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,58 @@

## 题目大意

`n` 个灯泡排成一行,编号从 `1``n`。最初,所有灯泡都关闭。每天只打开一个灯泡,直到 `n` 天后所有灯泡都打开。
**描述**:$n$ 个灯泡排成一行,编号从 $1$$n$。最初,所有灯泡都关闭。每天只打开一个灯泡,直到 $n$ 天后所有灯泡都打开。

给定一个长度为 `n` 的灯泡数组 `blubs`,其中 `bulls[i] = x` 意味着在第`i + 1` 天,我们会把在位置 `x` 的灯泡打开,其中 `i``0` 开始,`x``1` 开始。
给定一个长度为 $n$ 的灯泡数组 $blubs$,其中 `bulls[i] = x` 意味着在第 $i + 1$ 天,我们会把在位置 $x$ 的灯泡打开,其中 $i$$0$ 开始,$x$$1$ 开始。

再给定一个整数 `k`
再给定一个整数 $k$

要求:输出在第几天恰好有两个打开的灯泡,使得它们中间正好有 `k` 个灯泡且这些灯泡全部是关闭的 。如果不存在这种情况,则返回 `-1`。如果有多天都出现这种情况,请返回最小的天数 。
**要求**:输出在第几天恰好有两个打开的灯泡,使得它们中间正好有 $k$ 个灯泡且这些灯泡全部是关闭的 。如果不存在这种情况,则返回 $-1$。如果有多天都出现这种情况,请返回最小的天数 。

**说明**

- $n == bulbs.length$。
- $1 \le n \le 2 \times 10^4$。
- $1 \le bulbs[i] \le n$。
- $bulbs$ 是一个由从 $1$ 到 $n$ 的数字构成的排列。
- $0 \le k \le 2 \times 10^4$。

**示例**

- 示例 1:

```python
输入:
bulbs = [1,3,2],k = 1
输出:2
解释:
第一天 bulbs[0] = 1,打开第一个灯泡 [1,0,0]
第二天 bulbs[1] = 3,打开第三个灯泡 [1,0,1]
第三天 bulbs[2] = 2,打开第二个灯泡 [1,1,1]
返回2,因为在第二天,两个打开的灯泡之间恰好有一个关闭的灯泡。
```

- 示例 2:

```python
输入:bulbs = [1,2,3],k = 1
输出:-1
```

## 解题思路

`blubs[i]` 记录的是第 `i + 1` 天开灯的位置。我们将其转换一下,使用另一个数组 `days` 来存储每个灯泡的开灯时间,其中 `days[i]` 表示第 `i` 个位置上的灯泡的开灯时间。
### 思路 1:滑动窗口

- 使用 `ans` 记录最小满足条件的天数。维护一个窗口 `left``right`。其中 `right = left + k + 1`。使得区间 `(left, right)` 中所有灯泡(总共为 `k` 个)开灯时间都晚于 `days[left]``days[right]`
- 对于区间 `[left, right]``left < i < right`
- 如果出现 `days[i] < days[left]` 或者 `days[i] < days[right]`,说明不符合要求。将 `left``right` 移动到 `[i, i + k + 1]`,继续进行判断。
- 如果对于 `left < i < right` 中所有的 `i`,都满足 `days[i] >= days[left]` 并且 `days[i] >= days[right]`,说明此时满足要求。将当前答案与 `days[left]``days[right]` 中的较大值作比较。如果比当前答案更小,则更新答案。同时将窗口向右移动 `k `位。继续检测新的不相交间隔 `[right, right + k + 1]`
- 注意:之所以检测新的不相交间隔,是因为如果检测的是相交间隔,原来的 `right` 位置元素仍在区间中,肯定会出现 `days[right] < days[right_new]`,不满足要求。所以此时相交的区间可以直接跳过,直接检测不相交的间隔。
- 直到 `right >= len(days)` 时跳出循环,判断是否有符合要求的答案,并返回答案 `ans`
$blubs[i]$ 记录的是第 $i + 1$ 天开灯的位置。我们将其转换一下,使用另一个数组 $days$ 来存储每个灯泡的开灯时间,其中 $days[i]$ 表示第 $i$ 个位置上的灯泡的开灯时间。

## 代码
- 使用 $ans$ 记录最小满足条件的天数。维护一个窗口 $left$、$right$。其中 `right = left + k + 1`。使得区间 $(left, right)$ 中所有灯泡(总共为 $k$ 个)开灯时间都晚于 $days[left]$ 和 $days[right]$。
- 对于区间 $[left, right]$,$left < i < right$:
- 如果出现 $days[i] < days[left]$ 或者 $days[i] < days[right]$,说明不符合要求。将 $left$、$right$ 移动到 $[i, i + k + 1]$,继续进行判断。
- 如果对于 $left < i < right$ 中所有的 $i$,都满足 $days[i] \ge days[left]$ 并且 $days[i] \ge days[right]$,说明此时满足要求。将当前答案与 $days[left]$ 和 $days[right]$ 中的较大值作比较。如果比当前答案更小,则更新答案。同时将窗口向右移动 $k $位。继续检测新的不相交间隔 $[right, right + k + 1]$。
- 注意:之所以检测新的不相交间隔,是因为如果检测的是相交间隔,原来的 $right$ 位置元素仍在区间中,肯定会出现 $days[right] < days[right_new]$,不满足要求。所以此时相交的区间可以直接跳过,直接检测不相交的间隔。
- 直到 $right \ge len(days)$ 时跳出循环,判断是否有符合要求的答案,并返回答案 $ans$。

### 思路 1:代码

```python
class Solution:
Expand Down Expand Up @@ -57,3 +89,8 @@ class Solution:
return -1
```

### 思路 1:复杂度分析

- **时间复杂度**:$O(n)$,其中 $n$ 为数组 $bulbs$ 的长度。
- **空间复杂度**:$O(n)$。

Loading

0 comments on commit ace6e17

Please sign in to comment.