导语
每篇将有两道经典Java机试题,每道题后面均为大家附上代码,每一道题目力求:
- 能够在JDK11环境下编译
- 在Eclipse JavaIDE中运行通过
- 思路易想易懂易学
- 重点代码有注释
第003题 最大前缀(难度:★★☆☆☆)
题目描述:
求一个字符串的最大回文前缀长度。回文是指正反方向读起来都一样的字符串,比如“abcdcba”就是一个回文。"10010"中最大回文是“1001”,长度为4,所以输出4。一个字符串,至少包含一个字节。每个字节是一个字符。最大长度可能有几十万字节。
输入示例:
asdfdsa
1010
10011
10101001
apple
输出示例:
7
3
4
5
1
思路
1.利用String.charAt()方法分别从一头(start)一尾(end)开始比较。
2.当start != end时,且之前从未配对成功过,则end前移一位,start指向0,重复1;若之前已有配对成功,则只需start指向0,end不动,重复1。
3.当start == end 时,start后移一位,end前移一位。
代码
import java.util.Scanner;
public class Test{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String string = sc.nextLine(); //读取一行字符串
int start = 0, end = string.length() - 1; //start指向第一个位置,end指向最后一个位置
int count = end; //计数
boolean flag = false; //初始标记未曾有对应位相同
//跳出循环条件:start所指位置序号>end所指位置序号
while(start - end <0) {
if(string.charAt(start) == string.charAt(end)) {
flag = true; //已有对应位相同
start++; //start向后移一位
end--; //end向前移一位
}
else {
if(!flag) {
end--; //从未出现对应位相同时,才需end--
}
start = 0; //start重新指向第一位
count = end; //count指向现在的end位置
flag = false; //标记重置为false
}
}
System.out.println(count + 1); //count所指的序号+1就是最长前缀长度
}
}
运行结果
第004题 最短昵称(难度:★★★★☆)
题目描述:
小南瓜要注册一个昵称,为了体现自己昵称的独特性,小南瓜想使自己的昵称包含指定的 字符串,但是昵称注册有长度限制, 为此小南瓜要知道包含自己指定字符串最短的昵称长度是多少。第一行输入一个整数n,代表要包含的字符串个数,之后紧跟n行,输入要包含的字符串, 输出最短字符串长度。
输出示例:
3
ABC
CBA
AB
输出示例:
5
思路
本题稍微有点复杂,需要费点篇幅来详细说一下。
举个例子:现有三个字符串a:ACAB、b:ABA、c:CA。那么按照题意,我们要找到一个字符串,里面包含a、b 、c子串,而且这个字符串要最短。
直观想:要包含这三个子字符串,直接把他们连在一起,肯定能包含 :ACABABACA。但很显然,这个不是最短的,略加思索便可得知:ACABA才是最短的,里面既饱含a(ACABA), 又包含b(ACABA)、还包含c(ACABA)。
既然已经明白了何谓“最短”,那么我们该如何找到这个最短的字符串呢?首先最容易想到的,如果一个字符串中直接包含了另外一个字符串,那么直接可以不用考虑所包含的字符串了,如a和c的关系ACAB中包含着CA,我们很容易想到Java中使用String.contains()方法来判断长字符串中是否包含短字符串。要判断两两之间是否有包含关系,可以使用循环,但是显然,只能是长字符串包含短字符串。所以,在比较之前可以先对字符串按照长度先排序,按照从短到长排序,这样只需判断排在后面的字符串是否包含排在前面的字符串了。由此又自然而然想到,判断完,应该给予已包含的字符串一个标记true,告诉程序,这个字符串已经包含在其它字符串里了,无需再次考虑。所以字符串初始化均标记成false。
数据结构声明如下:
class Nickname{ private String string; //保存字符串 private boolean flag; //标记 public Nickname() { //构造函数 this.string = ""; this.flag = false; //flag初始化为false,代表该nickname不包含于任何字符串 } //setter、getter方法…… }
其次,我们再考虑:除了已经包含的字符串,没有被包含的字符串如何处理?观察a(ACAB)、b(ABA), 很容易发现,因为a的尾部包含“AB”,b的头部包含“AB”,才可能使得这两个字符串存在最短的可能,b连在a的后面,合并在一起得到ACABA。我们再考虑,a的头部有“A”,b的尾部也有“A”,那么可不可以把a连在b后面呢?即ABACAB,这样起码也比直接连接ABAACAB要短。当然可以,只不过,题目要求最短,所以选择前者连接方式。
好了,到现在,我们知道了,存在最短连接的两个字符串一定是如下两种情况:
1).短的尾部和长的头部有相同部分 如:ABA(短)、BAAC(长),最短为ABAAC
2).长的尾部和短的头部有相同部分 如:ACAB(长)、ABA(短),最短为ACABA如果短的尾部和长的头部、长的尾部和短的头部同时有相同部分,该选择哪一种连接呢?显然,我们要选择含有相同部分最多的那一种连接方式,正如我们举的例子a(ACAB)、b(ABA),因为a尾部和b的头部有2个(“AB”)相同的部分,a的头部和b的头部只有1个(“A”)相同部分,既然要求最短,那么我们当然希望,包含相同越多越好。所以对于两个字符串按照1)和2)分别比较,并分别添加一个计数器count_i和count_j:
//若果count_i > count_j,则将短字符串剪切到长字符串后面 if(count_i > count_j) { longString = longString + shortString.substring(count_i, shortString.length()); } //若果count_i <= count_j,则将长字符串剪切到短字符串后面 if(count_i <= count_j) { longString = shortString.substring(0, shortString.length() - count_j) + longString; }
将新拼接的字符串存入longString中,并将shortString的标记设置为true,重新排序。
//新得到的字符串放到长字符串nickname[y]中,同时将短字符串nickname[x]的标记设置为true nickname[y].setString(longString); nickname[x].setFlag(true); //重新排序,以保证新的字符串仍是按长度排好序 change(nickname, n);
代码
import java.util.Scanner;
class Nickname{
private String string;
private boolean flag;
public Nickname() { //构造函数
this.string = "";
this.flag = false; //flag初始化为false,代表该nickname不包含于任何字符串
}
public void setString(String string) { //setter、getter方法
this.string = string;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getString() {
return string;
}
public boolean getFlag() {
return flag;
}
}
public class Test{
public static Nickname[] change(Nickname nickname[],int n) {
//此处两个循环:将输入的字符串按照长度从短到长排序
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - 1; j++) {
if(nickname[j].getString().length() > nickname[j + 1].getString().length()) {
//nickname的两个成员均要交换
String temp_string = nickname[j + 1].getString();
boolean temp_bool = nickname[j + 1].getFlag();
nickname[j + 1].setString(nickname[j].getString());
nickname[j + 1].setFlag(nickname[j].getFlag());
nickname[j].setString(temp_string);
nickname[j].setFlag(temp_bool);
}
}
}
//此处两个循环:短字符串如若包含在长字符串里,则将该nickname的flag设置为true
for (int i = 0 ; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
if(nickname[j].getString().contains(nickname[i].getString())){
nickname[i].setFlag(true);
}
}
}
return nickname;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
Scanner scanner = new Scanner(System.in);
Nickname nickname [] = new Nickname[n]; //对象数组初始化对象
for (int i = 0; i < n; i++) {
nickname[i] = new Nickname();
nickname[i].setString(scanner.nextLine()); //读取一行字符串
}
//调用change()方法,进行排序和判断短字符串是否包含在长字符串里
change(nickname,n);
for (int x = 0,y = x + 1; x < n - 1 || y < n; x++,y++) {
String longString, shortString;
///连续两个if…else语句是为了找到两个flag值为false,且两者中间其他flag没有为false的nickname
if(nickname[x].getFlag() != false) {
continue;
}
else {
shortString = nickname[x].getString();
}
if (nickname[y].getFlag() != false) {
continue;
}
else {
longString = nickname[y].getString();
}
//先找到两个flag都是false的字符串,长的赋给longString,短的赋给shortString
int index_i,index_j;
int count_i = 0, count_j = 0;
//形如:shortString = "ABACC", longString = "BCDABA"
//短字符串的头部与长字符串尾部有相同部分
index_i = 0;
index_j = longString.length() - shortString.length() + 1;
//从长字符串倒数第k个位置开始比对,其中k是短字符串长度减1
for (int k = index_j; k < longString.length(); k++) {
index_j = k;
while(index_j < longString.length() && shortString.charAt(index_i) == longString.charAt(index_j)) {
index_i++;
index_j++;
count_i++;
}
//while语句执行完毕,如果是因为index_j索引到了longString的末尾,则跳出循环
if(index_j == longString.length()) {
break;
}
//否则意味着longString还并未索引完就因对应位不相等而跳出循环,则count_i重新计数,继续循环
else {
index_i = 0;
count_i = 0;
continue;
}
}
//形如:shortString = "CCABA", longString = "ABABCDA"
//短字符串的尾部与长字符串头部有相同部分
index_i = 1;
index_j = 0;
//从短字符串第2位开始比较
for (int k = index_i; k < shortString.length(); k++) {
index_i = k;
while(index_i < shortString.length() && shortString.charAt(index_i) == longString.charAt(index_j)) {
index_i++;
index_j++;
count_j++;
}
//while语句执行完毕,如果是因为index_i索引到了shortString的末尾,则跳出循环
if(index_i == shortString.length()) {
break;
}
//否则意味着shortString还并未索引完就因对应位不相等而跳出循环,则count_j重新计数,继续循环
else {
index_j = 0;
count_j = 0;
continue;
}
}
//若果count_i > count_j,则将短字符串剪切到长字符串后面
if(count_i > count_j) {
longString = longString + shortString.substring(count_i, shortString.length());
}
//若果count_i < count_j,则将长字符串剪切到短字符串后面
if(count_i <= count_j) {
longString = shortString.substring(0, shortString.length() - count_j) + longString;
}
//新得到的字符串放到长字符串nickname[y]中,同时将短字符串nickname[x]的标记设置为true
nickname[y].setString(longString);
nickname[x].setFlag(true);
//重新排序,以保证新的字符串仍是按长度排好序
change(nickname, n);
}
//定义一个空字符串
String s = "";
//将剩余标记仍然为false的字符串连在一起
for (int i = 0; i < n; i++) {
if(nickname[i].getFlag() == false) {
s = s + nickname[i].getString();
}
}
//此行输出最短昵称字符串,题目不要求输出,上机时可注释掉,这里为了让大家看到结果,更好理解
System.out.println(s);
//输出s的字符串长
System.out.println(s.length());
//输入关闭
sc.close();
scanner.close();
}
}
运行结果
我们再多输入几个例子来测试一下:
以上是本次两道Java机试题
如有不足,欢迎批评指正
欢迎阅读上一篇:Java 100道典型机试笔试题(01)
欢迎阅读下一篇:Java 100道典型机试笔试题(03)
作者:小南瓜
日期:2019年4月17日11:06