剑指Offer行榜【牛客网】练习(七)

本文解析了五道经典算法题,包括计算1出现次数、数组排序、求丑数、查找唯一字符和计算逆序对,提供了详细的思路与代码实现。
1、整数中1出现的次数

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

思路:
直接转成char[ ]统计

代码:

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int count = 0;
        for(int i=1;i<=n;i++){
            char[] str = (""+i).toCharArray();
            for(int j=0;j<str.length;j++){
                if(str[j]=='1'){
                    count++;
                }
            }
        }
        return count;
    }
}
2、把数组排成最小的数字

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

思路:
也就是说,把数组按照最高位最小的在前的序列排序。这个排序和String的排序相似,故先转为String再进行排序。

出错点:
1、考虑33,34,332,的情况,如果直接使用String的compareTo,顺序为33,332,34,但是实际上是332,33,34。所以如果第一位相同时,重写比较代码,当遇到332这个数字时,找到多出的那一位比较与第一个数字的大小,如果较小要排在前面(2<3)。
2、考虑1,11,111,在使用while寻找多出的那一位时(上述的2),如果全都和第一位相同,不要越界访问。

代码:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Solution {
    public String PrintMinNumber(int [] numbers) {
        ArrayList<String> num = new ArrayList<>();
        for(int i=0;i<numbers.length;i++){
            num.add(numbers[i]+"");
        }
        Collections.sort(num,new Comparator<String>(){
            public int compare(String s1,String s2){
                if(s1.charAt(0)!=s2.charAt(0)||s1.length()==s2.length()){
                    return s1.compareTo(s2);
                }else{
                    if(s1.compareTo(s2)<0){
                        //s1 len < s2 len 
                        return cmp(s1,s2);
                    }else{
                        return -cmp(s2,s1);
                    }
                }
            }
        });
        String result = "";
        for(int i=0;i<num.size();i++){
            result+=num.get(i);
        }
        return result;
    }
    public static int cmp(String s1,String s2){
        char first = s1.charAt(0);
        for(int i=1;i<s1.length();i++){
             if(s1.charAt(i)>s2.charAt(i)){
                 return 1;
             }else if(s1.charAt(i)<s2.charAt(i)){
                 return -1;
             }else{
                  continue;
             }
        }
        int index = s1.length();
        while(index<s2.length()&&first==s2.charAt(index)){
                            index++;
        }
        if(index==s2.length()){
            return 0;
        }
        if(first>s2.charAt(index)){
              return 1;
        }else{
              return -1;
        }
    }
}
3、丑数

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

思路:
如果直接按1,2,3,……判断会超时。那么我们只要从1,2,3,5逐步乘以2,3,5,按从小到大即可。
现在问题的关键就是,如何从小到大。一个数A可以乘以2,3,5,但是不一定比(A+1)* 2都小。
因此我们需要三个指针(*2,*3,*5),指向当前丑数,找出最小的下一位数,则将该指针指向下一位丑数(A+1),而其他指针仍然指向A。

代码:

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index<=0){
            return 0;
        }
        int[] uglys = new int[index];
        uglys[0] = 1;
        int pointer2 = 0;
        int pointer3 = 0;
        int pointer5 = 0;
        for(int i = 1; i< index;i++){
            int next = Math.min(uglys[pointer2]*2,
                                Math.min(uglys[pointer3]*3,uglys[pointer5]*5));
            if(next==uglys[pointer2]*2)pointer2++;
            if(next==uglys[pointer3]*3)pointer3++;
            if(next==uglys[pointer5]*5)pointer5++;
            uglys[i] = next;
        }
        return uglys[index-1];
    }
}
4、第一个只出现一次的字符

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

思路:
循环一次,记录每个字符的次数,然后找出出现为1的第一个字符。

代码:

import java.util.ArrayList;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if(str==null||str.length()==0){
            return -1;
        }
        ArrayList<Character> characters = new ArrayList<>();
        ArrayList<Integer> counts = new ArrayList<>();
        ArrayList<Integer> indexs = new ArrayList<>();
        for(int i=0;i<str.length();i++){
            Character c = str.charAt(i);
            int index = characters.indexOf(c);
            if(index>=0){
                int count = counts.get(index)+1;
                counts.remove(index);
                counts.add(index,count);
            }else{
                characters.add(c);
                counts.add(new Integer(1));
                indexs.add(i);
            }
        }
        int i = counts.indexOf(new Integer(1));
        if(i<0){
            return -1;
        }else{
            return indexs.get(i);
        }
    }
}
5、数组中的逆序对

题目描述:
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
示例:1,2,3,4,5,6,7,0
输出:7

思路:
实质就是排序,将每一个数按位移动后排成从小到大的顺序所需的移动次数即题解。
不管时间复杂度的话,及使用冒泡排序,依次比较,就可以得出结果。
但是题目有时间限制,采用归并排序。
归并排序的核心是计算移动次数counter += (mid-i+1)

注意点:
数字太大要用long,然后结果转int

代码:

public class Solution {
    
    long counter = 0;
    
    public int InversePairs(int [] array) {
        if(array==null||array.length==0){
            return -1;
        }
        mergeSort(array);
        return (int)(counter%1000000007);
    }
    public void mergeSort(int[] array){
        sort(array,0,array.length-1);
    }
    public void sort(int[] array,int left,int right){
        if(left<right){
            int mid = (left+right)/2;
            sort(array,left,mid);
            sort(array,mid+1,right);
            merge(array,left,mid,right);
        }
    }
    public void merge(int[] array, int left,int mid, int right){
        int[] temp = new int[right-left+1];
        int i = left, j = mid + 1, k = 0;
        while(i<=mid&&j<=right){
            if(array[i]<=array[j]){
                temp[k++] = array[i++];
            }else{
                temp[k++] = array[j++];
                counter += mid - i + 1;//core
            }
        }
        while(i<=mid){
            temp[k++] = array[i++];
        }
        while(j<=right){
            temp[k++] = array[j++];
        }
        for(int p=0;p<temp.length;p++){
            array[left+p] = temp[p];
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值