Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[pull] main from itcharge:main #1

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
pull merged 129 commits into AlgorithmAndLeetCode:main from itcharge:main
May 27, 2022
Merged
Changes from 1 commit
Commits
Show all changes
129 commits
Select commit Hold shift + click to select a range
edf6b32
Create Graph-Bellman-Ford.py
itcharge Mar 20, 2022
afb623a
Update 01.Hash-Table.md
itcharge Mar 21, 2022
5a18692
去除列表中重复题目
itcharge Mar 22, 2022
aeeb26a
更新目录章节列表
Mar 23, 2022
279255b
更新目录章节列表
Mar 23, 2022
a1f3ee0
更新题解列表
Mar 24, 2022
1b4be8a
Update 0001. 两数之和.md
Mar 28, 2022
de513d7
Update 01.Enumeration-Algorithm.md
Mar 28, 2022
19241f7
Update 01.Enumeration-Algorithm.md
Mar 29, 2022
dce7cd0
Update 01.Enumeration-Algorithm.md
Mar 29, 2022
b66ccbd
Update 01.Enumeration-Algorithm.md
Mar 29, 2022
092fa76
更新课程内容链接
Mar 29, 2022
1c77890
更新题解列表
Mar 30, 2022
1ebb2c1
更新课程内容链接
Mar 30, 2022
b6c3567
Update 01.Segment-Tree.md
Apr 1, 2022
5261ecd
Update 0078. 子集.md
Apr 1, 2022
d8949ea
更新题解列表
Apr 1, 2022
d49e419
Create 1450. 在既定时间做作业的学生人数.md
Apr 2, 2022
a12a916
Update LeetCode 解题报告空白.md
Apr 2, 2022
2a8e742
更新题解目录
Apr 2, 2022
8d7d466
Update 0204. 计数质数.md
Apr 6, 2022
c98b70d
Create 1925. 统计平方和三元组的数目.md
Apr 6, 2022
45b74c1
Create 0800. 相似 RGB 颜色.md
Apr 6, 2022
6f01d1c
更新题解列表
Apr 6, 2022
56cec98
Update 02.Algorithm-Complexity.md
Apr 6, 2022
c87996a
Update 0078. 子集.md
Apr 6, 2022
144761a
Update 0090. 子集 II.md
Apr 6, 2022
860f344
Update 剑指 Offer 57 - II. 和为s的连续正数序列.md
Apr 6, 2022
862f2f6
更新章节目录
Apr 7, 2022
c07e2ab
Update 03.LeetCode-Guide.md
Apr 8, 2022
cf216ed
Update 0091. 解码方法.md
Apr 8, 2022
2ce7012
Update 0198. 打家劫舍.md
Apr 8, 2022
c6ab45f
Update 0213. 打家劫舍 II.md
Apr 8, 2022
d616310
Update 1227. 飞机座位分配概率.md
Apr 8, 2022
664351c
Update 1486. 数组异或操作.md
Apr 8, 2022
ee79e53
Update 剑指 Offer II 089. 房屋偷盗.md
Apr 8, 2022
a93edaa
Update 剑指 Offer II 090. 环形房屋偷盗.md
Apr 8, 2022
d2c7af4
Update 0509. 斐波那契数.md
Apr 8, 2022
946591d
Update 0104. 二叉树的最大深度.md
Apr 8, 2022
6f82a91
更新文章代码格式
Apr 8, 2022
7575e44
Update 01.Recursive-Algorithm.md
Apr 8, 2022
31d9afe
Update 01.Recursive-Algorithm.md
Apr 8, 2022
e3c9bf5
Update 01.Recursive-Algorithm.md
Apr 8, 2022
17d0db8
Update 01.Recursive-Algorithm.md
Apr 11, 2022
c132bc5
更改题解标题格式
Apr 12, 2022
5c2dbe6
Update Array-MergeSort.py
Apr 14, 2022
2c373ef
Update 01.Divide-And-Conquer-Algorithm.md
Apr 14, 2022
56e57eb
更新公式格式
Apr 14, 2022
4c788aa
Update 01.Divide-And-Conquer-Algorithm.md
Apr 14, 2022
fbb04e2
Update 01.Recursive-Algorithm.md
itcharge Apr 18, 2022
3b11f52
Update 0303. 区域和检索 - 数组不可变.md
Apr 19, 2022
118b3e1
更新线段树模板
Apr 19, 2022
a0c5060
更新线段树模板
itcharge Apr 19, 2022
a59b2b0
更新题解列表
Apr 20, 2022
01e7dd9
Update 01.Backtracking-Algorithm.md
Apr 20, 2022
f5a441d
更新线段树模板
Apr 21, 2022
d9c77c0
Update 0303. 区域和检索 - 数组不可变.md
Apr 24, 2022
d5c8551
Update 0307. 区域和检索 - 数组可修改.md
Apr 24, 2022
0034d80
Update 0673. 最长递增子序列的个数.md
Apr 24, 2022
934b009
Update 1109. 航班预订统计.md
Apr 24, 2022
1fee32b
Update 1450. 在既定时间做作业的学生人数.md
Apr 24, 2022
ade2d16
Update 0370. 区间加法.md
Apr 24, 2022
a504a23
Create 1310. 子数组异或查询.md
Apr 24, 2022
d58ba3f
Create 0729. 我的日程安排表 I.md
Apr 24, 2022
4cca60b
Create 0731. 我的日程安排表 II.md
Apr 24, 2022
6a19d08
更新题解列表
Apr 24, 2022
4143b23
Update 01.Backtracking-Algorithm.md
Apr 26, 2022
679ac20
Update 01.Backtracking-Algorithm.md
Apr 26, 2022
3722ae9
Update 0046. 全排列.md
Apr 26, 2022
82ed40f
Update 0051. N 皇后.md
Apr 26, 2022
6bfe7db
Update 0078. 子集.md
Apr 26, 2022
7808a33
Update 01.Backtracking-Algorithm.md
Apr 26, 2022
0337985
更新课程信息
Apr 27, 2022
824e3fc
Update 01.Union-Find.md
May 5, 2022
8771e27
Update 01.Graph-Basic.md
May 6, 2022
0b278e3
Update 01.Union-Find.md
May 7, 2022
a55167d
Update 0547. 省份数量.md
May 9, 2022
a02c90f
Update 0990. 等式方程的可满足性.md
May 9, 2022
ffab9eb
更新 并查集文章
May 9, 2022
aa70ec9
更新并查集模板
May 9, 2022
1702217
更新并查集文章
May 9, 2022
76d3ccb
Update 01.Union-Find.md
May 10, 2022
39c00d3
更新贪心算法
May 10, 2022
4906387
Update 0455. 分发饼干.md
May 11, 2022
3fdca82
更新 并查集文章内容
May 11, 2022
ccef0ac
更新 贪心算法文章内容
May 11, 2022
c17114d
Update 0435. 无重叠区间.md
May 11, 2022
bd1586d
Update 0455. 分发饼干.md
May 11, 2022
8ede64f
更新 并查集文章内容
May 11, 2022
50ee50d
Update 01.Greedy-Algorithm.md
May 12, 2022
ed47e94
Create 1710. 卡车上的最大单元数.md
May 12, 2022
2058630
Update 0217. 存在重复元素.md
May 15, 2022
ad0d375
Update 0219. 存在重复元素 II.md
May 15, 2022
1bf455c
Update 0036. 有效的数独.md
May 15, 2022
556b42e
Update Course-Web-03.md
May 16, 2022
1ddf186
更新力扣新域名
May 18, 2022
836a4b2
Create 2276. 统计区间中的整数数目.md
May 18, 2022
7a87eb5
更新题解列表
May 18, 2022
f9ad233
Update 0005. 最长回文子串.md
May 19, 2022
96c49de
Create 1217. 玩筹码.md
May 19, 2022
63546bc
Create 1247. 交换字符使得字符串相同.md
May 19, 2022
f59d852
更新题解列表
May 19, 2022
c072756
Update 1247. 交换字符使得字符串相同.md
May 19, 2022
23a9161
Update 0189. 轮转数组.md
May 20, 2022
99359be
Create 1408. 数组中的字符串匹配.md
May 20, 2022
4a7ac52
Create 2156. 查找给定哈希值的子串.md
May 20, 2022
599bdf3
Update 02.String-Rabin-Karp.md
May 20, 2022
631f9ac
更新题解列表
May 20, 2022
cbd92c5
Update 2156. 查找给定哈希值的子串.md
May 20, 2022
aac56f7
Update 0189. 轮转数组.md
May 23, 2022
1eb829e
Create 1400. 构造 K 个回文字符串.md
itcharge May 23, 2022
676cf1d
Merge branch 'main' of https://github.com/itcharge/LeetCode-Py
itcharge May 23, 2022
7b2a477
Update 1400. 构造 K 个回文字符串.md
May 23, 2022
98f9927
Create 0921. 使括号有效的最少添加.md
May 23, 2022
92e3fa9
Create 1029. 两地调度.md
May 23, 2022
bed1393
Create 1605. 给定行和列的和求可行矩阵.md
May 23, 2022
d7e6d10
Create 0861. 翻转矩阵后的得分.md
May 23, 2022
4858672
更新题解列表
May 23, 2022
5779d8f
Update 0211. 添加与搜索单词 - 数据结构设计.md
May 24, 2022
1091fc3
Create 0846. 一手顺子.md
May 24, 2022
2d93c03
Create 1296. 划分数组为连续数字的集合.md
May 24, 2022
c5147f6
更新题解列表
May 24, 2022
c39e8ea
更新题解列表
May 25, 2022
841f1de
Create 0851. 喧闹和富有.md
May 25, 2022
c5c149e
更新题解列表
May 25, 2022
5723006
Create 0041. 缺失的第一个正数.md
May 26, 2022
e73e9ee
Update 0861. 翻转矩阵后的得分.md
May 26, 2022
b3ce2a4
Create 0201. 数字范围按位与.md
May 26, 2022
c00a40b
更新题解列表
May 26, 2022
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
Prev Previous commit
Next Next commit
Update 0673. 最长递增子序列的个数.md
  • Loading branch information
