This commit is contained in:
programmercarl
2024-07-26 11:35:38 +08:00
parent 717ad849cf
commit 2d9604baa5
20 changed files with 991 additions and 232 deletions

View File

@ -107,7 +107,9 @@
5. [数组209.长度最小的子数组](./problems/0209.长度最小的子数组.md) 5. [数组209.长度最小的子数组](./problems/0209.长度最小的子数组.md)
6. [数组:区间和](./problems/kamacoder/0058.区间和.md) 6. [数组:区间和](./problems/kamacoder/0058.区间和.md)
6. [数组59.螺旋矩阵II](./problems/0059.螺旋矩阵II.md) 6. [数组59.螺旋矩阵II](./problems/0059.螺旋矩阵II.md)
7. [数组:总结篇](./problems/数组总结篇.md) 7. [数组:区间和](./problems/kamacoder/0058.区间和.md)
8. [数组:开发商购买土地](./problems/kamacoder/0044.开发商购买土地.md)
9. [数组:总结篇](./problems/数组总结篇.md)
## 链表 ## 链表

View File

@ -1,6 +1,10 @@
# 44. 开发商购买土地 # 44. 开发商购买土地
> 本题为代码随想录后续扩充题目还没有视频讲解顺便让大家练习一下ACM输入输出模式笔试面试必备
[题目链接](https://kamacoder.com/problempage.php?pid=1044)
【题目描述】 【题目描述】
在一个城市区域内被划分成了n * m个连续的区块每个区块都拥有不同的权值代表着其土地价值。目前有两家开发公司A 公司和 B 公司,希望购买这个城市区域的土地。 在一个城市区域内被划分成了n * m个连续的区块每个区块都拥有不同的权值代表着其土地价值。目前有两家开发公司A 公司和 B 公司,希望购买这个城市区域的土地。
@ -57,7 +61,7 @@
如果本题要求 任何两个行(或者列)之间的数值总和,大家在[0058.区间和](./0058.区间和.md) 的基础上 应该知道怎么求。 如果本题要求 任何两个行(或者列)之间的数值总和,大家在[0058.区间和](./0058.区间和.md) 的基础上 应该知道怎么求。
就是前缀和的思路先统计好前n行的和 q[n],如果要求矩阵 a 行到 b行 之间的总和,那么就 q[b] - q[a - 1]就好。 就是前缀和的思路先统计好前n行的和 q[n],如果要求矩阵 a行 到 b行 之间的总和,那么就 q[b] - q[a - 1]就好。
至于为什么是 a - 1大家去看 [0058.区间和](./0058.区间和.md) 的分析,使用 前缀和 要注意 区间左右边的开闭情况。 至于为什么是 a - 1大家去看 [0058.区间和](./0058.区间和.md) 的分析,使用 前缀和 要注意 区间左右边的开闭情况。

View File

@ -1,6 +1,8 @@
# 58. 区间和 # 58. 区间和
> 本题为代码随想录后续扩充题目还没有视频讲解顺便让大家练习一下ACM输入输出模式笔试面试必备
[题目链接](https://kamacoder.com/problempage.php?pid=1070) [题目链接](https://kamacoder.com/problempage.php?pid=1070)
题目描述 题目描述
@ -97,11 +99,11 @@ int main() {
为什么呢? 为什么呢?
p[1] = vec[0] + vec[1]; `p[1] = vec[0] + vec[1];`
p[5] = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5]; `p[5] = vec[0] + vec[1] + vec[2] + vec[3] + vec[4] + vec[5];`
p[5] - p[1] = vec[2] + vec[3] + vec[4] + vec[5]; `p[5] - p[1] = vec[2] + vec[3] + vec[4] + vec[5];`
这不就是我们要求的 下标 2 到下标 5 之间的累加和吗。 这不就是我们要求的 下标 2 到下标 5 之间的累加和吗。
@ -109,15 +111,17 @@ p[5] - p[1] = vec[2] + vec[3] + vec[4] + vec[5];
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240627111319.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20240627111319.png)
p[5] - p[1] 就是 红色部分的区间和。 `p[5] - p[1]` 就是 红色部分的区间和。
而 p 数组是我们之前就计算好的累加和,所以后面每次求区间和的之后 我们只需要 O(1)的操作。 而 p 数组是我们之前就计算好的累加和,所以后面每次求区间和的之后 我们只需要 O(1) 的操作。
**特别注意** 在使用前缀和求解的时候,要特别注意 求解区间。 **特别注意** 在使用前缀和求解的时候,要特别注意 求解区间。
如上图,如果我们要求 区间下标 [2, 5] 的区间和,那么应该是 p[5] - p[1],而不是 p[5] - p[2]。 如上图,如果我们要求 区间下标 [2, 5] 的区间和,那么应该是 p[5] - p[1],而不是 p[5] - p[2]。
很多录友在使用前缀和的时候,分不清前缀和的区间,建议画一画图,模拟一下 思路会更清晰 **很多录友在使用前缀和的时候,分不清前缀和的区间,建议画一画图,模拟一下 思路会更清晰**。
本题C++代码如下:
```CPP ```CPP
#include <iostream> #include <iostream>

View File

@ -422,7 +422,8 @@ int main() {
## 其他语言版本 ## 其他语言版本
### Java ### Java
#### 邻接矩阵写法
邻接矩阵写法
```java ```java
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -477,7 +478,7 @@ public class Main {
} }
``` ```
#### 邻接表写法 邻接表写法
```java ```java
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
@ -533,7 +534,7 @@ public class Main {
} }
``` ```
### Python ### Python
#### 邻接矩阵写法 邻接矩阵写法
``` python ``` python
def dfs(graph, x, n, path, result): def dfs(graph, x, n, path, result):
if x == n: if x == n:
@ -566,7 +567,7 @@ if __name__ == "__main__":
main() main()
``` ```
#### 邻接表写法 邻接表写法
``` python ``` python
from collections import defaultdict from collections import defaultdict

