剑指Offer系列之题31~题35

31.连续子数组的最大和

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

根据数组特点,依次向后加,当和小于0时,说明再向后加也会小于下一位,所以直接从下一位开始计算。当和大于0但是小于之前的和时,将之前的和保存,在后面进行对比,若被超过就替换。


1.根据数组:

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        //连续最大和
        if(array.length<=0)
            return 0;

        //数组的特点
        int max=array[0];
        int temp=array[0];//暂存大于0的和
        for(int i=1;i<array.length;++i){
            if(max+array[i]<=0){//若当前和小于0,则从下一位重新开始计数
                if(max+array[i]>temp)//小于0也需判断是否大于最大值
                    temp=max+array[i];
                max=0;
            }else{//若当前和大于0
                if(max+array[i]<max){//判断该和是否小于之前的max
                    if(max+array[i]>temp)
                        temp=max;//若小于则保存之前的max;
                }else if(max+array[i]>temp){
                    temp=max+array[i];
                }
                max=max+array[i];
            }
        }

        return temp;

    }
}

2.动态规划:

F(i):以array[i]为末尾的子数组的最大值;res:当前所有子数组的最大值

F(i)=Math.max(F(i-1)+array[i],array[i]);以array[i]为末尾的子数组的最大值要么是前面序列+当前数字,要么是当前数字。

res=Math.max(F(i),res);找到所有子数组中的最大值。

public class Solution {
    public int FindGreatestSumOfSubArray(int[] array) {
        int fi=array[0];//以array[i]为末尾的子数组的最大值
        int res=array[0];//当前所有子数组的最大值
        for(int i=1;i<array.length;++i){
            fi=Math.max(fi+array[i],array[i]);
            res=Math.max(fi,res);
        }
        return res;
    }
}

32.1到n的整数中1出现的次数 🔺

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

暴力解:求/10的余数,然后/10,依次判断每一位是否为1。找规律:


1.暴力解:

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        //1到n中1出现的次数
        if(n<=0)
            return 0;
        int count=0;
        for(int i=1;i<=n;++i){
            int com=i;
            if(com%10==1)//余数为1则个位是1
                count++;
            while(com/10!=0){
                com=com/10;
                if(com%10==1){
                    count++;
                }
            }
        }
        return count;
    }
}

2.归纳,找规律:

个位开始分析:0-9;10-19;20-29……每十个数构成一个阶梯,每个阶梯个位上出现1次1。当不构成完整阶梯时,看/10的余数是否大于1,大于则出现一次1,否则不出现。个位1出现的次数可以总结为:n/10 + (n%10==0? 0:1)

十位:0-99;100-199;200-299……每一百个数构成一个阶梯,每个阶梯十位上出现10次1。当不构成完整阶梯时,看/100的余数是否大于19,大于则出现10次1,小于10则不出现,处于10-19间则出现余数-10+1 次1,十位上1出现的次数可以总结为:(n/100)*10+if(n%100 > 19) 10 else if(n%100 < 10) 0 else (n%100 -10+1)

百位:同上。百位上1出现的次数可以总结为:(n/1000)*100 +if(n%1000 > 199) 100 else if(n%1000 < 100) 0 else (n%1000 -100+1)

ps:若输入Integer.MAX_VALUE会返回一个负值。

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;
    }
}

参考牛客

3.剑指offer:

设N = abcde ,其中abcde分别为十进制中各位上的数字。如果要计算百位上1出现的次数,它要受到3方面的影响:百位上的数字,百位以下(低位)的数字,百位以上(高位)的数字

① 如果百位上数字为0,百位上可能出现1的次数由更高位决定。比如:12013,则可以知道百位出现1的情况可能是:100199,11001199,21002199,,...,1110011199,一共1200个。可以看出是由更高位数字(12)决定,并且等于更高位数字(12)乘以 当前位数(100)。

② 如果百位上数字为1,百位上可能出现1的次数不仅受更高位影响还受低位影响。比如:12113,则可以知道百位受高位影响出现的情况是:100199,11001199,21002199,,....,1110011199,一共1200个。和上面情况一样,并且等于更高位数字(12)乘以 当前位数(100)。但同时它还受低位影响,百位出现1的情况是:12100~12113,一共114个,等于低位数字(113)+1。

③ 如果百位上数字大于1(29),则百位上出现1的情况仅由更高位决定,比如12213,则百位出现1的情况是:100199,11001199,21002199,...,1110011199,1210012199,一共有1300个,并且等于更高位数字+1(12+1)乘以当前位数(100)。

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;
    }
}

33.把数组排成最小的数

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

快排,然后从前到后拼接。快排中比较的规则是:将两个数字拼接,哪个拼接后数字小,哪个数字小。


快排(更改比较规则):

