剑指Offer第六天(31~36)

原题链接:

第一题:整数中1出现的次数(从1到n整数中1出现的次数)

题目:

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

解析:

这个题目直接用常规的解法肯定太耗时间,要想到每一位和1的关系,拿5246,先是个位6,个位的变化范围是0~9,而这样的变化,会有524次,所以这里有524个1,又因为最后一次有个6,所以还要加一次,所以个位的1的个数是524+1 = 525,再看十位,十位上的数字是4,所以同理,这个位数的上的1的个数应该是52*10,注意这里不是52*1,因为,10位上的数后面10-20之间有十个1,且最后4>1,所以还要加上10,所以十位上的1的个数是52*10+10 = 530,这里要注意如果十位上的数字是1的话,就要看各位上的数是多少了,也就是10~20之间取多少个,这时候我们只要计算n%10+1就行了。然后同理推高位,可以得到1~5246中1的个数是(524*1+1)+(52*10+10)+(5*100+100) +(0*1000+1000) = 2655个。这是参照了大牛的解题思路代码如下:

public int NumberOf1Between1AndN_Solution(int n) {
        if(n < 1)return 0;
        int sum = 0,base = 1,round = n;
        while(round > 0) {
            int weight = round % 10;
            round /= 10;
            sum += round*base;
            if(weight > 1)sum += base;
            else if(weight == 1)sum += (n % base)+1;
            base *= 10;
        }
        return sum;
    }

第二题:把数组排成最小的数

题目:

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

解析

这个题目要想到字符串,想到一个排序规则,两个数a,b,把它们转换成字符串,把它们连起来,如果a+b > b+a(注意我这里+的意思是代表连起来),则我们要选择b+a,按照这个规则来排序,按照小的排在前面就行了。

public String PrintMinNumber(int[] numbers) {
        ArrayList<String>list = new ArrayList<String>();
        for(int i = 0; i < numbers.length; i++)list.add(numbers[i]+"");
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return (o1+o2).compareTo(o2+o1);  //按照降序排列(第一个大于第二个返回1-->升序排列)
            }
        });
        String str = "";
        for(String temp:list)str += temp;
        return str;
    }   

第三题:丑数

题目:

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

解析:

这个题目按照直接一个个判断是不是丑数的方法不是好方法,要想到的是可以从小到大计算出丑数,存到一个数组中,这里要注意的是丑数的顺序要是从小到大的排列(因为等下要按照下标取),所以每次生成的时候,要选择最小的丑数生成,设置三个小标i2,i3,i5记录三个因子各自的下标,每次比较生成即可。

 public int GetUglyNumber_Solution(int index) {
        if(index == 0)return 0;
        int[] ans = new int[index+1];
        int count = 0,i2 = 0,i3 = 0,i5 = 0;
        ans[0] = 1;
        while(count < index) {
            int temp = Math.min(ans[i2]*2, Math.min(ans[i3]*3, ans[i5]*5));
            if(temp == ans[i2] * 2)i2++;
            if(temp == ans[i3] * 3)i3++;
            if(temp == ans[i5] * 5)i5++;
            ans[++count] = temp;
        }
        return ans[index-1];
    }

第一个只出现一次的字符

题目:

在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置。

解析:

直接用哈希表,一个计数数组记录每个字母出现的次数,两次O(n)的遍历,第二次从前往后判断,只要字母只出现一次就直接返回下标即可。

public int FirstNotRepeatingChar(String str) {
        if(str == null || str.length() == 0)return -1;
        int[] a = new int[59]; //65~122
        for(int i = 0; i < str.length(); i++)a[str.charAt(i)-'A'+1]++;
        for(int i = 0; i < str.length(); i++)if(a[str.charAt(i)-'A'+1] == 1)return i;
        return -1;
    }

    //熟悉一下HashMap的写法
    public int FirstNotRepeatingChar2(String str) {
        if(str == null || str.length() == 0)return -1;
        HashMap<Character,Integer> mp = new HashMap<Character,Integer>();
        for(int i = 0; i < str.length(); i++) {
            if(!mp.containsKey(str.charAt(i))) {
                mp.put(str.charAt(i), 1);
            }else {
                mp.put(str.charAt(i), mp.get(str.charAt(i))+1);
            }
        }
        for(int i = 0; i < str.length(); i++)if(mp.get(str.charAt(i)) == 1)return i;
        return -1;
    }

数组中的逆序对

题目:

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

解析:

这个题目用到归并排序,分开到底之后,每次合并,判断一下两个数的大小,如果左边的data[i] > data[j],那么逆序数,就可以加上j - mid(前面的(mid ~ j)都会比data[i] 要小),并且把data[i]加到辅助数组的后面,一步一步归并即可。

public int InversePairs(int[] data) {
        if (data.length == 0 || data == null)return 0;
        int[] temp = new int[data.length];
        int sum = Merge(temp, data, 0, data.length - 1);
        return sum;
    }

    public static int Merge(int[] temp, int[] data, int l, int r) {
        if (l == r) {// 递归条件
            temp[l] = data[l];
            return 0;
        }
        int mid = l + (r - l) / 2;
        int leftcount = Merge(temp, data, l, mid);
        int rightcount = Merge(temp, data, mid + 1, r);
        int i = mid, j = r, k = r,count = 0;
        while (i >= l && j > mid) {
            if (data[i] > data[j]) {
                count += j - mid;
                if(count >= 1000000007)count %= 1000000007; //这里很大的时候要去余
                temp[k--] = data[i--];
            } else {
                temp[k--] = data[j--];
            }
        }
        while (i >= l)temp[k--] = data[i--];
        while (j > mid)temp[k--] = data[j--];
        for(int p = l; p <= r; p++)data[p] = temp[p];  //把已经排序的数组考到原数组中
        return (count + leftcount + rightcount)%1000000007;
    }

两个链表的第一个公共结点

题目:

输入两个链表,找出它们的第一个公共结点。

解析:

关键就是要理解结点,而不是结点的值,一旦有一个结点相同,后面的都会相同
这里写图片描述

    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null)return null;
        int len1 = getListLength(pHead1),len2 = getListLength(pHead2);
        int lenDif = len1 - len2;
        ListNode pLong = pHead1,pShort = pHead2;
        if(len2 > len1) {
            pLong = pHead2;
            pShort = pHead1;
            lenDif = len2 - len1;
        }
        for(int i = 0; i < lenDif; i++)pLong = pLong.next;
        for(;pLong != pShort && pLong != null && pShort != null; pLong = pLong.next,pShort = pShort.next);
        return pLong;
    }
    private static int getListLength(ListNode node) {
        int len = 0;
        ListNode p = node;
        while(p != null) {
            p = p.next;
            len++;
        }
        return len;
    }
阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页