-11. [动态规划:01背包理论基础](./problems/背包理论基础01背包-1.md)
-12. [动态规划:01背包理论基础(滚动数组)](./problems/背包理论基础01背包-2.md)
+11. [动态规划:01背包理论基础(二维dp数组)](./problems/背包理论基础01背包-1.md)
+12. [动态规划:01背包理论基础(一维dp数组)](./problems/背包理论基础01背包-2.md)
13. [动态规划:416.分割等和子集](./problems/0416.分割等和子集.md)
14. [动态规划:1049.最后一块石头的重量II](./problems/1049.最后一块石头的重量II.md)
15. [本周小结!(动态规划系列三)](./problems/周总结/20210121动规周末总结.md)
16. [动态规划:494.目标和](./problems/0494.目标和.md)
17. [动态规划:474.一和零](./problems/0474.一和零.md)
-18. [动态规划:完全背包总结篇](./problems/背包问题理论基础完全背包.md)
-19. [动态规划:518.零钱兑换II](./problems/0518.零钱兑换II.md)
-20. [本周小结!(动态规划系列四)](./problems/周总结/20210128动规周末总结.md)
-21. [动态规划:377.组合总和Ⅳ](./problems/0377.组合总和Ⅳ.md)
-22. [动态规划:70.爬楼梯(完全背包版本)](./problems/0070.爬楼梯完全背包版本.md)
-23. [动态规划:322.零钱兑换](./problems/0322.零钱兑换.md)
-24. [动态规划:279.完全平方数](./problems/0279.完全平方数.md)
-25. [本周小结!(动态规划系列五)](./problems/周总结/20210204动规周末总结.md)
-26. [动态规划:139.单词拆分](./problems/0139.单词拆分.md)
-27. [动态规划:多重背包理论基础](./problems/背包问题理论基础多重背包.md)
-28. [背包问题总结篇](./problems/背包总结篇.md)
+18. [动态规划:完全背包理论基础(二维dp数组)](./problems/背包问题理论基础完全背包.md)
+19. [动态规划:完全背包理论基础(一维dp数组)](./problems/背包问题完全背包一维.md)
+20. [动态规划:518.零钱兑换II](./problems/0518.零钱兑换II.md)
+21. [本周小结!(动态规划系列四)](./problems/周总结/20210128动规周末总结.md)
+22. [动态规划:377.组合总和Ⅳ](./problems/0377.组合总和Ⅳ.md)
+23. [动态规划:70.爬楼梯(完全背包版本)](./problems/0070.爬楼梯完全背包版本.md)
+24. [动态规划:322.零钱兑换](./problems/0322.零钱兑换.md)
+25. [动态规划:279.完全平方数](./problems/0279.完全平方数.md)
+26. [本周小结!(动态规划系列五)](./problems/周总结/20210204动规周末总结.md)
+27. [动态规划:139.单词拆分](./problems/0139.单词拆分.md)
+28. [动态规划:多重背包理论基础](./problems/背包问题理论基础多重背包.md)
+29. [背包问题总结篇](./problems/背包总结篇.md)
打家劫舍系列:
@@ -408,21 +409,6 @@
(持续更新中....)
-
-## 十大排序
-
-## 数论
-
-## 高级数据结构经典题目
-
-* 并查集
-* 最小生成树
-* 线段树
-* 树状数组
-* 字典树
-
-## 海量数据处理
-
# 补充题目
以上题目是重中之重,大家至少要刷两遍以上才能彻底理解,如果熟练以上题目之后还在找其他题目练手,可以再刷以下题目:
diff --git a/problems/0018.四数之和.md b/problems/0018.四数之和.md
index 64923e41..f3188b0f 100644
--- a/problems/0018.四数之和.md
+++ b/problems/0018.四数之和.md
@@ -35,7 +35,7 @@
四数之和,和[15.三数之和](https://programmercarl.com/0015.三数之和.html)是一个思路,都是使用双指针法, 基本解法就是在[15.三数之和](https://programmercarl.com/0015.三数之和.html) 的基础上再套一层for循环。
-但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是`[-4, -3, -2, -1]`,`target`是`-10`,不能因为`-4 > -10`而跳过。但是我们依旧可以去做剪枝,逻辑变成`nums[i] > target && (nums[i] >=0 || target >= 0)`就可以了。
+但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。比如:数组是`[-4, -3, -2, -1]`,`target`是`-10`,不能因为`-4 > -10`而跳过。但是我们依旧可以去做剪枝,逻辑变成`nums[k] > target && (nums[k] >=0 || target >= 0)`就可以了。
[15.三数之和](https://programmercarl.com/0015.三数之和.html)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0。
@@ -253,7 +253,7 @@ public class Solution {
for (int k = 0; k < nums.length; k++) {
// 剪枝处理
if (nums[k] > target && nums[k] >= 0) {
- break;
+ break; // 此处的break可以等价于return result;
}
// 对nums[k]去重
if (k > 0 && nums[k] == nums[k - 1]) {
@@ -262,7 +262,7 @@ public class Solution {
for (int i = k + 1; i < nums.length; i++) {
// 第二级剪枝
if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
- break;
+ break; // 注意是break到上一级for循环,如果直接return result;会有遗漏
}
// 对nums[i]去重
if (i > k + 1 && nums[i] == nums[i - 1]) {
@@ -802,3 +802,4 @@ end
+
diff --git a/problems/0019.删除链表的倒数第N个节点.md b/problems/0019.删除链表的倒数第N个节点.md
index fafef1f2..16312d0f 100644
--- a/problems/0019.删除链表的倒数第N个节点.md
+++ b/problems/0019.删除链表的倒数第N个节点.md
@@ -129,6 +129,36 @@ class Solution {
}
```
+
+```java
+class Solution {
+ public ListNode removeNthFromEnd(ListNode head, int n) {
+ // 创建一个新的哑节点,指向原链表头
+ ListNode s = new ListNode(-1, head);
+ // 递归调用remove方法,从哑节点开始进行删除操作
+ remove(s, n);
+ // 返回新链表的头(去掉可能的哑节点)
+ return s.next;
+ }
+
+ public int remove(ListNode p, int n) {
+ // 递归结束条件:如果当前节点为空,返回0
+ if (p == null) {
+ return 0;
+ }
+ // 递归深入到下一个节点
+ int net = remove(p.next, n);
+ // 如果当前节点是倒数第n个节点,进行删除操作
+ if (net == n) {
+ p.next = p.next.next;
+ }
+ // 返回当前节点的总深度
+ return net + 1;
+ }
+}
+```
+
+
### Python:
```python
diff --git a/problems/0020.有效的括号.md b/problems/0020.有效的括号.md
index f2f5cdd1..2475138e 100644
--- a/problems/0020.有效的括号.md
+++ b/problems/0020.有效的括号.md
@@ -166,12 +166,35 @@ class Solution {
deque.pop();
}
}
- //最后判断栈中元素是否匹配
+ //遍历结束,如果栈为空,则括号全部匹配
return deque.isEmpty();
}
}
```
+```java
+// 解法二
+// 对应的另一半一定在栈顶
+class Solution {
+ public boolean isValid(String s) {
+ Stack
+
diff --git a/problems/0059.螺旋矩阵II.md b/problems/0059.螺旋矩阵II.md
index c59ec033..94966126 100644
--- a/problems/0059.螺旋矩阵II.md
+++ b/problems/0059.螺旋矩阵II.md
@@ -715,26 +715,65 @@ object Solution {
### C#:
```csharp
-public class Solution {
- public int[][] GenerateMatrix(int n) {
- int[][] answer = new int[n][];
- for(int i = 0; i < n; i++)
- answer[i] = new int[n];
- int start = 0;
- int end = n - 1;
- int tmp = 1;
- while(tmp < n * n)
- {
- for(int i = start; i < end; i++) answer[start][i] = tmp++;
- for(int i = start; i < end; i++) answer[i][end] = tmp++;
- for(int i = end; i > start; i--) answer[end][i] = tmp++;
- for(int i = end; i > start; i--) answer[i][start] = tmp++;
- start++;
- end--;
- }
- if(n % 2 == 1) answer[n / 2][n / 2] = tmp;
- return answer;
+public int[][] GenerateMatrix(int n)
+{
+ // 参考Carl的代码随想录里面C++的思路
+ // https://www.programmercarl.com/0059.%E8%9E%BA%E6%97%8B%E7%9F%A9%E9%98%B5II.html#%E6%80%9D%E8%B7%AF
+ int startX = 0, startY = 0; // 定义每循环一个圈的起始位置
+ int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
+ int count = 1; // 用来给矩阵每个空格赋值
+ int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
+ int offset = 1;// 需要控制每一条边遍历的长度,每次循环右边界收缩一位
+
+ // 构建result二维数组
+ int[][] result = new int[n][];
+ for (int k = 0; k < n; k++)
+ {
+ result[k] = new int[n];
}
+
+ int i = 0, j = 0; // [i,j]
+ while (loop > 0)
+ {
+ i = startX;
+ j = startY;
+ // 四个For循环模拟转一圈
+ // 第一排,从左往右遍历,不取最右侧的值(左闭右开)
+ for (; j < n - offset; j++)
+ {
+ result[i][j] = count++;
+ }
+ // 右侧的第一列,从上往下遍历,不取最下面的值(左闭右开)
+ for (; i < n - offset; i++)
+ {
+ result[i][j] = count++;
+ }
+
+ // 最下面的第一行,从右往左遍历,不取最左侧的值(左闭右开)
+ for (; j > startY; j--)
+ {
+ result[i][j] = count++;
+ }
+
+ // 左侧第一列,从下往上遍历,不取最左侧的值(左闭右开)
+ for (; i > startX; i--)
+ {
+ result[i][j] = count++;
+ }
+ // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
+ startX++;
+ startY++;
+
+ // offset 控制每一圈里每一条边遍历的长度
+ offset++;
+ loop--;
+ }
+ if (n % 2 == 1)
+ {
+ // n 为奇数
+ result[mid][mid] = count;
+ }
+ return result;
}
```
diff --git a/problems/0070.爬楼梯.md b/problems/0070.爬楼梯.md
index a2f664a4..6a13a21c 100644
--- a/problems/0070.爬楼梯.md
+++ b/problems/0070.爬楼梯.md
@@ -130,8 +130,8 @@ public:
};
```
-* 时间复杂度:$O(n)$
-* 空间复杂度:$O(n)$
+* 时间复杂度:O(n)
+* 空间复杂度:O(n)
当然依然也可以,优化一下空间复杂度,代码如下:
@@ -154,8 +154,8 @@ public:
};
```
-* 时间复杂度:$O(n)$
-* 空间复杂度:$O(1)$
+* 时间复杂度:O(n)
+* 空间复杂度:O(1)
后面将讲解的很多动规的题目其实都是当前状态依赖前两个,或者前三个状态,都可以做空间上的优化,**但我个人认为面试中能写出版本一就够了哈,清晰明了,如果面试官要求进一步优化空间的话,我们再去优化**。
@@ -524,3 +524,4 @@ impl Solution {
+
diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md
index 98e1e98a..ce53e49a 100644
--- a/problems/0102.二叉树的层序遍历.md
+++ b/problems/0102.二叉树的层序遍历.md
@@ -1231,6 +1231,47 @@ impl Solution {
}
```
+#### C#:
+
+```C# 199.二叉树的右视图
+public class Solution
+{
+ public IList
diff --git a/problems/0235.二叉搜索树的最近公共祖先.md b/problems/0235.二叉搜索树的最近公共祖先.md
index 192bb031..3911261a 100644
--- a/problems/0235.二叉搜索树的最近公共祖先.md
+++ b/problems/0235.二叉搜索树的最近公共祖先.md
@@ -99,7 +99,7 @@ if (cur == NULL) return cur;
* 确定单层递归的逻辑
-在遍历二叉搜索树的时候就是寻找区间[p->val, q->val](注意这里是左闭又闭)
+在遍历二叉搜索树的时候就是寻找区间[p->val, q->val](注意这里是左闭右闭)
那么如果 cur->val 大于 p->val,同时 cur->val 大于q->val,那么就应该向左遍历(说明目标区间在左子树上)。
diff --git a/problems/0239.滑动窗口最大值.md b/problems/0239.滑动窗口最大值.md
index caa24d8d..651e4da4 100644
--- a/problems/0239.滑动窗口最大值.md
+++ b/problems/0239.滑动窗口最大值.md
@@ -267,7 +267,7 @@ class Solution {
//利用双端队列手动实现单调队列
/**
* 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
- * 单调队列类似 (tail -->) 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标)
+ * 单调递减队列类似 (head -->) 3 --> 2 --> 1 --> 0 (--> tail) (左边为头结点,元素存的是下标)
*/
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
@@ -281,7 +281,7 @@ class Solution {
while(!deque.isEmpty() && deque.peek() < i - k + 1){
deque.poll();
}
- // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
+ // 2.维护单调递减队列:新元素若大于队尾元素,则弹出队尾元素,直到满足单调性
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
@@ -890,7 +890,40 @@ public:
};
```
+### C
+
+```c
+int* maxSlidingWindow(int* nums, int numsSize, int k, int* returnSize) {
+ *returnSize = numsSize - k + 1;
+ int *res = (int*)malloc((*returnSize) * sizeof(int));
+ assert(res);
+ int *deque = (int*)malloc(numsSize * sizeof(int));
+ assert(deque);
+ int front = 0, rear = 0, idx = 0;
+
+ for (int i = 0 ; i < numsSize ; i++) {
+ while (front < rear && deque[front] <= i - k) {
+ front++;
+ }
+
+ while (front < rear && nums[deque[rear - 1]] <= nums[i]) {
+ rear--;
+ }
+
+ deque[rear++] = i;
+
+ if (i >= k - 1) {
+ res[idx++] = nums[deque[front]];
+ }
+ }
+
+ return res;
+}
+
+```
+
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益! 参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!
diff --git a/problems/0151.翻转字符串里的单词.md b/problems/0151.翻转字符串里的单词.md
index 7c0b7cb1..3dbd59b9 100644
--- a/problems/0151.翻转字符串里的单词.md
+++ b/problems/0151.翻转字符串里的单词.md
@@ -639,7 +639,46 @@ func reverse(b []byte) {
}
}
```
-
+```go
+//双指针解法。指针逆序遍历,将遍历后得到的单词(间隔为空格,用以区分)顺序放置在额外空间
+//时间复杂度O(n),空间复杂度O(n)
+func reverseWords(s string) string {
+ strBytes := []byte(s)
+ n := len(strBytes)
+ // 记录有效字符范围的起始和结束位置
+ start, end := 0, n-1
+ // 去除开头空格
+ for start < n && strBytes[start] == 32 {
+ start++
+ }
+ // 处理全是空格或空字符串情况
+ if start == n {
+ return ""
+ }
+ // 去除结尾空格
+ for end >= 0 && strBytes[end] == 32 {
+ end--
+ }
+ // 结果切片,预分配容量
+ res := make([]byte, 0, end-start+1)//这里挺重要的,本人之前没有预分配容量,每次循环都添加单词,导致内存超限(也可能就是我之前的思路有问题)
+ // 从后往前遍历有效字符范围
+ for i := end; i >= start; {
+ // 找单词起始位置,直接通过循环条件判断定位
+ for ; i >= start && strBytes[i] == 32; i-- {
+ }
+ j := i
+ for ; j >= start && strBytes[j]!= 32; j-- {
+ }
+ res = append(res, strBytes[j+1:i+1]...)
+ // 只在不是最后一个单词时添加空格
+ if j > start {
+ res = append(res, 32)
+ }
+ i = j
+ }
+ return string(res)
+}
+```
### JavaScript:
diff --git a/problems/0225.用队列实现栈.md b/problems/0225.用队列实现栈.md
index f0fe3a3c..73d9db1b 100644
--- a/problems/0225.用队列实现栈.md
+++ b/problems/0225.用队列实现栈.md
@@ -1277,6 +1277,95 @@ impl MyStack {
}
```
+### C:
+
+> C:单队列
+
+```c
+typedef struct Node {
+ int val;
+ struct Node *next;
+} Node_t;
+
+// 用单向链表实现queue
+typedef struct {
+ Node_t *head;
+ Node_t *foot;
+ int size;
+} MyStack;
+
+MyStack* myStackCreate() {
+ MyStack *obj = (MyStack *)malloc(sizeof(MyStack));
+ assert(obj);
+ obj->head = NULL;
+ obj->foot = NULL;
+ obj->size = 0;
+ return obj;
+}
+
+void myStackPush(MyStack* obj, int x) {
+
+ Node_t *temp = (Node_t *)malloc(sizeof(Node_t));
+ assert(temp);
+ temp->val = x;
+ temp->next = NULL;
+
+ // 添加至queue末尾
+ if (obj->foot) {
+ obj->foot->next = temp;
+ } else {
+ obj->head = temp;
+ }
+ obj->foot = temp;
+ obj->size++;
+}
+
+int myStackPop(MyStack* obj) {
+
+ // 获取末尾元素
+ int target = obj->foot->val;
+
+ if (obj->head == obj->foot) {
+ free(obj->foot);
+ obj->head = NULL;
+ obj->foot = NULL;
+ } else {
+
+ Node_t *prev = obj->head;
+ // 移动至queue尾部节点前一个节点
+ while (prev->next != obj->foot) {
+ prev = prev->next;
+ }
+
+ free(obj->foot);
+ obj->foot = prev;
+ obj->foot->next = NULL;
+ }
+
+ obj->size--;
+ return target;
+}
+
+int myStackTop(MyStack* obj) {
+ return obj->foot->val;
+}
+
+bool myStackEmpty(MyStack* obj) {
+ return obj->size == 0;
+}
+
+void myStackFree(MyStack* obj) {
+ Node_t *curr = obj->head;
+ while (curr != NULL) {
+ Node_t *temp = curr->next;
+ free(curr);
+ curr = temp;
+ }
+ free(obj);
+}
+
+```
+
+
diff --git a/problems/0349.两个数组的交集.md b/problems/0349.两个数组的交集.md
index 77dfc50a..93fa0931 100644
--- a/problems/0349.两个数组的交集.md
+++ b/problems/0349.两个数组的交集.md
@@ -123,6 +123,9 @@ public:
### Java:
版本一:使用HashSet
```Java
+// 时间复杂度O(n+m+k) 空间复杂度O(n+k)
+// 其中n是数组nums1的长度,m是数组nums2的长度,k是交集元素的个数
+
import java.util.HashSet;
import java.util.Set;
@@ -145,8 +148,15 @@ class Solution {
}
//方法1:将结果集合转为数组
-
- return resSet.stream().mapToInt(x -> x).toArray();
+ return res.stream().mapToInt(Integer::intValue).toArray();
+ /**
+ * 将 Set
+
diff --git a/problems/0383.赎金信.md b/problems/0383.赎金信.md
index eb83d3ec..1d739173 100644
--- a/problems/0383.赎金信.md
+++ b/problems/0383.赎金信.md
@@ -104,7 +104,7 @@ public:
};
```
-* 时间复杂度: O(n)
+* 时间复杂度: O(m+n),其中m表示ransomNote的长度,n表示magazine的长度
* 空间复杂度: O(1)
@@ -470,3 +470,4 @@ bool canConstruct(char* ransomNote, char* magazine) {
+
diff --git a/problems/0459.重复的子字符串.md b/problems/0459.重复的子字符串.md
index 18200562..988b2abf 100644
--- a/problems/0459.重复的子字符串.md
+++ b/problems/0459.重复的子字符串.md
@@ -390,6 +390,8 @@ public:
### Java:
+(版本一) 前缀表 减一
+
```java
class Solution {
public boolean repeatedSubstringPattern(String s) {
@@ -420,6 +422,45 @@ class Solution {
}
```
+(版本二) 前缀表 不减一
+
+```java
+/*
+ * 充分条件:如果字符串s是由重复子串组成的,那么它的最长相等前后缀不包含的子串一定是s的最小重复子串。
+ * 必要条件:如果字符串s的最长相等前后缀不包含的子串是s的最小重复子串,那么s必然是由重复子串组成的。
+ * 推得:当字符串s的长度可以被其最长相等前后缀不包含的子串的长度整除时,不包含的子串就是s的最小重复子串。
+ *
+ * 时间复杂度:O(n)
+ * 空间复杂度:O(n)
+ */
+class Solution {
+ public boolean repeatedSubstringPattern(String s) {
+ // if (s.equals("")) return false;
+ // 边界判断(可以去掉,因为题目给定范围是1 <= s.length <= 10^4)
+ int n = s.length();
+
+ // Step 1.构建KMP算法的前缀表
+ int[] next = new int[n]; // 前缀表的值表示 以该位置结尾的字符串的最长相等前后缀的长度
+ int j = 0;
+ next[0] = 0;
+ for (int i = 1; i < n; i++) {
+ while (j > 0 && s.charAt(i) != s.charAt(j)) // 只要前缀后缀还不一致,就根据前缀表回退j直到起点为止
+ j = next[j - 1];
+ if (s.charAt(i) == s.charAt(j))
+ j++;
+ next[i] = j;
+ }
+
+ // Step 2.判断重复子字符串
+ if (next[n - 1] > 0 && n % (n - next[n - 1]) == 0) { // 当字符串s的长度可以被其最长相等前后缀不包含的子串的长度整除时
+ return true; // 不包含的子串就是s的最小重复子串
+ } else {
+ return false;
+ }
+ }
+}
+```
+
### Python:
(版本一) 前缀表 减一
@@ -911,9 +952,54 @@ public bool RepeatedSubstringPattern(string s) {
return ss.Contains(s);
}
```
+### C
+
+```c
+// 前缀表不减一
+int *build_next(char* s, int len) {
+
+ int *next = (int *)malloc(len * sizeof(int));
+ assert(next);
+
+ // 初始化前缀表
+ next[0] = 0;
+
+ // 构建前缀表表
+ int i = 1, j = 0;
+ while (i < len) {
+ if (s[i] == s[j]) {
+ j++;
+ next[i] = j;
+ i++;
+ } else if (j > 0) {
+ j = next[j - 1];
+ } else {
+ next[i] = 0;
+ i++;
+ }
+ }
+ return next;
+}
+
+bool repeatedSubstringPattern(char* s) {
+
+ int len = strlen(s);
+ int *next = build_next(s, len);
+ bool result = false;
+
+ // 检查最小重复片段能否被长度整除
+ if (next[len - 1]) {
+ result = len % (len - next[len - 1]) == 0;
+ }
+
+ free(next);
+ return result;
+}
+
+```
+
-
diff --git a/problems/0494.目标和.md b/problems/0494.目标和.md
index dda3ad75..4a1fc6ab 100644
--- a/problems/0494.目标和.md
+++ b/problems/0494.目标和.md
@@ -151,13 +151,13 @@ if (abs(target) > sum) return 0; // 此时没有方案
本题则是装满有几种方法。其实这就是一个组合问题了。
-1. 确定dp数组以及下标的含义
+#### 1. 确定dp数组以及下标的含义
先用 二维 dp数组求解本题,dp[i][j]:使用 下标为[0, i]的nums[i]能够凑满j(包括j)这么大容量的包,有dp[i][j]种方法。
01背包为什么这么定义dp数组,我在[0-1背包理论基础](https://www.programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%E8%83%8C%E5%8C%85-1.html)中 确定dp数组的含义里讲解过。
-2. 确定递推公式
+#### 2. 确定递推公式
我们先手动推导一下,这个二维数组里面的数值。
@@ -264,7 +264,7 @@ if (nums[i] > j) dp[i][j] = dp[i - 1][j];
else dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];
```
-3. dp数组如何初始化
+#### 3. dp数组如何初始化
先明确递推的方向,如图,求解 dp[2][2] 是由 上方和左上方推出。
@@ -315,7 +315,7 @@ for (int i = 0; i < nums.size(); i++) {
}
```
-4. 确定遍历顺序
+#### 4. 确定遍历顺序
在明确递推方向时,我们知道 当前值 是由上方和左上方推出。
@@ -360,7 +360,7 @@ for (int j = 0; j <= bagSize; j++) { // 列,遍历背包
这里大家可以看出,无论是以上哪种遍历,都不影响 dp[2][2]的求值,用来 推导 dp[2][2] 的数值都在。
-5. 举例推导dp数组
+#### 5. 举例推导dp数组
输入:nums: [1, 1, 1, 1, 1], target: 3
@@ -421,7 +421,7 @@ public:
dp[i][j] 去掉 行的维度,即 dp[j],表示:填满j(包括j)这么大容积的包,有dp[j]种方法。
-2. 确定递推公式
+#### 2. 确定递推公式
二维DP数组递推公式: `dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i]];`
@@ -429,17 +429,17 @@ dp[i][j] 去掉 行的维度,即 dp[j],表示:填满j(包括j)这么
**这个公式在后面在讲解背包解决排列组合问题的时候还会用到!**
-3. dp数组如何初始化
+#### 3. dp数组如何初始化
在上面 二维dp数组中,我们讲解过 dp[0][0] 初始为1,这里dp[0] 同样初始为1 ,即装满背包为0的方法有一种,放0件物品。
-4. 确定遍历顺序
+#### 4. 确定遍历顺序
在[动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)中,我们系统讲过对于01背包问题一维dp的遍历。
遍历物品放在外循环,遍历背包在内循环,且内循环倒序(为了保证物品只使用一次)。
-5. 举例推导dp数组
+#### 5. 举例推导dp数组
输入:nums: [1, 1, 1, 1, 1], target: 3
@@ -526,7 +526,6 @@ dp[j] += dp[j - nums[i]];
## 其他语言版本
-
### Java
```java
class Solution {
@@ -671,18 +670,26 @@ class Solution:
# 创建二维动态规划数组,行表示选取的元素数量,列表示累加和
dp = [[0] * (target_sum + 1) for _ in range(len(nums) + 1)]
+ dp = [[0] * (target_sum + 1) for _ in range(len(nums))]
# 初始化状态
dp[0][0] = 1
+ if nums[0] <= target_sum:
+ dp[0][nums[0]] = 1
+ numZero = 0
+ for i in range(len(nums)):
+ if nums[i] == 0:
+ numZero += 1
+ dp[i][0] = int(math.pow(2, numZero))
# 动态规划过程
- for i in range(1, len(nums) + 1):
+ for i in range(1, len(nums)):
for j in range(target_sum + 1):
dp[i][j] = dp[i - 1][j] # 不选取当前元素
if j >= nums[i - 1]:
- dp[i][j] += dp[i - 1][j - nums[i - 1]] # 选取当前元素
+ dp[i][j] += dp[i - 1][j - nums[i]] # 选取当前元素
- return dp[len(nums)][target_sum] # 返回达到目标和的方案数
+ return dp[len(nums)-1][target_sum] # 返回达到目标和的方案数
```
diff --git a/problems/0518.零钱兑换II.md b/problems/0518.零钱兑换II.md
index 0d35fb7c..835df852 100644
--- a/problems/0518.零钱兑换II.md
+++ b/problems/0518.零钱兑换II.md
@@ -4,8 +4,6 @@
+
diff --git a/problems/1049.最后一块石头的重量II.md b/problems/1049.最后一块石头的重量II.md
index b40ed114..0d445a71 100644
--- a/problems/1049.最后一块石头的重量II.md
+++ b/problems/1049.最后一块石头的重量II.md
@@ -42,40 +42,41 @@
## 思路
-如果对背包问题不都熟悉先看这两篇:
+如果对背包问题不熟悉的话先看这两篇:
-* [动态规划:关于01背包问题,你该了解这些!](https://programmercarl.com/背包理论基础01背包-1.html)
-* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://programmercarl.com/背包理论基础01背包-2.html)
+* [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html)
+* [01背包理论基础(一维数组)](https://programmercarl.com/背包理论基础01背包-2.html)
-本题其实就是尽量让石头分成重量相同的两堆,相撞之后剩下的石头最小,**这样就化解成01背包问题了**。
+本题其实是尽量让石头分成重量相同的两堆(尽可能相同),相撞之后剩下的石头就是最小的。
-是不是感觉和昨天讲解的[416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)非常像了。
+一堆的石头重量是sum,那么我们就尽可能拼成 重量为 sum / 2 的石头堆。 这样剩下的石头堆也是 尽可能接近 sum/2 的重量。
+那么此时问题就是有一堆石头,每个石头都有自己的重量,是否可以 装满 最大重量为 sum / 2的背包。
-本题物品的重量为stones[i],物品的价值也为stones[i]。
+看到这里,大家是否感觉和昨天讲解的 [416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html)非常像了,简直就是同一道题。
-对应着01背包里的物品重量weight[i]和 物品价值value[i]。
+本题**这样就化解成01背包问题了**。
+
+**[416. 分割等和子集](https://programmercarl.com/0416.分割等和子集.html) 是求背包是否正好装满,而本题是求背包最多能装多少**。
+
+物品就是石头,物品的重量为stones[i],物品的价值也为stones[i]。
接下来进行动规五步曲:
-1. 确定dp数组以及下标的含义
+### 1. 确定dp数组以及下标的含义
**dp[j]表示容量(这里说容量更形象,其实就是重量)为j的背包,最多可以背最大重量为dp[j]**。
-可以回忆一下01背包中,dp[j]的含义,容量为j的背包,最多可以装的价值为 dp[j]。
+相对于 01背包,本题中,石头的重量是 stones[i],石头的价值也是 stones[i] 。
-相对于 01背包,本题中,石头的重量是 stones[i],石头的价值也是 stones[i] ,可以 “最多可以装的价值为 dp[j]” == “最多可以背的重量为dp[j]”
+“最多可以装的价值为 dp[j]” 等同于 “最多可以背的重量为dp[j]”
-2. 确定递推公式
+### 2. 确定递推公式
01背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
本题则是:**dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]);**
-一些同学可能看到这dp[j - stones[i]] + stones[i]中 又有- stones[i] 又有+stones[i],看着有点晕乎。
-
-大家可以再去看 dp[j]的含义。
-
-3. dp数组如何初始化
+### 3. dp数组如何初始化
既然 dp[j]中的j表示容量,那么最大容量(重量)是多少呢,就是所有石头的重量和。
@@ -95,7 +96,7 @@
vector
+
diff --git a/problems/周总结/20210107动规周末总结.md b/problems/周总结/20210107动规周末总结.md
index 4cab00cc..da2ebd30 100644
--- a/problems/周总结/20210107动规周末总结.md
+++ b/problems/周总结/20210107动规周末总结.md
@@ -99,7 +99,7 @@ public:
这道绝佳的面试题我没有用过,如果录友们有面试别人的需求,就把这个套路拿去吧。
-我在[通过一道面试题目,讲一讲递归算法的时间复杂度!](https://programmercarl.com/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.html)中,以我自己面试别人的真实经历,通过求x的n次方 这么简单的题目,就可以考察候选人对算法性能以及递归的理解深度,录友们可以看看,绝对有收获!
+我在[通过一道面试题目,讲一讲递归算法的时间复杂度!](../前序/递归算法的时间复杂度.md)中,以我自己面试别人的真实经历,通过求x的n次方 这么简单的题目,就可以考察候选人对算法性能以及递归的理解深度,录友们可以看看,绝对有收获!
## 周四
diff --git a/problems/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md
index 259a439b..d9b953c0 100644
--- a/problems/背包理论基础01背包-1.md
+++ b/problems/背包理论基础01背包-1.md
@@ -41,8 +41,6 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目,
有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。**每件物品只能用一次**,求解将哪些物品装入背包里物品价值总和最大。
-
-
这是标准的背包问题,以至于很多同学看了这个自然就会想到背包,甚至都不知道暴力的解法应该怎么解了。
这样其实是没有从底向上去思考,而是习惯性想到了背包,那么暴力的解法应该是怎么样的呢?
@@ -73,7 +71,7 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目,
依然动规五部曲分析一波。
-1. 确定dp数组以及下标的含义
+#### 1. 确定dp数组以及下标的含义
我们需要使用二维数组,为什么呢?
@@ -131,7 +129,7 @@ i 来表示物品、j表示背包容量。
**要时刻记着这个dp数组的含义,下面的一些步骤都围绕这dp数组的含义进行的**,如果哪里看懵了,就来回顾一下i代表什么,j又代表什么。
-2. 确定递推公式
+#### 2. 确定递推公式
这里在把基本信息给出来:
@@ -176,7 +174,7 @@ i 来表示物品、j表示背包容量。
递归公式: `dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);`
-3. dp数组如何初始化
+#### 3. dp数组如何初始化
**关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱**。
@@ -197,8 +195,8 @@ dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包
代码初始化如下:
```CPP
-for (int j = 0 ; j < weight[0]; j++) { // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。
- dp[0][j] = 0;
+for (int i = 1; i < weight.size(); i++) { // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。
+ dp[i][0] = 0;
}
// 正序遍历
for (int j = weight[0]; j <= bagweight; j++) {
@@ -236,7 +234,7 @@ for (int j = weight[0]; j <= bagweight; j++) {
**费了这么大的功夫,才把如何初始化讲清楚,相信不少同学平时初始化dp数组是凭感觉来的,但有时候感觉是不靠谱的**。
-4. 确定遍历顺序
+#### 4. 确定遍历顺序
在如下图中,可以看出,有两个遍历的维度:物品与背包重量
@@ -293,7 +291,7 @@ dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括
**其实背包问题里,两个for循环的先后循序是非常有讲究的,理解遍历顺序其实比理解推导公式难多了**。
-5. 举例推导dp数组
+#### 5. 举例推导dp数组
来看一下对应的dp数组的数值,如图:
diff --git a/problems/背包问题完全背包一维.md b/problems/背包问题完全背包一维.md
new file mode 100644
index 00000000..a8e241c3
--- /dev/null
+++ b/problems/背包问题完全背包一维.md
@@ -0,0 +1,211 @@
+
+# 完全背包-一维数组
+
+本题力扣上没有原题,大家可以去[卡码网第52题](https://kamacoder.com/problempage.php?pid=1052)去练习。
+
+## 算法公开课
+
+**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html):[带你学透完全背包问题! ](https://www.bilibili.com/video/BV1uK411o7c9/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。
+
+
+## 思路
+
+本篇我们不再做五部曲分析,核心内容 在 01背包二维 、01背包一维 和 完全背包二维 的讲解中都讲过了。
+
+上一篇我们刚刚讲了完全背包二维DP数组的写法:
+
+```CPP
+for (int i = 1; i < n; i++) { // 遍历物品
+ for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
+ if (j < weight[i]) dp[i][j] = dp[i - 1][j];
+ else dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
+ }
+}
+```
+
+压缩成一维DP数组,也就是将上一层拷贝到当前层。
+
+将上一层dp[i-1] 的那一层拷贝到 当前层 dp[i] ,那么 递推公式由:`dp[i][j] = max(dp[i - 1][j], dp[i][j - weight[i]] + value[i])` 变成: `dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i])`
+
+这里有录友想,这样拷贝的话, dp[i - 1][j] 的数值会不会 覆盖了 dp[i][j] 的数值呢?
+
+并不会,因为 当前层 dp[i][j] 是空的,是没有计算过的。
+
+变成 `dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i])` 我们压缩成一维dp数组,去掉 i 层数维度。
+
+即:`dp[j] = max(dp[j], dp[j - weight[i]] + value[i])`
+
+
+接下来我们重点讲一下遍历顺序。
+
+看过这两篇的话:
+
+* [01背包理论基础(二维数组)](https://programmercarl.com/背包理论基础01背包-1.html)
+* [01背包理论基础(一维数组)](https://programmercarl.com/背包理论基础01背包-2.html)
+
+就知道了,01背包中二维dp数组的两个for遍历的先后循序是可以颠倒了,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。
+
+**在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的**!
+
+因为dp[j] 是根据 下标j之前所对应的dp[j]计算出来的。 只要保证下标j之前的dp[j]都是经过计算的就可以了。
+
+遍历物品在外层循环,遍历背包容量在内层循环,状态如图:
+
+
+
+遍历背包容量在外层循环,遍历物品在内层循环,状态如图:
+
+
+
+看了这两个图,大家就会理解,完全背包中,两个for循环的先后循序,都不影响计算dp[j]所需要的值(这个值就是下标j之前所对应的dp[j])。
+
+先遍历背包再遍历物品,代码如下:
+
+```CPP
+for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
+ for(int i = 0; i < weight.size(); i++) { // 遍历物品
+ if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
+ }
+ cout << endl;
+}
+```
+
+先遍历物品再遍历背包:
+
+```CPP
+for(int i = 0; i < weight.size(); i++) { // 遍历物品
+ for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
+ if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
+ }
+}
+```
+
+整体代码如下:
+
+```cpp
+#include