牛客题解 | 小红取数

题目## 题目

题目链接

解题思路

  1. 问题分析:

    • 需要从数组中选择一些数,使其和为 k k k 的倍数且最大
    • 可以转化为求模运算的动态规划问题
    • 对于每个数,可以选择或不选择
  2. 动态规划设计:

    • 状态定义: 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[i1][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[i1][(ja[i]
    • 最终答案: d p [ n ] [ 0 ] dp[n][0] dp[n][0],如果为 0 0 0 则说明无解
  3. 实现要点:

    • 注意处理余数为负数的情况
    • 使用 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. 先访问根节点 1
  2. 然后访问左子树 (2,4,5)
  3. 最后访问右子树 (3,6,7)
    结果:1,2,4,5,3,6,7

中序遍历过程

  1. 先访问左子树 (4,2,5)
  2. 然后访问根节点 1
  3. 最后访问右子树 (6,3,7)
    结果:4,2,5,1,6,3,7

后序遍历过程

  1. 先访问左子树 (4,5,2)
  2. 然后访问右子树 (6,7,3)
  3. 最后访问根节点 1
    结果:4,5,2,6,7,3,1

3. 解题步骤

  1. 创建三个列表分别存储三种遍历结果
  2. 对每种遍历:
    • 如果节点为空,返回
    • 按照对应顺序访问根节点和递归遍历左右子树
  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为树的高度,递归调用栈的空间
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值