剑指offer_7(每日5题)来源:牛客网
1. 整数中1出现的次数(从1到n整数中1出现的次数)
题目:
求出113的整数中1出现的次数,并算出1001300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
分析:
//主要思路:设定整数点(如1、10、100等等)作为位置点i(对应n的各位、十位、百位等等),分别对每个数位上有多少包含1的点进行分析
//根据设定的整数位置,对n进行分割,分为两部分,高位n/i,低位n%i
//当i表示百位,且百位对应的数>=2,如n=31456,i=100,则a=314,b=56,此时百位为1的次数有a/10+1=32(最高两位0~31),每一次都包含100个连续的点,即共有(a%10+1)*100个点的百位为1
//当i表示百位,且百位对应的数为1,如n=31156,i=100,则a=311,b=56,此时百位对应的就是1,则共有a%10(最高两位0-30)次是包含100个连续点,当最高两位为31(即a=311),本次只对应局部点00~56,共b+1次,所有点加起来共有(a%10*100)+(b+1),这些点百位对应为1
//当i表示百位,且百位对应的数为0,如n=31056,i=100,则a=310,b=56,此时百位为1的次数有a/10=31(最高两位0~30)
//综合以上三种情况,当百位对应0或>=2时,有(a+8)/10次包含所有100个点,还有当百位为1(a%10==1),需要增加局部点b+1
//之所以补8,是因为当百位为0,则a/10==(a+8)/10,当百位>=2,补8会产生进位位,效果等同于(a/10+1)
参考代码:
/*C++*/
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
int count=0;
long long i=1;
for(i=1;i<=n;i*=10)
{
//i表示当前分析的是哪一个数位
int a = n/i,b = n%i;
count=count+(a+8)/10*i+(a%10==1)*(b+1);
}
return count;
}
};
/*Java*/
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
if(n <= 0)
return 0;
int count = 0;
for(long i = 1; i <= n; i *= 10){
long diviver = i * 10;
count += (n / diviver) * i + Math.min(Math.max(n % diviver - i + 1, 0), i);
}
return count;
}
}
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
int count = 0;//1的个数
int i = 1;//当前位
int current = 0,after = 0,before = 0;
while((n/i)!= 0){
current = (n/i)%10; //高位数字
before = n/(i*10); //当前位数字
after = n-(n/i)*i; //低位数字
//如果为0,出现1的次数由高位决定,等于高位数字 * 当前位数
if (current == 0)
count += before*i;
//如果为1,出现1的次数由高位和低位决定,高位*当前位+低位+1
else if(current == 1)
count += before * i + after + 1;
//如果大于1,出现1的次数由高位决定,//(高位数字+1)* 当前位数
else{
count += (before + 1) * i;
}
//前移一位
i = i*10;
}
return count;
}
}
2. 把数组排成最小的数
题目:
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
分析:
- 解题思路:
- 先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。
- 排序规则如下:
- 若ab > ba 则 a > b,
- 若ab < ba 则 a < b,
- 若ab = ba 则 a = b;
- 解释说明:
- 比如 “3” < "31"但是 “331” > “313”,所以要将二者拼接起来进行比较
参考代码:
import java.util.Arrays;
import java.util.Comparator;
public class Solution {
public String PrintMinNumber(int [] numbers) {
if(numbers == null || numbers.length == 0) return "";
int len = numbers.length;
String[] str = new String[len];
StringBuilder sb = new StringBuilder();
for(int i = 0; i < len; i++){
str[i] = String.valueOf(numbers[i]);
}
Arrays.sort(str,new Comparator<String>(){
@Override
public int compare(String s1, String s2) {
String c1 = s1 + s2;
String c2 = s2 + s1;
return c1.compareTo(c2);
}
});
for(int i = 0; i < len; i++){
sb.append(str[i]);
}
return sb.toString();
}
}
/*其它*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Solution {
public String PrintMinNumber(int [] numbers) {
int n;
String s="";
ArrayList<Integer> list= new ArrayList<Integer>();
n=numbers.length;
for(int i=0;i<n;i++){
list.add(numbers[i]);
}
Collections.sort(list, new Comparator<Integer>(){
public int compare(Integer str1,Integer str2){
String s1=str1+""+str2;
String s2=str2+""+str1;
return s1.compareTo(s2);
}
});
for(int j:list){
s+=j;
}
return s;
}
}
3. 丑数
题目:
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
分析:
通俗易懂的解释:
首先从丑数的定义我们知道,一个丑数的因子只有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放入三个队列;
……………………
疑问:
1.为什么分三个队列?
丑数数组里的数一定是有序的,因为我们是从丑数数组里的数乘以2,3,5选出的最小数,一定比以前未乘以2,3,5大,同时对于三个队列内部,按先后顺序乘以2,3,5分别放入,所以同一个队列内部也是有序的;
2.为什么比较三个队列头部最小的数放入丑数数组?
因为三个队列是有序的,所以取出三个头中最小的,等同于找到了三个队列所有数中最小的。
实现思路:
我们没有必要维护三个队列,只需要记录三个指针显示到达哪一步;“|”表示指针,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
………………
参考代码:
/*C++*/
class Solution {
public:
int GetUglyNumber_Solution(int index) {
// 0-6的丑数分别为0-6
if(index < 7) return index;
//p2,p3,p5分别为三个队列的指针,newNum为从队列头选出来的最小数
int p2 = 0, p3 = 0, p5 = 0, newNum = 1;
vector<int> arr;
arr.push_back(newNum);
while(arr.size() < index) {
//选出三个队列头最小的数
newNum = min(arr[p2] * 2, min(arr[p3] * 3, arr[p5] * 5));
//这三个if有可能进入一个或者多个,进入多个是三个队列头最小的数有多个的情况
if(arr[p2] * 2 == newNum) p2++;
if(arr[p3] * 3 == newNum) p3++;
if(arr[p5] * 5 == newNum) p5++;
arr.push_back(newNum);
}
return newNum;
}
};
/*其它*/
/*
说下思路,如果p是丑数,那么p=2^x * 3^y * 5^z
那么只要赋予x,y,z不同的值就能得到不同的丑数。
如果要顺序找出丑数,要知道下面几个特(fei)点(hua)。
对于任何丑数p:
(一)那么2*p,3*p,5*p都是丑数,并且2*p<3*p<5*p
(二)如果p<q, 那么2*p<2*q,3*p<3*q,5*p<5*q
现在说说算法思想:
由于1是最小的丑数,那么从1开始,把2*1,3*1,5*1,进行比较,得出最小的就是1
的下一个丑数,也就是2*1,
这个时候,多了一个丑数‘2’,也就又多了3个可以比较的丑数,2*2,3*2,5*2,
这个时候就把之前‘1’生成的丑数和‘2’生成的丑数加进来也就是
(3*1,5*1,2*2,3*2,5*2)进行比较,找出最小的。。。。如此循环下去就会发现,
每次选进来一个丑数,该丑数又会生成3个新的丑数进行比较。
上面的暴力方法也应该能解决,但是如果在面试官用这种方法,估计面试官只会摇头吧
。下面说一个O(n)的算法。
在上面的特(fei)点(hua)中,既然有p<q, 那么2*p<2*q,那么
“我”在前面比你小的数都没被选上,你后面生成新的丑数一定比“我”大吧,那么你乘2
生成的丑数一定比我乘2的大吧,那么在我选上之后你才有机会选上。
其实每次我们只用比较3个数:用于乘2的最小的数、用于乘3的最小的数,用于乘5的最小的
数。也就是比较(2*x , 3*y, 5*z) ,x>=y>=z的,
重点说说下面代码中p的作用:int p[] = new int[] { 0, 0, 0 }; p[0]表示最小用于
乘2比较数在数组a中的【位置】。 */
public class Solution {
final int d[] = { 2, 3, 5 };
public int GetUglyNumber_Solution(int index) {
if(index == 0) return 0;
int a[] = new int[index];
a[0] = 1;
int p[] = new int[] { 0, 0, 0 };
int num[] = new int[] { 2, 3, 5 };
int cur = 1;
while (cur < index) {
int m = finMin(num[0], num[1], num[2]);
if (a[cur - 1] < num[m])
a[cur++] = num[m];
p[m] += 1;
num[m] = a[p[m]] * d[m];
}
return a[index - 1];
}
private int finMin(int num2, int num3, int num5) {
int min = Math.min(num2, Math.min(num3, num5));
return min == num2 ? 0 : min == num3 ? 1 : 2;
}
}
4. 第一个只出现一次的字符
题目:
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
参考代码:
说一下解题思路哈,其实主要还是hash,利用每个字母的ASCII码作hash来作为数组的index。首先用一个58长度的数组来存储每个字母出现的次数,为什么是58呢,主要是由于A-Z对应的ASCII码为65-90,a-z对应的ASCII码值为97-122,而每个字母的index=int(word)-65,比如g=103-65=38,而数组中具体记录的内容是该字母出现的次数,最终遍历一遍字符串,找出第一个数组内容为1的字母就可以了,时间复杂度为O(n)
public static int solution(String str){
int[] words = new int[58];
for(int i = 0;i<str.length();i++){
words[((int)str.charAt(i))-65] += 1;
}
for(int i=0;i<str.length();i++){
if(words[((int)str.charAt(i))-65]==1)
return i;
}
return -1;
}
//C++
class Solution {
public:
int FirstNotRepeatingChar(string str) {
map<char, int> mp;
for(int i = 0; i < str.size(); ++i)
mp[str[i]]++;
for(int i = 0; i < str.size(); ++i){
if(mp[str[i]]==1)
return i;
}
return -1;
}
};
5. 数组中的逆序对
题目:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
输入描述:
题目保证输入的数组中没有的相同的数字数据范围: 对于%50的数据,size<=10^4 对于%75的数据,size<=10^5 对于%100的数据,size<=2*10^5
示例1
输入:
1,2,3,4,5,6,7,0
输出:
7
参考代码:
//方法复杂度高,运行时间长,通不过
public class Solution {
public int InversePairs(int [] array) {
int number=0;
for(int i=0;i<array.length;i++){
for(int j=i+1;j<array.length;j++){
if(array[i]>array[j])
number++;
}
}
return number%1000000007;
}
}
//归并排序
public class Solution {
public int InversePairs(int [] array) {
if(array==null || array.length<=0){
return 0;
}
int pairsNum=mergeSort(array,0,array.length-1);
return pairsNum;
}
public int mergeSort(int[] array,int left,int right){
if(left==right){
return 0;
}
int mid=(left+right)/2;
int leftNum=mergeSort(array,left,mid);
int rightNum=mergeSort(array,mid+1,right);
return (Sort(array,left,mid,right)+leftNum+rightNum)%1000000007;
}
public int Sort(int[] array,int left,int middle,int right){
int current1=middle;
int current2=right;
int current3=right-left;
int temp[]=new int[right-left+1];
int pairsnum=0;
while(current1>=left && current2>=middle+1){
if(array[current1]>array[current2]){
temp[current3--]=array[current1--];
pairsnum+=(current2-middle); //这个地方是current2-middle!!!!
if(pairsnum>1000000007)//数值过大求余
{
pairsnum%=1000000007;
}
}else{
temp[current3--]=array[current2--];
}
}
while(current1>=left){
temp[current3--]=array[current1--];
}
while(current2>=middle+1){
temp[current3--]=array[current2--];
}
//将临时数组赋值给原数组
int i=0;
while(left<=right){
array[left++]=temp[i++];
}
return pairsnum;
}
}