字符串
IP合法问题的判断
题目描述:
判断IPv4的地址是否合法
解题思路:
逻辑上需要做如下判断:
(1)安装’.'来切割字符串,注意java中使用split("\.")需要使用转译字符
(2)判断字符是否是合法的数字,连空格都需要考虑在内。
(3)使用’.'切割的字符串的个数只能为4;
另外,作为牛客网系统,在处理输入和输出的时候,要格外注意,其内部使用了多个测试案例,因此需要使用whlie()循环来不断读取测试数据,否则报错,第二,在需要考虑字符串中间有空格时,建议使用java.util.Scanner工具,以空格为单位来间接读取输入。
给出一段参考程序(可直接运行在牛客网的测评系统中)。
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner in = new Scanner(System.in);
while(in.hasNext()){
String s = in.nextLine();
if (isNormalIP(s)) {
System.out.println("YES");
}else {
System.out.println("NO");
}
}
}
// strs中只有[0..9]和 .
private static boolean isNormalIP(String strs) {
String[] ip = strs.split("\\.");//转译\
if (ip.length != 4) {
return false;
}
for (int i = 0; i < ip.length; i++) {
if (!isNumric(ip[i])) {
return false;
}
int tmp = Integer.parseInt(ip[i]);
if (tmp < 0 || tmp > 255) {
return false;
}
}
return true;
}
private static boolean isNumric(String strs) {
for (int i = 0; i < strs.length(); i++) {
char cur = strs.charAt(i);
if (cur < '0' || cur > '9') {
return false;
}
}
return true;
}
}
字符统计
题目描述:
对字符中的各个英文字符(大小写分开统计),数字,空格进行统计,并按照统计个数由多到少输出,如果统计的个数相同,则按照ASII码由小到大排序输出 。如果有其他字符,则对这些字符不用进行统计。
思路分析:
对于统计字符串个数问题,一般采用哈希数组,数组的下标为字符的ASCII值,其对应的值为该从字符的个数。由于需要排序,故,应该先找到最大的个数,对该数组找寻最大值。然后从最大值依次递减,寻找命中的元素,这样下来就能找到依次找到最大,次打,第三大 ,以至最小。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
System.out.println(solution(str));
}
}
public static String solution(String str){
char[] ch=str.toCharArray();
//ACSII一共128个
int[] count=new int[128];
for(int i=0;i<ch.length;i++){
count[ch[i]]++;//统计每个字符的个数
}
//找到最大的个数
int max=0;
for(int i=0;i<count.length;i++){
if(max<count[i]){
max=count[i];
}
}
StringBuilder sb=new StringBuilder();
while(max>0){//为0表示没有出现过
for(int i=0;i<count.length;i++){
if(count[i]==max){
sb.append((char)i);//要将int前制转化为char
}
}
max--;;//除去最大的。
}
return sb.toString();
}
}
字符串排序问题
题目描述:
按字典顺序排序一个字符串,该字符串中均为ACSII字符,排序过程,遵循如下规则:
(1)不区分大小写,大小写按照输入顺序确定。
(2)非字母字符在原来位置
解题思路:
分两部分处理:
(1)字母部分字典排序。字母含有26个,通过与首字母的’a’或’A’的偏移量来表示26个字母,如:a=(char)(‘a’+0)、b=(char)(‘a’+1)、B=(char)(‘A’+1);故可以设置一个索引变量i来表达26个字母。通过这个i的遍历按从小到大的顺序来匹配输入字符子串,如果有则保存到中间容器中。直到结束。
(2)处理非字母字符,仍然是依次遍历输入的字符串中的字符,如果发现不是字母,则插入到该索引处,这里可以使用一个StringBuilder中的工具方法,sb.insert(i,char),在i位置处插入字符char
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
System.out.println(sortString(str));
}
}
public static String sortString(String str){
char[] ch=str.toCharArray();
StringBuilder sb=new StringBuilder();
//先处理26个英文字母(大小写不区分,按输入顺序排序)
//ASCII中,对应的A-Z为:65-90 a-z为:97-122
for(int i=0;i<26;i++){
for(int j=0;j<ch.length;j++){
if(ch[j]==(i+'a') || ch[j]==(i+'A') ){
//i+'a' 为某个小写字母,i+'A'为某个大写字母
sb.append(ch[j]);
}
}
}
//处理非字母数字
//从ch中依次找到非字母字符,然后插入到原先的位置
for(int i=0;i<ch.length;i++){
//非字母
if(!((ch[i]>='a' && ch[i]<='z') || (ch[i]>='A' && ch[i]<='Z'))){
sb.insert(i,ch[i]);
}
}
return sb.toString();
}
}
字符串合并处理
题目描述:
有两个字符串str1和str2,按如下步骤进行处理:
(1)将两个字符串合并为str3
(2)按照str3的字符索引的奇偶性分别进行升序排序
(3)然后对于该字符串中’0’~‘9’ ‘a’~‘f’ ‘A’~‘F’ 需要在其16进制的基础上进行逆转,若逆转后实是字母还需要并同时转化为大写字母的形式。
解题思路:
题目的解题步骤如上所示,但是有几个库函数值得我们去学习:
(1)Integer.toHexString()//将整型转化为16进制类型的数据,注意转化后不一定为4bit,因为前制的0会省略,需要额外处理
(2)当已知一个小写字符:ch,使用:(ch-‘a’)+'A’可以转化为大写
(3)将字符数组转化为char[] ch 转化为字符串:String.valueOf(ch);
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String[] strs=str.split(" ");
System.out.println(MergeString(strs[0],strs[1]));
}
}
public static String MergeString(String str1,String str2){
String str=str1+str2;
ArrayList<Character> event=new ArrayList<>();
ArrayList<Character> odd=new ArrayList<>();
char[] chars=str.toCharArray();
//拆分奇偶
for(int i=0;i<chars.length;i++){
if(i%2==0){
event.add(chars[i]);
}else{
odd.add(chars[i]);
}
}
//分别排序
Collections.sort(event);
Collections.sort(odd);
//合并与处理
int eventIndex=0;
int oddIndex=0;
for(int i=0;i<chars.length;i++){
char ch;
if(i%2==0){
ch=event.get(eventIndex++);
}else{
ch=odd.get(oddIndex++);
}
if((ch>='0' && ch<='9') || ( ch>='a' && ch<='f') || (ch>='A' && ch<='F') ){
//16进制的翻转处理
chars[i]=reverseChar(ch);
}else{
chars[i]=ch;
}
}
//生成字符串
return String.valueOf(chars);
}
public static char reverseChar(char c){
String res;
if (c >= 'a' && c <= 'f') {
res = Integer.toBinaryString(c - 'a' + 10);
} else if (c >= 'A' && c <= 'F') {
res = Integer.toBinaryString(c-'A'+10);
} else {
res = Integer.toBinaryString(c - '0');
}
while (res.length() < 4) { //不是所有的数字转换后刚好为4位,这个在其他位转化中也同样使用
//例如char的范围为0-255,那么如果为想转化为8位的话,0只有1位而3只有2位为11
res = "0" + res;
}
int result = 0;
for (int i = 0; i < res.length(); i++) {
result += (res.charAt(i) - '0') * (Math.pow(2, i));//翻转后的原值
}
char ch;
if (result > 9) {
ch = (char) (result - 10 + 'a');
} else {
ch = (char) (result + '0');
}
if (ch >= 'a' && ch <= 'z') ch = (char) (ch - 'a' + 'A');
return ch;
}
}
单词倒排
题目描述:
对字符串中的所有单词进行倒排。
说明:
1、构成单词的字符只有26个大写或小写英文字母;
2、非构成单词的字符均视为单词间隔符;
3、要求倒排后的单词间隔符以一个空格表示;如果原字符串中相邻单词间有多个间隔符时,倒排转换后也只允许出现一个空格间隔符;
4、每个单词最长20个字母;
解题思路:
(1)用一个StringBuilder提取出所有的字母,将非字母(包括空格本身)用空格代替
(2)将上面的字符串按照空格split()成字符串数组
(3)注意,需要为字符串数组中的每个字符串去除空格,因为单词后面只有一个空格要么没有。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
System.out.println(processString(str));
}
}
public static String processString(String str){
StringBuilder sb=new StringBuilder();
StringBuilder sb2=new StringBuilder();
for(int i=0;i<str.length();i++){
char c=str.charAt(i);
if((c>='a' && c<='z') || (c>='A' && c<='Z')){
sb.append(c);
}else{
sb.append(' ');
}
}
String[] strs=sb.toString().trim().split(" ");
for(int i=strs.length-1;i>=0;i--){
if(i==0){
sb2.append(strs[i].trim());//去除空格
}else{
if(!(strs[i].equals(" "))){
sb2.append(strs[i].trim()+" ");
}
}
}
return sb2.toString();
}
}
字符分类处理
题目描述:
从R依次中取出R< i>,对I进行处理,找到满足条件的I< j>:
I< j>整数对应的数字需要连续包含R< i>对应的数字。比如R< i>为23,I< j>为231,那么I< j>包含了R< i>,条件满足 。
按R< i>从小到大的顺序:
(1)先输出R< i>;
(2)再输出满足条件的I< j>的个数;
(3)然后输出满足条件的I< j>在I序列中的位置索引(从0开始);
(4)最后再输出I< j>。
附加条件:
(1)R< i>需要从小到大排序。相同的R< i>只需要输出索引小的以及满足条件的I< j>,索引大的需要过滤掉
(2)如果没有满足条件的I< j>,对应的R< i>不用输出
(3)最后需要在输出序列的第一个整数位置记录后续整数序列的个数(不包含“个数”本身)
序列I:15,123,456,786,453,46,7,5,3,665,453456,745,456,786,453,123(第一个15表明后续有15个整数)
序列R:5,6,3,6,3,0(第一个5表明后续有5个整数)
输出:30, 3,6,0,123,3,453,7,3,9,453456,13,453,14,123,6,7,1,456,2,786,4,46,8,665,9,453456,11,456,12,786
说明:
30----后续有30个整数
3----从小到大排序,第一个R< i>为0,但没有满足条件的I< j>,不输出0,而下一个R< i>是3
6— 存在6个包含3的I< j>
0— 123所在的原序号为0
123— 123包含3,满足条件
解题思路:
整体思路很简单,主要要会使用一些库函数:
(1)对一个数组即可以排序,又可以去重,TreeSet
(2)数据预处理:将I和R数据集的前缀剥离,获得一个真正数据集合
(3)使用TreeSet处理R数据集合,使其成为有序不重复的数据
(4)使用库函数,String中的str1.contains(str2),判断str1是否包含str2
(5)实际上可以用Map来存储< indexR,I>;
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
String[] strI = str.split(" ");
int[] I = new int[Integer.parseInt(strI[0])];
// 获取I数据
for (int i = 1, k = 0; i < strI.length; i++, k++) {
I[k] = Integer.parseInt(strI[i]);
}
String[] strR = br.readLine().split(" ");
int[] R = new int[Integer.parseInt(strR[0])];
// 获取R数据
for (int i = 1, k = 0; i < strR.length; i++, k++) {
R[k] = Integer.parseInt(strR[i]);
}
// 处理
List<List<Data>> res = process(I, R);
print(res);
}
}
public static class Data {
int R;// R[i]
int I;
int indexI;// 数据在I中的索引;
public Data(int R, int indexI, int I) {
this.R = R;
this.indexI = indexI;
this.I = I;
}
}
// 数字num1中是否连续包含num2
public static boolean isContains(int num1, int num2) {
String str1 = num1 + "";
String str2 = num2 + "";
return str1.contains(str2) ? true : false;
}
public static List<List<Data>> process(int[] I, int[] R) {
List<List<Data>> res = new LinkedList<>();
Set<Integer> RSet = new TreeSet<>();
// 2.使用TreeSet可以排序并且去重
for (int i = 0; i < R.length; i++) {
RSet.add(R[i]);
}
for (Integer r : RSet) {
List<Data> list = new LinkedList<Data>();
for (int j = 0; j < I.length; j++) {
// I[j]中连续包含R[i]
if (isContains(I[j], r)) {
Data data = new Data(r, j, I[j]);
list.add(data);
}
}
// 每一个RI对应一个list
if (!list.isEmpty()) {
res.add(list);
}
}
return res;
}
public static void print(List<List<Data>> res) {
StringBuilder sb1 = new StringBuilder();
// 处理输出:相同额序列号为一组数据
int total = 0;
for (int i = 0; i < res.size(); i++) {
List<Data> resultList = res.get(i);
int count = resultList.size();
total += (2 + count * 2);
sb1.append(resultList.get(0).R + " " + count + " ");
StringBuilder sb2 = new StringBuilder();
for (int j = 0; j < count; j++) {
Data data = resultList.get(j);
sb2.append(data.indexI + " " + data.I + " ");
}
sb1.append(sb2.toString().trim() + " ");
}
System.out.println(total + " " + sb1.toString().trim());
}
}
牌的大小比较
题目描述:
一副牌有 3~10 J(11)、Q(12)、K(13)、A(14)、2(15)和joker、JOKER构成,现在规定有两首牌:他们只能是个、对子、三张、顺子、4张炸弹、王炸弹。输入:2 2 2 2-K K表示:牌1为4个2,牌2为对k,则输出2 2 2 2;
解题思路:
关键在于比较的顺序,如果比较的不得当,讨论的范围很大,故我们先从大到小去讨论,每往下走,就会排除很多其他情况可以大大减少讨论的次数;同时为了便于比较大小,将J、Q、K、A、2均转化为数字。
讨论的顺序如下:
(1)两手牌,只要有一手牌位“joker JOKER”,则必然输出"joker JOKER"
(2)然后谈论4张牌,除了(1),此时4张牌是最的,若两手牌的长度均为4,比较牌面的大小,否则输出4张牌
(3)当排除了(1)和(2)时候,此时在判断两手牌的长度是否相等,不相等,则类型不同,无法比较大小,否则比较牌面的大小,输出牌面大的牌
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String[] strs=str.split("-");
String res=compare(strs);
System.out.println(res);
}
}
public static String compare(String[] strs){
//情况1:两手牌只要出现对王,必然最大
if(strs[0].equals("joker JOKER") || strs[1].equals("joker JOKER")){
return "joker JOKER";
}
//情况2:没有对王出现,看是否有炸弹出现
String[] first=strs[0].split(" ");
String[] second=strs[1].split(" ");
first=convertToString(first);
second=convertToString(second);
int len1=first.length;
int len2=second.length;
if(len1==4 || len2==4){
//具体情况讨论:
if(len1==4 && len2==4){
int tmp=Integer.parseInt(first[0])-Integer.parseInt(second[0]);
if(tmp>0){
return strs[0];
}else{
return strs[1];
}
}else if(len1==4){
return strs[0];
}else{
return strs[1];
}
}
//情况3:最后只剩下 个 对子 三个 和顺子
if(len1!=len2){
//不同类型无法比较
return "ERROR";
}
//长度相等时,比较首张牌的大小
int tmp=Integer.parseInt(first[0])-Integer.parseInt(second[0]);
if(tmp>0){
return strs[0];
}else{
return strs[1];
}
}
//将牌里面的A J Q K A 转化为数字形式的字符串,便于后面统一比较大小
public static String[] convertToString(String[] str){
for(int i=0;i<str.length;i++){
switch(str[i]){
case "J":
str[i]="11";
break;
case "Q":
str[i]="12";
break;
case "K":
str[i]="13";
break;
case "A":
str[i]="14";
break;
case "2":
str[i]="15";
break;
}
}
return str;
}
}
统计字符个数
题目描述:
给一个字符串,只包含’0’~‘9’ 和 大小写字符,字符出现的个数,不区分大小写
解题思路:
使用哈希数组,可以将字符统一转换位大写,判断时候,也转换大写
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String s=br.readLine();
System.out.println(getCount(str,s));
}
}
public static int getCount(String str,String s){
int[] count=new int[256];
for(int i=0;i<str.length();i++){
char ch=str.charAt(i);
if(ch>='a' && ch<='z'){
ch=Character.toUpperCase(ch);
}
count[ch]++;
}
char c=s.charAt(0);
if(c>='a' && c<='z'){
c=Character.toUpperCase(c);
}
return count[c];
}
}
进制转换
题目描述:
将16进制转换成10进制数
解题技巧:
‘0’~'9’转化成相应的10进制:ch-‘0’;
小写字母转化:ch-‘A’+10;
大写字母转化:ch-‘a’+10;
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
String s = str.substring(2, str.length());
int res = process(s);
System.out.println(res);
}
}
public static int process(String str) {
int[] strs = convert(str);
int len = strs.length;
int res = 0;
for (int i = 0; i< len; i++) {
res += strs[i] * Math.pow(16, len-i-1);
}
return res;
}
public static int[] convert(String str) {
int len = str.length();
int[] strs = new int[len];
for (int i = 0; i < len; i++) {
char ch = str.charAt(i);
if (ch >= 'A' && ch <= 'Z') {
strs[i] = ch-'A'+10;//通过差值
} else if(ch>='a' && ch<='z') {
strs[i] = ch-'a'+10;
}else{
strs[i]=ch-'0';
}
}
return strs;
}
}
坐标移动
题目描述:
A10表示:坐标先左移动10,W为先上,S为向下,D为向右;给出一系列以";"分割的这种字符串A10;S20;W10;D30;X;A1A;B10A11;;A10;求出最后的位置;注意:要过滤掉无效的表达如:A01(01不是一个合法的二位数字),AR10(R不是正确的移动方向表达)。
解题思路:
本题最关键在于判断诸如"A10"是否为合法的移动坐标操作,可以使用正则表达式,String类型提供str.matches(pattern)函数,我们可以写相应的表达式;
这里有几个关键点
“1[1-9][0-9]?$”
^字符串起始点
$ 表达是结束位置
[AWSD] 需要匹配4个字符中的任意一个
[1-9] 需要匹配1-9中的任意一个数字
\\d+ 匹配一个数字
. 匹配任意字符
-------限定符-----
* 匹配前面的表达式0次或多次
+ 匹配前面的表达式1次或者多次
? 匹配前面子表达式0次或者1次
() 子表达式
{n,m},最少匹配n次,最多匹配m次
{n}匹配n次
{n,}至少匹配n次
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String[] strs=str.split(";");
int x=0;
int y=0;
for(int i=0;i<strs.length;i++){
if(isNormal(strs[i])){
String dir=strs[i].substring(0,1);
int step=Integer.parseInt(strs[i].substring(1,strs[i].length()));
if(dir.equals("A")){
x-=step;
}
if(dir.equals("D")){
x+=step;
}
if(dir.equals("W")){
y+=step;
}
if(dir.equals("S")){
y-=step;
}
}
}
System.out.println(x+","+y);
}
}
public static boolean isNormal(String str){
return str.matches("^[AWSD][1-9][0-9]?$");
}
}
ip地址分类
题目描述:
请解析IP地址和对应的掩码,进行分类识别。要求按照A/B/C/D/E类地址归类,不合法的地址和掩码单独归类。
所有的IP地址划分为 A,B,C,D,E五类
A类地址1.0.0.0~126.255.255.255;
B类地址128.0.0.0~191.255.255.255;
C类地址192.0.0.0~223.255.255.255;
D类地址224.0.0.0~239.255.255.255;
E类地址240.0.0.0~255.255.255.255;
私网IP范围是:
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255
解题思路:
(1)如何将一个IP类型的字符串转换成32bit的int?
int res=0
String[] nums=str.split("\\.");
res |=(Integer.parseInt(nums[0]) & 0xFF) << 24;
res |=(Integer.parseInt(nums[1]) & 0xFF) << 16;
res |=(Integer.parseInt(nums[2]) & 0xFF) << 8;
res |=(Integer.parseInt(nums[3]) & 0xFF) << 0;
最后得出的res则为二进制类型的IP
(2)检验子网掩码mask的有效性
可以使用枚举的方式,0xFF000000、0XFF800000、0XFFC00000、0XFFE00000、。。。。0XFFFFFFFF
(3)分类的具体步骤
3.1)判断子网掩码是否有效,若是,进入3.2),否则进入3.3;
3.2)判断当前IP是否属于A、B、C、D、E;其中A、B、C中还需呀继续判断是否是私有地址
3.3)无效数字+1;
看具体代码的实现
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
int a = 0;// 记录a的数量
int b = 0;
int c = 0;
int d = 0;
int e = 0;
int err = 0;
int pri = 0;
while ((str = br.readLine()) != null ) {
String[] strs = str.split("~");
String ip = strs[0];
String mask = strs[1];
String[] ips = ip.split("\\.");
// 先判断子网掩码是否合法
if (isMask(mask)) {
// 判断ip是否有效
if (isIP(ip)) {
// 开始分类
int tmp0 = Integer.parseInt(ips[0]);
int tmp1 = Integer.parseInt(ips[1]);
if (tmp0 >= 1 && tmp0 <= 126) {// A类
a++;
// 继续判断是否属于私有地址
if (tmp0 == 10) {
pri++;
}
}else if (tmp0 >= 128 && tmp0 <= 191) {// B类
b++;
if (tmp0 == 172 && (tmp1 >= 16 && tmp1 <= 31)) {
pri++;
}
}else if (tmp0 >= 192 && tmp0 <= 223) {// C类
c++;
if (tmp0 == 192 && tmp1 == 168) {
pri++;
}
}else if (tmp0 >= 224 && tmp0 <= 239) {
d++;
}else if (tmp0 >= 240 && tmp0 <= 255) {
e++;
}
} else {
err++;
}
} else {
err++;
}
}
System.out.println(a + " " + b + " " + c + " " + d + " " + e + " " + err + " " + pri);
}
// 判断掩码是否合法
public static boolean isMask(String str) {
int mask=parseIp(str);
if(mask==-1){
return false;
}
return mask == 0xFF000000 || mask == 0xFF800000 || mask == 0xFFC00000 || mask == 0xFFE00000
|| mask == 0xFFF00000 || mask == 0xFFF80000 || mask == 0xFFFC0000 || mask == 0xFFFE0000
|| mask == 0xFFFF0000 || mask == 0xFFFF8000 || mask == 0xFFFFC000 || mask == 0xFFFFE000
|| mask == 0xFFFFF000 || mask == 0xFFFFF800 || mask == 0xFFFFFC00 || mask == 0xFFFFFE00
|| mask == 0xFFFFFF00 || mask == 0xFFFFFF80 || mask == 0xFFFFFFC0 || mask == 0xFFFFFFE0
|| mask == 0xFFFFFFF0 || mask == 0xFFFFFFF8 || mask == 0xFFFFFFFC || mask == 0xFFFFFFFE
|| mask == 0xFFFFFFFF;
}
public static int parseIp(String ipStr) {
try {
int ip = 0;
String[] nums = ipStr.split("\\.");
ip |= ((Integer.parseInt(nums[0]) & 0xFF) << 24);
ip |= ((Integer.parseInt(nums[1]) & 0xFF) << 16);
ip |= ((Integer.parseInt(nums[2]) & 0xFF) << 8);
ip |= (Integer.parseInt(nums[3]) & 0xFF);
return ip;
} catch (Exception e) {
return -1;
}
}
// 判断IP地址是否合法
public static boolean isIP(String str) {
return parseIp(str)==-1?false:true;
}
}
ip地址互相转换
题目描述:
原理:ip地址的每段可以看成是一个0-255的整数,把每段拆分成一个二进制形式组合起来,然后把这个二进制数转变成
一个长整数。
举例:一个ip地址为10.0.3.193
每段数字 相对应的二进制数
10 00001010
0 00000000
3 00000011
193 11000001
组合起来即为:00001010 00000000 00000011 11000001,转换为10进制数就是:167773121,即该IP地址转换后的数字就是它了。
的每段可以看成是一个0-255的整数,需要对IP地址进行校验
解题思路:
利用<< 与 & 运算来求解,为了防止数据溢出,最好使用Long类型的数据
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
Long ip=Long.parseLong(br.readLine());
Long a=toHex(str);
String b=toIP(ip);
System.out.println(a);
System.out.println(b);
}
}
// 将ip地址转化为10进制
public static Long toHex(String str) {
Long ip = 0L;
String[] strs = str.split("\\.");
ip |= ((Long.parseLong(strs[0])) << 24);
ip |= ((Long.parseLong(strs[1])) << 16);
ip |= ((Long.parseLong(strs[2])) << 8);
ip |= ((Long.parseLong(strs[3])) << 0);
return ip;
}
public static String toIP(Long ip) {
//使用右移动>>和 & 运算
StringBuilder sb=new StringBuilder();
String ip0=String.valueOf((ip >> 24 )& 0xFF);//取最高8bit
String ip1=String.valueOf((ip >> 16 )& 0xFF);//取后面8bit
String ip2=String.valueOf((ip >> 8 ) & 0xFF);
String ip3=String.valueOf((ip >> 0 ) & 0xFF);
sb.append(ip0).append(".").append(ip1).append(".").append(ip2).append(".").append(ip3);
return sb.toString();
}
}
ip地址是否在统一局域网内
题目描述:
判断两个ip地址是否位于同一个局域网内,同时需要自己判断子网掩码和ip地址的合法性。
输出:ip地址或掩码不合格,输出1,在同一个局域网输出0 不在同一个局域网输出2
解题思路:
使用<<运算符和 & 运算符可以轻松转化为二进制IP(32bit),为了保证数据的不溢出和合法性,建议使用try{}catch{}包裹起来。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String mask=str;
String ip1=br.readLine();
String ip2=br.readLine();
System.out.println(isSameIp(mask,ip1,ip2));
}
}
//IP字符串转化为长整型二进制形式
public static int toIP(String str){
int ip=0;
String[] strs=str.split("\\.");
try{
ip |=(Integer.parseInt(strs[0]))<<24;
ip |=(Integer.parseInt(strs[1]))<<16;
ip |=(Integer.parseInt(strs[2]))<<8;
ip |=(Integer.parseInt(strs[3]))<<0;
return ip;
}catch(Exception e){
return -1;
}
}
public static boolean isMask(String str){
int mask=toIP(str);
if(mask==-1){
return false;
}
return mask == 0xFF000000 || mask == 0xFF800000 || mask == 0xFFC00000 || mask == 0xFFE00000
|| mask == 0xFFF00000 || mask == 0xFFF80000 || mask == 0xFFFC0000 || mask == 0xFFFE0000
|| mask == 0xFFFF0000 || mask == 0xFFFF8000 || mask == 0xFFFFC000 || mask == 0xFFFFE000
|| mask == 0xFFFFF000 || mask == 0xFFFFF800 || mask == 0xFFFFFC00 || mask == 0xFFFFFE00
|| mask == 0xFFFFFF00 || mask == 0xFFFFFF80 || mask == 0xFFFFFFC0 || mask == 0xFFFFFFE0
|| mask == 0xFFFFFFF0 || mask == 0xFFFFFFF8 || mask == 0xFFFFFFFC || mask == 0xFFFFFFFE
|| mask == 0xFFFFFFFF;
}
public static int isSameIp(String mask,String str1,String str2){
if(!isMask(mask) || toIP(str1)==-1 || toIP(str2)==-1){
return 1;
}
int netMaskt=toIP(mask);
int ip1=toIP(str1);
int ip2=toIP(str2);
return (ip1 & netMaskt)==(ip2 & netMaskt)?0:2;
}
}
错误记录
题目描述:
开发一个简单错误记录功能小模块,能够记录出错的代码所在的文件名称和行号。\
处理:
1、 记录最多8条错误记录,循环记录(或者说最后只输出最后出现的八条错误记录),对相同的错误记录(净文件名(保留最后16位)称和行号完全匹配)只记录一条,错误计数增加;
2、 超过16个字符的文件名称,只记录文件的最后有效16个字符;
3、 输入的文件可能带路径,记录文件名称不能带路径。
解题思路:
LinkedHashMap的使用,LinkedHashMap会保存插入记录的顺序,底层是链表,而HashCode底层是Hash表,随机访问速度快,可能产生哈希冲突!,这道题选用LinkedHashMap,key使用:文件名+" "+行号,value为:该记录对应的条数;
如果重复则value+1,否则,value=1;由于取最后8条记录,故可以使用一个计数器count数到倒数第8条记录来,小技巧:count>map.size()-8;
import java.io.*;
import java.util.Map;
import java.util.LinkedHashMap;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
Map<String,Integer> map=new LinkedHashMap<String,Integer>();
String str="";
while((str=br.readLine())!=null){
String[] strs=str.split(" ");
int len=strs[0].length();
int i=strs[0].lastIndexOf("\\");
String name=strs[0].substring(i+1);
if(name.length()>16){
name=name.substring(name.length()-16);
}
String key=name+" "+strs[1];//以文件名 行号为键 ,值为出现的次数
if(map.containsKey(key)){
map.put(key,map.get(key)+1);
}else{
map.put(key,1);
}
}
int count=0;
for(String key:map.keySet()){
count++;
if(count>map.size()-8){
System.out.println(key+" "+map.get(key));
}
}
}
}
密码验证
题目描述:
给出一个加密的字符串,要同时满足以下三个条件才算合格:
1.长度超过8位
2.包括大小写字母.数字.其它符号,以上四种至少三种
3.不能有相同长度大于2的子串重复
解题思路:
针对第三个条件,我们仅仅需要判断,当前字符串是否存在两个长度为3的相同的子串,如果存在,则不符合,否则符合,依次截取str1=(i,i+3),str2=(i+3,len),判断str2.contains(str1)即可得出答案
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
if(process1(str) && process2(str)){
System.out.println("OK");
}else{
System.out.println("NG");
}
}
}
public static boolean process1(String str){
int a=0;
int b=0;
int c=0;
int d=0;
int len=str.length();
if(len<=8){
return false;
}
for(int i=0;i<len;i++){
char ch=str.charAt(i);
if(ch>='0' && ch<='9'){
if(a==0){
a++;
}
}else if(ch>='a' && ch<='z'){
if(b==0){
b++;
}
}else if(ch>='A' && ch<='Z'){
if(c==0){
c++;
}
}else{
if(d==0){
d++;
}
}
}
if((a+b+c+d)>=3){
return true;
}else{
return false;
}
}
//判断字符串是否符合条件3
public static boolean process2(String str){
int len=str.length();
for(int i=0;i<len-3;i++){
String str1=str.substring(i,i+3);
String str2=str.substring(i+3,len);
if(str2.contains(str1)){
return false;
}
}
return true;
}
}
删除字符串数组中出现次数最少的字符
题目描述:
实现删除字符串中出现次数最少的字符,若多个字符出现次数一样,则都删除。输出删除这些单词后的字符串,字符串中其它字符保持原来的顺序。字符串只包含小写英文字母, 不考虑非法输入,输入的字符串长度小于等于20个字节。
解题思路:
(1)哈希数组通常用于字符串数组中字符个数的统计,由于这道题目只有小写字母,可以将char-'a’作为索引,来统计个字符出现的次数,最后还原数据的时候,可以用char+‘a’,String.valueOf((char)(ch+‘a’))可以得到该字符串。以差值做索引是惯用技巧
(2)str.replaceAll(rex,"");可用于替换所有的rex字符串为"",也即为空。
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
int min=Integer.MAX_VALUE;
while((str=br.readLine())!=null){
char[] strs=str.toCharArray();
int[] count=new int[26];
if(strs.length>20){
continue;
}
//设置hash数组
for(int i=0;i<strs.length;i++){
count[strs[i]-'a']++;
min=min>count[strs[i]-'a']?count[strs[i]-'a']:min;
}
for(int i=0;i<count.length;i++){
if(count[i]==min){
String rex=String.valueOf((char)(i+'a'));
str=str.replaceAll(rex,"");//差值表示位置,
}
}
System.out.println(str);
}
}
}
密码截取
题目描述:
Catcher是MCA国的情报员,他工作时发现敌国会用一些对称的密码进行通信,比如像这些ABBA,ABA,A,123321,但是他们有时会在开始或结束时加入一些无关的字符以防止别国破解。比如进行下列变化 ABBA->12ABBA,ABA->ABAKK,123321->51233214 。因为截获的串太长了,而且存在多种可能的情况(abaaab可看作是aba,或baaab的加密形式),Cathcer的工作量实在是太大了,他只能向电脑高手求助,你能帮Catcher找出最长的有效密码串吗?
解题思路:
使用中心拓展法则,考虑到字符串的长度分为偶数和奇数,当为偶数的时候,以i=1为中心,开始向两头拓展(i-1,i),当为偶数的时候,拓展的范围为:(i-1,i+1)
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int max=Integer.MIN_VALUE;
//中心扩展法,分奇数和偶数来探讨
for(int i=1;i<str.length();i++){
//若长度为奇数
int l=i-1;
int r=i;
while(l>=0 && r<str.length() && str.charAt(l)==str.charAt(r)){
int len =r-l+1;
if(max<len){
max=len;
}
l--;
r++;
}
//若长度为偶数
l=i-1;
r=i+1;
while(l>=0 && r<str.length() && str.charAt(l)==str.charAt(r)){
int len =r-l+1;
if(max<len){
max=len;
}
l--;
r++;
}
}
System.out.println(max);
}
}
}
字符串加密
题目描述:
有一种技巧可以对数据进行加密,它使用一个单词作为它的密匙。下面是它的工作原理:首先,选择一个单词作为密匙,如TRAILBLAZERS。如果单词中包含有重复的字母,只保留第1个,其余几个丢弃。现在,修改过的那个单词属于字母表的下面,如下所示:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
T R A I L B Z E S C D F G H J K M N O P Q U V W X Y
上面其他用字母表中剩余的字母填充完整。在对信息进行加密时,信息中的每个字母被固定于顶上那行,并用下面那行的对应字母一一取代原文的字母(字母字符的大小写状态应该保留)。因此,使用这个密匙,Attack AT DAWN(黎明时攻击)就会被加密为Tpptad TP ITVH。
解题思路:
这道题目的核心在于根据密钥获取字母表,可以利用hash数组,hash数组通常可以用来去重,一个小技巧就是:利用当前字符c于首字母的’a’或者’A’的差值来表示哈希数组的索引。
直接看代码
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
String key=getKey(str);
str=br.readLine();
String res=encription(key, str);
System.out.println(res);
}
}
public static String getKey(String key){
StringBuilder sb=new StringBuilder();
String str=key.toUpperCase()+"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int[] count=new int[26];
for(int i=0;i<str.length();i++){
char c=str.charAt(i);
if(count[c-'A']==0){
sb.append(c);
count[c-'A']++;
}
}
return sb.toString();
}
public static String encription(String key,String str){
StringBuilder sb=new StringBuilder();
char[] keys=key.toCharArray();
for(int i=0;i<str.length();i++){
char c=str.charAt(i);
if(c>='a' && c<='z'){
sb.append((char)('a'+keys[c-'a']-'A'));//将大写字母转化为小写;
}else if(c>='A' && c<='Z'){
sb.append((char)keys[c - 'A']);
}else{
sb.append(c);//空格
}
}
return sb.toString();
}
}
将数字转化成英文表达
题目描述:
如:1024=one hundred and twenty four;
解题思路:
(1)递归的使用
(2)细节的处理。数据分段处理,按照表达方式的不同,可以分成以下几个段:[ 0,20),[ 20,100),[ 100,1000),[ 1000,1000 000)、[1000 000,100 000 000)
其中:其中 hundred 和 后面的数字之间要有“and”相连,其余的不用。
num/10 和num%100
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int num=Integer.parseInt(str);
System.out.println(parseEnglish(num));
}
}
public static String parseEnglish(int num){
//[0-19]的表示方法
String[] numStr={"zero","one","two","three","four","five",
"six","seven","eight","nine","ten","eleven","twelve",
"thirteen","fourteen","fifteen","sixteen","seventeen",
"eighteen","ninteen"};
String[] numStr2={"","","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"};
//0到20,从numStr中获取
if(num>=0 && num<=20){
return numStr[num];
}else if(num>=20 && num<100){
int a=num/10;
int b=num%10;
if(b!=0){
return numStr2[a]+" "+parseEnglish(b);
}else{
return numStr2[a];//整10
}
}else if(num>=100 && num<1000){
int a=num/100;
int b=num%100;
if(b!=0){
return parseEnglish(a)+ " hundred"+" and "+parseEnglish(b);
}else{
return parseEnglish(a)+" hundred";
}
}else if(num>=1000 && num<1000000){
int a=num/1000;
int b=num%1000;
if(b!=0){
return parseEnglish(a)+ " thousand "+parseEnglish(b);
}else{
return parseEnglish(a)+" thousand";
}
}else if(num>=1000000 && num<100000000){
int a=num/1000000;
int b=num%1000000;
if(b!=0){
return parseEnglish(a)+ " million "+parseEnglish(b);
}else{
return parseEnglish(a)+" million";
}
}
return "error";
}
}
字符串中字符出现数量的排序
题目描述:
给出一个名字,该名字有26个字符串组成,定义这个字符串的“漂亮度”是其所有字母“漂亮度”的总和。
每个字母都有一个“漂亮度”,范围在1到26之间。没有任何两个字母拥有相同的“漂亮度”。字母忽略大小写。
给出多个名字,计算每个名字最大可能的“漂亮度”。
解题思路:
- 思路1
(1)哈希数组计算每个字符的字符出现的次数
(2)对哈希数组按降序排序
(3)出现的次数越多,应该赋予更大权值,才能保证总的权值最大
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
StringBuilder sb = new StringBuilder();
int n = Integer.parseInt(str);
for (int i = 0; i < n; i++) {
String name = br.readLine();
int sum = 0;
sum = getButify(name);
sb.append(sum + "\n");
}
System.out.print(sb.toString());
}
}
public static int getButify(String str) {
int sum = 0;
int[] count = new int[26];
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c >= 'a' && c <= 'z') {
count[c - 'a']++;
} else if (c >= 'A' && c <= 'Z') {
count[c - 'A']++;
}
}
Arrays.sort(count);
int j = 26;
for (int i = count.length-1; i>=0; i--) {
sum += count[i] * j;
j--;
}
return sum;
}
}
- 思路2
好好学习以下List和Map之间的映射关系
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
StringBuilder sb=new StringBuilder();
int n = Integer.parseInt(str);
for (int i = 0; i < n; i++) {
String name = br.readLine();
int sum = 0;
sum=getButify(name);
sb.append(sum+"\n");
}
System.out.print(sb.toString());
}
}
public static int getButify(String str){
Map<Character,Integer> map=new HashMap<>();
int sum=0;
for(int i=0;i<str.length();i++){
char c=str.charAt(i);
if(map.containsKey(c)){
map.put(c, map.get(c)+1);
}else{
map.put(c, 1);
}
}
List<Map.Entry<Character, Integer>> list = new ArrayList<>(map.entrySet());
Collections.sort(list,(Map.Entry<Character,Integer> o1,Map.Entry<Character,Integer> o2)->o2.getValue()-o1.getValue() );
int i=26;
for(Map.Entry<Character,Integer> e:list){
sum+=e.getValue()*i;
i--;
}
return sum;
}
}
按字节截取字符
题目描述:
编写一个截取字符串的函数,输入为一个字符串和字节数,输出为按字节截取的字符串。但是要保证汉字不被截半个,如"我ABC"4,应该截为"我AB",输入"我ABC汉DEF"6,应该输出为"我ABC"而不是"我ABC+汉的半个"。
解题思路:
字母,+1,其他的字符+2,当大于count时,直接输出。
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
String[] strs=str.split(" ");
int count =Integer.parseInt(strs[1]);
System.out.println(process(strs[0],count));
}
}
public static String process(String str,int count){
int num=0;
StringBuilder sb = new StringBuilder();
for(int i=0;i<str.length();i++){
char c=str.charAt(i);
if((c>='a' && c<='z') || (c>='A' && c<='Z')){
num++;
if(num>count){
return sb.toString();
}
sb.append(c);
}else{
num+=2;
if(num>count){
return sb.toString();
}
sb.append(c);
}
}
return sb.toString();
}
}
查找兄弟单词
题目描述:
兄弟单词指的是两个长度一样,其内部字符的组成顺序不一样的两个单词成为兄弟单词
输入:
3 abc bca cab abc 1
输入说明:3为字典的中单词的数量,[ abc,bca,cab]为一个字典,abc为key 从兄弟字典中查找索引为1的单词
输出描述:
兄弟字典的数量
从兄弟字典中按照索引查找到的index
解题思路:
题目并不难,难的是在于读取题目的意思,最重要的是如何快速判断两个字符串是否为兄弟节点,兄弟节点必须保证长度相同,其内部知识字符的顺序不同而已,故我们可以利用Array.sort(char[] c)对字符串的字符数组进行排序,这种对字符的排序默认就是字典排序。排完序后,比价两个字符数组是否完全相同即可,可用到API函数,Arrays.equals(char[], char[]);
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String[] strs=str.split(" ");
int n=Integer.parseInt(strs[0]);
String key=strs[n+1];
int index=Integer.parseInt(strs[n+2]);
List<String> dic=new LinkedList<String>();
//字典
for(int i=1;i<=n;i++){
if(isBrother(strs[i],key) && !key.equals(strs[i])){
dic.add(strs[i]);
}
}
Collections.sort(dic);//按字典排序
int count =dic.size();
System.out.println(count);
if(index>=1 && index<=dic.size()){//索引可能会有溢出的情况
System.out.println(dic.get(index-1));//索引从1开始
}
}
}
public static boolean isBrother(String str1,String str2){
if(str1.length()!=str2.length()){
return false;
}
char[] strs1=str1.toCharArray();
char[] strs2=str2.toCharArray();
Arrays.sort(strs1);
Arrays.sort(strs2);
return Arrays.equals(strs1,strs2);
}
}
截取字符串中最长的数字字符串
题目描述:
输入一个字符串。
输出字符串中最长的数字字符串和它的长度,中间用逗号间隔。如果有相同长度的串,则要一块儿输出(中间不间隔),但是长度还是一串的长度,与数字字符串间用逗号间隔
解题思路:
正常处理的思路;用list来缓存最大的长度字符串,每一次更新最大长度时,list必须要清空,当长度相等时,继续向list中加入;
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
getMaxLen(str);
}
}
public static void getMaxLen(String str){
ArrayList<String> list=new ArrayList<>();
int len=str.length();
int maxLen=0;
int i=0;
while(i<len){
char cur=str.charAt(i);
if(cur>='0' && cur<='9'){
int j=i+1;//当前元素的下一个元素
while(j<len && str.charAt(j)>='0' && str.charAt(j)<='9'){
j++;
}
//j指向非数字,或者超出str的长度
int tmpLen=j-i;
if(tmpLen>maxLen){
maxLen=tmpLen;
//更新为更加长的,先清空list
list.clear();
list.add(str.substring(i,j));
}else if(tmpLen==maxLen){
list.add(str.substring(i,j));
}
//重新设定i的位置
i=j+1;
}else{
i++;
}
}
for(String s:list){
System.out.print(s);
}
System.out.println(","+maxLen);
}
}
字符串加*处理
题目描述:
将一个字符中所有出现的数字前后加上符号“*”,其他字符保持不变
解题思路:
和上一题几乎一样的思路,逐个读取字符串中的字符,若为字母直接入stringBuilder,若为数字的话,找到连续的数字(j=i+1),从[i,j)为连续的数字,两边加**处理,这和从字符串中提取最长的数字串本质上是一样的思路
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String res=solution(str);
System.out.println(res);
}
}
public static String solution(String str){
StringBuilder sb=new StringBuilder();
int len=str.length();
int i=0;
while(i<len){
char cur=str.charAt(i);
if(cur>='0' && cur<='9'){
int j=i+1;
while(j<len && str.charAt(j)>='0' && str.charAt(j)<='9'){
j++;
}
//j指向非数字
String s2="*"+str.substring(i,j)+"*";
sb.append(s2);
i=j;
}else{
sb.append(cur);
i++;
}
}
return sb.toString();
}
}
参数解析
题目描述:
在命令行输入如下命令:
xcopy /s c:\ d:\,
各个参数如下:
参数1:命令字xcopy
参数2:字符串/s
参数3:字符串c:\
参数4: 字符串d:\
请编写一个参数解析程序,实现将命令行各个参数解析出来
解题思路:
逐个解析,所有从字符串中提取子串都可以用这个框架
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int i=0;
int len=str.length();
StringBuilder sb=new StringBuilder();
int count=0;
while(i<len){
char c=str.charAt(i);
if(c==' '){
count++;
sb.append("\n");//换行
i++;
}else if(c=='"'){
int j=i+1;
while(str.charAt(j)!='"' ){
j++;
}
//j此时再第二个”上
sb.append(str.substring(i+1,j));
i=j+1;
}else{
sb.append(c);
i++;
}
}
count++;//最后一个没有换行,所以需要+1;
System.out.println(count);
System.out.println(sb.toString());
}
}
}
截取DNA序列
题目描述:
一个DNA序列由A/C/G/T四个字母的排列组合组成。G和C的比例(定义为GC-Ratio)是序列中G和C两个字母的总的出现次数除以总的字母数目(也就是序列长度)。在基因工程中,这个比例非常重要。因为高的GC-Ratio可能是基因的起始点。
给定一个很长的DNA序列,以及要求的最小子序列长度,研究人员经常会需要在其中找出GC-Ratio最高的子序列。
解题思路:
窗口滑动策略
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int len =Integer.parseInt(br.readLine());
System.out.println(getMaxLen(str,len));
}
}
public static String getMaxLen(String str,int len){
if(len>str.length()){
return str;
}
//[i,j),窗口移动策略
StringBuilder sb=new StringBuilder();
float maxRatio=0.0f;
int count=0;
int i=0;
int j=len;
for(int index=0;index<j;index++){
if(str.charAt(index)=='C' ||str.charAt(index)=='G'){
count++;
}
}
while(j<=str.length()){
//计算Ratio
float tmp=(float)count/len;
if(tmp>maxRatio){
maxRatio=tmp;
sb.setLength(0);
sb.append(str.substring(i,j));
}
//向右移动窗口
if(str.charAt(i)=='C' || str.charAt(i)=='G'){
count--;
}
if(j<str.length() && (str.charAt(j)=='C' || str.charAt(j)=='G')){
count++;
}
i++;
j++;
}
return sb.toString();
}
}
计算日期
题目描述:
根据输入的日期,计算是这一年的第几天。。
详细描述:
输入某年某月某日,判断这一天是这一年的第几天?。
测试用例有多组,注意循环输入
解题思路:
主要关键点:闰年的判定方法。闰年一定能被4整除,被4整除但不能被100整除;若被100整除,则必须被400整除;正所谓:4年一润,百年不润,若百年润,则400年再润。
import java.io.*;
public class Main{
static int[] leapMouth={0,31,29,31,30,31,30,31,31,30,31,30,31};
static int[] nomalMoth={0,31,28,31,30,31,30,31,31,30,31,30,31};
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String[] strs=str.split(" ");
int year=Integer.parseInt(strs[0]);
int mouth=Integer.parseInt(strs[1]);
int day=Integer.parseInt(strs[2]);
System.out.println(convert(year,mouth,day));
}
}
public static int convert(int year,int mouth,int day){
int res=0;
if(year<=0 || mouth<=0 || mouth>12 || day<=0){
return -1;
}
//月数和日期不符合
boolean flag=isLeapYaear(year);
if(flag){
if(day>leapMouth[mouth]){
return -1;
}
}else{
if(day>nomalMoth[mouth]){
return -1;
}
}
for(int i=1;i<mouth;i++){
if(flag){//闰年
res+=leapMouth[i];
}else{
res+=nomalMoth[i];
}
}
res+=day;//加上最后一个月的day
return res;
}
//判断年份是否为闰年
//4年一润,一定能被4整除
//100年不润,但不能被100整除
//400年在润,若被100整除,必须被400整除
public static boolean isLeapYaear(int year){
if(year%4==0){
if(year%100==0){
if(year%400==0){
return true;
}else{
return false;
}
}else{
return true;
}
}else{
return false;
}
}
}
大整数相加
题目描述:
请设计一个算法完成两个超长正整数的加法。
输入:
99999999999999999999999999999999999999999999999999
1
输出:
100000000000000000000000000000000000000000000000000
解题思路:
str1和str2从后往前依次相加,i和j分别指向str1和str2最后一位字符,计算进位(sum/10),当前位为:(sum%10),字符转数字:str.charAt(i)-‘0’;str1+str2有三种情况
(1)str1.length==str2.length
(2)str1.length>str2.length
(3)str1.length< str2.length
故进入循环的条件件应该为:i>= 0 || j>=0 || carry!=0
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String str2=br.readLine();
//进位
int carry=0;
int i=str.length()-1;
int j=str2.length()-1;
int sum=0;
StringBuilder sb=new StringBuilder();
while(i>=0 || j>=0 || carry!=0){
sum=carry;
if(i>=0){
sum+=str.charAt(i)-'0';
i--;
}
if(j>=0){
sum+=str2.charAt(j)-'0';
j--;
}
//判断进位
carry=sum / 10;
sb.append(sum % 10);
}
System.out.println(sb.reverse().toString());
}
}
}
计算字符串的距离
题目描述:
对于不同的字符串,我们希望能有办法判断相似程度,我们定义了一套操作方法来把两个不相同的字符串变得相同,具体的操作方法如下:
1 修改一个字符,如把“a”替换为“b”。
2 增加一个字符,如把“abdd”变为“aebdd”。
3 删除一个字符,如把“travelling”变为“traveling”。
比如,对于“abcdefg”和“abcdef”两个字符串来说,我们认为可以通过增加和减少一个“g”的方式来达到目的。上面的两种方案,都只需要一次操作。把这个操作所需要的次数定义为两个字符串的距离,而相似度等于“距离+1”的倒数。也就是说,“abcdefg”和“abcdef”的距离为1,相似度为1/2=0.5.
给定任意两个字符串,你是否能写出一个算法来计算出它们的相似度呢?
解题思路:
(1)lev[ i][j]用来表示字符串a的[ 1…i]和字符串b[ 1…j]的levenshtein距离;
(2)插入和删除操作互为逆过程:a删除指定字符变b等同于b插入指定字符变a;
(3)如果a[ i] == b[j ],则说明a[ i]和b[ j]分别加入a,b之后不会影响levenshtein距离,lev[ i][j] = lev[ i-1][j-1] + 0;
(4)如果a[ i] != b[ j],则需要考虑3种情况的可能:
(4.1)a中插入字符,即lev[ i][j] = lev[ i-1][j] + 1;
(4.2)b中插入字符,即lev[ i][j] = lev[ i][j-1] + 1;
(4.3)a[ i]替换成b[j],lev[ i][j] = lev[ i-1][j-1] + 1;
取这4种情况的最小值。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
String str1 = "";
while((str1=br.readLine())!=null){
char a[]=str1.toCharArray();
String b=br.readLine();
char c[]=b.toCharArray();
int dp[][]=new int [a.length+1][b.length()+1];
for(int i=1;i<=a.length;i++)
dp[i][0]=i;
for(int i=1;i<=c.length;i++)
dp[0][i]=i;
for(int i=1;i<a.length+1;i++) {
for(int j=1;j<c.length+1;j++) {
if(a[i-1]==c[j-1])
dp[i][j]=dp[i-1][j-1];
else
dp[i][j]=Math.min(dp[i-1][j]+1, Math.min(dp[i][j-1]+1, dp[i-1][j-1]+1));
}
}
System.out.println("1/" + (1+dp[a.length][c.length]));
}
}
}
字符串匹配
题目描述:
判断短字符串中的所有字符是否在长字符串中全部出现
解题思路:
哈希数组
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str1="";
while((str1=br.readLine())!=null){
boolean res=true;
int[] count=new int[256];
String str2=br.readLine();
for(int i=0;i<str2.length();i++){
char c=str2.charAt(i);
count[c]++;
}
for(int i=0;i<str1.length();i++){
if(count[str1.charAt(i)]<=0){
res=false;
break;
}
}
if(res){
System.out.println("true");
}else{
System.out.println("false");
}
}
}
}
人民币转换
题目描述:
1、中文大写金额数字前应标明“人民币”字样。中文大写金额数字应用壹、贰、叁、肆、伍、陆、柒、捌、玖、拾、佰、仟、万、亿、元、角、分、零、整等字样填写。(30分)
2、中文大写金额数字到“元”为止的,在“元”之后,应写“整字,如¥ 532.00应写成“人民币伍佰叁拾贰元整”。在”角“和”分“后面不写”整字。(30分)
3、阿拉伯数字中间有“0”时,中文大写要写“零”字,阿拉伯数字中间连续有几个“0”时,中文大写金额中间只写一个“零”字,如¥6007.14,应写成“人民币陆仟零柒元壹角肆分“。(40分)
解题思路:
这类题目,只要特殊处理几个特例,其他的按照顺序来转换
(1)万、亿、等前面多个1,应该读成拾万而不是壹拾万,不需要读个位。如10 0000读如拾万,编程中排除这种特殊情况
(2) 在万、亿等权位上为0的情况,无需读零,
(3)当前位不为0,前一位为0,需要补0,再读
(4)规则:各位数+权值单位
import java.io.*;
import java.util.*;
public class Main {
public static String[] ge={"零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"};
public static String[] ot = { "", "拾", "佰", "仟", "万", "拾", "佰", "仟", "亿", "拾",
"佰", "仟", "万", "拾", "佰", "仟","万" };
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
String[] strs=str.split("\\.");
String a=readBeforePlot(strs[0]);
String b=readAfterPlot(strs[1]);
System.out.println("人民币"+a+b);
}
}
//读取小数点后面的数
public static String readAfterPlot(String num){
StringBuilder sb=new StringBuilder();
if(num.equals("00")){
sb.append("整");
}
if(num.charAt(0)>'0'){
sb.append(ge[num.charAt(0)-'0']).append("角");
}
if(num.charAt(1)>'0') {
sb.append(ge[num.charAt(1)-'0']).append("分");
}
return sb.toString();
}
//读取小数点前面的整数
public static String readBeforePlot(String s){
StringBuilder sb=new StringBuilder();
if(s.equals("0")){
return "";
}
int j=s.length()-1;
//特殊情况的处理:万、亿、等前面多个1,应该读成拾万而不是壹拾万,不需要读个位。
if(!(j%4==1 && s.charAt(0)=='1')){
sb.append(ge[s.charAt(0)-'0']);
}
sb.append(ot[j]);
//从i=1开始,从左往右读取
for(int i=1;i<s.length();i++){
//在万、亿等权位上为0的情况,无需读零,
if((j-i)%4==0 &&s.charAt(i)=='0'){
sb.append(ot[j-i]);
continue;
}
//当前位不为0,前一位为0,需要补0
if(s.charAt(i)!='0'){
if(s.charAt(i-1)=='0'){
sb.append("零");
}
sb.append(ge[s.charAt(i)-'0']);
sb.append(ot[j-i]);
}
}
sb.append("元");
return sb.toString();
}
}
数组
数组合并成有序数组
题目描述:
将两个整型数组按照升序(两个数组严格递增)合并,并且过滤掉重复数组元素。
解题思路:
双指针的使用,首先对这两个数组进行排序。设置两个指针分别指向两个数组,依次比较两指针所指向的元素大小,将较小的元素放入中间数组,并且相应指针后移一位。当两个指针指向的元素相等时,随意选择一个元素放进中间数组,为了去重的需要 ,此时需要将两个指针都后移一位。最后将两个数组中任意一个数组剩余的所有元素放进中间数组中,这里同样需要去重,原理就是将中间元素的前一位和待放入的元素比较,只有不等,才放进去。
import java.io.*;
import java.util.Arrays;
public class Main{
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
int len1 = Integer.parseInt(str);
String[] strs = br.readLine().split(" ");
int[] nums1 = new int[len1];
for (int i = 0; i < len1; i++) {
nums1[i] = Integer.parseInt(strs[i]);
}
int len2 = Integer.parseInt(br.readLine());
strs = br.readLine().split(" ");
int[] nums2 = new int[len2];
for (int i = 0; i < len2; i++) {
nums2[i] = Integer.parseInt(strs[i]);
}
// 合并
System.out.println(meregeArray(len1, nums1, len2, nums2));
}
br.close();
}
public static String meregeArray(int len1, int[] arr1, int len2, int[] arr2) {
StringBuilder sb = new StringBuilder();
int[] arr3 = new int[len1 + len2];
int i = 0;
int j = 0;
int k = 0;
Arrays.sort(arr1);
Arrays.sort(arr1);
while (i < len1 && j < len2) {
if (arr1[i] < arr2[j]) {
arr3[k++] = arr1[i++];
} else if (arr1[i] > arr2[j]) {
arr3[k++] = arr2[j++];
} else {
// 相等
arr3[k++] = arr2[j++];
i++;
}
}
// 继续填补剩余元素
for (int m = i; m < len1; m++) {
// 和合并数组前一个元素对比,不相等才加(去重)
if (arr3[k - 1] != arr1[m]) {
arr3[k] = arr1[m];
k++;
}
}
for (int m = j; m < len2; m++) {
// 和合并数组前一个元素对比,不相等才加(去重)
if (arr3[k - 1] != arr2[m]) {
arr3[k] = arr2[m];
k++;
}
}
// 转化为字符串(k有几位则遍历几位)
for (int m = 0; m < k; m++) {
sb.append(arr3[m]);
}
return sb.toString();
}
}
蛇形矩阵
问题描述:
样例输入
5
样例输出
1 3 6 10 15
2 5 9 14
4 8 13
7 12
11
解题思路:
没有硬技巧,找数字规律,对数字不敏感的人可以放弃了。
第0行,j=1,每一位数字为:(jj+j)/2-0
第1行,j=2,每一位数字为:(jj+j)/2-1.
第2行,j=3,每一位数字为:(jj+j)/2-2.
第i行,j=i+1,每一位数字为:(jj+j)/2-i.
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
for(int i=0;i<n;i++){
for(int j=i+1;j<=n;j++){
System.out.print((j*j+j)/2-i+" ");
}
System.out.println("");
}
}
}
}
MP3模拟
题目描述:
MP3 Player因为屏幕较小,显示歌曲列表的时候每屏只能显示几首歌曲,用户要通过上下键才能浏览所有的歌曲。为了简化处理,假设每屏只能显示4首歌曲,光标初始的位置为第1首歌。
1. 歌曲总数<=4的时候,不需要翻页,只是挪动光标位置。
光标在第一首歌曲上时,按Up键光标挪到最后一首歌曲;光标在最后一首歌曲时,按Down键光标挪到第一首歌曲。
1. 歌曲总数大于4的时候(以一共有10首歌为例):
特殊翻页:屏幕显示的是第一页(即显示第1 – 4首)时,光标在第一首歌曲上,用户按Up键后,屏幕要显示最后一页(即显示第7-10首歌),同时光标放到最后一首歌上。同样的,屏幕显示最后一页时,光标在最后一首歌曲上,用户按Down键,屏幕要显示第一页,光标挪到第一首歌上。
3. 一般翻页:屏幕显示的不是第一页时,光标在当前屏幕显示的第一首歌曲时,用户按Up键后,屏幕从当前歌曲的上一首开始显示,光标也挪到上一首歌曲。光标当前屏幕的最后一首歌时的Down键处理也类似。
**解题思路:**
无论怎么复杂多变,每一页的数据只需要指定一个start和当前指针cur即可,根据每种情况,相应的移动start和cur,最后【start,start+3】为页内列表和cur为当前指针指向的歌曲。
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
String e=br.readLine();
getList(n,e);
}
}
public static void getList(int n,String str){
//初始化指针
int cur=1;
int start=1;
char[] strs=str.toCharArray();
for(int i=0;i<strs.length;i++){
if(n<=4){
if(strs[i]=='U'){
if(cur==1){
cur=n;
}else{
cur--;
}
}else if(strs[i]=='D'){
if(cur==n){
cur=1;
}else{
cur++;
}
}
}else{//当n>4时
if(strs[i]=='U'){
if(cur==1){
cur=n;
start=cur-3;
}else if(cur==start){
cur--;
start--;
}else{
cur--;
}
}else if(strs[i]=='D'){
if(cur==n){
cur=1;
start=1;
}else if(cur==start+3){//超过一页的范围
cur++;
start++;
}else{
cur++;
}
}
}
}
StringBuilder sb=new StringBuilder();
//打印页面:【start,start+3】
for(int i=1;i<=4;i++){
if(i<=n){
sb.append(start+i-1+" ");
}
}
System.out.println(sb.toString().trim());
System.out.println(cur);
}
}
数学相关
关于牛客网输出输出处理流选取的问题
在牛客网做练习的时候,需要自己处理输入输出,在java中一般有两种方法:
- BufferedReader,
import java.io.*;
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String strs="";
while((strs=br.readLine())!=null){
int n=Integer.parseInt(strs);
System.out.println(add(n));
}
- Scanner
import java.util.Scanner;
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int n=sc.nextInt();
System.out.println(add(n));
}
BufferedReader要比Scanner要快,但是过程相对复杂一点,很套路化,笔试过程建议使用BufferedReader,同时注意,牛客网有多组测试案例,需要使用while()循环判断。同时他们的测试用例每组做完需要用println()换行来分割开的。
lcm和gcd问题
问题描述:
最小公倍数和最大公约数问题是数学中最基本的问题,一定要掌握。
解题思路:
根据公式:两数之积=最小公倍数*最大公约数。最大公约数可以使用辗转相除法。
具体参见如下代码:
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int A =sc.nextInt();
int B=sc.nextInt();
int res=lcm(A,B);
System.out.print(res);
}
}
//最小公倍数*最大公约数=两数之积
private static int lcm(int a,int b){
return (a*b)/gcd(a,b);
}
//最大公约数
private static int gcd(int a,int b){
if(b==0){
return a;
}
return gcd(b,a%b);
}
}
完美数问题
题目描述:
一个自然数有很多因子,除了本身之外的因子为真因子,这个自然数的所有真因子之和为他本身,则它为完备数。
解题思路:
最普通的方法是按照枚举的方法,列举所有的因子,
n
=
p
∗
q
,
且
p
<
=
n
<
=
q
n=p*q,且p<=\sqrt{n}<=q
n=p∗q,且p<=n<=q,那么可以从i=2开始列举处所有的因子:n%i==0,则p=i为一个因子,q=n/i为另外一个因子。
n
为
p
和
q
的
一
个
分
割
线
\sqrt{n}为p和q的一个分割线
n为p和q的一个分割线,列举出了p,q=n/p,所以是不需要遍历到n的,只需要遍历到
n
\sqrt{n}
n即可。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){//有很多组测试案例,必须要有while循环
int n=Integer.parseInt(str);
int count=0;
for(int i=2;i<=n;i++){
if(isPerfect(i)){
count++;
}
}
System.out.println(count);
}
}
//判断一个数是否为完备数
public static boolean isPerfect(int n){
int sum=1;//1是任何整数的约数。
for(int i=2;i*i<=n;i++){//为什么从2开始?i=1时,得到的因素是本身
if(n % i == 0){//枚举 n 的因数
sum+=i;//被除数是因数之一
sum+=n/i;//商是另一个因数。
}
}
return sum==n;
}
}
质数和合数问题
问题描述:
对于大于1的整数,如果只能分解为1和它本身的乘积,那么他是一个质数(素数);反之为一个合数!
解题思路:
假设一个数n为合数,那么 n = n n = p q , 1 < p < = q n=\sqrt{n}\sqrt{n}=pq,1<p<=q n=nn=pq,1<p<=q,所以判断 [ 2 , n ] [2,\sqrt{n}] [2,n]以内是否有能整除n的数,如果有,那么它必然不可能为质数。
public static boolean isPrimeNumber(int n) {
if (n < 2) {
return false;
}
int squareRoot=1;
while(squareRoot*squareRoot<n){
squareRoot++;
}
for (int i = 2; i <= squareRoot; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
拓展题目1
题目描述
找到n以内的所有质数。
思路:
把自然数中是质素倍数的数去掉,剩余的就是质速。
/**
* 运用埃拉托色筛选法筛选出所有小于等于n的质数
*/
public static boolean[] sieveOfEratosthenes(int n) {
boolean[] isPrime = new boolean[n + 1];
//初始化,默认所有都是质数
for (int i = 0; i <= n; i++) {
isPrime[i] = true;
}
//筛选,将所有质数的倍数都标记为非质数(最初只知道2是质数)
for (int i = 2; i <= n / i; i++) {
if (isPrime[i]) {
for (int j = 2; j <= n/i; j++) {
isPrime[i * j] = false;
}
}
}
return isPrime;
}
拓展题目2
题目描述:
查找一个偶数最邻近的两个素数构成。
思路:
一个偶数可以从中间拆开,从中间开始往左右两边各找两个质数,第一个找到的一定是相邻最近的,因为每次迭代的时候,这两个质数都应该是相背而行,距离会越来越远,找到之后,立即结束迭代,即break一下。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
//从中间开始找,才能以最快的速度找到最小值
for(int i=n/2;i>=2;i--){
if(isPrimer(i) && isPrimer(n-i)){
System.out.println(i);
System.out.println(n-i);
break;
}
}
}
}
//判断是否为质数
public static boolean isPrimer(int n){
if(n<2){
return false;
}
//自定义求平方根函数
int squareRoot=1;
while(squareRoot*squareRoot<n){
squareRoot++;
}
for(int i=2;i<=squareRoot;i++){
if(n%i==0){
return false;
}
}
return true;
}
}
质因素分解
题目描述:
将一个数分解成质因子乘积的形式,并且所有的质因子从小到大排序
解题思路:
判断质因子,a=a/i;先判断i是否为质因子,然后判断a开始分解因子(整除),则商为剩余的被除数进行迭代。
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNextLong()){
long n=sc.nextLong();
String res=solution(n);
System.out.println(res);
}
}
public static String solution(long n){
StringBuilder sb=new StringBuilder();
long a=n;
long i=2;
while(i<=a){
if(isPrimer(i) && a%i==0){
sb.append(i+" ");
//求出剩余因子
a=a/i;
}else{
i++;
}
}
return sb.toString();
}
//判断一个数是否为质数
public static boolean isPrimer(long n){
for(long i=2;i*i<=n;i++){
if(n%i==0){
return false;
}
}
return true;
}
}
素数伴侣
题目描述:
若两个正整数的和为素数,则这两个正整数称之为“素数伴侣”,如2和5、6和13,它们能应用于通信加密。现在密码学会请你设计一个程序,从已有的N(N为偶数)个正整数中挑选出若干对组成“素数伴侣”,挑选方案多种多样,例如有4个正整数:2,5,6,13,如果将5和6分为一组中只能得到一组“素数伴侣”,而将2和5、6和13编组将得到两组“素数伴侣”,能组成“素数伴侣”最多的方案称为“最佳方案”,当然密码学会希望你寻找出“最佳方案”。
解题思路:
一个素数伴侣必须要满足:奇数+偶数。因此这道题目的大方向可以现件输入数组处理成奇数集合和偶数集合。然后遍历每一个奇数集合,在对应的偶数集合中去匹配,这里要实现两个基本的方法:
(1)素数的判断
(2)奇数集合中的一个奇数,能否在偶数集合中匹配成功。
如何匹配:dfs算法
对于给定的一个奇数odd,让它依次和event集合中的元素匹配,当然在匹配之前设置一个Visited[]数组来避免重复搜索,第二,matched[]数组记录当前event集合中的元素所匹配的odd(如果第j个event没有伴侣,则将odd和event配对,否则,第j个event已经有odd1伴侣了,如果说这个odd1伴侣能重新找到其他的非event伴侣,则也可以配对)
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
String[] strs=br.readLine().split(" ");
int[] nums=new int[n];
for(int i=0;i<n;i++){
nums[i]=Integer.parseInt(strs[i]);
}
int res=doublePrimer(nums);
System.out.println(res);
}
}
//1.将输入数组分成奇数和偶数两个数组
//2.遍历奇数组,匹配每一个偶数组
public static int doublePrimer(int[] nums){
List<Integer> odd=new ArrayList<>();
List<Integer> event=new ArrayList<>();
for(int i=0;i<nums.length;i++){
if(nums[i]%2==0){
event.add(nums[i]);
}else{
odd.add(nums[i]);
}
}
int[] matched=new int[event.size()];
int count=0;
//遍历奇数组,匹配每一个偶数组
for(int i=0;i<odd.size();i++){
//特别注意,这个visited每一次都要清空,每一个Odd都要分配一个独立的visited
int[] visited=new int[event.size()];
if(find(odd.get(i),event,visited,matched)){
count++;
}
}
return count;
}
//遍历每一个odd,从event中找寻匹配的值,使用visited[event.length]标记该event是否找寻过
//用matched[event.length]=odd表示该event匹配的odd。才dfs算法
public static boolean find(int odd,List<Integer> event,int[] visited,int[] matched){
for(int j=0;j<event.size();j++){
if(visited[j]==0 && isPrimer(odd+event.get(j))){
//当前event每被访问过,并且odd[i]+event[j]是素数
//先设置其被访问
visited[j]=1;
if(matched[j]==0 || find(matched[j],event,visited,matched)){
//如果当前event[j]还没有匹配任何odd,则配对成功;
//如果当前event[j]已经存在伴侣odd1,那么当则个odd1能够重新找到新的伴侣event[k]时,则odd可以和event[j]配对
matched[j]=odd;//表示当前的event已经成功和odd配对
return true;
}
}
}
return false;
}
//判断一个数是否是质数
public static boolean isPrimer(int n){
for(int i=2;i*i<=n;i++){
if(n%i==0){
return false;
}
}
return true;
}
}
立方根
题目描述:
不使用库函数,求解一个数字的立方根
解题思路:
二分法:[0,a ],找到中间的数mid,mid* mid* mid=input,则找到直接输出,如果小于结果,表明下一轮mid应该取大一点,即min=mid;否则,max=mid;通过这种方式逐渐缩小搜索的范围,并且算法的时间复杂度为log(N)级别。
机试注意实现:
(1)精度的设置,精度越高,耗时越多
(2)输出格式的控制,使用printf("%.1f",output),保留1位小数
参考代码
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc =new Scanner(System.in);
double res=0;
while(sc.hasNext()){
double input=sc.nextDouble();
res=getCubeRoot(input);
System.out.printf("%.1f\n",res);
}
}
public static double getCubeRoot(double input){
double min=0;
double max=input;
double mid=0;
while(max-min>0.00001){
mid=(max+min)/2;
if(mid*mid*mid<input){
//证明mid取小了,[min,mid,max]
min=mid;
}else if(mid*mid*mid>input){
//mid取大了,缩小max的边界
max=mid;
}else{
//恰好
return mid;
}
}
//当精度max-min<指定值时,取max和min均可以
return max;
}
}
等差数列问题
题目描述:
功能:等差数列 2,5,8,11,14。。。。
输入:正整数N >0
输出:求等差数列前N项和
返回:转换成功返回 0 ,非法输入与异常返回-1
本题为多组输入,请使用while(cin>>)等形式读取数据
import java.util.*;
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String strs="";
while((strs=br.readLine())!=null){
int n=Integer.parseInt(strs);
System.out.println(add(n));//一定要使用println,多组连续的测试案例将导致输出的结果连续,应该换行。
}
}
private static int add(int N){
if(N<=0){
return -1;
}
int res=0;
for (int i=2,n=0;n<N;n++,i+=3){
res+=i;
}
return res;
}
}
验证尼可彻斯定律
题目描述:
验证尼科彻斯定理,即:任何一个整数m的立方都可以写成m个连续奇数之和。
例如
1^3=1
2^3=3+5
3^3=7+9+11
4^3=13+15+17+19
解题思路:
(1)等式的右边是一个等差数列,d=2,等差数列的前n和公式为: S n = n ( a 1 + a n ) / 2 S_n=n(a_1+a_n)/2 Sn=n(a1+an)/2,通项公式为: a n = a 1 + ( n − 1 ) d a_n=a_1+(n-1)d an=a1+(n−1)d,联立方程可求得 a 1 = m 2 − m + 1 a_1=m^2-m+1 a1=m2−m+1
(2)根据首项,每次+2,连续加m-1次,即可得出答案,使用StringBuilder来缓存结果。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int m=Integer.parseInt(str);
System.out.println(getSequeOddNum(m));
}
}
public static String getSequeOddNum(int m){
int a1=m*m-m+1;//根拒等差数列的前n项之和通项公式,可以求得首项的表达式
//总共有m项目,在迭代加m-1次即可
StringBuilder sb=new StringBuilder();
sb.append(a1);
for(int i=0;i<m-1;i++){
a1+=2;
sb.append("+"+a1);
}
return sb.toString();
}
}
取近似值
题目描述:
写出一个程序,接受一个正浮点数值,输出该数值的近似整数值。如果小数点后数值大于等于5,向上取整;小于5,则向下取整。
参考代码,还是要注意输入输出
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
while(sc.hasNextFloat()){
float n=sc.nextFloat();
System.out.print(func(n));
}
}
public static int func(float n){
int i=(int)n;//不进行4舍5入操作,直接去除小数点位置
return (n-i)>=0.5?i+1:i;
}
}
真分数转化为埃及分数
题目描述:
埃及分数:分子为1的数。
真分数:分子< 分目的分数。
输入一个真分数,将该真分数转化为埃及分数之和的形式
解题思路:
这道题需要经过一点点数学推导。
b ÷ a = q + r ( q 为 商 , r 为 余 数 , a > r ) , 可 得 : b = a q + r , 进 一 步 得 : b / a = q + r / a > = q + 1 , 两 边 取 倒 数 得 : a / b < = 1 / ( q + 1 ) b \div a=q+r(q为商,r为余数,a>r) ,可得:b=aq+r,进一步得:b/a=q+r/a>=q+1,两边取倒数得:a/b<=1/(q+1) b÷a=q+r(q为商,r为余数,a>r),可得:b=aq+r,进一步得:b/a=q+r/a>=q+1,两边取倒数得:a/b<=1/(q+1),即a/b的第一个埃及分数可以为1/q+1,令c=q+1,那剩余: a / b − 1 / c = ( a c − b ) / b c a/b-1/c=(ac-b)/bc a/b−1/c=(ac−b)/bc,这个时候使用相同的逻辑进行计算,即令a=ac-b,b=bc。直到b%a==0(能整除必能化成埃及分数) 或者 a= =1(已经为埃及分数)才停止循环。
import java.io.*;
public class Main{
public static void main(String[] ars) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String[] s= str.split("/");
int a=Integer.parseInt(s[0]);//分子a
int b=Integer.parseInt(s[1]);//分母b
while(a!=1 && b%a!=0 ){
int q=b/a;//取商
int c=q+1;
System.out.print("1/"+c+"+");
a=a*c-b;
b=b*c;
}
//当退出循环时,b能整除a;
System.out.println("1/"+b/a);
}
}
}
线性插值运算
题目描述:
假设编号M和N对应的测量值为A和B,则需要在M到N之间的(N-M-1)次差值运算,通过这个计算公式可以知道连续的M和N之间不需要插值运算,M与N之间第i个插值为:A+((B-A)/(N-M))*i;如果M和N重复,则丢弃后来出现的数组。
输入:
1.输入两个整数m和n,m表示原始测量的个数,n表示插值后最大的测量个数
2.测量数组,[ 编号,测量值];且测量编号已经升序排序;
输出:
经过插值处理后的测量组;
解题步骤:
按题目要求依次处理,逐一读取数据:
(1)第一组测量数据是不需要插值的,直接输出即可;由于下一组数据要和这组数据对比,故应该记录这一组的数据
(2)从第二组数据开始,需要和上一组数据进行比对以确定是否需要进行插值或者丢弃处理。使用一个循环读取接下来的数据组,对每一组数据进行解析,与上一组的编号相同,则不输出这组数据,和上组数据的编号的差值大于1,则进行差值运算,这里再使用一个循环来完成所有的差值;最后,在输出完插值之后,一定要记得输出当前的读取到的数据
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String[] strs=str.split(" ");
int total=Integer.parseInt(strs[0]);//测量数据的组数
if(total>0){
//首行数据不必插值
String[] data=br.readLine().split(" ");
int num=Integer.parseInt(data[0]);
int value=Integer.parseInt(data[1]);
System.out.println(num+" "+value);
//处理后面的数据,需要对比之前的编号,以进行下一步的判断
int preNum=num;
int preValue=value;
//读取下一组数据
for(int j=0;j<total-1;j++){
String[] nextData=br.readLine().split(" ");
num=Integer.parseInt(nextData[0]);
value=Integer.parseInt(nextData[1]);
//处理(preNum,num)之间的每一个插值
if(preNum==num){
//重复值的要丢弃
continue;
}
for(int i=preNum+1;i<num;i++){
int insertValue=preValue+((value-preValue)/(num-preNum))*(i-preNum);
System.out.println(i+" "+insertValue);
}
//更新preNum和num
preNum=num;
preValue=value;
System.out.println(num+" "+value);
}
}
}
}
}
自守数
题目描述:
n为n的平方的尾数,成为自守数。
解题思路:
如果n为自守数,那么n*n-n一定能整除(10,100,1000,取决于n有几位,n位两位则100,n为3位1000)
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = null;
while ((str = br.readLine()) != null) {
int n=Integer.valueOf(str);
int count=0;
for(int i=0;i<=n;i++){
int temp=i;
int j=1;
while(temp!=0){
temp=temp/10;
j=j*10;
}
if((i*i-i)%j==0){
count++;
}
}
System.out.println(count);
}
}
}
方法二仅仅做参考
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
int count=0;
for(int i=0;i<=n;i++){
int tmp=i%10;
if(tmp==0 || tmp==1 || tmp==5 || tmp==6){
if(solution(i)){
count++;
}
}
}
System.out.println(count);
}
}
public static boolean solution(int n){
String str1=n+"";
int sum=n*n;
String str2=sum+"";
int index=str2.length()-str1.length();
if(index>=0){
String str=str2.substring(index);
if(str.equals(str1)){
return true;
}else{
return false;
}
}
return false;
}
}
计算矩阵乘法的体量
题目描述:
输入多行,先输入要计算乘法的矩阵个数n,每个矩阵的行数,列数,总共2n的数,最后输入要计算的法则
输入
3
50 10
10 20
20 5
(A(BC))
输出:
3500
解题思路:
四则运算会用到栈这个数据结构,由于矩阵运算不具备交换率,所以应该从表达式的后面往前进行入栈,当前字符是‘)’,直接入栈,当前字符为‘(’,出栈两个矩阵,在栈中存放的并不是矩阵的本身,而是矩阵在输入的矩阵集合(arr[i][0]为第i个矩阵的行数,arr[i][1]为第i个矩阵的列数)中的索引,根据这个索引能能到跟矩阵相关的参数,如矩阵的行数和列数;
(1)A*B运算体量,A的行数 *(A的列数 或者B的行数) *B的列数;即sum=arr[i][0] *arr[i][1] *arr[j][1];
(2)stack存放的是当前矩阵对应的索引i,当从stack中取出两个数i和j时,通过arr[][]可以得到他们的行数和列数
,注意,‘)’要出栈,然后更新B*C计算后的列数,因为A *(B * C)中B *C的结果的列数决定了最后结果的列数,因此更新arr[b][1]=arr[c][1];
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
int[][] arr=new int[n][2];
for(int i=0;i<n;i++){
String[] tmp=br.readLine().split(" ");
arr[i][0]=Integer.parseInt(tmp[0]);
arr[i][1]=Integer.parseInt(tmp[1]);
}
int num=arr.length-1;
Stack<Integer> stack=new Stack<>();
String input=br.readLine();
int len=input.length();
int sum=0;
for(int i=len-1;i>=0;i--){
char c=input.charAt(i);
if(c==')'){
//入栈
stack.push(-1);//-1表示')';
}else if(c=='('){
//出栈两个操作数
int n1=stack.pop();//B
int n2=stack.pop();//c
sum+=arr[n1][0]*arr[n1][1]*arr[n2][1];//A*B-->A的行数*A的列数*B的列数
//取出')'
stack.pop();
//计算中间值,并继续入栈
arr[n1][1]=arr[n2][1];//tmp=B*C,C的列数决定了A*(B*C)的列数
stack.push(n1);//A*tmp//关键要知道tmp的列数
}else{
stack.push(num);//num为输入矩阵行索引
num--;
}
}
System.out.println(sum);
}
}
}
高精度大整数相加
题目描述:
输入两个表示整数的字符串(可能为正数和负数),求他们的和
解题思路:
整数相加可以存在的情况为:
(1)两个正数相加
(2)两个正数相减。
举个例子:-123+123=123-123(第二个加数减去第一加数的绝对值)
- 对于两个正数str1和str2相加而言,其具体的步骤为:
(1)从两个字符串的最低为开始计算,依次取出当前字符所表示的数字。
(2)sum+=第一个加数;sum+=第二个加数;sum+=进位;最低位的进位默认是0
(3)判断当前为之和是否会产生进位,carry=sum/10==1?1:0;
(4)循环(1)~(3)只有当两个字符串都结束时候才退出循环,退出循环后,还需要注意,最高位可能产生进位,若carry>0,则还需要加上进位信息,否则不加。
- 对于两正数相减,其基本原理差不多,我们可以先处理:str1-str2,str1>str2的情况
(1)从两个字符串的最低为开始计算,依次取出当前字符所表示的数字。
(2)sum+=被减数;sum-=减数;sum-=借位;其中,sum每次循环默认为10,
(3)判断运算是否需要借位,由于当x< y时, (10+x-y)/10==0,此时会像其高位借一个位,即carry=1,否则carry=0;
(4)循环(1)~(3)只有当两个字符串都结束时候才退出循环,退出循环后,还需要注意,最前面的0是为为无效数据,应该给予处理。
对于任意两个正数相减均可以用str1(大)-str2(小)来转换,这里面使用str1.compareTo(str2)来比较当长度相同时的str1和str2的大小。
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str1 = "";
while ((str1 = br.readLine()) != null) {
String str2=br.readLine();
String res=add(str1, str2);
//String res= solution(str1,str2);
System.out.println(res);
}
}
public static String solution(String str1, String str2) {
// 分类讨论
// 两个负数
char tmp1 = str1.charAt(0);
char tmp2 = str2.charAt(0);
if (tmp1 == '-' && tmp2 == '-') {
String num1 = str1.substring(1);
String num2 = str2.substring(1);
return "-" + add(num1, num2);
} else if (tmp1 == '-' || tmp2 == '-') {
if (tmp1 == '-') {
// tmp2-tmp1;
String num1 = str1.substring(1);
String num2 = str2;
return subStract2(num2, num1);
}else {
// tmp1-tmp2;
String num1 = str1;
String num2 = str2.substring(1);
return subStract2(num1, num2);
}
} else {
return add(str1, str2);
}
}
//不区分str1和str2的大小
public static String subStract2(String str1,String str2){
str1=str1.trim();
str2=str2.trim();
int len1 =str1.length();
int len2 =str2.length();
if(len1>len2){
return subStr(str1, str2);
}else if(len1==len2){
int res=str1.compareTo(str2);
if(res>0){//str1>str2
return subStr(str1, str2);
}else if(res==0){
return "0";
}else{
return "-"+ subStr(str2, str1);
}
}else{
return "-"+subStr(str2, str1);
}
}
// 两个正数相减,
//其中规定:str1>str2。
public static String subStr(String num1, String num2) {
int i=num1.length()-1;
int j=num2.length()-1;
int len=num1.length()>num2.length()?num1.length():num2.length();
int[] res=new int[len];
int brower=0;//最高位默认没有向前借位
int k=res.length-1;
while(i>=0 && j>=0){
int sum=10;//每一位的和初始化为10;
sum+=i>=0?(num1.charAt(i)-'0'):0;//被减数
sum-=j>=0?(num2.charAt(j)-'0'):0;//减数
sum-=brower;
//该 位之差
res[k--] = sum%10;
brower=sum/10==0?1:0;//当前位不够,向前借1位,10+x-y<10,由于x<y,才会向前借一位
i--;
j--;
}
StringBuilder sb=new StringBuilder();
int index=0;
while(res[index]==0){
index++;
}
for(int l=index;l<res.length;l++){
sb.append(res[l]);
}
return sb.toString();
}
// 两个整数相加数相加
public static String add(String str1, String str2) {
str1=str1.trim();
str2=str2.trim();
int i=str1.length()-1;
int j=str2.length()-1;
int len=str1.length()>str2.length()?str1.length():str2.length();
int[] res=new int[len];
int carry=0;//进位
int k=res.length-1;
while(i>=0 || j>=0){
int sum = 0;
sum+=i>=0?(str1.charAt(i)-'0'):0;//第一个加数
sum+=j>=0?(str2.charAt(j)-'0'):0;//第二个加数
sum+=carry;//上一位的进位
//更新进位信息
carry=sum/10;
res[k--]=sum%10;
i--;
j--;
}
//前面有可能多一个'0',需要去掉
StringBuilder sb=new StringBuilder();
if(carry!=0){
sb.append(carry);
}
for(int e:res){
sb.append(e);
}
return sb.toString();
}
}
队列和栈
计算字符串算术表达式
题目描述:
输入字符串长度不超过100,合法的字符包括”+, -, *, /, (, )”,”0-9”,字符串内容的合法性及表达式语法的合法性由做题者检查。本题目只涉及整型计算。
解题思路:
方法一(中缀表达式)
字符串表达式的值计算是栈的一个经典应用。这道题需要设计两个栈,操作数栈和操作符栈。解析表达式的过程为:
(1)依次读取表达式每个字符,数字入数栈,运算符入操作符栈。当读取的字符为数字的时候不能立即入栈,因为后面可能也是个数字,即是个大于10的数字,我们需要连续地读取出所有数字,直到下一个字符不是数字为止。
(2)当读取到的字符是操作符时,要遵循以下是几个步骤:
2.1 当前操作符的优先级别<=操作符栈顶的优先级别,要先出栈操作符和两个相应的操作数,这个过程一直循环直到当前操作符优先级别>操作符栈顶的元素或者操作符栈顶为空。
2.2 当前操作符的优先级别>=操作符栈顶的优先级别,直接入栈。
2.3 如果操作符栈顶为空,直接入栈。
(3)括号的处理
3.1 如果是'(' ,直接入栈
3.2 如果是')',则从操作符栈和数栈中分别出栈运算,直到操作符的栈顶元素为')'或者栈为空为止。
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String express = "";
while ((express = br.readLine()) != null) {
int res = solution(express);
System.out.println(res);
}
}
public static int solution(String express) {
Stack<Integer> numStack = new Stack<Integer>();
Stack<Character> operaStack = new Stack<Character>();
char ch = '\u0000';// 每次扫描到的字符
int num1 = 0;
int num2 = 0;
char oper = '\u0000';
int res = 0;
String keepNum = "";// 用于拼接多位数
for (int index = 0; index < express.length(); index++) {
ch = express.charAt(index);
// 暂时不考虑负数的情况(实际情况很复杂)
if (isOpera(ch)) {// 如果是运算符,则考虑入运算符栈
if (!operaStack.isEmpty()) {
// 判断当前字符和栈顶字符的优先级
if (priority(ch) <= priority(operaStack.peek())) {
// 当前字符元素优先级<=栈顶字符
// 当前字符元素优先级<=栈顶字符
do {
num1 = numStack.pop();
num2 = numStack.pop();
oper = operaStack.pop();
res = cal(num1, num2, oper);
numStack.push(res);
} while (!operaStack.isEmpty() && priority(ch) <= priority(operaStack.peek()));
operaStack.push(ch);
} else {
operaStack.push(ch);
}
} else {
// 操作数栈为空,直接进栈
operaStack.push(ch);
}
} else if (ch >= '0' && ch <= '9') {// 数字
keepNum += ch;
if (index == express.length() - 1) {
numStack.push(Integer.parseInt(keepNum));
} else {
// 当前字符的下一位是字符,才将数字入栈
char tmp = express.substring(index + 1, index + 2).charAt(0);
if (isOpera(tmp) || tmp == ')') {
numStack.push(Integer.parseInt(keepNum));
keepNum = "";// 要清空
}
}
} else if (ch == '(') {
operaStack.push(ch);
} else if (ch == ')') {
// 全部出栈直到(出来为止。
while (operaStack.peek()!= '(') {
num1 = numStack.pop();
num2 = numStack.pop();
oper = operaStack.pop();
res = cal(num1, num2, oper);
numStack.push(res);
}
operaStack.pop();// '(',继续出栈。
}
}
// 计算
while (!operaStack.isEmpty()) {
num1 = numStack.pop();
num2 = numStack.pop();
oper = operaStack.pop();
res = cal(num1, num2, oper);
numStack.push(res);
}
int res2 = numStack.pop();
return res2;
}
// 判断一个字符是否是运算符
public static boolean isOpera(char ch) {
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
}
// 四则运算
public static int cal(int num1, int num2, char oper) {
int res = 0;
switch (oper) {
case '+':
res = num1 + num2;
break;
case '-':
res = num2 - num1;
break;
case '*':
res = num2 * num1;
break;
case '/':
res = num2 / num1;
break;
default:
break;
}
return res;
}
// 获取符号的优先级别,数字越大,优先级别越高
public static int priority(char ch) {
if (ch == '+' || ch == '-') {
return 1;
} else if (ch == '*' || ch == '/') {
return 2;
} else {
return 0;
}
}
}
方法二(后缀表达式)
中缀表达式比较符合人从左到右的逻辑思维,但是,处理存在多重"()“和”[]"时,还是有点复杂,使用后缀表达式来计算。所以程序需要完成两个任务:
- 将输入的中缀表达式转换为后缀表达式的形式
步骤为:
(1)设置一个操作符栈和存放最后结果的的list。
(2)从左到右依次读取输入的字符串,如果当前字符为数字,则直接放进list,如果是非数字,需要分类讨论:
(2.1)若为'(' 或 '[' 或 '{',直接入操作符栈
(2.2)若为')' 或 ']' 或 '}',则从操作符栈中出栈并放进res中,直到栈顶元素为:'(' 或 '[' 或 '{'中的一种则停止。注意最后一个左半边括号也要出操作符栈,但不能放进res中。
(2.3)若为 + - * / ,则需要判断当前符号与符号栈顶的元素的优先级,若低于或等于栈顶优先级,则持续地将操作符栈出栈并放到res中,直到和栈顶优先级相等才停止。最后必须把当前的符号放进符号栈中。
(3)当输入的字符串遍历完成之后,把操作符栈中的剩余元素放进res中,最后这个res的顺寻则为后缀表达式。
- 直接计算后缀表达式
当知道了后缀表达式以后,则计算记就比中缀表达式要简单,
(1)设置一个栈来存储最后的结果
(2)依次读取后缀表达式,如果是数字的化话,直接入栈
(3)如果是+ - * / ,则从栈中取出两个操作数进行相应的四则运算,注意,-和/的操作数顺序需要逆转一下。
(4)后缀表达式遍历完成之后,栈中剩余一个元素就为计算的结果。
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] ars) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
List<String> infixList=getInfixExpress(str);//根据字符串获取中缀表达式的list
List<String> sufixList=getSuffixExpress(infixList);//将中缀转化为后缀
int res=calculate(sufixList);//计算后缀表达式的值
System.out.println(res);
}
}
//将中缀表达式转化为后缀表达式(以list来存储)
private static List<String> getSuffixExpress(List<String> list){
Stack<String> operStack=new Stack<>();//存放操作符的
List<String> res=new ArrayList<String>();//存放后缀表达式
//遍历中缀表达式list
for(String item:list){
//如果是数字,则加入res中
if(item.matches("\\d+")){
res.add(item);
}else if(item.equals("(") || item.equals("[") || item.equals("{")){
//如果是(、[、{中的一种,则直接入符号栈。
operStack.push(item);
}else if(item.equals(")") || item.equals("]") || item.equals("}")){
//如果是(、[、{ 中的一种,则operStack中的符号放入res中,直到遇到右括号或者为空为止。
while(!operStack.isEmpty() && !operStack.peek().equals("(") && !operStack.peek().equals("[") && !operStack.peek().equals("{" )){
res.add(operStack.pop());
}
//最后的左括号一定也要出来。
operStack.pop();
}else{
//剩余的情况就是:+ - * /
//这里需要比较栈顶元素和当前元素的优先级别
while(!operStack.isEmpty() && priority(item)<=priority(operStack.peek())){
//当前操作符优先级小于栈顶的优先级,则出栈加入res,直到其大于栈顶元素优级为止
res.add(operStack.pop());
}
//当前符号要进入operStack
operStack.push(item);
}
}
//将operStack剩余的操作符依次弹出并加入res
while(!operStack.isEmpty()){
res.add(operStack.pop());
}
return res;
}
//计算后缀表达式的值(表达式中只有操作数和运算符号)
private static int calculate(List<String> list){
Stack<String> stack=new Stack<String>();
for(String item:list){
//使用正则表达式匹配数字
if(item.matches("\\d+")){
stack.push(item);
}else{
//是运算符,出栈两个数,进行四则运算
int num1=Integer.parseInt(stack.pop());
int num2=Integer.parseInt(stack.pop());
int res=0;
if(item.equals("+")){
res=num1+num2;
}else if(item.equals("-")){
res=num2-num1;//注意顺序
}else if(item.equals("*")){
res=num1*num2;
}else{
res=num2/num2;//注意顺序
}
stack.push(res+"");//把计算结果放进栈中。
}
}
//最后留在栈中的元素是运算结果。
return Integer.parseInt(stack.pop());
}
//获取中缀表达式的list
private static List<String> getInfixExpress(String str){
List<String> res=new ArrayList<String>();
int i=0;
String keepNum="";
char c;
do{
if((c=str.charAt(i))<'0' || (c=str.charAt(i))>'9'){//非数字的时候
res.add(c+"");
i++;
}else{//为数字的时候
keepNum="";//每次处理新的多位数时都需要清空
while(i<str.length() && ( (c=str.charAt(i))>='0' && (c=str.charAt(i))<='9' )){
keepNum+=c;
i++;
}
res.add(keepNum);
}
}while(i<str.length());
return res;
}
private static boolean isNumic(char ch){
return ch>='0' && ch<='9';
}
//获取+ - * /的优先级别,数字越大,优先级越高
private static int priority(String str){
if(str.equals("+") || str.equals("-")){
return 1;
}else if(str.equals("*") || str.equals("/")){
return 2;
}else{
return 0;
}
}
}
递归问题
放苹果问题
题目描述:
M个苹果放入n个盘子中,总共有多少种方法
解题思路:
当n>m时候,多余的盘子不会增加放置的种类数,即只有m个盘子起作用,f(m,m);
当n<=m时候,分为两类:有空盘和无空盘,有空盘时,空几个??使用递归依次算出空1,2,3,…个盘子,即f(m,n-1),递归的结束条件是:n=1时,只有一种放法;无空盘时,每个盘子至少装一个,剩下m-n个苹果,m-n种苹果放到n个盘子种有多少种放法呢??不就是f(m-n,n)吗?此时的递归结束条件为:m=0时,本来是0种方法,但由于每个盘子至少放了一个苹果,故应该为1种方法。两类情况相加即可。
当没有苹果时,为1种放法。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String[] strs=str.split(" ");
int m=Integer.parseInt(strs[0]);
int n=Integer.parseInt(strs[1]);
System.out.println(putApple(m,n));
}
}
public static int putApple(int m,int n){
//递归结束条件
//n=1,只有一个盘子时,只有一种方法
//m=0,没有盘子,只有一种方法
if(m==0 || n==1){
return 1;
}
//若盘子比苹果多,剩余的盘子并不会影响放法
if(n>m){
return putApple(m,m);
}else{
//当苹果数大于或等于盘子数目时候:
//分两类讨论:有盘子剩余和没盘子剩余
//使用的盘子数量,1,2,3,...n,则分别剩余n-1,n-2,n-3,1,0;使用递归
//没有盘子剩余,每个盘子至少装有一个苹果,剩余m-n个放到n个盘子中;
return putApple(m,n-1)+putApple(m-n,n);
}
}
}
平分数组问题
题目描述:
编写一个函数,传入一个int型数组,返回该数组能否分成两组,使得两组中各元素加起来的和相等,并且,所有5的倍数必须在其中一个组中,所有3的倍数在另一个组中(不包括5的倍数),能满足以上条件,返回true;不满足时返回false。
解题思路:
(1)迭代分组,计算sum3、sum5和将不能被5整除也不能被3整除的数放进一个list中,并同时计算整个数组的和sum
题目要求sum3 = = sum5,首先我们知道sum平均分成sum3和sum5,则sum必须为偶数,其次,int target=(sum-(sum3+sum5))/2=sum/2-sum3 || sum/2-sum5,如果我们能够在list中凑出和为target的元素集合,那么我们就可以将list中的元素分配到sum3和sum5中去。
(2)在list集合中找到n个元素的和为target可以使用dfs算法或者递归
具体来说就是,boolean dfs(list,index,target);分两种情况,当前元素list.get(index)符合要求,则继续递归:dfs(list,index+1,target-list.get(index)),或者 dfs(list,index+1,target);
递归结束的条件为:indexlist.size(),若:target0,则返回true。
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
String[] strs=br.readLine().split(" ");
int[] nums=new int[n];
//存放既不能被5整除又不能被3整除的数字;
LinkedList<Integer> list=new LinkedList<Integer>();
int sum3=0;
int sum5=0;
int sum=0;
for(int i=0;i<n;i++){
int cur=Integer.parseInt(strs[i]);
if(cur%5==0){
sum5+=cur;
}else if(cur%3==0){
sum3+=cur;
}else{
list.add(cur);
}
//计算所有元素的和
sum+=cur;
}
//sum3==sum5,则sum必须为偶数
// int target=(sum-(sum5+sum3))/2=sum/2-sum5 能否在list中找凑出来,能凑出来证明可以
//平分给sum3和sum5两个集合,则题目要求符合。
int target=sum/2-sum3;//或者sum/2-sum5
if(sum%2!=0){
System.out.println("false");
}else{
if(findTarget(list,0,target)){
System.out.println("true");
}else{
System.out.println("false");
}
}
}
}
//dfs算法:
//在list集合中能否凑到和为target的值。
//从[start,list.size]范围中招
public static boolean findTarget(LinkedList<Integer> list,int start,int len,int target){
if(len==0){
return target==0;
}
if(target<len*list.get(start)){
return false;//升序排序,则一定找不到这种target了
}
for(int i=start;i<=list.size()-len;i++){
if(i>start && list.get(i)==list.get(i-1)){
continue;//去除重复值
}
if(findTarget(list,start+1,len-1,target-list.get(i))){
return true;
}
}
return false;
}
//递归法
public static boolean findTarget(LinkedList<Integer> list,int start,int target){
if(start==list.size()){
return target==0;
}
//当前start符合要求,或则不符合要求
return findTarget(list,start+1,target-list.get(start)) || findTarget(list,start+1,target);
}
}
汽水瓶换汽水
题目描述:
有这样一道智力题:“某商店规定:三个空汽水瓶可以换一瓶汽水。小张手上有十个空汽水瓶,她最多可以换多少瓶汽水喝?”答案是5瓶,方法如下:先用9个空瓶子换3瓶汽水,喝掉3瓶满的,喝完以后4个空瓶子,用3个再换一瓶,喝掉这瓶满的,这时候剩2个空瓶子。然后你让老板先借给你一瓶汽水,喝掉这瓶满的,喝完以后用3个空瓶子换一瓶满的还给老板。如果小张手上有n个空汽水瓶,最多可以换多少瓶汽水喝?
解题思路:
n/3=a…b;这一轮喝掉a瓶,下一轮有a+b个空瓶,使用递归调用,递归结束条件:当n==2时,可以借一个,即return 1,当n< 2时,直接结束递归调用。
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
if(n!=0){
System.out.println(getCount(n));
}else{
return ;//结束
}
}
}
public static int getCount(int n){
//递归的结束条件
//当手中的空瓶子为2个时,可以借一瓶
if(n==2){
return 1;
}
if(n<2){
return 0;
}
int a=n/3;
int b=n%3;
int rest=a+b;
return a+getCount(rest);
}
}
24点游戏
题目描述:
问题描述:给出4个1-10的数字,通过加减乘除,得到数字为24就算胜利
输入:
4个1-10的数字。[ 数字允许重复,但每个数字仅允许使用一次,测试用例保证无异常数字]
输出:
true or false
解题思路
回溯+递归,实际上也是枚举算法,从input[ 4]中挨个取出两个数(为了避免重复的操作,两个数应该不同)进行四则运算,两个数进行四则运算(+、-、*、/:注意:-法有a-b和b-a两种情况,而/的分母不能为0,也有a/b和b/两种情况)的所有结果存放道一个集合中(去重);依次将结果集合放入新的数组中,然后再次递归运算,知道结果集合只有一个元素为止,这个元素就是这一轮四则运算的结果,判断其是否为24;
小技巧:
这里使用了回溯的方法,为了更好恢复现场,这里可以新建一个ArrayList来替代数组,每次将剩余的元素和四则运算的结果放入该ArrayList中(追加),然后递归传入该ArrayList,最后恢复现场,删除ArrayList的追加的元素(remove方法)。
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
String[] strs=str.split(" ");
ArrayList<Double> input=new ArrayList<>();
for(int i=0;i<strs.length;i++){
input.add(Double.parseDouble(strs[i]));
}
boolean res=solve(input);
if(res){
System.out.println("true");
}else{
System.out.println("false");
}
}
}
/**
* 回溯方法
* @param nums
* @return
*/
public static boolean solve(List<Double> numbers){
if(numbers.size()==1){
//只剩下最后一个元素的时候,判断该元素是否为24,
return Math.abs(numbers.get(0)-24)<1e-6;
}
//从numbers中取出两个数做四则运算+、-、*、/
for(int i=0;i<numbers.size();i++){
for(int j=0;j<numbers.size();j++){
//取的两个元素不能相同
if(i!=j){
//采用回溯+递归的方式,需要恢复现场,因此另外开辟一个数组num来保存剩余元素
ArrayList<Double> nums=new ArrayList<>();
for(int k=0;k<numbers.size();k++){
if(k!=i && k!=j){
nums.add(numbers.get(k));
}
}
/**************两个数进行4则运算*****************/
Set<Double> set=calculate(numbers.get(i), numbers.get(j));
for(Double d:set){//对四种结果分别验证
nums.add(d);//将计算结果放入nums
if(solve(nums)){
return true;//只要有一个符合,就立即结束
}
//恢复现场
nums.remove(nums.size()-1);
}
}
}
}
return false;//所有的结果都没有找到,则结束
}
/**
* @description 返回两个数计算得到的结果集
*/
public static Set<Double> calculate(double a, double b) {
Set<Double> res = new HashSet<Double>();
res.add(a - b);
res.add(b - a);
res.add(a + b);
res.add(a * b);
if (a != 0) {
res.add(b / a);
}
if (b != 0) {
res.add(a / b);
}
return res;
}
}
24点游戏(列出每一种可能)
题目描述:
和上题题目意思一样,只不过需要输出路径
解题思路:
枚举,暴力求解
现在贴一个参考代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
private static String[] op = new String[]{"+","-","*","/"};
public static void main(String[] args) throws IOException {
BufferedReader re=new BufferedReader(new InputStreamReader(System.in));
String input;
while((input = re.readLine()) != null && !"".equals(input)){
String[] ss = input.split(" ");
int a = getInputNum(ss[0]);
int b = getInputNum(ss[1]);
int c = getInputNum(ss[2]);
int d = getInputNum(ss[3]);
// 只要有joker,直接返回ERROR
if(a==-1||b==-1||c==-1||d==-1){
System.out.println("ERROR");
continue;
}
compute(a,b,c,d);
}
}
/**
* 24点计算方法穷举
* @param a
* @param b
* @param c
* @param d
*/
public static void compute(int a,int b,int c,int d) {
int[] arr={a,b,c,d};
// 运算符穷举数组
String[][] arr1 = symbol();
for(int i=0;i<4;i++){// 第一个数字
for(int j=0;j<4;j++){// 第二个数字
for(int k=0;k<4;k++){// 第三个数字
for(int p=0;p<4;p++){// 第四个数字
if((i!=j)&&(i!=k)&&(i!=p)&&(j!=k)&&(j!=p)&&(k!=p)){// 如果四个数字互不相等才计算,不然一个字符就会出现两次
// 遍历运算符穷举数组
for(String[] str:arr1){
// 依次计算,得出最终结论
int sum = sumNum(arr[i], arr[j], str[0]);
sum=sumNum(sum, arr[k], str[1]);
sum=sumNum(sum, arr[p], str[2]);
if(sum==24){
// 如果结果等于24,返回结果
String str1=change2(arr[i])+str[0]+change2(arr[j])+str[1]+change2(arr[k])+str[2]+change2(arr[p])+"";
System.out.println(str1);
return;
}
}
}
}
}
}
}
// 穷举之后仍然没有结果,返回none
System.out.println("NONE");
}
/**
* 穷举所有可能的运算符组合
* @return
*/
public static String[][] symbol() {
//运算符共三个,每个四种可能性,4*4*4中运算符组合,每个组合有三个运算符
String[][] symbol = new String[64][3];
int p =0;
for(int i=0;i<4;i++){// 第一个运算符
for(int j=0;j<4;j++){// 第二个运算符
for(int k=0;k<4;k++){// 第三个运算符
symbol[p++]=new String[]{op[i],op[j],op[k]};
}
}
}
return symbol;
}
/**
* 两个数字计算结果
* @param a
* @param b
* @param symb
* @return
*/
public static int sumNum(int a, int b, String symb) {
switch(symb){
case "+":
return a+b;
case "-":
return a-b;
case "*":
return a*b;
case "/":
return a/b;
default:
return 0;
}
}
/**
* 字符串转数字
* @param str
* @return
*/
public static int getInputNum(String str){
switch(str.toUpperCase()){
case "A":
return 1;
case "J":
return 11;
case "Q":
return 12;
case "K":
return 13;
case "JOKER":
return -1;
default:
return Integer.parseInt(str);
}
}
/**
* 数字转化为字符串
* @param i
* @return
*/
public static String change2(int i) {
switch(i){
case 1:
return "A";
case 11:
return "J";
case 12:
return "Q";
case 13:
return "K";
default:
return String.valueOf(i);
}
}
}
火车进出站问题
问题描述:
给定一个正整数N代表火车数量,0< N < 10,接下来输入火车入站的序列,一共N辆火车,每辆火车以数字1-9编号,火车站只有一个方向进出,同时停靠在火车站的列车中,只有后进站的出站了,先进站的才能出站。要求以字典序排序输出火车出站的序列号。
解题思路:
递归+回溯;Stack1表示站台外可以进站的火车,stack2表示站台中等待出栈的火车,List存放已经出栈的火车,每列火车用空格隔开。用Statck1来完成队列的功能,反序输入,栈可以更好地实现回溯算法中的"恢复现场功能",具体的代码很简单,主要是思想要掌握
import java.util.*;
import java.io.*;
public class Main{
private static Stack<String> in =new Stack<>(); //入站
private static Stack<String> wait= new Stack<>(); //等待出站
private static List<String> list=new ArrayList<>();
public static void trainOut(String str){
if(wait.isEmpty() && in.isEmpty()){
list.add(str.trim());
return;
}
//出站一辆车
if(!wait.isEmpty()){
String tmp = wait.pop();
trainOut(str+" "+tmp);
//恢复
wait.push(tmp);
}
if(!in.isEmpty()){
String tmp = in.pop();
wait.push(tmp);
trainOut(str);
//恢复
in.push(tmp);
wait.pop();
}
}
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
int n = Integer.parseInt(str);
String[] strs = br.readLine().split(" ");
for(int i = strs.length - 1; i >= 0; i--){
in.push(strs[i]);
}
trainOut("");
Collections.sort(list);
for(String s:list)
System.out.println(s);
}
}
}
位运算
提取不重复的整数
题目描述:
输入一个int型整数,按照从右向左的阅读顺序,返回一个不含重复数字的新的整数。
解题思路:
(1)使用hash数组存储’0’~'9’出现的个数:count[ str.charAt(i)-‘0’],常用技巧,当前字符与该系列字符的首个字符的差值作为hash数组的索引。
(2)从左往右依次读取,只有count中对应的数量为0时,才加入数组。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str=null;
while((str=br.readLine())!=null){
System.out.println(solution(str));
}
}
public static String solution(String str){
char[] strs=str.toCharArray();
int[] count=new int[10];//0~9
StringBuilder sb=new StringBuilder();
for(int i=strs.length-1;i>=0;i--){
if(count[strs[i]-'0']==0){
sb.append(strs[i]-'0');
count[strs[i]-'0']++;
}
}
return sb.toString();
}
}
n&(n-1)的骚操作
题目描述:
输入一个int型的正整数,计算出该int型数据在内存中存储时1的个数。
解题思路:
使用bit运算符号
(1)每进行一次n&(n-1),都会使得n的最低位的首1变为0,利用这个性质,可以迭代进行n=n&(n-1),当n最后变成0时,就进行了多少次与运算,也就能计算出,1的个数;
import java.util.*;
public class Main{
public static void main(String[] strs){
Scanner sc=new Scanner(System.in);
while(sc.hasNext()){
int num=sc.nextInt();
int count=0;
while(num!=0){
count++;
num=num&(num-1);
}
System.out.println(count);
}
}
}
最大连续bit
题目描述:
输入一个整数,找出其二进制中最大的连续1的个数
解题技巧:
(1)库函数的使用:Integer.toBinarryString(int a);
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
str=Integer.toBinaryString(Integer.parseInt(str));
int count=0;
int max=0;
for(int i=0;i<str.length();i++){
if('1'==str.charAt(i)){
count++;
if(count>max){
max=count;
}
}else{
count=0;
}
}
System.out.println(max);
}
}
}
图问题
数独问题
题目描述:
数独(Sudoku)是一款大众喜爱的数字逻辑游戏。玩家需要根据9X9盘面上的已知数字,推算出所有剩余空格的数字,并且满足每一行、每一列、每一个粗线宫内的数字均含1-9,并且不重复。
输入:
包含已知数字的9X9盘面数组[空缺位以数字0表示]
输出:
完整的9X9盘面数组
解题思路分析:
数独必须要满足一下三个条件:
(1)每一行不能有重复的数字
(2)每一列不能有重复的数字
(3)每一个 3 * 3区域矩阵不能有重复数字
因此解决这个问题的第一个关键点就是实现这三个功能,在第三步中,当给定横坐标i和纵坐标j时,可以得出这个位置是属于哪个区域,即上下左右四个边界满足:
上边界:i / 3 * 3
下边界:i / 3 * 3+2
左边界:j / 3 * 3
右边界:j / 3 * 3+2
在这个边界范围内,寻找是否有重复。
接下来算法的主体使用DFS搜索算法(回溯算法)算法的主要步骤为:
(1)从(0,0)坐标开始,从上到下,从左到右,逐个位置遍历。
(2)当该位置元素不为0时,继续下一个位置(服从上到下,左到右的策略)
(3)当该位置元素为0时,需要从[1,9]中选取一个数字填充进去,在填充之前,需要判断该元素是否满足数独的三个条件,只有满足才加入。注意: 总共有9个选择,每做完一个选择(递归调用自己)之后,要回溯,也就是撤销选者。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
int[][] matrix=new int[9][9];
while((str=br.readLine())!=null){
// 构造matrix
for (int i = 0; i < 9; i++) {
String[] s =str.split(" ");
for (int j = 0; j < 9; j++) {
matrix[i][j] = Integer.parseInt(s[j]);
}
str=br.readLine();
}
//创建数独
sudoku(matrix,0,0);
//打印结果
for(int i=0;i<9;i++){
for(int j=0;j<9;j++){
if(j==8){
System.out.println(matrix[i][j]);//最后一列不打空格,并换行
}else{
System.out.print(matrix[i][j]+" ");
}
}
}
}
}
//从matrix[i][j]开始填充数,如果可行返回true
//从上到下,从左到右
private static boolean sudoku(int[][] matrix,int i,int j){
if(i>8){//递归结束
return true;
}
//只有matrix[i][j]==0,才需要填充
if(matrix[i][j]!=0){
//继续下一个元素
if(j<8 && sudoku(matrix,i,j+1)){//继续先左填充
return true;
}else if(j>=8 && sudoku(matrix,i+1,0)){//从下一行开始填充
return true;
}
}else{//需要填充,那1到9开始分别尝试
for(int num=1;num<=9;num++){
if(check(matrix,i,j,num)){
//该元素合法,则填充,并继续dfs下一个元素
matrix[i][j]=num;//填充
if(j<8 && sudoku(matrix,i,j+1)){//继续填充
return true;
}else if(j>=8 && sudoku(matrix,i+1,0)){//从下一行开始填充
return true;
}
//如果走到这一步,表示没返回,即填充num不合格,则置零,填充另外一个数num
matrix[i][j]=0;//回溯
}
}
}
return false;
}
//判断matrix[i][j]填充元素num是否符合规则
private static boolean check(int[][] matrix,int i,int j,int num){
//判断行规则,行中不能和num重复
for(int m=0;m<9;m++){
if(matrix[i][m]==num){
return false;
}
}
//判断列规则,列中不能和num重复
for(int m=0;m<9;m++){
if(matrix[m][j]==num){
return false;
}
}
//判断3*3的区块中没有和num重复
//根据i和j计算区块的范围,上下和左右边界
int row_start=i/3*3;//上边界
int row_end=row_start+3;//下边界
int col_start=j/3*3;//左边界
int col_end=col_start+3;//右边界
for(int k=row_start;k<row_end;k++){
for(int l=col_start;l<col_end;l++){
if(matrix[k][l]==num){
return false;
}
}
}
return true;
}
}
迷宫问题(打印最短路径)
题目描述:
一个N*M阶矩阵,0表示通路,1表示可以墙壁,从(0,0)位置开始通向(N-1,M-1),打印出最短路径。
解题思路:
使用广度优先进行图的比遍历
广度优先搜索的模板:
while(!queue.isEmpty()){
current=queue.poll();
//找到该节点相邻的点(潜在的下一个节点)
next=getNext(current);
if(next not in visited){
queue.add(next);
visited[next]=true;//标记该节点被访问过
}
}
//为了记录最短路径,可以将visited[]更换成comForm[i]数组,改数组记录了current节点的下一个节点next,那么comFrom[end]表示节点end从节点comFrom[end]过来,由此,我们逐级向前推,直到comFrom[end]=0是,就找到了起点;
把这些点逆序输出就是路径。
二维转一维
将n* m的矩阵可以转化为arr[n * m ],p(x,y)==>index=x*m+y;
package bob.com.graph;
import java.io.*;
import java.util.List;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class BFS {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = null;
while ((str = br.readLine()) != null) {
String[] strs = str.split(" ");
int n = Integer.parseInt(strs[0]);
int m = Integer.parseInt(strs[1]);
int[] map = new int[n * m];
int k = 0;
for (int i = 0; i < n; i++) {
String[] nums = br.readLine().split(" ");
for (int j = 0; j < m; j++, k++) {
map[k] = Integer.parseInt(nums[j]);
}
}
int[] comeFrom = new int[n * m];
int step=bfs(map, comeFrom,n,m);
printPath(map, m, comeFrom);
System.out.print(step);
}
}
public static int bfs(int[] map, int[] comeFrom, int n,int m) {
Queue<Integer> queue = new LinkedList<Integer>();
List<Integer> list = new LinkedList<Integer>();
int step=0;
int start = 0;// 设置起点
queue.offer(start);
while (!queue.isEmpty()) {
int size=queue.size();
//以当前节点为中心,向周围辐射
for(int i=0;i<size;i++){
int current = queue.poll();// 队首元素
if (current == map.length - 1) {
return step;//走到终点结束了
}
// 当前位置的邻接位置
list = getNeibor(current, map, n, m);
for (Integer next : list) {
if (!isContain(comeFrom, next)) {
queue.offer(next);
//记录路径
comeFrom[next] = current;
}
}
}
step++;
}
return step;
}
public static void printPath(int[] map, int m, int[] comeFrom) {
Stack<Integer> stack = new Stack<Integer>();
int current = map.length - 1;
while (current != 0) {
stack.push(current);
current = comeFrom[current];
}
stack.push(0);
while (!stack.isEmpty()) {
int pos = stack.pop();
System.out.println("(" + pos / m+ "," + pos % m + ")");
}
}
public static boolean isContain(int[] arr,int a){
for(int i=0;i<arr.length;i++){
if(a==arr[i]){
return true;
}
}
return false;
}
// 找到pos的下一个位置
// pos为当前点位置索引,map为当前的地图,n为矩阵的行数
public static List<Integer> getNeibor(int pos, int[] map, int n, int m) {
List<Integer> res = new LinkedList<Integer>();
int newPos = 0;
int x = pos / m;
int y = pos % m;
int newX = 0;
int newY = 0;
// 上、下、左、右
// 以一个可行的位置不能超出地图的边界,且不能是墙壁
newX = x - 1;
newY = y;
newPos = newX * m + newY;
if ((newX >= 0 && newX < n) && (newY >= 0 && newY < m) && map[newPos] != 1) {
res.add(newPos);
}
newX = x + 1;
newY = y;
newPos = newX * m + newY;
if ((newX >= 0 && newX < n) && (newY >= 0 && newY < m) && map[newPos] != 1) {
res.add(newPos);
}
newX = x;
newY = y - 1;
newPos = newX * m + newY;
if ((newX >= 0 && newX < n) && (newY >= 0 && newY < m) && map[newPos] != 1) {
res.add(newPos);
}
newX = x;
newY = y + 1;
newPos = newX * m + newY;
if ((newX >= 0 && newX < n) && (newY >= 0 && newY < m) && map[newPos] != 1) {
res.add(newPos);
}
return res;
}
}
动态规划
最长升序列问题
题目描述:
6个点的高度各为 2 5 1 5 4 5
如从第1格开始走,最多为3步, 2 4 5
从第2格开始走,最多只有1步,5
而从第3格开始走最多有3步,1 4 5
从第5格开始走最多有2步,4 5
所以这个结果是3。
解题思路:
(1)dp[ i]表示以i结尾数组的最长升序列个数。显然dp[]初始化均为1(包括本身在内)
(2)dp[ i]可以根据前面的位置j(j< i )来推断,即当arr[ j]< arr[ i]情况下,dp[ j]+1,j就可以求得了,当然dp[ j] 可能可以找到很多组,我们只要最大的,因此在来个遍历即可。
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
String[] strs=br.readLine().split(" ");
int[] nums=new int[n];
for(int i=0;i<n;i++){
nums[i]=Integer.parseInt(strs[i]);
}
System.out.println(getMaxStep(nums,n));
}
}
public static int getMaxStep(int[] nums,int length){
int[] dp=new int[length];//dp[i]表示i位置起步开始走,最多走的步数
for(int i=0;i<length;i++){
//初始化dp[],
dp[i]=1;
//从当前位置i之前的某个位置j可以推导到i;
//两种情况:dp[j]+1,前一个位置再跳一步的步数比初始化的dp[i]要大,
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
dp[i]=Math.max(dp[i],dp[j]+1);//dp[j]+1>dp[i]:dp[j]+1:dp[i];
}
}
}
int max=0;
for(int i=0;i<length;i++){
if(max<dp[i]){
max=dp[i];
}
}
return max;
}
}
称砝码
题目描述:
现有一组砝码,重量互不相等,分别为m1,m2,m3…mn;
每种砝码对应的数量为x1,x2,x3…xn。现在要用这些砝码去称物体的重量(放在同一侧),问能称出多少种不同的重量。
注:称重重量包括0
解题思路:
动态规划:设置一个dp数组,砝码组能表示的最大重量为:maxWeight+=weight[i]*count[i];动态数组dp的长度为maxWeight+1,其元素类型为boolean,表示当前砝码是否已经被选择,默认为false;
(1)base_case,dp[ 0]=true,dp[ maxWeight]=true;
(3)可选的砝码集合为weight
(4)计算所有的砝码能构成的最大重量sum
(5)以增量1的方式,列举[ 0,sum]的所有情况,成为砝码库。
(6)从可选的砝码集合中依次挑选砝码i,其重量weight[ i],去砝码库中匹配,如果dp[sum-weight[ i]]==true,表示匹成功,则砝码库中sum重量的砝码i为一个可行方案,并将其添加到dp数组中去。
import java.io.*;
import java.util.List;
import java.util.LinkedList;
import java.util.HashSet;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
int[] weight=new int[n];
int[] count=new int[n];
String[] str1=br.readLine().split(" ");
String[] str2=br.readLine().split(" ");
for(int i=0;i<n;i++){
weight[i]=Integer.parseInt(str1[i]);
count[i]=Integer.parseInt(str2[i]);
}
int res=process(weight,count);
System.out.println(res);
}
}
public static int process(int[] weight,int[] count){
int sum=0;//所有的砝码集合能表示的最大重量
for(int i=0;i<weight.length;i++){
sum+=weight[i]*count[i];
}
//动态数组
boolean[] dp=new boolean[sum+1];
//base_case
dp[0]=true;//0也算一种情况
dp[sum]=true;//所有的砝码必然组成最大的砝码重量
for(int i=0;i<weight.length;i++){
for(int j=0;j<count[i];j++){//每一个重量类型的砝码的个数,都要尝试
for(int k=sum;k>=weight[i];k--){
if(dp[k-weight[i]]){//k重量的砝码匹配成功
dp[k]=true;
}
}
}
}
int a=0;
for(boolean b:dp){
if(b){
a++;
}
}
return a;
}
}
解发2
比使用动态规划,使用set的去重特性来逐一尝试,这种方式较好理解,但是时间复杂度和空间复杂度都比较高,仅做参考
import java.io.*;
import java.util.List;
import java.util.LinkedList;
import java.util.HashSet;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
int n=Integer.parseInt(str);
int[] weight=new int[n];
int[] count=new int[n];
String[] str1=br.readLine().split(" ");
String[] str2=br.readLine().split(" ");
for(int i=0;i<n;i++){
weight[i]=Integer.parseInt(str1[i]);
count[i]=Integer.parseInt(str2[i]);
}
int res=process(weight,count);
System.out.println(res);
}
}
public static int process(int[] weight,int[] count){
List<Integer> list=new LinkedList<Integer>();
HashSet<Integer> set=new HashSet<Integer>();
HashSet<Integer> set2=new HashSet<Integer>(set);
for(int i=0;i<count.length;i++){
for(int j=0;j<count[i];j++){
list.add(weight[i]);
}
}
//利用set去重,set{0},0表示不加砝码
set.add(0);//初始化含有一个0
for(Integer l:list){
for(Integer s:set ){
set2.add(s+l);//set2作为缓存使用
}
set.addAll(set2);//将缓存中的数据全部添加到set中
set2.clear();//清空缓存
}
return set.size();
}
}
最长公共子串
题目描述:
两个字符串最长的公共子串。
解题思路:
动态规划:
(1)dp[][]确定。dp[i][j]表示:str1[0,i]与str2[0,j]公共子串的长度;
(2)base_case:dp[i][0]=0 dp[0][j]=0(只要有一个字符串为0,则公共子串必为0)
(3)状态转移方程,dp[i][j]=dp[i-1][j-1]+1;i和j从1开始,均要从base_case中推导出来
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String str2=br.readLine();
System.out.println(getCommonLen(str,str2));
}
}
public static int getCommonLen(String str1,String str2){
int len1=str1.length();
int len2=str2.length();
int max=0;
int[][] dp=new int[len1][len2];//str1:[0,i] str2:[0,j]
//base_case
for(int i=0;i<len1;i++){
dp[i][0]=0;
}
for(int i=0;i<len2;i++){
dp[0][i]=0;
}
for(int i=1;i<len1;i++){
for(int j=1;j<len2;j++){
if(str1.charAt(i-1)==str2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1]+1;
max=Math.max(max,dp[i][j]);
}
}
}
return max;
}
}
拓展:并输出最长的公共子串,若有相同,输出第一个
import java.io.*;
import java.util.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String str2=br.readLine();//字符串2
if(str.length()>str2.length()){
System.out.println(getCommonStr(str,str2));
}else{
System.out.println(getCommonStr(str2,str));
}
}
}
//获取str1.length>=str2.length;
public static String getCommonStr(String str1,String str2){
String str="";
int len=0;
for(int i=0;i<str2.length();i++){
for(int j=i+len;j<str2.length();j++){
String subStr=str2.substring(i,j+1);
if(str1.contains(subStr)){
len=j-i+1;
str=subStr;
}else{
break;
}
}
}
return str;
}
}
------
方法二,动态规划
public static void main(String[] args) throws IOException{
BufferedReader sc = new BufferedReader(new InputStreamReader(System.in));
String line = "";
while((line=sc.readLine())!=null){
String strA = line;
String strB = sc.readLine();
if (strA.length() > strB.length()) {
String temp = strA;
strA = strB;//str1是较短的子串
strB = temp;
}
System.out.println(findMaxCommonStr(strA,strB));
}
}
private static String findMaxCommonStr(String str1, String str2) {
char[] str1Char = str1.toCharArray();
char[] str2Char = str2.toCharArray();
int[][] dp = new int[str1Char.length + 1][str2Char.length + 1];
int maxLen = 0;
int start = 0;
for (int i = 1; i <= str1Char.length; i++) {
for (int j = 1; j <= str2Char.length; j++) {
if (str1Char[i - 1] == str2Char[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
if (dp[i][j] > maxLen) {
maxLen = dp[i][j];
start = i - maxLen;//记录最长公共子串的起始位置
}
}
}
}
return str1.substring(start, start + maxLen);
}
}
下棋
题目描述:
请编写一个函数(允许增加子函数),计算n x m的棋盘格子(n为横向的格子数,m为竖向的格子数)沿着各自边缘线从左上角走到右下角,总共有多少种走法,要求不能走回头路,即:只能往右和往下走,不能往左和往上走。
解题思路:
(1)由于只能向下和向右边走,故dp[i][j]=dp[i][j-1]+dp[i-1][j];
(2)base_case: dp[ 0][0]=1;
(3)若,i=0时候,dp[i][j]=dp[i][j-1],当j=0时,dp[i][j]=dp[i-1][j];
import java.io.*;
public class Main{
public static void main(String[] args) throws IOException{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
String str="";
while((str=br.readLine())!=null){
String[] strs=str.split(" ");
int n=Integer.parseInt(strs[0]);
int m=Integer.parseInt(strs[1]);
System.out.println(getCount(n,m));
}
}
public static int getCount(int n,int m){
int[][] dp=new int[n+1][m+1];
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
if(i==0 && j==0){
dp[i][j]=1;//起点
continue;
}
if(i==0){
dp[i][j]=dp[i][j-1];
}else if(j==0){
dp[i][j]=dp[i-1][j];
}else{
dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
}
}
return dp[n][m];
}
}
编辑距离
题目描述:
Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。编辑距离的算法是首先由俄国科学家Levenshtein提出的,故又叫Levenshtein Distance。
解题思路:
动态规划经典题目。
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str1 = "";
while ((str1 = br.readLine()) != null) {
String str2=br.readLine();
int res=getCount(str1,str2);
System.out.println(res);
}
}
public static int getCount(String str1,String str2){
int len1=str1.length();
int len2=str2.length();
int[][] dp=new int[len1+1][len2+1];
for(int i=1;i<=len1;i++){
dp[i][0]=i;
}
for(int j=1;j<=len2;j++){
dp[0][j]=j;
}
for(int i=1;i<len1+1;i++){
for(int j=1;j<len2+1;j++){
if(str1.charAt(i-1)==str2.charAt(j-1)){//索引从1开始
//不影响距离
dp[i][j]=dp[i-1][j-1];
}else{
dp[i][j]=Math.min(dp[i-1][j],Math.min(dp[i][j-1],dp[i-1][j-1]))+1;
}
}
}
return dp[len1][len2];
}
}
购物清单
题目描述:
物品分为:主件和附件,购买附件时必须要购买其依附的主件,购买主件时,可以不购买相应的附件,也可以购买其中一个附件或者两个附件都购买,以1~5 表示每个物品的重要度,假设小明有N元,问如何购买才能使得商品的价格和重要度乘积之和最大。
解题思路:
该题本质上是一道0-1背包问题,使用动态规划来做,设置dp[ i][j]表示:购买前i个主件,剩余的钱为j的情况下所能买到的价格与重要度乘积;
(1)当物品为附件时,不装入背包
(2)当物品为主件时,有4中情况,情况1:只有主件,情况2:主件+附件1;情况3:主件+附件1,情况4:主件+附件1+附件2.从四种选择中选取最大的价值放入背包。
在正式使用之前,我们设置一个包装类,使用面向对象的思想
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int money = sc.nextInt();
int n = sc.nextInt();
if(n<=0||money<=0) System.out.println(0);
good[] Gs = new good[n+1];
for (int i = 1; i <= n; i++) {
int v = sc.nextInt();
int p = sc.nextInt();
int q = sc.nextInt();
Gs[i] = new good(v,p,q);//一定要创建对象
if(q>0){//为附件,
if(Gs[q].a1==0){
Gs[q].setA1(i);
}else {
Gs[q].setA2(i);
}
}
}
int[][] dp = new int[n+1][money+1];
for (int i = 1; i <= n; i++) {
int v=0,v1=0,v2=0,v3=0,tempdp=0,tempdp1=0,tempdp2=0,tempdp3=0;
//只有主件
v = Gs[i].v;
tempdp = Gs[i].p*v;
if(Gs[i].a1!=0){//主件加附件1
v1 = Gs[Gs[i].a1].v+v;
tempdp1 = tempdp + Gs[Gs[i].a1].v*Gs[Gs[i].a1].p;
}
if(Gs[i].a2!=0){//主件加附件2
v2 = Gs[Gs[i].a2].v+v;
tempdp2 = tempdp + Gs[Gs[i].a2].v*Gs[Gs[i].a2].p;
}
if(Gs[i].a1!=0&&Gs[i].a2!=0){//主件加附件1和附件2
v3 = Gs[Gs[i].a1].v+Gs[Gs[i].a2].v+v;
tempdp3 = tempdp + Gs[Gs[i].a1].v*Gs[Gs[i].a1].p + Gs[Gs[i].a2].v*Gs[Gs[i].a2].p;
}
for(int j=1; j<=money; j++){
if(Gs[i].q > 0) { //当物品i是附件时,相当于跳过
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = dp[i-1][j];//当前物品不放入背包
//分四种情况讨论,同时考虑附件
if(j>=v&&v!=0) dp[i][j] = Math.max(dp[i][j],dp[i-1][j-v]+tempdp);//剩余钱必须大于价格,且物品价格不能为0
if(j>=v1&&v1!=0) dp[i][j] = Math.max(dp[i][j],dp[i-1][j-v1]+tempdp1);
if(j>=v2&&v2!=0) dp[i][j] = Math.max(dp[i][j],dp[i-1][j-v2]+tempdp2);
if(j>=v3&&v3!=0) dp[i][j] = Math.max(dp[i][j],dp[i-1][j-v3]+tempdp3);
}
}
}
System.out.println(dp[n][money]);
}
/**
* 定义物品类
*/
private static class good{
public int v; //物品的价格
public int p; //物品的重要度
public int q; //物品的主附件ID
public int a1=0; //附件1ID
public int a2=0; //附件2ID
public good(int v, int p, int q) {
this.v = v;
this.p = p;
this.q = q;
}
public void setA1(int a1) {
this.a1 = a1;
}
public void setA2(int a2) {
this.a2 = a2;
}
}
}
最长回文子序列
题目描述:
给出一个字符串S,找到最长的回文子序列的长度;
解题思路:
动态规划
套路:一般字符串最长最短问题,均可以设置dp[ i][j],当处理的是一个字符串时,dp[ i][j]表示,arr[ i…j]的最值,当处理的为两个字符串时,dp[ i][j]通常表示为:arr1[ 0…i]和arr2[ 0…j]的公共最值问题。
对于这道题目而言,dp[ i][j]表示arr[i…j]的会问子串最大长度。因此:
(1)base_case:dp[ i][i]=1,当只有一个字符时,最大回文为1
(2)转移方程(递推状态),如何根据根据条件来递推出dp[ i][j],若str.charAt(i)==str.charAt(j),那么可以根据str(i+1,j-1)的最大回文长度+2得到,即dp[ i][j]=dp[ i+1][j-1];若不等时,i和j位置的字符不可能同时出现在str(i+1,j-1)中,那么把他们分别加入str(i+1,j-1)中,取最大值。
(3)遍历方式;由于dp[ i][j]需要从dp[ i+1][j-1]中递推出来,故,i是反向遍历for(int i=len-1;i>=0;i–);而j-1—>j是顺序遍历,故for(int j=0;j=i+1;j++);注意,当j<i时,为0
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
int len=str.length();
int[][] dp=new int[len][len];//dp[i][j]:表示str(i,j)的最大回文长度。
//base_cae
for(int i=0;i<len;i++){
dp[i][i]=1;
}
//转移方程,递推方程
//dp[i][j]=dp[i+1][j-1]+2;
//dp[i][j]=Math.max(dp[i][j-1],dp[i+1][j])
for(int i=len-1;i>=0;i--){
for(int j=i+1;j<len;j++){
if(str.charAt(i)==str.charAt(j)){
dp[i][j]=dp[i+1][j-1]+2;
}else{
dp[i][j]=Math.max(dp[i+1][j],dp[i][j-1]);
}
}
}
System.out.println(dp[0][len-1]);
}
}
}
合唱队
题目描述:
N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的K位同学排成合唱队形。
合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2…,K,他们的身高分别为T1,T2,…,TK, 则他们的身高满足存在i(1<=i<=K)使得T1< T2 <…< Ti-1< Ti > Ti+1 >…> TK。
你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
解题思路:
先求出符合合唱队序列的最长子序列的长度(矮–高--矮),然后用数组总的长度-合唱队最长子序列长度即为需要出队的最少人数。
那怎么来求符合合唱队序列的最长子序列长度呢,可以将问题才分为两个最长递增子序列问题
(1)从左往右方向,求出每一个元素high[ 0…i]的最长子序列长度len1
(2)从右往左方向,求出high[ i…len-1]的最长递增子序列长度 len2
(3)max(len1+len2-1)即为符合合唱队序列的最长子序列长度。
一个重要的子问题:最长增长子序列问题
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]){
dp[i] = Math.max(dp[i], dp[j] + 1);//因为j有很多种情况,取能获得最长递增子序列的那种情况
}
}
}
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while ((str = br.readLine()) != null) {
int n=Integer.parseInt(str);
String[] strs=br.readLine().split(" ");
int[] high=new int[n];
int[] num_L=new int[n];//从左到右的递增子序列
int[] num_R=new int[n];//从右到左的递增子序列
for(int i=0;i<n;i++){
high[i]=Integer.parseInt(strs[i]);
}
//high[i]的左侧,从左到右的最长递增子序列+当前元素
for(int i=0;i<n;i++){
for(int j=0;j<i;j++){
//high[0...i]的最长递增子序列
if(high[j]<high[i]){
num_L[i]=Math.max(num_L[i],num_L[j]);
}
}
num_L[i]=num_L[i]+1;//加上本身元素
}
//high[i]右侧,从右到左的最长递增子序列+当前元素
for(int i=n-1;i>=0;i--){
for(int j=n-1;j>i;j--){
if(high[i]>high[j]){
num_R[i]=Math.max(num_R[i],num_R[j]);
}
}
num_R[i]=num_R[i]+1;
}
int max=0;
//符合合唱队元素特性的最长子序列的个数为
for(int i=0;i<n;i++){
max=Math.max(num_L[i]+num_R[i]-1,max);//本身元素重复计算了两次,故减1
}
System.out.println(n-max);//需要出列的元素=总个数-最大的合唱队子序列长度。
}
}
}
AWSD ↩︎