一、把数组排成最小的数:
1、题目:
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
2、解题思路:
重写Comparator的compare()方法。
3、代码实现:
public class Test23 {
public static void main(String[] args) {
int[] array = new int[]{6,2,4,41,5};
PrintMinNumber(array);
}
//解题思路:
//通过比较器重写compare方法
public static String PrintMinNumber(int [] numbers) {
ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<numbers.length;i++){
list.add(numbers[i]);
}
Collections.sort(list,new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
String str1=o1+""+o2;
String str2=o2+""+o1;
return str1.compareTo(str2);
}
});
StringBuilder builder = new StringBuilder();
for(Integer num:list){
builder.append(num);
}
System.out.println(builder.toString());
return builder.toString();
}
}
二、丑数:
1、题目:
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
2、解题思路:
参考牛客网的“事无巨细,悉究本末”:https://www.nowcoder.com/questionTerminal/6aa9e04fc3794f68acf8778237ba065b
2.1 通俗易懂的解释:
首先从丑数的定义我们知道,一个丑数的因子只有2,3,5,那么丑数p = 2 ^ x * 3 ^ y * 5 ^ z,换句话说一个丑数一定由另一个丑数乘以2或者乘以3或者乘以5得到,那么我们从1开始乘以2,3,5,就得到2,3,5三个丑数,在从这三个丑数出发乘以2,3,5就得到4,6,10,6,9,15,10,15,25九个丑数,我们发现这种方法会得到重复的丑数,而且我们题目要求第N个丑数,这样的方法得到的丑数也是无序的。那么我们可以维护三个队列:
(1)丑数数组: 1
乘以2的队列:2
乘以3的队列:3
乘以5的队列:5
选择三个队列头最小的数2加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(2)丑数数组:1,2
乘以2的队列:4
乘以3的队列:3,6
乘以5的队列:5,10
选择三个队列头最小的数3加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(3)丑数数组:1,2,3
乘以2的队列:4,6
乘以3的队列:6,9
乘以5的队列:5,10,15
选择三个队列头里最小的数4加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(4)丑数数组:1,2,3,4
乘以2的队列:6,8
乘以3的队列:6,9,12
乘以5的队列:5,10,15,20
选择三个队列头里最小的数5加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;
(5)丑数数组:1,2,3,4,5
乘以2的队列:6,8,10,
乘以3的队列:6,9,12,15
乘以5的队列:10,15,20,25
选择三个队列头里最小的数6加入丑数数组,但我们发现,有两个队列头都为6,所以我们弹出两个队列头,同时将12,18,30放入三个队列;
……………………
2.2 疑问:
2.2.1 为什么分三个队列?
丑数数组里的数一定是有序的,因为我们是从丑数数组里的数乘以2,3,5选出的最小数,一定比以前未乘以2,3,5大,同时对于三个队列内部,按先后顺序乘以2,3,5分别放入,所以同一个队列内部也是有序的;
2.2.2 为什么比较三个队列头部最小的数放入丑数数组?
因为三个队列是有序的,所以取出三个头中最小的,等同于找到了三个队列所有数中最小的。
2.3 实现思路:
我们没有必要维护三个队列,只需要记录三个指针显示到达哪一步;“|”表示指针,arr表示丑数数组;
(1)1
|2
|3
|5
目前指针指向0,0,0,队列头arr[0] * 2 = 2, arr[0] * 3 = 3, arr[0] * 5 = 5
(2)1 2
2 |4
|3 6
|5 10
目前指针指向1,0,0,队列头arr[1] * 2 = 4, arr[0] * 3 = 3, arr[0] * 5 = 5
(3)1 2 3
2| 4 6
3 |6 9
|5 10 15
目前指针指向1,1,0,队列头arr[1] * 2 = 4, arr[1] * 3 = 6, arr[0] * 5 = 5
………………
3、代码实现:
public class Test24 {
//解题思路:创建三个队列 https://www.nowcoder.com/ta/coding-interviews
public int GetUglyNumber_Solution(int index) {
if(index<7)
return index;
//创建三个队列的头指针,newNum代表的是选出的最小数
int t2=0,t3=0,t5=0;
int newNum=1;
//创建一个数组,存放丑数
ArrayList<Integer> list = new ArrayList<>();
list.add(newNum);
for(int i=1;i<index;i++){
//选出三个队列里面最小的数
newNum=Math.min(list.get(t2)*2,Math.min(list.get(t3)*3,list.get(t5)*5));
//这三个if有可能进入一个或者多个,进入多个是三个队列头最小的数有多个的情况
if(newNum == list.get(t2)*2) t2++;
if(newNum == list.get(t3)*3) t3++;
if(newNum == list.get(t5)*5) t5++;
list.add(newNum);
}
return newNum;
}
}
三、二进制中1的个数:
1、题目:
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
2、思路:
第一种:使用java内置的的toBinaryString方法来实现;
第二种:利用位运算符来实现:
(1)将n与n-1做与运算,会把最右边的1去掉。比如: 1100 & 1011 = 1000 ,即 12 & 11 = 8,再利用计算器count++计算出有多少个 1 即可。
(2)详细说明:如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来他的二进制处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
(3)举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011。我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
3、代码实现:
//利用java内置的toBinaryString方法来实现:
public static int NumberOf1(int n){
int count = 0;
String str=Integer.toBinaryString(n);
for(int i=0;i<str.length();i++){
if(str.charAt(i)=='1'){
count++;
}
}
return count;
}
//利用位运算符来实现:
public static int NumberOf2(int n){
int count = 0;
if(n==0)
return count;
if(n!=0){
while(n!=0){
count++;
n=n&(n-1);
}
}
return count;
}
四、表示数值的字符串:
1、题目:
实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
2、代码实现:
public class Test11 {
private int index = 0;
//第二种解法:剑指offer解法:
public boolean isNumeric(char[] str){
if(str.length<1){
return false;
}
boolean flag = scanInteger(str);
if(index<str.length && str[index]=='.'){
index++;
flag = scanUnsignedInteger(str) || flag;
}
if(index < str.length && (str[index] =='E' || str[index]=='e')){
index++;
flag = flag && scanInteger(str);
}
return flag && index == str.length;
}
private boolean scanInteger(char[] str){
if(index<str.length && (str[index]=='+' || str[index]=='-') )
index++;
return scanUnsignedInteger(str);
}
private boolean scanUnsignedInteger(char[] str) {
int start = index;
while(index < str.length && str[index]>='0' && str[index] <='9')
index++;
return start<index;//是否存在整数
}
//第一种解法:正则表达式
public boolean isNumeric1(char[] str) {
String string = String.valueOf(str);
return string.matches("[\\+\\-]?\\d*(\\.\\d+)?([eE][\\+\\-]?\\d+)?");
}
/*
以下对正则进行解释:
[\\+\\-]? -> 正或负符号出现与否
\\d* -> 整数部分是否出现,如-.34 或 +3.34均符合
(\\.\\d+)? -> 如果出现小数点,那么小数点后面必须有数字;
否则一起不出现
([eE][\\+\\-]?\\d+)? -> 如果存在指数部分,那么e或E肯定出现,+或-可以不出现,
紧接着必须跟着整数;或者整个部分都不出现
*/
}
四、替换空格:
1、题目描述:
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
2、解题思路:
第一种:从前往后替换,后面的字符要不断往后移动,要多次移动,所以效率低下;
第二种:从后往前,先计算需要多少空间,然后从后往前移动,则每个字符只为移动一次,这样效率更高一点。
3、代码实现:
public String replaceSpace(StringBuffer str) {
int spaceNum = 0;//原字符串中空格的个数
for(int i = 0;i<str.length();i++){
if(str.charAt(i)==' '){
spaceNum++;
}
}
int indexOld = str.length()-1;//indexOld为为替换前的str下标
int newLength = str.length() + spaceNum*2;//计算空格转换成%20之后的str长度
int indexNew = newLength -1;//indexNew为为把空格替换为%20后的str下标
str.setLength(newLength);;//使str的长度扩大到转换成%20之后的长度,防止下标越界
for(;indexOld>=0 && indexOld<indexNew;--indexOld){
if(str.charAt(indexOld)==' '){
str.setCharAt(indexNew--, '0');
str.setCharAt(indexNew--, '2');
str.setCharAt(indexNew--, '%');
}else{
str.setCharAt(indexNew--, str.charAt(indexOld));
}
}
return str.toString();
}
}