diff --git a/README.md b/README.md index 19d1bebb..96852d70 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,9 @@ 5. [数组:209.长度最小的子数组](./problems/0209.长度最小的子数组.md) 6. [数组:区间和](./problems/kamacoder/0058.区间和.md) 6. [数组:59.螺旋矩阵II](./problems/0059.螺旋矩阵II.md) -7. [数组:总结篇](./problems/数组总结篇.md) +7. [数组:区间和](./problems/kamacoder/0058.区间和.md) +8. [数组:开发商购买土地](./problems/kamacoder/0044.开发商购买土地.md) +9. [数组:总结篇](./problems/数组总结篇.md) ## 链表 diff --git a/problems/kamacoder/0044.开发商购买土地.md b/problems/kamacoder/0044.开发商购买土地.md index 73e9e8c5..37bb98ed 100644 --- a/problems/kamacoder/0044.开发商购买土地.md +++ b/problems/kamacoder/0044.开发商购买土地.md @@ -1,6 +1,10 @@ # 44. 开发商购买土地 +> 本题为代码随想录后续扩充题目,还没有视频讲解,顺便让大家练习一下ACM输入输出模式(笔试面试必备) + +[题目链接](https://kamacoder.com/problempage.php?pid=1044) + 【题目描述】 在一个城市区域内,被划分成了n * m个连续的区块,每个区块都拥有不同的权值,代表着其土地价值。目前,有两家开发公司,A 公司和 B 公司,希望购买这个城市区域的土地。 @@ -57,7 +61,7 @@ 如果本题要求 任何两个行(或者列)之间的数值总和,大家在[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) 的分析,使用 前缀和 要注意 区间左右边的开闭情况。 diff --git a/problems/kamacoder/0058.区间和.md b/problems/kamacoder/0058.区间和.md index 0e478c68..f5ce08dc 100644 --- a/problems/kamacoder/0058.区间和.md +++ b/problems/kamacoder/0058.区间和.md @@ -1,6 +1,8 @@ # 58. 区间和 +> 本题为代码随想录后续扩充题目,还没有视频讲解,顺便让大家练习一下ACM输入输出模式(笔试面试必备) + [题目链接](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 之间的累加和吗。 @@ -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) -p[5] - p[1] 就是 红色部分的区间和。 +`p[5] - p[1]` 就是 红色部分的区间和。 -而 p 数组是我们之前就计算好的累加和,所以后面每次求区间和的之后 我们只需要 O(1)的操作。 +而 p 数组是我们之前就计算好的累加和,所以后面每次求区间和的之后 我们只需要 O(1) 的操作。 **特别注意**: 在使用前缀和求解的时候,要特别注意 求解区间。 如上图,如果我们要求 区间下标 [2, 5] 的区间和,那么应该是 p[5] - p[1],而不是 p[5] - p[2]。 -很多录友在使用前缀和的时候,分不清前缀和的区间,建议画一画图,模拟一下 思路会更清晰。 +**很多录友在使用前缀和的时候,分不清前缀和的区间,建议画一画图,模拟一下 思路会更清晰**。 + +本题C++代码如下: ```CPP #include diff --git a/problems/kamacoder/0098.所有可达路径.md b/problems/kamacoder/0098.所有可达路径.md index 073d1a2e..5e87cd7e 100644 --- a/problems/kamacoder/0098.所有可达路径.md +++ b/problems/kamacoder/0098.所有可达路径.md @@ -422,7 +422,8 @@ int main() { ## 其他语言版本 ### Java -#### 邻接矩阵写法 + +邻接矩阵写法 ```java import java.util.ArrayList; import java.util.List; @@ -477,7 +478,7 @@ public class Main { } ``` -#### 邻接表写法 +邻接表写法 ```java import java.util.ArrayList; import java.util.LinkedList; @@ -533,7 +534,7 @@ public class Main { } ``` ### Python -#### 邻接矩阵写法 +邻接矩阵写法 ``` python def dfs(graph, x, n, path, result): if x == n: @@ -566,7 +567,7 @@ if __name__ == "__main__": main() ``` -#### 邻接表写法 +邻接表写法 ``` python from collections import defaultdict diff --git a/problems/kamacoder/0109.冗余连接II.md b/problems/kamacoder/0109.冗余连接II.md index 9c2f4039..80fe8777 100644 --- a/problems/kamacoder/0109.冗余连接II.md +++ b/problems/kamacoder/0109.冗余连接II.md @@ -225,6 +225,7 @@ int main() { vec.push_back(i); } } + // 情况一、情况二 if (vec.size() > 0) { // 放在vec里的边已经按照倒叙放的,所以这里就优先删vec[0]这条边 if (isTreeAfterRemoveEdge(edges, vec[0])) { diff --git a/problems/kamacoder/0113.国际象棋.md b/problems/kamacoder/0113.国际象棋.md new file mode 100644 index 00000000..966aced3 --- /dev/null +++ b/problems/kamacoder/0113.国际象棋.md @@ -0,0 +1,59 @@ + +# 113.国际象棋 + +广搜,但本题如果广搜枚举马和象的话会超时。 + +广搜要只枚举马的走位,同时判断是否在对角巷直接走象 + +```CPP +#include +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; +} + +``` diff --git a/problems/kamacoder/0121.小红的区间翻转.md b/problems/kamacoder/0121.小红的区间翻转.md new file mode 100644 index 00000000..6e10aab7 --- /dev/null +++ b/problems/kamacoder/0121.小红的区间翻转.md @@ -0,0 +1,132 @@ + +# 121. 小红的区间翻转 + +比较暴力的方式,就是直接模拟, 枚举所有 区间,然后检查其翻转的情况。 + +在检查翻转的时候,需要一些代码优化,否则容易超时。 + +```CPP +#include +#include +using namespace std; + +bool canTransform(const vector& a, const vector& 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 a(n); + vector 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 +#include + +using namespace std; + +int main() { + int n; + cin >> n; + vector 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 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; +} +``` diff --git a/problems/kamacoder/0142.两个字符串的最小ASCII删除总和.md b/problems/kamacoder/0142.两个字符串的最小ASCII删除总和.md new file mode 100644 index 00000000..ff34581f --- /dev/null +++ b/problems/kamacoder/0142.两个字符串的最小ASCII删除总和.md @@ -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 +#include +using namespace std; +int main() { + string s1, s2; + cin >> s1 >> s2; + vector> dp(s1.size() + 1, vector(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)) +``` diff --git a/problems/kamacoder/0143.最长同值路径.md b/problems/kamacoder/0143.最长同值路径.md new file mode 100644 index 00000000..bf46c895 --- /dev/null +++ b/problems/kamacoder/0143.最长同值路径.md @@ -0,0 +1,237 @@ + + +# 143. 最长同值路径 + + +本题两个考点: + +1. 层序遍历构造二叉树 +2. 树形dp,找出最长路径 + +对于写代码不多,或者动手能力比较差的录友,第一个 构造二叉树 基本就被卡主了。 + +```CPP +#include +#include +#include + +using namespace std; + +// 定义二叉树节点结构 +struct TreeNode { + int val; + TreeNode* left; + TreeNode* right; + TreeNode(int x) : val(x), left(NULL), right(NULL) {} +}; + +// 根据层序遍历数组构建二叉树 +TreeNode* constructBinaryTree(const vector& levelOrder) { + if (levelOrder.empty()) return NULL; + + TreeNode* root = new TreeNode(stoi(levelOrder[0])); + queue 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 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 levelOrder) { + if (levelOrder.isEmpty()) return null; + + TreeNode root = new TreeNode(Integer.parseInt(levelOrder.get(0))); + Queue 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 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) + + +``` diff --git a/problems/kamacoder/0144.字典序最小的01字符串.md b/problems/kamacoder/0144.字典序最小的01字符串.md new file mode 100644 index 00000000..1528fdbd --- /dev/null +++ b/problems/kamacoder/0144.字典序最小的01字符串.md @@ -0,0 +1,66 @@ + +# 0144.字典序最小的01字符串 + +贪心思路:移动尽可能 移动前面的1 ,这样可以是 字典序最小 + +从前到后遍历,遇到 0 ,就用前面的 1 来交换 + +```CPP +#include +#include +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)); + } +} +``` diff --git a/problems/kamacoder/0145.数组子序列的排列.md b/problems/kamacoder/0145.数组子序列的排列.md new file mode 100644 index 00000000..757fe0b2 --- /dev/null +++ b/problems/kamacoder/0145.数组子序列的排列.md @@ -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 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 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() + +``` diff --git a/problems/kamacoder/0146.传送树.md b/problems/kamacoder/0146.传送树.md new file mode 100644 index 00000000..4cafb040 --- /dev/null +++ b/problems/kamacoder/0146.传送树.md @@ -0,0 +1,65 @@ + + + + +# 146. 传送树 + +本题题意是比较绕的,我后面给补上了 【提示信息】对 题目输出样例讲解一下,相对会容易理解的多。 + +```CPP +#include +#include +#include +using namespace std; + +vector> edge; // 邻接表来存图 +vector 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) << ' '; + } +} + +``` diff --git a/problems/kamacoder/0147.三珠互斥.md b/problems/kamacoder/0147.三珠互斥.md new file mode 100644 index 00000000..ed14b3ce --- /dev/null +++ b/problems/kamacoder/0147.三珠互斥.md @@ -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 +using namespace std; + +int main(){ + int t; + cin >> t; + int n, k, a1, a2, a3; + vector 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; +} +``` diff --git a/problems/kamacoder/0148.扑克牌同花顺.md b/problems/kamacoder/0148.扑克牌同花顺.md new file mode 100644 index 00000000..b5d27a24 --- /dev/null +++ b/problems/kamacoder/0148.扑克牌同花顺.md @@ -0,0 +1,53 @@ + +首先我们要定义一个结构体,来存放我们的数据 + +`map<花色,{同一花色牌集合,同一花色的牌对应的牌数量}>` + +再遍历 每一个花色下,每一个牌 的数量 + +代码如下详细注释: + + +```CPP +#include +using namespace std; + +string cards[] = {"H","S","D","C"}; +typedef long long ll; +struct color +{ + set st; // 同一花色 牌的集合 + map cnt; // 同一花色 牌对应的数量 +}; +unordered_map 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; +} +``` diff --git a/problems/kamacoder/0150.极长连续段的权值.md b/problems/kamacoder/0150.极长连续段的权值.md new file mode 100644 index 00000000..25fbd781 --- /dev/null +++ b/problems/kamacoder/0150.极长连续段的权值.md @@ -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 +#include +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; +} +``` diff --git a/problems/kamacoder/两个字符串的最小ASCII删除总和.md b/problems/kamacoder/两个字符串的最小ASCII删除总和.md deleted file mode 100644 index 5425a605..00000000 --- a/problems/kamacoder/两个字符串的最小ASCII删除总和.md +++ /dev/null @@ -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 -#include -using namespace std; -int main() { - string s1, s2; - cin >> s1 >> s2; - vector> dp(s1.size() + 1, vector(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; - -} -``` - diff --git a/problems/kamacoder/好数组.md b/problems/kamacoder/好数组.md new file mode 100644 index 00000000..55853fa8 --- /dev/null +++ b/problems/kamacoder/好数组.md @@ -0,0 +1,56 @@ + + +算是贪心 + +整体思路是移动到中间位置(中位数),一定是 移动次数最小的。 + +有一个数可以不改变,对数组排序之后, 最小数 和 最大数 一定是移动次数最多的,所以分别保留最小 和 最大的不变。 + +中间可能有两个位置,所以要计算中间偏前 和 中间偏后的 + +代码如下: + +```CPP + +#include + +using namespace std; + +int main() { + int n; + cin >> n; + vector 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; +} +``` diff --git a/problems/kamacoder/小红的区间翻转.md b/problems/kamacoder/小红的区间翻转.md deleted file mode 100644 index fd9f6ab3..00000000 --- a/problems/kamacoder/小红的区间翻转.md +++ /dev/null @@ -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; - } -} diff --git a/problems/kamacoder/最长同值路径.md b/problems/kamacoder/最长同值路径.md deleted file mode 100644 index 68aeb845..00000000 --- a/problems/kamacoder/最长同值路径.md +++ /dev/null @@ -1,81 +0,0 @@ - - - - -```CPP -#include -#include -#include - -using namespace std; - -// 定义二叉树节点结构 -struct TreeNode { - int val; - TreeNode* left; - TreeNode* right; - TreeNode(int x) : val(x), left(NULL), right(NULL) {} -}; - -// 根据层序遍历数组构建二叉树 -TreeNode* constructBinaryTree(const vector& levelOrder) { - if (levelOrder.empty()) return NULL; - - TreeNode* root = new TreeNode(stoi(levelOrder[0])); - queue 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 levelOrder(n); - for (int i = 0; i < n ; i++) cin >> levelOrder[i]; - - TreeNode* root = constructBinaryTree(levelOrder); - dfs(root); - cout << result << endl; - - return 0; -} -``` - diff --git a/problems/数组总结篇.md b/problems/数组总结篇.md index f026e41b..7c2fd947 100644 --- a/problems/数组总结篇.md +++ b/problems/数组总结篇.md @@ -117,6 +117,13 @@ 相信大家有遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实**真正解决题目的代码都是简洁的,或者有原则性的**,大家可以在这道题目中体会到这一点。 +### 前缀和 + +> 代码随想录后续补充题目 + +* [数组:求取区间和](https://programmercarl.com/kamacoder/0058.区间和.html) + +前缀和的思路其实很简单,但非常实用,如果没接触过的录友,也很难想到这个解法维度,所以 这是开阔思路 而难度又不高的好题。 ## 总结