杨世超 committed Apr 24, 2022
commit 0034d8014ed24581b89f722632490cc10e337fb1
177 changes: 169 additions & 8 deletions Solutions/0673. 最长递增子序列的个数.md
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,51 @@

## 题目大意

给定一个未排序的整数数组 nums,找到最长递增子序列的个数。
**描述**:给定一个未排序的整数数组 `nums`。

**要求**:返回最长递增子序列的个数。

**说明**:

- 子数列必须是严格递增的。
- 1ドル \le nums.length \le 2000$。
- $-10^6 \le nums[i] \le 10^6$。

**示例**:

```Python
输入 [1,3,5,4,7]
输出 2
解释 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。
```

## 解题思路

最长递增子序列的长度可以用动态规划来做。可以先做题目 [0300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/)。
### 思路 1:动态规划

可以先做题目 [0300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/)。

动态规划的状态 dp[i] 表示为:以第 i 个数字结尾的前 i 个元素中最长严格递增子序列的长度。
动态规划的状态 `dp[i]` 表示为:以第 `i` 个数字结尾的前 `i` 个元素中最长严格递增子序列的长度。

遍历前 i 个数字,0 ≤ j ≤ i:
两重循环遍历前 `i` 个数字,对于 0ドル \le j \le i$:

- 当 `nums[j] < nums[i]` 时,`nums[i]` 可以接在 `nums[j]` 后面,此时以第 i 个数字结尾的最长严格递增子序列长度 + 1,即 `dp[i] = dp[j] + 1`。
- 当 `nums[j] < nums[i]` 时,`nums[i]` 可以接在 `nums[j]` 后面,此时以第 `i` 个数字结尾的最长严格递增子序列长度 + 1,即 `dp[i] = dp[j] + 1`。
- 当 `nums[j] ≥ nums[i]` 时,可以直接跳过。

