牛客网公司真题(笔试)系列
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);
}
}
总结
一道题调了一上午,难顶…