mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-05 14:46:51 +08:00
Update
This commit is contained in:
@ -62,7 +62,7 @@
|
||||
如果你是算法老手,这篇攻略也是复习的最佳资料,如果把每个系列对应的总结篇,快速过一遍,整个算法知识体系以及各种解法就重现脑海了。
|
||||
|
||||
|
||||
目前「代码随想录」刷题攻略更新了:**200多篇文章,精讲了200道经典算法题目,共60w字的详细图解,部分难点题目还搭配了20分钟左右的视频讲解**。
|
||||
目前「代码随想录」刷题攻略更新了:**200多篇文章,精讲了200道经典算法题目,共60w字的详细图解,大部分题目都搭配了20分钟左右的视频讲解**,视频质量很好,口碑很好,大家可以去看看,视频列表:[代码随想录视频讲解](https://www.bilibili.com/video/BV1fA4y1o715)。
|
||||
|
||||
**这里每一篇题解,都是精品,值得仔细琢磨**。
|
||||
|
||||
|
@ -1,145 +0,0 @@
|
||||
# 完全背包的排列问题模拟
|
||||
|
||||
#### Problem
|
||||
|
||||
1. 排列问题是完全背包中十分棘手的问题。
|
||||
2. 其在迭代过程中需要先迭代背包容量,再迭代物品个数,使得其在代码理解上较难入手。
|
||||
|
||||
#### Contribution
|
||||
|
||||
本文档以力扣上[组合总和IV](https://leetcode.cn/problems/combination-sum-iv/)为例,提供一个二维dp的代码例子,并提供模拟过程以便于理解
|
||||
|
||||
#### Code
|
||||
|
||||
```cpp
|
||||
int combinationSum4(vector<int>& nums, int target) {
|
||||
// 定义背包容量为target,物品个数为nums.size()的dp数组
|
||||
// dp[i][j]表示将第0-i个物品添加入排列中,和为j的排列方式
|
||||
vector<vector<int>> dp (nums.size(), vector(target+1,0));
|
||||
|
||||
// 表示有0,1,...,n个物品可选择的情况下,和为0的选择方法为1:什么都不取
|
||||
for(int i = 0; i < nums.size(); i++) dp[i][0] = 1;
|
||||
|
||||
// 必须按列遍历,因为右边数组需要知道左边数组最低部的信息(排列问题)
|
||||
// 后面的模拟可以更清楚的表现这么操作的原因
|
||||
for(int i = 1; i <= target; i++){
|
||||
for(int j = 0; j < nums.size(); j++){
|
||||
// 只有nums[j]可以取的情况
|
||||
if(j == 0){
|
||||
if(nums[j] > i) dp[j][i] = 0;
|
||||
// 如果背包容量放不下 那么此时没有排列方式
|
||||
else dp[j][i] = dp[nums.size()-1][i-nums[j]];
|
||||
// 如果背包容量放的下 全排列方式为dp[最底层][容量-该物品容量]排列方式后面放一个nums[j]
|
||||
}
|
||||
// 有多个nums数可以取
|
||||
else{
|
||||
// 如果背包容量放不下 那么沿用0-j-1个物品的排列方式
|
||||
if(nums[j] > i) dp[j][i] = dp[j-1][i];
|
||||
// 如果背包容量放得下 在dp[最底层][容量-该物品容量]排列方式后面放一个nums[j]后面放个nums[j]
|
||||
// INT_MAX避免溢出
|
||||
else if(i >= nums[j] && dp[j-1][i] < INT_MAX - dp[nums.size()-1][i-nums[j]])
|
||||
dp[j][i] = dp[j-1][i] + dp[nums.size()-1][i-nums[j]];
|
||||
}
|
||||
}
|
||||
}
|
||||
// 打印dp数组
|
||||
for(int i = 0; i < nums.size(); i++){
|
||||
for(int j = 0; j <= target; j++){
|
||||
cout<<dp[i][j]<<" ";
|
||||
}
|
||||
cout<<endl;
|
||||
}
|
||||
return dp[nums.size()-1][target];
|
||||
}
|
||||
```
|
||||
|
||||
#### Simulation
|
||||
|
||||
##### 样例 nums = [2,3,4], target = 6
|
||||
|
||||
##### 1. 初始化一个3x7的dp数组
|
||||
|
||||
1 0 0 0 0 0 0
|
||||
1 0 0 0 0 0 0
|
||||
1 0 0 0 0 0 0
|
||||
|
||||
dp\[0-2\]\[0\] = 1,含义是有nums[0-2]物品时使得背包容量为0的取法为1,作用是在取到nums[i]物品使得背包容量为nums[i]时取法为1。
|
||||
|
||||
##### 2.迭代方式
|
||||
|
||||
必须列优先,因为右边的数组在迭代时需要最左下的数组最终结果。
|
||||
|
||||
##### 3. 模拟过程
|
||||
|
||||
i = 1, j = 0 dp\[0\]\[1\] = 0,表示在物品集合{2}中无法组成和为1
|
||||
i = 1, j = 1 dp\[1\]\[1\] = 0,表示在物品集合{2,3}中无法组成和为1
|
||||
i = 1, j = 2 dp\[2\]\[1\] = 0,表示在物品集合{2,3,4}中无法组成和为1
|
||||
|
||||
1 0 0 0 0 0 0
|
||||
1 0 0 0 0 0 0
|
||||
1 **0** 0 0 0 0 0
|
||||
|
||||
此时dp\[2\]\[1\]作为第1列最底部的元素,表示所有物品都有的情况下组成和为1的排列方式为0
|
||||
|
||||
————————————————————————————
|
||||
|
||||
i = 2, j = 0 dp\[0\]\[2\] = 1,表示在物品集合{2}中取出和为2的排列有{2}
|
||||
i = 2, j = 1 dp\[1\]\[2\] = 1,表示在物品集合{2,3}中取出和为2的排列有{2}
|
||||
i = 2, j = 2 dp\[2\]\[2\] = 1,表示在物品集合{2,3,4}中取出和为2的排列有{2}
|
||||
|
||||
1 0 1 0 0 0 0
|
||||
1 0 1 0 0 0 0
|
||||
1 0 **1** 0 0 0 0
|
||||
|
||||
此时dp\[2\]\[2\]作为第2列最底部的元素,表示所有物品都有的情况下和为2的排列方式有1个 (类比成一维dp即dp[2]=dp[0])
|
||||
|
||||
————————————————————————————
|
||||
|
||||
i = 3, j = 0 dp\[0\]\[3\] = 0,表示在物品集合{2}中无法取出和为3
|
||||
i = 3, j = 1 dp\[1\]\[3\] = 1,表示在物品集合{2,3}中取出和为3的排列有{3}
|
||||
i = 3, j = 2 dp\[2\]\[3\] = 1,表示在物品集合{2,3,4}中取出和为3的排列有{3}
|
||||
|
||||
1 0 1 0 0 0 0
|
||||
1 0 1 1 0 0 0
|
||||
1 0 1 **1** 0 0 0
|
||||
|
||||
此时dp\[2\]\[3\]作为第3列最底部的元素,表示所有物品都有的情况下和为3的排列方式有1个(类比成一维dp即dp[3]=dp[0])
|
||||
|
||||
————————————————————————————
|
||||
|
||||
i = 4, j = 0 dp\[0\]\[4\] = 1,表示在物品集合{2}中取出和为4的排列有在原有的排列{2}后添加一个2成为{2,2}(从第2列底部信息继承获得)
|
||||
i = 4, j = 1 dp\[1\]\[4\] = 1,表示在物品集合{2,3}中取出和为4的排列有{2,2}
|
||||
i = 4, j = 2 dp\[2\]\[4\] = 2,表示在物品集合{2,3,4}中取出和为4的排列有{{2,2},{4}}({2,2}的信息从该列头上获得)
|
||||
|
||||
1 0 1 0 1 0 0
|
||||
1 0 1 1 1 0 0
|
||||
1 0 1 1 **2** 0 0
|
||||
|
||||
此时dp\[2\]\[4\]作为第4列最底部的元素,表示所有物品都有的情况下和为4的排列方式有2个
|
||||
|
||||
————————————————————————————
|
||||
|
||||
i = 5, j = 0 dp\[0\]\[5\] = 1,表示在物品集合{2}中取出和为5的排列有{3,2} **(3的信息由dp[2]\[3]获得,即将2放在3的右边)**
|
||||
i = 5, j = 1 dp\[1\]\[5\] = 2,表示在物品集合{2,3}中取出和为5的排列有{{2,3},{3,2}} **({3,2}由上一行信息继承,{2,3}是从dp[2] [2]获得,将3放在2的右边)**
|
||||
i = 5, j = 2 dp\[2\]\[5\] = 2,表示在物品集合{2,3,4}中取出和为5的排列有{{2,3},{3,2}}
|
||||
|
||||
1 0 1 0 1 1 0
|
||||
1 0 1 1 1 2 0
|
||||
1 0 1 1 2 **2** 0
|
||||
|
||||
此时dp\[2\]\[5\]作为第5列最底部的元素,表示所有物品都有的情况下和为5的排列方式有2个
|
||||
|
||||
————————————————————————————
|
||||
|
||||
i = 6, j = 0 dp\[0\]\[6\] = 2,表示在物品集合{2}中取出和为6的排列有{{2,2,2},{4,2}} **(信息由dp[2]\[4]获得,即将2放在{2,2}和{4}的右边)**
|
||||
i = 6, j = 1 dp\[1\]\[6\] = 3,表示在物品集合{2,3}中取出和为6的排列有{{2,2,2},{4,2},{3,3}} **({2,2,2},{4,2}由上一行信息继承,{3,3}是从dp[2] [3]获得,将3放在3的右边)**
|
||||
i = 6, j = 2 dp\[2\]\[6\] = 4,表示在物品集合{2,3,4}中取出和为6的排列有{{2,2,2},{4,2},{3,3},{2,4}} **({2,2,2},{4,2},{3,3}由上一行继承,{2,4}从dp[2]获得,将4放在2的右边)**
|
||||
|
||||
1 0 1 0 1 1 2
|
||||
1 0 1 1 1 2 3
|
||||
1 0 1 1 2 2 **4**
|
||||
|
||||
此时dp\[2\]\[6\]作为第6列最底部的元素,表示所有物品都有的情况下和为6的排列方式有4个,为{2,2,2},{4,2},{3,3},{2,4}。
|
||||
|
||||
|
||||
|
@ -54,9 +54,9 @@ int function2(int x, int n) {
|
||||
|
||||
```CPP
|
||||
int function3(int x, int n) {
|
||||
if (n == 0) {
|
||||
return 1;
|
||||
}
|
||||
if (n == 0) return 1;
|
||||
if (n == 1) return x;
|
||||
|
||||
if (n % 2 == 1) {
|
||||
return function3(x, n / 2) * function3(x, n / 2)*x;
|
||||
}
|
||||
@ -93,9 +93,8 @@ int function3(int x, int n) {
|
||||
|
||||
```CPP
|
||||
int function4(int x, int n) {
|
||||
if (n == 0) {
|
||||
return 1;
|
||||
}
|
||||
if (n == 0) return 1;
|
||||
if (n == 1) return x;
|
||||
int t = function4(x, n / 2);// 这里相对于function3,是把这个递归操作抽取出来
|
||||
if (n % 2 == 1) {
|
||||
return t * t * x;
|
||||
@ -124,9 +123,8 @@ int function4(int x, int n) {
|
||||
|
||||
```CPP
|
||||
int function3(int x, int n) {
|
||||
if (n == 0) {
|
||||
return 1;
|
||||
}
|
||||
if (n == 0) return 1;
|
||||
if (n == 1) return x;
|
||||
if (n % 2 == 1) {
|
||||
return function3(x, n / 2) * function3(x, n / 2)*x;
|
||||
}
|
||||
|
@ -89,7 +89,7 @@
|
||||
在C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示:
|
||||
|
||||
| 集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
|
||||
| ------------------ | -------- | -------- | ---------------- | ------------ | -------- | -------- |
|
||||
| --- | --- | ---- | --- | --- | --- | --- |
|
||||
| std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
|
||||
| std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
|
||||
| std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
|
||||
@ -97,11 +97,12 @@
|
||||
std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
|
||||
|
||||
| 映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
|
||||
| ------------------ | -------- | -------- | ---------------- | ------------ | -------- | -------- |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| std::map | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) |
|
||||
| std::multimap | 红黑树 | key有序 | key可重复 | key不可修改 | O(log n) | O(log n) |
|
||||
| std::unordered_map | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) |
|
||||
|
||||
|
||||
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。
|
||||
|
||||
当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。
|
||||
|
Reference in New Issue
Block a user