则状态转移方程为:`dp[i] = max(dp[i], dp[j] + 1)`,`0 ≤ j ≤ i`,`nums[j] < nums[i]`。

最后再遍历一遍 dp 数组,求出最大值即为最长递增子序列的长度。

现在求最长递增子序列的个数。则需要在求解的过程中维护一个 count 数组,用来保存以 nums[i] 结尾的最长递增子序列的个数。
现在求最长递增子序列的个数。则需要在求解的过程中维护一个 `count` 数组,用来保存以 `nums[i]` 结尾的最长递增子序列的个数。

对于 `0 ≤ j ≤ i`:
对于 0ドル \le j \le i$:

- 当 `nums[j] < nums[i]`,而且 `dp[j] + 1 > dp[i]` 时,说明第一次找到 `dp[j] + 1`长度且以`nums[i]`结尾的最长递增子序列,则以 `nums[i]` 结尾的最长递增子序列的组合数就等于以 `nums[j]` 结尾的组合数,即 `count[i] = count[j]`。
- 当 `nums[j] < nums[i]`,而且 `dp[j] + 1 == dp[i]` 时,说明以 `nums[i]` 结尾且长度为 `dp[j] + 1` 的递增序列已找到过一次了,则以 `nums[i]` 结尾的最长递增子序列的组合数要加上以 `nums[j]` 结尾的组合数,即 `count[i] += count[j]`。

