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 2be8922b..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:
(版本一) 前缀表 减一
@@ -648,6 +689,29 @@ var repeatedSubstringPattern = function (s) {
};
```
+> 正则匹配
+```javascript
+/**
+ * @param {string} s
+ * @return {boolean}
+ */
+var repeatedSubstringPattern = function(s) {
+ let reg = /^(\w+)\1+$/
+ return reg.test(s)
+};
+```
+> 移动匹配
+```javascript
+/**
+ * @param {string} s
+ * @return {boolean}
+ */
+var repeatedSubstringPattern = function (s) {
+ let ss = s + s;
+ return ss.substring(1, ss.length - 1).includes(s);
+};
+```
+
### TypeScript:
> 前缀表统一减一
@@ -853,8 +917,10 @@ impl Solution {
}
```
### C#
+
+> 前缀表不减一
+
```csharp
-// 前缀表不减一
public bool RepeatedSubstringPattern(string s)
{
if (s.Length == 0)
@@ -879,9 +945,61 @@ public int[] GetNext(string s)
}
```
+> 移动匹配
+```csharp
+public bool RepeatedSubstringPattern(string s) {
+ string ss = (s + s).Substring(1, (s + s).Length - 2);
+ 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 6ddf774d..4a1fc6ab 100644
--- a/problems/0494.目标和.md
+++ b/problems/0494.目标和.md
@@ -670,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 833d5ed6..835df852 100644
--- a/problems/0518.零钱兑换II.md
+++ b/problems/0518.零钱兑换II.md
@@ -546,7 +546,7 @@ object Solution {
}
}
```
-## C
+### C
```c
int change(int amount, int* coins, int coinsSize) {
@@ -593,37 +593,3 @@ public class Solution
-
-----------
-
-
-
-回归本题,动规五步曲来分析如下:
-
-1. 确定dp数组以及下标的含义
-
-dp[j]:凑成总金额j的货币组合数为dp[j]
-
-2. 确定递推公式
-
-dp[j] 就是所有的dp[j - coins[i]](考虑coins[i]的情况)相加。
-
-所以递推公式:dp[j] += dp[j - coins[i]];
-
-**这个递推公式大家应该不陌生了,我在讲解01背包题目的时候在这篇[494. 目标和](https://programmercarl.com/0494.目标和.html)中就讲解了,求装满背包有几种方法,公式都是:dp[j] += dp[j - nums[i]];**
-
-3. dp数组如何初始化
-
-首先dp[0]一定要为1,dp[0] = 1是 递归公式的基础。如果dp[0] = 0 的话,后面所有推导出来的值都是0了。
-
-那么 dp[0] = 1 有没有含义,其实既可以说 凑成总金额0的货币组合数为1,也可以说 凑成总金额0的货币组合数为0,好像都没有毛病。
-
-但题目描述中,也没明确说 amount = 0 的情况,结果应该是多少。
-
-这里我认为题目描述还是要说明一下,因为后台测试数据是默认,amount = 0 的情况,组合数为1的。
-
-下标非0的dp[j]初始化为0,这样累计加dp[j - coins[i]]的时候才不会影响真正的dp[j]
-
-dp[0]=1还说明了一种情况:如果正好选了coins[i]后,也就是j-coins[i] == 0的情况表示这个硬币刚好能选,此时dp[0]为1表示只选coins[i]存在这样的一种选法。
-
-----------------
diff --git a/problems/0738.单调递增的数字.md b/problems/0738.单调递增的数字.md
index ff438e98..f2cfee04 100644
--- a/problems/0738.单调递增的数字.md
+++ b/problems/0738.单调递增的数字.md
@@ -273,22 +273,20 @@ class Solution:
### Go
```go
func monotoneIncreasingDigits(n int) int {
- s := strconv.Itoa(N)//将数字转为字符串,方便使用下标
- ss := []byte(s)//将字符串转为byte数组,方便更改。
- n := len(ss)
- if n <= 1 {
- return n
- }
- for i := n-1; i > 0; i-- {
- if ss[i-1] > ss[i] { //前一个大于后一位,前一位减1,后面的全部置为9
- ss[i-1] -= 1
- for j := i; j < n; j++ { //后面的全部置为9
- ss[j] = '9'
- }
- }
- }
- res, _ := strconv.Atoi(string(ss))
- return res
+ s := strconv.Itoa(n)
+ // 从左到右遍历字符串,找到第一个不满足单调递增的位置
+ for i := len(s) - 2; i >= 0; i-- {
+ if s[i] > s[i+1] {
+ // 将该位置的数字减1
+ s = s[:i] + string(s[i]-1) + s[i+1:]
+ // 将该位置之后的所有数字置为9
+ for j := i + 1; j < len(s); j++ {
+ s = s[:j] + "9" + s[j+1:]
+ }
+ }
+ }
+ result, _ := strconv.Atoi(s)
+ return result
}
```
@@ -447,3 +445,4 @@ public class Solution
+
diff --git a/problems/kamacoder/0044.开发商购买土地.md b/problems/kamacoder/0044.开发商购买土地.md
index 739e2cad..64804842 100644
--- a/problems/kamacoder/0044.开发商购买土地.md
+++ b/problems/kamacoder/0044.开发商购买土地.md
@@ -212,13 +212,14 @@ public class Main {
int horizontalCut = 0;
for (int i = 0; i < n; i++) {
horizontalCut += horizontal[i];
- result = Math.min(result, Math.abs(sum - 2 * horizontalCut));
+ result = Math.min(result, Math.abs((sum - horizontalCut) - horizontalCut));
+ // 更新result。其中,horizontalCut表示前i行的和,sum - horizontalCut表示剩下的和,作差、取绝对值,得到题目需要的“A和B各自的子区域内的土地总价值之差”。下同。
}
int verticalCut = 0;
for (int j = 0; j < m; j++) {
verticalCut += vertical[j];
- result = Math.min(result, Math.abs(sum - 2 * verticalCut));
+ result = Math.min(result, Math.abs((sum - verticalCut) - verticalCut));
}
System.out.println(result);
@@ -526,3 +527,89 @@ int main()
}
```
+
+### Go
+
+前缀和
+
+```go
+package main
+
+import (
+ "fmt"
+ "os"
+ "bufio"
+ "strings"
+ "strconv"
+ "math"
+)
+
+func main() {
+ var n, m int
+
+ reader := bufio.NewReader(os.Stdin)
+
+ line, _ := reader.ReadString('\n')
+ line = strings.TrimSpace(line)
+ params := strings.Split(line, " ")
+
+ n, _ = strconv.Atoi(params[0])
+ m, _ = strconv.Atoi(params[1])//n和m读取完成
+
+ land := make([][]int, n)//土地矩阵初始化
+
+ for i := 0; i < n; i++ {
+ line, _ := reader.ReadString('\n')
+ line = strings.TrimSpace(line)
+ values := strings.Split(line, " ")
+ land[i] = make([]int, m)
+ for j := 0; j < m; j++ {
+ value, _ := strconv.Atoi(values[j])
+ land[i][j] = value
+ }
+ }//所有读取完成
+
+ //初始化前缀和矩阵
+ preMatrix := make([][]int, n+1)
+ for i := 0; i <= n; i++ {
+ preMatrix[i] = make([]int, m+1)
+ }
+
+ for a := 1; a < n+1; a++ {
+ for b := 1; b < m+1; b++ {
+ preMatrix[a][b] = land[a-1][b-1] + preMatrix[a-1][b] + preMatrix[a][b-1] - preMatrix[a-1][b-1]
+ }
+ }
+
+ totalSum := preMatrix[n][m]
+
+ minDiff := math.MaxInt32//初始化极大数,用于比较
+
+ //按行分割
+ for i := 1; i < n; i++ {
+ topSum := preMatrix[i][m]
+
+ bottomSum := totalSum - topSum
+
+ diff := int(math.Abs(float64(topSum - bottomSum)))
+ if diff < minDiff {
+ minDiff = diff
+ }
+ }
+
+ //按列分割
+ for j := 1; j < m; j++ {
+ topSum := preMatrix[n][j]
+
+ bottomSum := totalSum - topSum
+
+ diff := int(math.Abs(float64(topSum - bottomSum)))
+ if diff < minDiff {
+ minDiff = diff
+ }
+ }
+
+ fmt.Println(minDiff)
+}
+```
+
diff --git a/problems/二叉树的统一迭代法.md b/problems/二叉树的统一迭代法.md
index 037cf110..a6d4e3ff 100644
--- a/problems/二叉树的统一迭代法.md
+++ b/problems/二叉树的统一迭代法.md
@@ -238,7 +238,7 @@ class Solution {
while (!st.empty()) {
TreeNode node = st.peek();
if (node != null) {
- st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
+ st.pop(); // 将该节点弹出,避免重复操作,下面再将右左中节点添加到栈中(前序遍历-中左右,入栈顺序右左中)
if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈)
if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈)
st.push(node); // 添加中节点
@@ -266,11 +266,10 @@ public List
+
diff --git a/problems/背包问题理论基础完全背包.md b/problems/背包问题理论基础完全背包.md
index 0cc6e915..ea658f7e 100644
--- a/problems/背包问题理论基础完全背包.md
+++ b/problems/背包问题理论基础完全背包.md
@@ -316,7 +316,51 @@ print(knapsack(n, bag_weight, weight, value))
### JavaScript
+```js
+const readline = require('readline').createInterface({
+ input: process.stdin,
+ output: process.stdout
+});
+let input = [];
+readline.on('line', (line) => {
+ input.push(line.trim());
+});
+
+readline.on('close', () => {
+ // 第一行解析 n 和 v
+ const [n, bagweight] = input[0].split(' ').map(Number);
+
+ /// 剩余 n 行解析重量和价值
+ const weight = [];
+ const value = [];
+ for (let i = 1; i <= n; i++) {
+ const [wi, vi] = input[i].split(' ').map(Number);
+ weight.push(wi);
+ value.push(vi);
+ }
+
+
+ let dp = Array.from({ length: n }, () => Array(bagweight + 1).fill(0));
+
+ for (let j = weight[0]; j <= bagweight; j++) {
+ dp[0][j] = dp[0][j-weight[0]] + value[0];
+ }
+
+ for (let i = 1; i < n; i++) {
+ for (let j = 0; j <= bagweight; j++) {
+ if (j < weight[i]) {
+ dp[i][j] = dp[i - 1][j];
+ } else {
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - weight[i]] + value[i]);
+ }
+ }
+ }
+
+ console.log(dp[n - 1][bagweight]);
+});
+
+```