剑指offer 31-35

31.整数中1出现的次数

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?
为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。
ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

这个题在前面的一篇文章里面有写到过更普及的K出现的次数,关键点有两个:
1.统计的是k出现的次数,而不是包含k的数字的个数,例如统计1出现的次数,那么11出现了2次,因此可以直接采用对每一位进行分析出现K的次数,然后把所有的结果相加即可;
2.对每一位进行分析的时候要分大于K,小于K,等于K三种情况来讨论,以n=31245,k=2为例
1这一位,a=3,b=245,最大的是22999,因此可能结果为2000-2999,12000-12999,22000-22999,共a1000=3000个;
2这一位,a=31,b=45,前面的从0-30都没问题,可以取31
100种,但当前面为31时,只能取31200-31245共46种,故共计3100+46=3146种;
4这一位,与小于K大致相同,但更多一些,a=312,b=5,那么从200-299,1200-1299,2200-2299....31200-31299均可,共(a+1)*10=3130种
按这个思路,把每一位出现K的可能加起来就是最后K出现的总次数
代码如下

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        if(n<0){
            return 0;
        }
        //用copyn来设置循环中止的条件,每次循环都使得copyn = copyn/10;
        int copyn = n;
        //用cnt来记录当前的位置,如个位就是10的0次方位
        int cnt = 0;
        //用sum来记录最后的结果
        int sum = 0;
        while(copyn!=0){
            copyn /= 10;
            //对于a,mid,b以312的2这一位为例
            //a = 312/10=31
            int a = n/(int)Math.pow(10,cnt+1);
            //mid = 312%10/1=2
            int mid = n%(int)Math.pow(10,cnt+1)/(int)Math.pow(10,cnt);
            //b = 312%10%1 = 0
            int b = n%(int)Math.pow(10,cnt+1)%(int)Math.pow(10,cnt);
            if(mid<1){
                sum += a*(int)Math.pow(10,cnt);
            }
            if(mid==1){
                sum += a*(int)Math.pow(10,cnt)+b+1;
            }
            if(mid>1){
                sum += (a+1)*(int)Math.pow(10,cnt);
            }
            cnt++;
        }
        return sum;
    }
}

32.把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

这个题的思路就是比较给定数组中的任意两个数,例如3和32,由于323小于332,所以32排在3前面,再比较3和321,由于3213小于3321,所以321排在3前面,最后比较32和321,由于32132小于32321,所以321排在32前面,故排序大小顺序为[321,32,3]最后拼接起来得到的就是最后的结果,即最小的数321323,由于考虑整数可能越界,而且整数拼接起来可能比较麻烦,因此这里采用字符串进行操作,且在对字符串进行排序的时候用到lamda表达式;
代码如下

import java.util.Arrays;

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        if(numbers==null||numbers.length==0){
            return "";
        }
        //用一个字符串数组存储所有数字转化成的字符串
        String[] nums = new String[numbers.length];
        for(int i=0;i(s1+s2).compareTo(s2+s1));
        //最后将全部结果拼接起来,得到最后的输出
        String res = "";
        for(String s:nums){
            res += s;
        }
        return res;
    }
}

33.丑数

把只包含质因子2、3和5的数称作丑数(Ugly Number)。
例如6、8都是丑数,但14不是,因为它包含质因子7。 
习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