View File

@ -225,6 +225,7 @@ int main() {
vec.push_back(i); vec.push_back(i);
} }
} }
// 情况一、情况二
if (vec.size() > 0) { if (vec.size() > 0) {
// 放在vec里的边已经按照倒叙放的所以这里就优先删vec[0]这条边 // 放在vec里的边已经按照倒叙放的所以这里就优先删vec[0]这条边
if (isTreeAfterRemoveEdge(edges, vec[0])) { if (isTreeAfterRemoveEdge(edges, vec[0])) {

View File

@ -0,0 +1,59 @@
# 113.国际象棋
广搜,但本题如果广搜枚举马和象的话会超时。
广搜要只枚举马的走位,同时判断是否在对角巷直接走象
```CPP
#include <iostream>
using namespace std;
const int N = 100005, mod = 1000000007;
using ll = long long;
int n, ans;
int dir[][2] = {{1, 2}, {1, -2}, {-1, 2}, {-1, -2}, {2, 1}, {2, -1}, {-2, -1}, {-2, 1}};
int main() {
int x1, y1, x2, y2;
cin >> n;
while (n--) {
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
if (x1 == x2 && y1 == y2) {
cout << 0 << endl;
continue;
}
// 判断象走一步到达
int d = abs(x1 - x2) - abs(y1 - y2);
if (!d) {cout << 1 << endl; continue;}
// 判断马走一步到达
bool one = 0;
for (int i = 0; i < 8; ++i) {
int dx = x1 + dir[i][0], dy = y1 + dir[i][1];
if (dx == x2 && dy == y2) {
cout << 1 << endl;
one = true;
break;
}
}
if (one) continue;
// 接下来为两步的逻辑, 象走两步或者马走一步,象走一步
// 象直接两步可以到达,这个计算是不是同颜色的格子,象可以在两步到达所有同颜色的格子
int d2 = abs(x1 - x2) + abs(y1 - y2);
if (d2 % 2 == 0) {
cout << 2 << endl;
continue;
}
// 接下来判断马 + 象的组合
bool two = 0;
for (int i = 0; i < 8; ++i) {
int dx = x1 + dir[i][0], dy = y1 + dir[i][1];
int d = abs(dx - x2) - abs(dy - y2);
if (!d) {cout << 2 << endl; two = true; break;}
}
if (two) continue;
// 剩下的格子全都是三步到达的
cout << 3 << endl;
}
return 0;
}
```

View File

@ -0,0 +1,132 @@
# 121. 小红的区间翻转
比较暴力的方式,就是直接模拟, 枚举所有 区间,然后检查其翻转的情况。
在检查翻转的时候,需要一些代码优化,否则容易超时。
```CPP
#include <iostream>
#include <vector>
using namespace std;
bool canTransform(const vector<int>& a, const vector<int>& b, int left, int right) {
// 提前检查翻转区间的值是否可以匹配
for (int i = left, j = right; i <= right; i++, j--) {
if (a[i] != b[j]) {
return false;
}
}
// 检查翻转区间外的值是否匹配
for (int i = 0; i < left; i++) {
if (a[i] != b[i]) {
return false;
}
}
for (int i = right + 1; i < a.size(); i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
int main() {
int n;
cin >> n;
vector<int> a(n);
vector<int> b(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0; i < n; i++) {
cin >> b[i];
}
int count = 0;
// 遍历所有可能的区间
for (int left = 0; left < n; left++) {
for (int right = left; right < n; right++) {
// 检查翻转区间 [left, right] 后a 是否可以变成 b
if (canTransform(a, b, left, right)) {
count++;
}
}
}
cout << count << endl;
return 0;
}
```
也可以事先计算好,最长公共前缀,和最长公共后缀。
在公共前缀和公共后缀之间的部分进行翻转操作,这样我们可以减少很多不必要的翻转尝试。
通过在公共前缀和后缀之间的部分,找到可以通过翻转使得 a 和 b 相等的区间。
以下 为评论区 卡码网用户码鬼的C++代码
```CPP
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> a(n), b(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0; i < n; i++) {
cin >> b[i];
}
vector<int> prefix(n, 0), suffix(n, 0);
// 计算前缀相等的位置
int p = 0;
while (p < n && a[p] == b[p]) {
prefix[p] = 1;
p++;
}
// 计算后缀相等的位置
int s = n - 1;
while (s >= 0 && a[s] == b[s]) {
suffix[s] = 1;
s--;
}
int count = 0;
// 遍历所有可能的区间
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
// 判断前缀和后缀是否相等
if ((i == 0 || prefix[i - 1] == 1) && (j == n - 1 || suffix[j + 1] == 1)) {
// 判断翻转后的子数组是否和目标数组相同
bool is_palindrome = true;
for (int k = 0; k <= (j - i) / 2; k++) {
if (a[i + k] != b[j - k]) {
is_palindrome = false;
break;
}
}
if (is_palindrome) {
count++;
}
}
}
}
cout << count << endl;
return 0;
}
```

View File

@ -0,0 +1,108 @@
# 142. 两个字符串的最小 ASCII 删除总和
本题和[代码随想录:两个字符串的删除操作](https://www.programmercarl.com/0583.%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.html) 思路基本是一样的。
属于编辑距离问题,如果想彻底了解,建议看看「代码随想录」的编辑距离总结篇。
本题dp数组含义
dp[i][j] 表示 以i-1为结尾的字符串word1和以j-1位结尾的字符串word2想要达到相等所需要删除元素的最小ASCII 删除总和。
如果 s1[i - 1] 与 s2[j - 1] 相同,则不用删:`dp[i][j] = dp[i - 1][j - 1]`
如果 s1[i - 1] 与 s2[j - 1] 不相同删word1 的 最小删除和: `dp[i - 1][j] + s1[i - 1]` 删word2的最小删除和 `dp[i][j - 1] + s2[j - 1]`
取最小值: `dp[i][j] = min(dp[i - 1][j] + s1[i - 1], dp[i][j - 1] + s2[j - 1])`
```CPP
#include <iostream>
#include <vector>
using namespace std;
int main() {
string s1, s2;
cin >> s1 >> s2;
vector<vector<int>> dp(s1.size() + 1, vector<int>(s2.size() + 1, 0));
// s1 如果变成空串的最小删除ASCLL值综合
for (int i = 1; i <= s1.size(); i++) dp[i][0] = dp[i - 1][0] + s1[i - 1];
// s2 如果变成空串的最小删除ASCLL值综合
for (int j = 1; j <= s2.size(); j++) dp[0][j] = dp[0][j - 1] + s2[j - 1];
for (int i = 1; i <= s1.size(); i++) {
for (int j = 1; j <= s2.size(); j++) {
if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = min(dp[i - 1][j] + s1[i - 1], dp[i][j - 1] + s2[j - 1]);
}
}
cout << dp[s1.size()][s2.size()] << endl;
}
```
### Java
```Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String s1 = scanner.nextLine();
String s2 = scanner.nextLine();
int[][] dp = new int[s1.length() + 1][s2.length() + 1];
// s1 如果变成空串的最小删除ASCII值综合
for (int i = 1; i <= s1.length(); i++) {
dp[i][0] = dp[i - 1][0] + s1.charAt(i - 1);
}
// s2 如果变成空串的最小删除ASCII值综合
for (int j = 1; j <= s2.length(); j++) {
dp[0][j] = dp[0][j - 1] + s2.charAt(j - 1);
}
for (int i = 1; i <= s1.length(); i++) {
for (int j = 1; j <= s2.length(); j++) {
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i - 1][j] + s1.charAt(i - 1), dp[i][j - 1] + s2.charAt(j - 1));
}
}
}
System.out.println(dp[s1.length()][s2.length()]);
scanner.close();
}
}
```
### python
```python
def min_delete_sum(s1: str, s2: str) -> int:
dp = [[0] * (len(s2) + 1) for _ in range(len(s1) + 1)]
# s1 如果变成空串的最小删除ASCII值综合
for i in range(1, len(s1) + 1):
dp[i][0] = dp[i - 1][0] + ord(s1[i - 1])
# s2 如果变成空串的最小删除ASCII值综合
for j in range(1, len(s2) + 1):
dp[0][j] = dp[0][j - 1] + ord(s2[j - 1])
for i in range(1, len(s1) + 1):
for j in range(1, len(s2) + 1):
if s1[i - 1] == s2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = min(dp[i - 1][j] + ord(s1[i - 1]), dp[i][j - 1] + ord(s2[j - 1]))
return dp[len(s1)][len(s2)]
if __name__ == "__main__":
s1 = input().strip()
s2 = input().strip()
print(min_delete_sum(s1, s2))
```

View File

@ -0,0 +1,237 @@
# 143. 最长同值路径
本题两个考点:
1. 层序遍历构造二叉树
2. 树形dp找出最长路径
对于写代码不多,或者动手能力比较差的录友,第一个 构造二叉树 基本就被卡主了。
```CPP
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
// 定义二叉树节点结构
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
// 根据层序遍历数组构建二叉树
TreeNode* constructBinaryTree(const vector<string>& levelOrder) {
if (levelOrder.empty()) return NULL;
TreeNode* root = new TreeNode(stoi(levelOrder[0]));
queue<TreeNode*> q;
q.push(root);
int i = 1;
while (!q.empty() && i < levelOrder.size()) {
TreeNode* current = q.front();
q.pop();
if (i < levelOrder.size() && levelOrder[i] != "null") {
current->left = new TreeNode(stoi(levelOrder[i]));
q.push(current->left);
}
i++;
if (i < levelOrder.size() && levelOrder[i] != "null") {
current->right = new TreeNode(stoi(levelOrder[i]));
q.push(current->right);
}
i++;
}
return root;
}
int result = 0;
// 树形DP
int dfs(TreeNode* node) {
if (node == NULL) return 0;
int leftPath = dfs(node->left);
int rightPath = dfs(node->right);
int leftNum = 0, rightNum = 0;
if (node->left != NULL && node->left->val == node->val) {
leftNum = leftPath + 1;
}
if (node->right != NULL && node->right->val == node->val) {
rightNum = rightPath + 1;
}
result = max(result, leftNum + rightNum);
return max(leftNum, rightNum);
}
int main() {
int n;
cin >> n;
vector<string> levelOrder(n);
for (int i = 0; i < n ; i++) cin >> levelOrder[i];
TreeNode* root = constructBinaryTree(levelOrder);
dfs(root);
cout << result << endl;
return 0;
}
```
### Java
```Java
import java.util.*;
class TreeNode {
int val;
TreeNode left, right;
TreeNode(int x) {
val = x;
left = null;
right = null;
}
}
public class Main {
public static int result = 0;
public static TreeNode constructBinaryTree(List<String> levelOrder) {
if (levelOrder.isEmpty()) return null;
TreeNode root = new TreeNode(Integer.parseInt(levelOrder.get(0)));
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
int i = 1;
while (!queue.isEmpty() && i < levelOrder.size()) {
TreeNode current = queue.poll();
if (i < levelOrder.size() && !levelOrder.get(i).equals("null")) {
current.left = new TreeNode(Integer.parseInt(levelOrder.get(i)));
queue.add(current.left);
}
i++;
if (i < levelOrder.size() && !levelOrder.get(i).equals("null")) {
current.right = new TreeNode(Integer.parseInt(levelOrder.get(i)));
queue.add(current.right);
}
i++;
}
return root;
}
public static int dfs(TreeNode node) {
if (node == null) return 0;
int leftPath = dfs(node.left);
int rightPath = dfs(node.right);
int leftNum = 0, rightNum = 0;
if (node.left != null && node.left.val == node.val) {
leftNum = leftPath + 1;
}
if (node.right != null && node.right.val == node.val) {
rightNum = rightPath + 1;
}
result = Math.max(result, leftNum + rightNum);
return Math.max(leftNum, rightNum);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
sc.nextLine(); // consume the newline character
List<String> levelOrder = new ArrayList<>();
for (int i = 0; i < n; i++) {
levelOrder.add(sc.next());
}
TreeNode root = constructBinaryTree(levelOrder);
dfs(root);
System.out.println(result);
sc.close();
}
}
```
### python
```python
from typing import List, Optional
from collections import deque
import sys
class TreeNode:
def __init__(self, val: int = 0, left: 'TreeNode' = None, right: 'TreeNode' = None):
self.val = val
self.left = left
self.right = right
def construct_binary_tree(level_order: List[str]) -> Optional[TreeNode]:
if not level_order:
return None
root = TreeNode(int(level_order[0]))
queue = deque([root])
i = 1
while queue and i < len(level_order):
current = queue.popleft()
if i < len(level_order) and level_order[i] != "null":
current.left = TreeNode(int(level_order[i]))
queue.append(current.left)
i += 1
if i < len(level_order) and level_order[i] != "null":
current.right = TreeNode(int(level_order[i]))
queue.append(current.right)
i += 1
return root
result = 0
def dfs(node: Optional[TreeNode]) -> int:
global result
if node is None:
return 0
left_path = dfs(node.left)
right_path = dfs(node.right)
left_num = right_num = 0
if node.left is not None and node.left.val == node.val:
left_num = left_path + 1
if node.right is not None and node.right.val == node.val:
right_num = right_path + 1
result = max(result, left_num + right_num)
return max(left_num, right_num)
if __name__ == "__main__":
input = sys.stdin.read
data = input().strip().split()
n = int(data[0])
level_order = data[1:]
root = construct_binary_tree(level_order)
dfs(root)
print(result)
```

View File

@ -0,0 +1,66 @@
# 0144.字典序最小的01字符串
贪心思路:移动尽可能 移动前面的1 ,这样可以是 字典序最小
从前到后遍历,遇到 0 ,就用前面的 1 来交换
```CPP
#include <iostream>
#include <string>
using namespace std;
int main() {
int n,k;
cin >> n >> k;
string s;
cin >> s;
for(int i = 0; i < n && k > 0; i++) {
if(s[i] == '0') {
// 开始用前面的 1 来交换
int j = i;
while(j > 0 && s[j - 1] == '1' && k > 0) {
swap(s[j], s[j - 1]);
--j;
--k;
}
}
}
cout << s << endl;
return 0;
}
```
Java
```Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int k = scanner.nextInt();
scanner.nextLine(); // 消耗掉换行符
String s = scanner.nextLine();
char[] ch = s.toCharArray();
for (int i = 0; i < n && k > 0; i++) {
if (ch[i] == '0') {
// 开始用前面的 1 来交换
int j = i;
while (j > 0 && ch[j - 1] == '1' && k > 0) {
char tmp = ch[j];
ch[j] = ch[j - 1];
ch[j - 1] = tmp;
j--;
k--;
}
}
}
System.out.println(new String(ch));
}
}
```

View File

@ -0,0 +1,98 @@
# 145. 数组子序列的排列
每个元素出现的次数相乘就可以了。
注意 “长度为 m 的数组1 到 m 每个元素都出现过,且恰好出现 1 次。” 题目中有n个元素所以我们要统计的就是 1 到 n 元素出现的个数。
因为如果有一个元素x 大于n了 那不可能出现 长度为x的数组 且 1 到 x 每个元素都出现过。
```CPP
#include "bits/stdc++.h"
using namespace std;
int main(){
int n;
int x;
cin >> n;
unordered_map<int, int> umap;
for(int i = 0; i < n; ++i){
cin >> x;
if(umap.find(x) != umap.end()) umap[x]++;
else umap[x] = 1;
}
long long res = 0;
long long num = 1;
for (int i = 1; i <= n; i++) {
if (umap.find(i) == umap.end()) break; // 如果i都没出现后面得数也不能 1 到 m 每个元素都出现过
num = (num * umap[i]) % 1000000007;
res += num;
res %= 1000000007;
}
cout << res << endl;
}
```
```Java
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < n; i++) {
int x = sc.nextInt();
map.put(x, map.getOrDefault(x, 0) + 1);
}
long res = 0;
long num = 1;
for (int i = 1; i <= n; i++) {
if (!map.containsKey(i)) break; // 如果i都没出现后面得数也不能1到m每个元素都出现过
num = (num * map.get(i)) % 1000000007;
res += num;
res %= 1000000007;
}
System.out.println(res);
sc.close();
}
}
```
```python
def main():
import sys
input = sys.stdin.read
data = input().split()
n = int(data[0])
umap = {}
for i in range(1, n + 1):
x = int(data[i])
if x in umap:
umap[x] += 1
else:
umap[x] = 1
res = 0
num = 1
MOD = 1000000007
for i in range(1, n + 1):
if i not in umap:
break # 如果i都没出现后面得数也不能1到m每个元素都出现过
num = (num * umap[i]) % MOD
res = (res + num) % MOD
print(res)
if __name__ == "__main__":
main()
```

View File

@ -0,0 +1,65 @@
# 146. 传送树
本题题意是比较绕的,我后面给补上了 【提示信息】对 题目输出样例讲解一下,相对会容易理解的多。
```CPP
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int>> edge; // 邻接表来存图
vector<int> nxt;
int n;
/*
* 递归函数用于找到每个节点的下一个传送门节点并记录在nxt数组中。
* 遍历当前节点的所有子节点递归调用findNext以确保子节点的nxt值已经计算出来。
* 更新当前节点的nxt值为其子节点中编号最小的节点。
* 如果当前节点是叶子节点即没有子节点则将其nxt值设置为自身。
*/
void findNext(int node) {
for (int v : edge[node]) {
findNext(v);
if (nxt[node] == -1 || nxt[node] > min(v, nxt[v])) {
nxt[node] = min(v, nxt[v]);
}
}
// 叶子节点
if (nxt[node] == -1) {
nxt[node] = node;
}
}
// 计算从节点u出发经过若干次传送门到达叶子节点所需的步数。
// 通过不断访问nxt节点直到到达叶子节点记录访问的节点数。
int get(int u) {
int cnt = 1;
while (nxt[u] != u) {
cnt++;
u = nxt[u];
}
return cnt;
}
int main() {
cin >> n;
edge.resize(n + 1);
nxt.resize(n + 1, -1);
for (int i = 1; i <= n; ++i) {
int a, b;
cin >> a >> b;
edge[a].push_back(b);
}
findNext(1);
for (int i = 1; i <= n; ++i) {
cout << get(i) << ' ';
}
}
```

View File

@ -0,0 +1,40 @@
1. 如果k * 3 大于 n 了,那说明一定没结果,如果没想明白,大家举个例子试试看
2. 分别求出三个红珠子之间的距离
3. 对这三段距离从小到大排序 y1, y2, y3
4. 如果第一段距离y1 小于k说明需要交换 k - y 次, 同理 第二段距离y2 小于k说明需要交换 k - y2 次
5. y1 y2 都调整好了不用计算y3因为 y3是距离最大
```CPP
#include<bits/stdc++.h>
using namespace std;
int main(){
int t;
cin >> t;
int n, k, a1, a2, a3;
vector<int> dis(3);
while (t--) {
cin >> n >> k >> a1 >> a2 >> a3;
if(k * 3 > n){
cout << -1 << endl;
continue;
}
dis[0] = min(abs(a1 - a2), n - abs(a1 - a2));
dis[1] = min(abs(a1 - a3), n - abs(a1 - a3));
dis[2] = min(abs(a3 - a2), n - abs(a3 - a2));
sort(dis.begin(), dis.end());
int result = 0;
if (dis[0] < k) result += (k - dis[0]);
if (dis[1] < k) result += (k - dis[1]);
cout << result << endl;
}
return 0;
}
```

View File

@ -0,0 +1,53 @@
首先我们要定义一个结构体,来存放我们的数据
`map<花色,{同一花色牌集合,同一花色的牌对应的牌数量}>`
再遍历 每一个花色下,每一个牌 的数量
代码如下详细注释:
```CPP
#include<bits/stdc++.h>
using namespace std;
string cards[] = {"H","S","D","C"};
typedef long long ll;
struct color
{
set<int> st; // 同一花色 牌的集合
map<int, ll> cnt; // 同一花色 牌对应的数量
};
unordered_map<string, color> umap;
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int x, y;
string card;
cin >> x >> y >> card;
umap[card].st.insert(x);
umap[card].cnt[x] += y;
}
ll sum = 0;
// 遍历每一个花色
for (string cardOne : cards) {
color colorOne = umap[cardOne];
// 遍历 同花色 每一个牌
for (int number : colorOne.st) {
ll numberCount = colorOne.cnt[number]; // 获取牌为number的数量是 numberCount
// 统计 number 到 number + 4 都是否有牌用cal 把 number 到number+4 的数量记下来
ll cal = numberCount;
for (int j = number + 1; j <= number + 4; j++) cal = min(cal, colorOne.cnt[j]);
// 统计结果
sum += cal;
// 把统计过的同花顺数量减下去
for (int j = number + 1; j <= number + 4; j++) colorOne.cnt[j] -= cal;
}
}
cout << sum << endl;
}
```

View File

@ -0,0 +1,46 @@
按照动态规划的思路,每增加一位,对已有结果会有什么变化呢?
输入 字符串s 里面有 0 和 1
我们先计算出 s[i-1] 的 子串权值和 a
如果 s[i] 和 s[i - 1] 相同
下标0 到 i 这段字符串 的子串权值和就是 a + 1即算上s[i]本身的连续串数量加上
如果 s[i] 和 s[i - 1] 不相同
```CPP
#include <iostream>
#include <string>
using namespace std;
int main() {
int n;
cin >> n;
string s;
cin >> s;
long long result = 1;
long long a = 1;
for (int i = 1; i < n; ++i) {
if (s[i] == s[i - 1]) {
a += 1;
result += a;
} else {
a = a + i + 1;
result += a;
}
}
cout << result << endl;
return 0;
}
```

View File

@ -1,30 +0,0 @@
本题和[代码随想录:两个字符串的删除操作](https://www.programmercarl.com/0583.%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.html) 思路基本是一样的。
```CPP
#include <iostream>
#include <vector>
using namespace std;
int main() {
string s1, s2;
cin >> s1 >> s2;
vector<vector<int>> dp(s1.size() + 1, vector<int>(s2.size() + 1, 0));
// s1 如果变成空串的最小删除ASCLL值综合
for (int i = 1; i <= s1.size(); i++) dp[i][0] = dp[i - 1][0] + s1[i - 1];
// s2 如果变成空串的最小删除ASCLL值综合
for (int j = 1; j <= s2.size(); j++) dp[0][j] = dp[0][j - 1] + s2[j - 1];
for (int i = 1; i <= s1.size(); i++) {
for (int j = 1; j <= s2.size(); j++) {
if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
else dp[i][j] = min(dp[i - 1][j] + s1[i - 1], dp[i][j - 1] + s2[j - 1]);
}
}
cout << dp[s1.size()][s2.size()] << endl;
}
```

View File

@ -0,0 +1,56 @@
算是贪心
整体思路是移动到中间位置(中位数),一定是 移动次数最小的。
有一个数可以不改变,对数组排序之后, 最小数 和 最大数 一定是移动次数最多的,所以分别保留最小 和 最大的不变。
中间可能有两个位置,所以要计算中间偏前 和 中间偏后的
代码如下:
```CPP
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<long> arr(n);
for (int i = 0; i < n; ++i) {
cin >> arr[i];
}
sort(arr.begin(), arr.end());
if (arr[0] == arr[n - 1]) {
cout << 1 << endl;
return 0;
}
long cnt = 0L;
long cnt1 = 0L;
// 如果要保留一个不改变,要不不改最小的,要不不改最大的。
// 取中间偏前的位置
long mid = arr[(n - 2) / 2];
// 不改最大的
for (int i = 0; i < n - 1; i++) {
cnt += abs(arr[i] - mid);
}
// 取中间偏后的位置
mid = arr[n / 2];
// 不改最小的
for (int i = 1; i < n; i++) {
cnt1 += abs(arr[i] - mid);
}
cout << min(cnt, cnt1) << endl;
return 0;
}
```

View File

@ -1,109 +0,0 @@
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] a = new int[n];
int[] b = new int[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
}
for (int i = 0; i < n; i++) {
b[i] = sc.nextInt();
}
int p = -1, s = -1;
for (int i = 0; i < n; i++) {
if (a[i] == b[i]) p = i;
else break;
}
for (int j = n - 1 ; j >= 0 ; j--) {
if (a[j] == b[j]) s = j;
else break;
}
boolean[][] dp = new boolean[n][n];
int res = 0;
for (int j = 0; j < n; j++) {
for (int i = j ; i >= 0 ; i--) {
if (i == j) dp[i][j] = a[i] == b[i];
else if (i + 1 == j) dp[i][j] = (a[i] == b[j] && a[j] == b[i]);
else {
dp[i][j] = dp[i+1][j-1] && (a[i] == b[j] && a[j] == b[i]);
}
if (dp[i][j] && (i == 0 || p >= i-1) && (j == n - 1 || j+1 >= s)) res++;
}
}
System.out.println(res);
}
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] a = new int[n];
int[] b = new int[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
}
for (int i = 0; i < n; i++) {
b[i] = sc.nextInt();
}
int count = 0;
// 遍历所有可能的区间
for (int left = 0; left < n; left++) {
for (int right = left; right < n; right++) {
// 检查翻转区间 [left, right] a 是否可以变成 b
if (canTransform(a, b, left, right)) {
count++;
}
}
}
System.out.println(count);
}
private static boolean canTransform(int[] a, int[] b, int left, int right) {
// 提前检查翻转区间的值是否可以匹配
for (int i = left, j = right; i <= right; i++, j--) {
if (a[i] != b[j]) {
return false;
}
}
// 检查翻转区间外的值是否匹配
for (int i = 0; i < left; i++) {
if (a[i] != b[i]) {
return false;
}
}
for (int i = right + 1; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
}

View File

@ -1,81 +0,0 @@
```CPP
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
// 定义二叉树节点结构
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
// 根据层序遍历数组构建二叉树
TreeNode* constructBinaryTree(const vector<string>& levelOrder) {
if (levelOrder.empty()) return NULL;
TreeNode* root = new TreeNode(stoi(levelOrder[0]));
queue<TreeNode*> q;
q.push(root);
int i = 1;
while (!q.empty() && i < levelOrder.size()) {
TreeNode* current = q.front();
q.pop();
if (i < levelOrder.size() && levelOrder[i] != "null") {
current->left = new TreeNode(stoi(levelOrder[i]));
q.push(current->left);
}
i++;
if (i < levelOrder.size() && levelOrder[i] != "null") {
current->right = new TreeNode(stoi(levelOrder[i]));
q.push(current->right);
}
i++;
}
return root;
}
int result = 0;
int dfs(TreeNode* node) {
if (node == NULL) return 0;
int leftPath = dfs(node->left);
int rightPath = dfs(node->right);
int leftNum = 0, rightNum = 0;
if (node->left != NULL && node->left->val == node->val) {
leftNum = leftPath + 1;
}
if (node->right != NULL && node->right->val == node->val) {
rightNum = rightPath + 1;
}
result = max(result, leftNum + rightNum);
return max(leftNum, rightNum);
}
int main() {
int n;
cin >> n;
vector<string> levelOrder(n);
for (int i = 0; i < n ; i++) cin >> levelOrder[i];
TreeNode* root = constructBinaryTree(levelOrder);
dfs(root);
cout << result << endl;
return 0;
}
```

View File

@ -117,6 +117,13 @@
相信大家有遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实**真正解决题目的代码都是简洁的,或者有原则性的**,大家可以在这道题目中体会到这一点。 相信大家有遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实**真正解决题目的代码都是简洁的,或者有原则性的**,大家可以在这道题目中体会到这一点。
### 前缀和
> 代码随想录后续补充题目
* [数组:求取区间和](https://programmercarl.com/kamacoder/0058.区间和.html)
前缀和的思路其实很简单,但非常实用,如果没接触过的录友,也很难想到这个解法维度,所以 这是开阔思路 而难度又不高的好题。
## 总结 ## 总结