题目## 题目
解题思路
-
问题分析:
- 需要从数组中选择一些数,使其和为 k k k 的倍数且最大
- 可以转化为求模运算的动态规划问题
- 对于每个数,可以选择或不选择
-
动态规划设计:
- 状态定义: d p [ i ] [ j ] dp[i][j] dp[i][j] 表示考虑前 i i i 个数,和除以 k k k 余 j j j 时的最大值
- 状态转移:
- 不选第 i i i 个数: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i−1][j]
- 选第 i i i 个数: d p [ i ] [ j ] = d p [ i − 1 ] [ ( j − a [ i ] dp[i][j] = dp[i-1][(j-a[i]%k+k)%k] + a[i] dp[i][j]=dp[i−1][(j−a[i]
- 最终答案: d p [ n ] [ 0 ] dp[n][0] dp[n][0],如果为 0 0 0 则说明无解
-
实现要点:
- 注意处理余数为负数的情况
- 使用 l o n g long long l o n g long long 防止溢出
- 初始化时要设置为负无穷,避免无解情况的干扰
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
long long maxKDivisibleSum(int n, int k, vector<int>& nums) {
// dp[i][j]表示前i个数中选择一些数,和除以k余j的最大值
vector<vector<long long>> dp(n + 1, vector<long long>(k, -1e18));
dp[0][0] = 0; // 初始状态
// 遍历每个数
for(int i = 0; i < n; i++) {
for(int j = 0; j < k; j++) {
if(dp[i][j] == -1e18) continue;
// 不选第i个数
dp[i+1][j] = max(dp[i+1][j], dp[i][j]);
// 选第i个数
int new_mod = (j + nums[i] % k) % k;
dp[i+1][new_mod] = max(dp[i+1][new_mod], dp[i][j] + nums[i]);
}
}
return dp[n][0] <= 0 ? -1 : dp[n][0];
}
int main() {
int n, k;
cin >> n >> k;
vector<int> nums(n);
for(int i = 0; i < n; i++) {
cin >> nums[i];
}
cout << maxKDivisibleSum(n, k, nums) << endl;
return 0;
}
import java.util.*;
public class Main {
public static long maxKDivisibleSum(int n, int k, long[] nums) {
// dp[j]表示余数为j的最大和
long[] dp = new long[k];
long[] newDp = new long[k];
Arrays.fill(dp, Long.MIN_VALUE);
dp[0] = 0; // 初始状态
// 遍历每个数
for(int i = 0; i < n; i++) {
Arrays.fill(newDp, Long.MIN_VALUE);
for(int j = 0; j < k; j++) {
if(dp[j] == Long.MIN_VALUE) continue;
// 不选第i个数
newDp[j] = Math.max(newDp[j], dp[j]);
// 选第i个数
int new_mod = (int)((j + nums[i] % k) % k + k) % k; // 处理负数
newDp[new_mod] = Math.max(newDp[new_mod], dp[j] + nums[i]);
}
// 更新dp数组
long[] temp = dp;
dp = newDp;
newDp = temp;
}
return dp[0] <= 0 ? -1 : dp[0];
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
long[] nums = new long[n];
for(int i = 0; i < n; i++) {
nums[i] = sc.nextLong(); // 使用nextLong()而不是nextInt()
}
System.out.println(maxKDivisibleSum(n, k, nums));
sc.close();
}
}
def max_k_divisible_sum(n: int, k: int, nums: list) -> int:
# dp[i][j]表示前i个数中选择一些数,和除以k余j的最大值
dp = [[-float('inf')] * k for _ in range(n + 1)]
dp[0][0] = 0 # 初始状态
# 遍历每个数
for i in range(n):
for j in range(k):
if dp[i][j] == -float('inf'):
continue
# 不选第i个数
dp[i+1][j] = max(dp[i+1][j], dp[i][j])
# 选第i个数
new_mod = (j + nums[i] % k) % k
dp[i+1][new_mod] = max(dp[i+1][new_mod], dp[i][j] + nums[i])
return -1 if dp[n][0] <= 0 else dp[n][0]
def main():
n, k = map(int, input().split())
nums = list(map(int, input().split()))
print(max_k_divisible_sum(n, k, nums))
if __name__ == "__main__":
main()
算法及复杂度
- 算法:动态规划
- 时间复杂度: O ( n × k ) \mathcal{O}(n \times k) O(n×k),其中 n n n 是数组长度, k k k 是给定的除数
- 空间复杂度: O ( n × k ) \mathcal{O}(n \times k) O(n×k),用于存储 d p dp dp 数组
解题思路
1. 基本概念
二叉树的三种遍历方式是按照访问根节点的顺序来定义的:
- 前序遍历:根节点 -> 左子树 -> 右子树
- 中序遍历:左子树 -> 根节点 -> 右子树
- 后序遍历:左子树 -> 右子树 -> 根节点
2. 图解示例
1
/ \
2 3
/ \ / \
4 5 6 7
前序遍历过程:
- 先访问根节点 1
- 然后访问左子树 (2,4,5)
- 最后访问右子树 (3,6,7)
结果:1,2,4,5,3,6,7
中序遍历过程:
- 先访问左子树 (4,2,5)
- 然后访问根节点 1
- 最后访问右子树 (6,3,7)
结果:4,2,5,1,6,3,7
后序遍历过程:
- 先访问左子树 (4,5,2)
- 然后访问右子树 (6,7,3)
- 最后访问根节点 1
结果:4,5,2,6,7,3,1
3. 解题步骤
- 创建三个列表分别存储三种遍历结果
- 对每种遍历:
- 如果节点为空,返回
- 按照对应顺序访问根节点和递归遍历左右子树
- 将三个列表组合成二维数组返回
4. 注意事项
- 处理空树的情况
- 注意递归的终止条件
- 确保遍历顺序正确
- 注意左右子树的访问顺序
代码
class Solution {
public:
vector<int> preOrder, inOrder, postOrder; // 存储三种遍历结果
// 前序遍历:根->左->右
void preOrderTraversal(TreeNode* root) {
if (!root) return;
preOrder.push_back(root->val); // 先访问根节点
preOrderTraversal(root->left); // 再遍历左子树
preOrderTraversal(root->right); // 最后遍历右子树
}
// 中序遍历:左->根->右
void inOrderTraversal(TreeNode* root) {
if (!root) return;
inOrderTraversal(root->left); // 先遍历左子树
inOrder.push_back(root->val); // 再访问根节点
inOrderTraversal(root->right); // 最后遍历右子树
}
// 后序遍历:左->右->根
void postOrderTraversal(TreeNode* root) {
if (!root) return;
postOrderTraversal(root->left); // 先遍历左子树
postOrderTraversal(root->right); // 再遍历右子树
postOrder.push_back(root->val); // 最后访问根节点
}
vector<vector<int>> threeOrders(TreeNode* root) {
// 清空结果数组
preOrder.clear();
inOrder.clear();
postOrder.clear();
// 执行三种遍历
preOrderTraversal(root);
inOrderTraversal(root);
postOrderTraversal(root);
// 返回结果
return {preOrder, inOrder, postOrder};
}
};
import java.util.*;
public class Solution {
private List<Integer> preOrder = new ArrayList<>();
private List<Integer> inOrder = new ArrayList<>();
private List<Integer> postOrder = new ArrayList<>();
// 前序遍历
private void preOrderTraversal(TreeNode root) {
if (root == null) return;
preOrder.add(root.val);
preOrderTraversal(root.left);
preOrderTraversal(root.right);
}
// 中序遍历
private void inOrderTraversal(TreeNode root) {
if (root == null) return;
inOrderTraversal(root.left);
inOrder.add(root.val);
inOrderTraversal(root.right);
}
// 后序遍历
private void postOrderTraversal(TreeNode root) {
if (root == null) return;
postOrderTraversal(root.left);
postOrderTraversal(root.right);
postOrder.add(root.val);
}
public int[][] threeOrders(TreeNode root) {
// 清空结果列表
preOrder.clear();
inOrder.clear();
postOrder.clear();
// 执行三种遍历
preOrderTraversal(root);
inOrderTraversal(root);
postOrderTraversal(root);
// 转换为二维数组
int[][] result = new int[3][];
result[0] = preOrder.stream().mapToInt(Integer::intValue).toArray();
result[1] = inOrder.stream().mapToInt(Integer::intValue).toArray();
result[2] = postOrder.stream().mapToInt(Integer::intValue).toArray();
return result;
}
}
class Solution:
def threeOrders(self, root: TreeNode) -> List[List[int]]:
pre_order = []
in_order = []
post_order = []
# 前序遍历
def pre_traverse(node):
if not node:
return
pre_order.append(node.val) # 根
pre_traverse(node.left) # 左
pre_traverse(node.right) # 右
# 中序遍历
def in_traverse(node):
if not node:
return
in_traverse(node.left) # 左
in_order.append(node.val) # 根
in_traverse(node.right) # 右
# 后序遍历
def post_traverse(node):
if not node:
return
post_traverse(node.left) # 左
post_traverse(node.right) # 右
post_order.append(node.val) # 根
# 执行三种遍历
pre_traverse(root)
in_traverse(root)
post_traverse(root)
return [pre_order, in_order, post_order]
算法及复杂度分析
- 算法:递归,树的遍历
- 时间复杂度: O ( n ) \mathcal{O}(n) O(n),每个节点都需要访问一次
- 空间复杂度: O ( h ) \mathcal{O}(h) O(h),h为树的高度,递归调用栈的空间
1282

被折叠的 条评论
为什么被折叠?