该题的核心思想就是新的丑数依然是由老的丑数构成的,我们需要建立一个新的数组不断存储新加入的丑数,然后用三个指针指向2,3,5所在的该丑数数组的位置,然后用2,3,5指针所指的位置的丑数分别去乘以2,3,5选择最小的,然后使得构成这个新丑数的这个数的指针向后移动一位,如果相同,则同时移动一位,例如23和32,则index_2和index_3同时后移一位;
代码如下

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index<=0){
            return 0;
        }
        //用3个指针指向2,3,5指向的丑数数组中的位置
        int index_2 = 0,index_3 = 0,index_5 = 0;
        int[] ugly_nums = new int[index];
        ugly_nums[0] = 1;
        for(int i=1;i

34.第一个只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,
并返回它的位置, 如果没有则返回 -1(需要区分大小写).

本题最直观的就是用hashmap来统计字符出现的次数,也可以建立一个整数数组,通过将字符转化为ASCII码,然后整数数组对应位置上加一,然后再遍历原字符串或转化为的字符数组,返回第一个在整数数数组中为1的值
流程为

1.将String转为一个字符数组c,如“abc”--->{‘a’,‘b’,‘c’};
2.建立一个容量为256的整型数组arr,每一位的下标对应着ASCII值
3.遍历字符数组c,例如遍历到了'a',那么就在arr[(int)'a']的位置对应的值+1
4.最后再遍历一遍c,找到第一个在arr中的值为1的那个字符,例如遍历到了'a'且arr[int(a)]==1,那么就返回这个'a'所在的位置即可

代码如下

public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if(str.length()==0||str==null){
            return -1;
        }
        //将String转化为字符数组
        char[] arr = str.toCharArray();
        //建立一个整型数组,作用类似于hashmap,其中res的下标i--->字符的ASCII值
        int[] res = new int[256];
        //遍历一遍arr,使res中对应位置的hash值加一
        for(char c:arr){
            res[(int)c]++;
        }
        //找到第一个hash值为1的字符,输出它的位置
        for(int i=0;i

35.数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。
输入一个数组,求出这个数组中的逆序对的总数P。
并将P对1000000007取模的结果输出。 即输出P%1000000007

该题的思路就是使用归并排序,就是通过递归将数组拆分为最小的依次相邻的两个单元,然后用两个指针分别指向左右数组的头部,若左边指针指向的元素大于右边指针指向的元素,那么在左边数组中指针指向的元素后面的数全部都大于右边数组中指针指向的元素,这些全部都构成逆序对,归并排序中我们还需要一个辅助数组去存储顺序大小排列好的元素,这才叫做归并,最后统计所有这些逆序对,就是我们需要的结果,注意不要超出界限。
代码如下

public class Solution {
    //防止cnt溢出所以用一个long类型
    private long cnt = 0;
    private int[] tmp;  

    public int InversePairs(int[] nums) {
        tmp = new int[nums.length];
        mergeSort(nums, 0, nums.length - 1);
        return (int)(cnt % 1000000007);
    }

    private void mergeSort(int[] nums, int l, int h) {
        if (h - l < 1)
            return;
        //求出中值分界线
        int m = l + (h - l) / 2;
        //归并排序,这里采用递归,可以想象,第一次递归到最结尾的时候
        //一个长度为2的数组,对左右继续递归都只有一个元素了,因此直接return
        //然后再执行merge这个函数,此时长度为2,相当于最小的一个子单元的归并排序
        mergeSort(nums, l, m);
        mergeSort(nums, m + 1, h);
        merge(nums, l, m, h);
    }

    private void merge(int[] nums, int l, int m, int h) {
        //指针i指向左边数组的头部,j指向右边数组的头部,k指向辅助数组的当前元素位置,
        //我们要将两个数组中的元素依次比较大小,然后存入辅助数组中
        //再将原数组中从l到h的所有元素替换为辅助数组中从l到h的所有元素
        int i = l, j = m + 1, k = l;
        //这里不用&&而用||是因为可能左右中某个数组的指针走到头了
        //那么此时我们需要做的就是把另外一个数组的所有元素替换到辅助数组中
        //因此while中是并列的四个条件
        while (i <= m || j <= h) {
            if (i > m)
                tmp[k] = nums[j++];
            else if (j > h)
                tmp[k] = nums[i++];
            else if (nums[i] <= nums[j])
                tmp[k] = nums[i++];
            else {
                tmp[k] = nums[j++];
                //这里是说,当左边的某个元素大于右边的元素的时候
                //那么左边数组中这个元素后的所有元素都大于当前右边指向的这个元素
                //统计左边数组中这个元素后的所有元素的个数就是我们要求的逆序对
                this.cnt += m - i + 1;
            }
            k++;
        }
        for (k = l; k <= h; k++)
            nums[k] = tmp[k];
    }
}

你可能感兴趣的:(剑指offer 31-35)