- 然后根据遍历 dp 数组得到的最长递增子序列的长度 max_length,然后再一次遍历 dp 数组,将所有 `dp[i] == max_length` 情况下的组合数 `coun[i]` 累加起来,即为最长递增序列的个数。

## 代码
### 思路 1:动态规划代码

```Python
class Solution:
Expand All @@ -56,3 +74,146 @@ class Solution:
return res
```

### 思路 2:线段树

题目中 `nums` 的长度 为 $[1, 2000],ドル值域为 $[-10^6, 10^6]$。

值域范围不是特别大,我们可以直接用线段树保存整个值域区间。但因为数组的长度只有 `2000`,所以算法效率更高的做法是先对数组进行离散化处理。把数组中的元素按照大小依次映射到 `[0, len(nums) - 1]` 这个区间。

1. 构建一棵长度为 `len(nums)` 的线段树,其中每个线段树的节点保存一个二元组。这个二元组 `val = [length, count]` 用来表示:以当前节点为结尾的子序列所能达到的最长递增子序列长度 `length` 和最长递增子序列对应的数量 `count`。
2. 顺序遍历数组 `nums`。对于当前元素 `nums[i]`:
3. 查找 `[0, nums[i - 1]]` 离散化后对应区间节点的二元组,也就是查找以区间 `[0, nums[i - 1]]` 上的点为结尾的子序列所能达到的最长递增子序列长度和其对应的数量,即 `val = [length, count]`。
- 如果所能达到的最长递增子序列长度为 `0`,则加入 `nums[i]` 之后最长递增子序列长度变为 `1`,且数量也变为 `1`。
- 如果所能达到的最长递增子序列长度不为 `0`,则加入 `nums[i]` 之后最长递增子序列长度 +1,但数量不变。
4. 根据上述计算的 `val` 值更新 `nums[i]` 对应节点的 `val` 值。
5. 然后继续向后遍历,重复进行第 `3` ~ `4` 步操作。
6. 最后查询以区间 `[0, nums[len(nums) - 1]]` 上的点为结尾的子序列所能达到的最长递增子序列长度和其对应的数量。返回对应的数量即为答案。

### 思路 2:线段树代码