import java.util.ArrayList;

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        if(numbers.length<=0)
            return "";
        //最小数字,最高位尽可能小,如果第一位相等,则选第二位尽可能小,依次类推
        String result="";
        //遍历,每一轮找出一个最小值,此处的最小值指排序后的数小,其就是小值
        ArrayList<Integer> temp=new ArrayList<>();
        //根据比较规则进行排序,然后从头到尾进行拼接
        quickSort(numbers,0,numbers.length-1);
        for(int i=0;i<numbers.length;++i){
            result+=numbers[i];
        }
        return result;
    }

    public boolean newCompare(int a,int b){//判断a是否小于b
        String left="";
        left+=a;
        left+=b;
        String right="";
        right+=b;
        right+=a;
        if(left.compareTo(right)<0)//可能存在超出int范围的情况,所以利用String的比较
            return true;
        return false;
    }

    public void quickSort(int a[],int low,int high){
        if(low<high){
            int index=partition(a,low,high);
            quickSort(a,low,index-1);
            quickSort(a,index+1,high);
        }
    }

    public int partition(int a[],int low,int high){
        int mid=a[low];
        while(low<high){
            while(low<high && !newCompare(a[high],a[low]))
                high--;
            a[low]=a[high];
            while(low<high && newCompare(a[low],mid))
                low++;
            a[high]=a[low];
        }
        a[low]=mid;
        return low;
    }
}

34.丑数

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

暴力解:对每个数判断是否满足(可能超时)。

空间换时间。


1.暴力解:

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        //只包含质因子2,3,5的数叫做丑数
        if(index<=0)
            return 0;
        if(index==1)
            return 1;
        int n=1;
        int p=1;
        while(n<index){
            p++;
            if(isUgly(p))
                n++;
        }
        return p;
    }

    public boolean isUgly(int p){
        while(p%2==0)
            p=p/2;
        while(p%3==0)
            p=p/3;
        while(p%5==0)
            p=p/5;
        if(p==1)
            return true;
        return false;
    }
}

2.空间换时间:

第一位是1,然后第二位是1*2、1*3 、1*5中的最小值2,2进入数组。然后下一位是1*3、 1*5、 2*2 、2*3 、2*5中的最小值。

可以看出:2*3 、2*5是不必要计算的,因为2比1大,前面有1*3、 1*5未进入数组,2*3 、2*5必定比这两个大,所以只需计算2*2。

即此时第一位只用来计算*3,*5,第二位用来*2。当3入组后,第一位只用来*5,第二位用来*2 *3,依此类推。

综上,对*2 、*3 、*5的值分别维护一个指针。当一个值的乘运算是最小值,入数组之后,将其指针后移一位。

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        //只包含质因子2,3,5的数叫做丑数
        if(index<=0)
            return 0;
        if(index<7)
            return index;
        int result[]=new int[index];
        result[0]=1;
        int p2=0;//分别对应*2 3 5的数所在位置
        int p3=0;
        int p5=0;
        for(int i=1;i<index;++i){
            //当前位的丑数一定是之前丑数*2,*3,*5里的最小值
            result[i]=Math.min(Math.min(result[p2]*2,result[p3]*3),result[p5]*5);
            //若当前数是最小值,则需将其后移,否则其会一直是最小
            if(result[i]==result[p2]*2)
                p2++;
            if(result[i]==result[p3]*3)
                p3++;
            if(result[i]==result[p5]*5)
                p5++;
        }
        return result[index-1];
    }
}

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

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

暴力解:利用有序Map存储字符和出现次数,然后遍历找到第一个次数为1的,输出其在字符串中的位置。

利用Ascii码。


1.暴力解:

import java.util.LinkedHashMap;
import java.util.Map;

public class Solution {
    public int FirstNotRepeatingChar(String str) {
        //有部分只出现一次的字符,找到其中第一个
        if(str==null)
            return -1;
        int len=str.length();
        if(len==1)
            return 0;
        Map<Character,Integer> charMap=new LinkedHashMap<>();//有序map,保证字符加入的顺序
        for(int i=0;i<len;++i){//遍历
            //判断是否出现过
            if(!charMap.containsKey(str.charAt(i))){//没有则以1次入map
                charMap.put(str.charAt(i),1);
            }else{
                int count=charMap.get(str.charAt(i));//获取该字符的出现次数
                charMap.put(str.charAt(i),++count);//+1
            }
        }
        for(int i=0;i<len;++i){
            if(charMap.get(str.charAt(i)) == 1)
                return i;
        }
        return -1;
    }
}

2.Ascii码:

利用每个字母的ASCII码作hash来作为数组的index。

首先用一个长度为58的数组来存储每个字母出现的次数,为什么是58呢,主要是由于A-Z对应的ASCII码为65-90a-z对应的ASCII码值为97-122。每个字母的index=int(word)-65,比如g=103-65=38。为了大小写统一地计算减65,所以将ASCII码值为91-96的这六位也计算进去,所以长度为58

而数组中具体记录的内容是该字母出现的次数,最终遍历一遍字符串,找出第一个数组内容为1的字母就可以了,时间复杂度为O(n)。

public class Solution {
    public int FirstNotRepeatingChar(String str) {
        //有部分只出现一次的字符,找到其中第一个
        //根据字符的ascii码
        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;
    }
}

如有错误,欢迎指正

posted @ 2020-04-14 12:29  雨落成尘  阅读(178)  评论(0编辑  收藏  举报