美团笔试真题——最优二叉树Ⅱ

牛客网公司真题(笔试)系列

2024/3/26

美团2021校招笔试-编程题(通用编程试题,第10场)

一、题目

在这里插入图片描述
在这里插入图片描述

二、未通过测试
import java.util.Scanner;
import java.lang.Math;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] nums = new int[n];
        for(int i = 0; i < n; i++){
            nums[i] = in.nextInt();
        }

        //中序遍历特点:任意节点作为根节点,则其左边所有节点组成左子树,右边所有节点组成右子树
        //思路:三维动态规划,
        //  dp[i][j][k]代表,在子区间nums[i-j]内,以k为根节点的最优子结构
        int[][][] dp = new int[n][n][n];

        //初始化一个节点的子结构
        for(int i = 0; i < n; i++){
            dp[i][i][i] = 0;  //应该为0,不应该是nums[i],那也不对啊,万一整个树只有一个结点呢?
        }
        //初始化两个节点的子结构
        for(int i = 0; i < n-1; i++){
            dp[i][i+1][i] = nums[i] * nums[i+1];
            dp[i][i+1][i+1] = nums[i] * nums[i+1];
        }

        for(int i = 0; i < n-2; i++){  //左边界
            for(int j = i+2; j < n; j++){  //右边界
                for(int k = i; k <= j; k++){   //拟作为根节点的中间节点
                    //左子树最小
                    //这里初始化left需要考虑两点:
                    //1. 如果只先让left = 0(考虑i==k的情况),那下面遍历中间节点时,则left就一直为min的0;所以当i不等于k时,需要先赋一个不为0的值
                    //2. 而既然i不等于k了,那么此时k-1一定 >= i;所以不用加if(k-1 >= i)的判断了
                    int left = i == k ? 0 : dp[i][k-1][i] + nums[i] * nums[k];
                    //若k-1 < i,下面循环自然不会执行,不需要额外加判断
                    for(int x = i; x <= k-1; x++){
                        left = Math.min(left, dp[i][k-1][x] + nums[x] * nums[k]);
                    }

                    //右子树最小
                    int right = k == j ? 0 : dp[k+1][j][k+1] + nums[k] * nums[k+1];
                    for(int y = k+1; y <= j; y++){
                        right = Math.min(right, dp[k+1][j][y] + nums[k] * nums[y]);
                    }

                    //总的最小:左+右
                    dp[i][j][k] = left + right;
                }
            }
        }

        int result = dp[0][n-1][0];
        //遍历整个树的中间节点,寻找最优
        for(int i = 0; i < n; i++){
            result = Math.min(result, dp[0][n-1][i]);
        }

        System.out.println(result);

    }
}

此解法输入:
3
1 2 3
时的输出结果是正确的,输出5
但在牛客acm里面测试不通过,还没找到错误原因…

找到问题所在了:

for(int i = 0; i < n-2; i++){  //左边界
            for(int j = i+2; j < n; j++){  //右边界
                for(int k = i; k <= j; k++){   //拟作为根节点的中间节点

这么遍历是不对的,因为会出现dp[][][]数组赋值顺序有误的问题,导致有些dp在需要的时候没有被计算过,值仍然为0
举个例子:

中序遍历:1 2 3 4
当计算dp[0][3][0]的时候,计算右子树right时,会用到dp[1][3][1],
而由于最外层遍历为i,此时i仍然=0,i=1的dp并没有被计算过,
导致dp[1][3][1]仍然=0,而事实并非如此

正确解法应该按长度从小到大遍历,
这样就能保证在用到左右子序列(长度小于当前长度的子区间)时候,都已经被计算过了

三、终于过了。。。普天同庆!!!!!
import java.util.Scanner;
import java.lang.Math;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int[] nums = new int[n];
        for(int i = 0; i < n; i++){
            nums[i] = in.nextInt();
        }

        //中序遍历特点:任意节点作为根节点,则其左边所有节点组成左子树,右边所有节点组成右子树
        //思路:三维动态规划,
        //  dp[i][j][k]代表,在子区间nums[i-j]内,以k为根节点的最优子结构
        int[][][] dp = new int[n][n][n];

        if(n == 1){
            System.out.println(nums[0]);
            return;
        }

        //初始化一个节点的子结构
        for(int i = 0; i < n; i++){
            dp[i][i][i] = 0;
            //不能是nums[i],而应该是0
            //因为相加的时候,动态规划时加的是边而不应该加节点本身
        }

        //初始化两个节点的子结构
        for(int i = 0; i < n-1; i++){
            dp[i][i+1][i] = nums[i] * nums[i+1];
            dp[i][i+1][i+1] = nums[i] * nums[i+1];
        }

        for(int l = 2; l < n; l++){  //子结构长度 (注意:间距最小是2,因为间距是1的时候即两个节点子结构已经初始化过了;间距最大是n-1,所以要<n)
            for(int i = 0; i < n-l; i++){  //左边界  (也就是 i+l <= n-1,即右边界<=n-1, n-1为数组最右侧下标)
                for(int m = i; m <= i+l; m++){   //拟作为根节点的中间节点
                    //左子树最小
                    //这里初始化left需要考虑两点:
                    //1. 如果只先让left = 0(考虑i==k的情况),那下面遍历中间节点时,则left就一直为min的0;所以当i不等于k时,需要先赋一个不为0的值
                    //2. 而既然i不等于k了,那么此时k-1一定 >= i;所以不用加if(k-1 >= i)的判断了
                    int left = i == m ? 0 : dp[i][m-1][i] + nums[i] * nums[m];
                    //若k-1 < i,下面循环自然不会执行,不需要额外加判断
                    for(int x = i; x <= m-1; x++){
                        left = Math.min(left, dp[i][m-1][x] + nums[x] * nums[m]);
                    }

                    //右子树最小
                    int right = m == i+l ? 0 : dp[m+1][i+l][m+1] + nums[m+1] * nums[m];
                    for(int y = m+1; y <= i+l; y++){
                        right = Math.min(right, dp[m+1][i+l][y] + nums[y] * nums[m]);
                    }

                    //总的最小:左+右
                    dp[i][i+l][m] = left + right;
                }
            }
        }

        int result = dp[0][n-1][0];
        //遍历整个树的中间节点,寻找最优
        for(int i = 0; i < n; i++){
            result = Math.min(result, dp[0][n-1][i]);
        }

        System.out.println(result);

    }
}

总结

一道题调了一上午,难顶…

  • 18
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值