```Python
# 线段树的节点类
class SegTreeNode:
def __init__(self, val=[0, 1]):
self.left = -1 # 区间左边界
self.right = -1 # 区间右边界
self.val = val # 节点值(区间值)



# 线段树类
class SegmentTree:
# 初始化线段树接口
def __init__(self, size):
self.size = size
self.tree = [SegTreeNode() for _ in range(4 * self.size)] # 维护 SegTreeNode 数组
if self.size > 0:
self.__build(0, 0, self.size - 1)

# 单点更新接口:将 nums[i] 更改为 val
def update_point(self, i, val):
self.__update_point(i, val, 0)

# 区间查询接口:查询区间为 [q_left, q_right] 的区间值
def query_interval(self, q_left, q_right):
return self.__query_interval(q_left, q_right, 0)


# 以下为内部实现方法

# 构建线段树实现方法:节点的存储下标为 index,节点的区间为 [left, right]
def __build(self, index, left, right):
self.tree[index].left = left
self.tree[index].right = right
if left == right: # 叶子节点,节点值为对应位置的元素值
self.tree[index].val = [0, 0]
return

mid = left + (right - left) // 2 # 左右节点划分点
left_index = index * 2 + 1 # 左子节点的存储下标
right_index = index * 2 + 2 # 右子节点的存储下标
self.__build(left_index, left, mid) # 递归创建左子树
self.__build(right_index, mid + 1, right) # 递归创建右子树

self.tree[index].val = self.merge(self.tree[left_index].val, self.tree[right_index].val) # 向上更新节点的区间值

# 单点更新实现方法:将 nums[i] 更改为 val,节点的存储下标为 index
def __update_point(self, i, val, index):
left = self.tree[index].left
right = self.tree[index].right

if left == i and right == i:
self.tree[index].val = self.merge(self.tree[index].val, val)
return

mid = left + (right - left) // 2 # 左右节点划分点
left_index = index * 2 + 1 # 左子节点的存储下标
right_index = index * 2 + 2 # 右子节点的存储下标
if i <= mid: # 在左子树中更新节点值
self.__update_point(i, val, left_index)
else: # 在右子树中更新节点值
self.__update_point(i, val, right_index)

self.tree[index].val = self.merge(self.tree[left_index].val, self.tree[right_index].val) # 向上更新节点的区间值


# 区间查询实现方法:在线段树中搜索区间为 [q_left, q_right] 的区间值
def __query_interval(self, q_left, q_right, index):
left = self.tree[index].left
right = self.tree[index].right

if left >= q_left and right <= q_right: # 节点所在区间被 [q_left, q_right] 所覆盖
return self.tree[index].val # 直接返回节点值
if right < q_left or left > q_right: # 节点所在区间与 [q_left, q_right] 无关
return [0, 0]

mid = left + (right - left) // 2 # 左右节点划分点
left_index = index * 2 + 1 # 左子节点的存储下标
right_index = index * 2 + 2 # 右子节点的存储下标
res_left = [0, 0]
res_right = [0, 0]
if q_left <= mid: # 在左子树中查询
res_left = self.__query_interval(q_left, q_right, left_index)
if q_right > mid: # 在右子树中查询
res_right = self.__query_interval(q_left, q_right, right_index)

# 返回合并结果
return self.merge(res_left, res_right)

# 向上合并实现方法
def merge(self, val1, val2):
val = [0, 0]
if val1[0] == val2[0]: # 递增子序列长度一致,则合并后最长递增子序列个数为之前两者之和
val = [val1[0], val1[1] + val2[1]]
elif val1[0] < val2[0]: # 如果递增子序列长度不一致,则合并后最长递增子序列个数取较长一方的个数
val = [val2[0], val2[1]]
else:
val = [val1[0], val1[1]]
return val

class Solution:
def findNumberOfLIS(self, nums: List[int]) -> int:

# 离散化处理
num_dict = dict()
nums_sort = sorted(nums)
for i in range(len(nums_sort)):
num_dict[nums_sort[i]] = i

# 构造线段树
self.STree = SegmentTree(len(nums_sort))

for num in nums:
index = num_dict[num]
# 查询 [0, nums[index - 1]] 区间上以 nums[index - 1] 结尾的子序列所能达到的最长递增子序列长度和对应数量
val = self.STree.query_interval(0, index - 1)
# 如果当前最长递增子序列长度为 0,则加入 num 之后最长递增子序列长度为 1,且数量为 1
# 如果当前最长递增子序列长度不为 0,则加入 num 之后最长递增子序列长度 +1,但数量不变
if val[0] == 0:
val = [1, 1]
else:
val = [val[0] + 1, val[1]]
self.STree.update_point(index, val)
return self.STree.query_interval(0, len(nums_sort) - 1)[1]
```

AltStyle によって変換されたページ (->オリジナル) /