剑指offer_7

剑指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;
        }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值