之前已经用c++做了一遍,这次再用java来一遍,记录下来,监督自己。
简单题就不记录了,值得回味的记录一下。
1.购物单(动态规划,基于knapsack01问题的拓展)
题目描述
王强今天很开心,公司发给N元的年终奖。王强决定把年终奖用于购物,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 附件
电脑 打印机,扫描仪
书柜 图书
书桌 台灯,文具
工作椅 无
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 0 个、 1 个或 2 个附件。附件不再有从属于自己的附件。王强想买的东西很多,为了不超出预算,他把每件物品规定了一个重要度,分为 5 等:用整数 1 ~ 5 表示,第 5 等最重要。他还从因特网上查到了每件物品的价格(都是 10 元的整数倍)。他希望在不超过 N 元(可以等于 N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第 j 件物品的价格为 v[j] ,重要度为 w[j] ,共选中了 k 件物品,编号依次为 j 1 , j 2 ,……, j k ,则所求的总和为:
v[j 1 ]*w[j 1 ]+v[j 2 ]*w[j 2 ]+ … +v[j k ]*w[j k ] 。(其中 * 为乘号)
请你帮助王强设计一个满足要求的购物单。
输入描述:
输入的第 1 行,为两个正整数,用一个空格隔开:N m
(其中 N ( <32000 )表示总钱数, m ( <60 )为希望购买物品的个数。)
从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数 v p q
(其中 v 表示该物品的价格( v<10000 ), p 表示该物品的重要度( 1 ~ 5 ), q 表示该物品是主件还是附件。如果 q=0 ,表示该物品为主件,如果 q>0 ,表示该物品为附件, q 是所属主件的编号)
输出描述:
输出文件只有一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值( <200000 )。
示例1
输入
复制
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出
复制
2200
解题思路:
1.先看knapsack01,N件物品价值不同,重量不同,现将他们尽可能的装入一个承载量为M公斤的背包里,问能带走的最大价值是多少?
状态转移方程:dp[n][m] = max(dp[n-1][m], dp[n-1][m-w[n]]+v[n])
dp[n-1][m]表示不包含第n件物品,承载量达到m时能获取的最大价值;
dp[n-1][m-w[n]]+v[n]表示包含第n件物品能获得的最大值;
然后两个值再取最大值就是n件商品装入重量上线为m的背包中能带走的最大价值。
2.再看购物车这道题,主件存在,附件才能被购买,所以附件跟主件绑定,单独的附件是不可以直接购买的。一个主件最多有两个附件,所以一个主件能贡献的最大值至多只需要考虑四种情况:买主件、买主件+附件1、买主件+附件2、买主件+附件1+附件2。
状态转移方程:dp[n][m] = max(dp[n-1][m], dp[n-1][m-m[k]]+v[k])
import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int money = sc.nextInt();
int n = sc.nextInt();
MainGood[] goods = new MainGood[n+1];
goods[0] = new MainGood(0,0,0);
for(int i=0;i<n;i++){
int tempP = sc.nextInt();
int tempPri = sc.nextInt();
int tempC = sc.nextInt();
if(tempC > 0){
if(goods[tempC].subCode1 == 0){
goods[tempC].setSubCode1(i+1);
}else{
goods[tempC].setSubCode2(i+1);
}
goods[i+1] = new MainGood(tempP, tempPri, tempC);
}else{
goods[i+1] = new MainGood(tempP, tempPri, tempC);
}
}
int[][] dp = new int[n+1][money+1];
for(int i=1;i<=n;i++){
for(int j=1;j<=money;j++){
if(goods[i].code>0){
dp[i][j] = dp[i-1][j];
}else{
int totalPrice0, totalPrice1, totalPrice2, totalPrice3, v0, v1, v2, v3;
dp[i][j] = dp[i-1][j];
totalPrice0 = goods[i].price;
v0 = totalPrice0 * goods[i].priority;
totalPrice1 = totalPrice0 + goods[goods[i].subCode1].price;
v1 = v0+goods[goods[i].subCode1].price*goods[goods[i].subCode1].priority;
totalPrice2 = totalPrice0+goods[goods[i].subCode2].price;
v2 = v0+goods[goods[i].subCode2].price*goods[goods[i].subCode2].priority;
totalPrice3 = totalPrice0 + goods[goods[i].subCode1].price + goods[goods[i].subCode2].price;
v3 = v0+goods[goods[i].subCode1].price*goods[goods[i].subCode1].priority+goods[goods[i].subCode2].price*goods[goods[i].subCode2].priority;
if(totalPrice0<=j) dp[i][j] = Math.max(dp[i][j], dp[i-1][j-totalPrice0]+v0);
if(totalPrice1<=j) dp[i][j] = Math.max(dp[i][j], dp[i-1][j-totalPrice1]+v1);
if(totalPrice2<=j) dp[i][j] = Math.max(dp[i][j], dp[i-1][j-totalPrice2]+v2);
if(totalPrice3<=j) dp[i][j] = Math.max(dp[i][j], dp[i-1][j-totalPrice3]+v3);
}
}
}
System.out.println(dp[n][money]);
}
}
class MainGood{
int price;
int priority;
int code; //大于0就是附件
int subCode1;
int subCode2;
MainGood(int price, int priority, int code){
this.price = price;
this.priority = priority;
this.code = code;
}
void setSubCode1(int code1){
this.subCode1 = code1;
}
void setSubCode2(int code2){
this.subCode2 = code2;
}
}
2.简单错误记录
开发一个简单错误记录功能小模块,能够记录出错的代码所在的文件名称和行号。
处理:
1、 记录最多8条错误记录,循环记录,最后只用输出最后出现的八条错误记录。对相同的错误记录只记录一条,但是错误计数增加。最后一个斜杠后面的带后缀名的部分(保留最后16位)和行号完全匹配的记录才做算是”相同“的错误记录。
2、 超过16个字符的文件名称,只记录文件的最后有效16个字符;
3、 输入的文件可能带路径,记录文件名称不能带路径。
4、循环记录时,只以第一次出现的顺序为准,后面重复的不会更新它的出现时间,仍以第一次为准
输入描述:
每组只包含一个测试用例。一个测试用例包含一行或多行字符串。每行包括带路径文件名称,行号,以空格隔开。
输出描述:
将所有的记录统计并将结果输出,格式:文件名 代码行数 数目,一个空格隔开,如:
示例1
输入
复制
D:\zwtymj\xccb\ljj\cqzlyaszjvlsjmkwoqijggmybr 645
E:\je\rzuwnjvnuz 633
C:\km\tgjwpb\gy\atl 637
F:\weioj\hadd\connsh\rwyfvzsopsuiqjnr 647
E:\ns\mfwj\wqkoki\eez 648
D:\cfmwafhhgeyawnool 649
E:\czt\opwip\osnll\c 637
G:\nt\f 633
F:\fop\ywzqaop 631
F:\yay\jc\ywzqaop 631
输出
复制
rzuwnjvnuz 633 1
atl 637 1
rwyfvzsopsuiqjnr 647 1
eez 648 1
fmwafhhgeyawnool 649 1
c 637 1
f 633 1
ywzqaop 631 2
注意:这里有个坑,重复的错误可能在最开头,不能只记录最后的八条,不然就会把开头的这个错误又刷新到最后八条里。
import java.util.Scanner;
import java.util.List;
import java.util.ArrayList;
public class Main{
public static void main(String[] args){
List<OneErr> result = new ArrayList<OneErr>();
Scanner sc = new Scanner(System.in);
while(sc.hasNextLine()){
String oneLine = sc.nextLine();
String[] l = oneLine.split(" ");
String path = l[0];
int line = Integer.parseInt(l[1]);
int slashPos = path.lastIndexOf('\\');
path = path.substring(slashPos+1);
if(path.length()>16) path = path.substring(path.length() - 16);
boolean found = false;
for(int i =0;i<result.size();i++){
if(result.get(i).fileName.equals(path) && result.get(i).lineNum == line){
result.get(i).cnt++;
found = true;
break;
}
}
if(!found){
OneErr oneErr = new OneErr(path, line, 1);
result.add(oneErr);
}
}
int j = result.size()>=8?(result.size()-8):0;
for(;j<result.size();j++)
System.out.printf("%s %d %d\n", result.get(j).fileName, result.get(j).lineNum, result.get(j).cnt);
}
}
class OneErr{
String fileName;
int lineNum;
int cnt;
public OneErr(String f, int l, int c){
this.fileName = f;
this.lineNum = l;
this.cnt = c;
}
}
3.合唱队
题目描述
计算最少出列多少位同学,使得剩下的同学排成合唱队形
说明:
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足存在i(1<=i<=K)使得T1<T2<…<Ti-1Ti+1>…>TK。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
注意不允许改变队列元素的先后顺序
请注意处理多组输入输出!
输入描述:
整数N
输出描述:
最少需要几位同学出列
示例1
输入
8
186 186 150 200 160 130 197 200
输出
4
注意:
1.先递增后递减的一个子列(不是连续子串),重点就是不是连续的;
2.最后输出的是去除的人数,不是留在队伍中的人数;
3.方法就是求一个非连续最长递增子列,然后反过来来一遍,再两两相加减去一,得到最长递增递减非连续子列,然后总长度减去这个长度。
import java.util.Scanner;
public class Main{
public static void main(String[] args){
int n;
Scanner sc = new Scanner(System.in);
while(sc.hasNextInt()){
n = sc.nextInt();
int[] arr = new int[n];
int[] dpLR = new int[n];
int[] dpRL = new int[n];
for(int i=0;i<n;i++){
int temp = sc.nextInt();
arr[i] = temp;
dpLR[i] = 1;
for(int j=0;j<i;j++){
if(arr[i]>arr[j]) dpLR[i] = Math.max(dpLR[i], dpLR[j]+1);
}
}
for(int i=n-1;i>=0;i--){
dpRL[i] = 1;
for(int k = i+1;k<n;k++){
if(arr[k]<arr[i]) dpRL[i] = Math.max(dpRL[i], dpRL[k]+1);
}
}
int result = 1;
for(int i=0;i<n;i++){
if(dpLR[i]+dpRL[i] - 1>result) result = dpLR[i]+dpRL[i]-1;
}
System.out.println(n - result);
}
}
}
4.前序遍历和中序遍历确定一棵二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
示例1
输入
复制
[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]
返回值
复制
{1,2,5,3,4,6,7}
注意:
1.子树只有一个节点或者pr-pl<0的场景要考虑到。
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
//1.用前序遍历和中序遍历重构二叉树;2.用层序遍历储存到数组中
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
int len = pre.length;
TreeNode root = generateBinaryTree(pre, in, 0, len-1, 0, len-1);
return root;
}
public TreeNode generateBinaryTree(int[] pre, int[] in, int pl, int pr, int il, int ir){
if(pr - pl<0 || ir - il<0) return null;
if(pl == pr) return new TreeNode(pre[pl]);
int val = pre[pl];
int rootIdx = getIdxInOrderTraversal(val, in);
TreeNode root = new TreeNode(val);
//递归生成左子树
int pll, plr, ill, ilr, prl, prr, irl, irr;
pll = pl+1;
ilr = rootIdx - 1;
ill = il;
plr = pll + (ilr - ill);
root.left = generateBinaryTree(pre, in, pll, plr, ill, ilr);
//递归生成右子树
prr = pr;
irl = rootIdx + 1;
irr = ir;
prl = prr - (irr - irl);
root.right = generateBinaryTree(pre, in, prl, prr, irl, irr);
return root;
}
private int getIdxInOrderTraversal(int value, int[] in){
for(int i=0;i<in.length;i++)
if(value == in[i]) return i;
return -1;
}
}