《剑指offer》-- 把数组排成最小的数、丑数、二进制中1的个数、表示数值的字符串、替换空格

一、把数组排成最小的数:

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();
	 }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张维鹏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值