【面试编程题】-9剑指offer之优化时间和空间效率

来源:互联网 发布:关联矩阵法案例 编辑:程序博客网 时间:2024/05/29 06:59

下面的例题来自剑指offer第5章的内容。很多公司的面试官都把代码的时间效率当做一个考查重点。面试官除了考查应聘者的编程能力之外,还关注应聘者有没有不断优化效率、追求完美的态度和能力。

1.数组中出现次数超过一半的数字

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

思路:
如果采用暴力方,对array[i]都查看是否该值超过一半,时间复杂度为n的平方,太高。如果先排序再查看时间复杂度为nlogn。也可以直接利用额外的空间一次遍历进行次数统计,找出超过一半的数。

public class Solution {    public int MoreThanHalfNum_Solution(int [] array) {        int len=array.length;        if(len==0)            return 0;        int key=array[0];        int count=1;        for(int i=1;i<len;i++){            if(array[i]==key)                count++;            else{                if(count>0)                    count--;                else{                    key=array[i];                    count=1;                }            }        }        if(count>0 && checkMoreHalf(array,key))            return key;        return 0;    }    public boolean checkMoreHalf(int[] array,int key){        int count=0;        for(int i=0;i<array.length;i++){            if(array[i]==key)                count++;        }        if(count>array.length/2)            return true;        return false;    }}

由于该值超过数组的一半那么排序过后该值一定处于中间位置,也就是中位数。可以利用快速排序找partition的方法找到中位数(和找第k大的数类似)。

 public int MoreThanHalfNum_Solution(int [] array) {        int len=array.length;        if(len==0)            return 0;        int p=partition(array, 0, len-1);        while(p!=len/2){            if(p>len/2)                p=partition(array,0,p-1);            else                p=partition(array,p+1,len-1);        }        if(checkMoreHalf(array,array[p]))            return array[p];        return 0;    }    public int partition(int[] array,int i,int j){        int x=array[i];        while(i<j){            while(i<j && array[j]>=x)                j--;            if(i<j){                array[i]=array[j];                i++;            }            while(i<j && array[i]<=x)                i++;            if(i<j){                array[j]=array[i];                j--;            }        }        array[i]=x;        return i;    }    public boolean checkMoreHalf(int[] array,int key){        int count=0;        for(int i=0;i<array.length;i++){            if(array[i]==key)                count++;        }        if(count>array.length/2)            return true;        return false;}

2.最小的K个数

题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

思路:
和上题一样采用快速排序中partition的思想,时间复杂度是n

import java.util.ArrayList;public class Solution {    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {        ArrayList<Integer> result=new ArrayList<Integer>();        int len=input.length;        if(len==0 || len<k || k==0)            return result;        int p=partition(input,0,len-1);        while(p!=k-1){            if(p>k)                p=partition(input,0,p-1);            else                p=partition(input,p+1,len-1);        }        for(int i=0;i<=p;i++){            result.add(input[i]);        }        return result;    }     public int partition(int[] array,int i,int j){        int x=array[i];        while(i<j){            while(i<j && array[j]>=x)                j--;            if(i<j){                array[i]=array[j];                i++;            }            while(i<j && array[i]<=x)                i++;            if(i<j){                array[j]=array[i];                j--;            }        }        array[i]=x;        return i;    }}

这个题类似于Top k问题。所以可以利用容器来保存当前top k,然后一次遍历后面的数,对容器里的top k进行变换,使得容错中的数始终是top k的数。容器可以使用堆、平衡二叉树、红黑数等。时间复杂度是nlogn。

3.连续子数组的最大和

题目描述

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

思路:
可以实现动态规划,用个表记录前面位置子数组的最大和,然后利用该表一步步扩大。
也可以采用贪心,取当前和最大的,如下:

public class Solution {    public int FindGreatestSumOfSubArray(int[] array) {        int len=array.length;        if(len==0)            return 0;        int max=array[0];        int sum=array[0];        for(int i=1;i<len;i++){            sum=Math.max(sum+array[i], array[i]);            max=Math.max(max, sum);        }        return max;    }}

4.整数中1出现的次数(从1到n整数中1出现的次数)

题目描述

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

思路:
如果对1到n每个数都计算其1出现的个数,时间复杂度太高了。这道题需要利用数字特点和规律,计算每一位上1出现的次数:

例如百位上1出现次数,数值n在百位上的值是curNum则:

  • if(curNum==0)

    1出现的次数等于比百位更高位数*100。例如n=1023,高位数就是1,百位上出现1的次数是1*100;
  • if(curNum==1)

    1出现的次数等于比百位更高位数*100,再加上低位上的数,再加1。例如n=1123,高位数就是1,低位数是23,百位上出现1的次数是1*100+23+1; 
  • if(curNum>1)

     1出现的次数等于比百位更(高位数+1)*100,例如n=1223,高位数就是1,次数百位上出现1的次数是(1+1)*100;
public class Solution {    public int NumberOf1Between1AndN_Solution(int n) {        int count=0;        int factor=1;        int curNum;        int highNum;        int lowNum;        while(n/factor!=0){             curNum=(n/factor)%10;             lowNum=n%factor;             highNum=n/(factor*10);             if(curNum==0)                 count+=highNum*factor;             if(curNum==1)                 count+=highNum*factor+lowNum+1;             if(curNum>1)                 count+=(highNum+1)*factor;             factor*=10;        }        return count;    }}

5.把数组排成最小的数

题目描述

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

思路:

可以利用全排列,然后比较所以排序的值找出最小的数,此时时间复杂度是n!。可以利用排序直接排出一个最小的数。

import java.util.ArrayList;import java.util.Arrays;import java.util.Comparator;public class Solution {    public String PrintMinNumber(int [] numbers) {        int len=numbers.length;        if(len==0)            return "";        String[] str=new String[len];        for(int i=0;i<len;i++){            str[i]=numbers[i]+"";        }        Arrays.sort(str, new Comparator<String>(){            @Override            public int compare(String o1, String o2) {                // TODO Auto-generated method stub                String s1=o1+o2;                String s2=o2+o1;                return s1.compareTo(s2);            }                       });        String result="";        for(int i=0;i<len;i++){            result+=str[i];        }        return result;    }}

6.丑数

题目描述

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

思路:
如果从1开始每个数都判断是不是丑数,时间复杂度太高。根据丑数的定义,丑数应该是例一个丑数乘以2、3、5的结果。因此我们可以建一个数组,里面的数字是排好序的丑数,每一个丑数都是前面丑数乘以2、3、5得到的。

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

7、第一个只出现一次的字符

题目描述

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

思路:
这题没想到什么特别的方法,只能利用HashMap进行统计字符出现的次数,找出第一个次数为1的字符。

import java.util.Map;import java.util.HashMap;public class Solution {    public int FirstNotRepeatingChar(String str) {        Map<Character,Integer> map=new HashMap<>();        int len=str.length();        for(int i=0;i<len;i++){            char c=str.charAt(i);            int count=1;            if(map.containsKey(c))                count+=map.get(c);            map.put(c, count);        }        for(int i=0;i<len;i++){            if(map.get(str.charAt(i))==1)                return i;        }        return -1;    }}

8.数组中的逆序对

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
输入描述:
题目保证输入的数组中没有的相同的数字
数据范围:
对于%50的数据,size<=10^4
对于%75的数据,size<=10^5
对于%100的数据,size<=2*10^5

输入例子:
1,2,3,4,5,6,7,0

输出例子:
7

思路:
如果采用暴力法,对每个array[i]都统计一遍逆序对,时间复杂度是n的平方。我们可以利用归并排序的思想(对一个有序的的数组排序排序时间复杂度更低),所以这里对利用有序的数组算逆序对更快。

public class Solution { public int InversePairs(int [] array) {        int from=0;        int to=array.length-1;        return countPairs(array, from,to)%1000000007;    }    public int countPairs(int[] array,int from,int to){        int count=0;        if(from<to){            int mid=(from+to)/2;            count=countPairs(array,from,mid);            count+=countPairs(array,mid+1,to);            int[] newarray=new int[to-from+1];                      int i=from;            int j=mid+1;            int m=0;            while(i<=mid && j<=to ){                if(array[i]>array[j]){                    count+=mid-i+1;                    if(count>1000000007)//数值过大求余                    {                        count%=1000000007;                    }                    newarray[m++]=array[j++];                }                else{                    newarray[m++]=array[i++];                }                           }            while(i<=mid){                newarray[m++]=array[i++];            }            while(j<=to){                newarray[m++]=array[j++];            }            m=0;            for(i=from;i<=to;i++){                array[i]=newarray[m++];            }        }        return count;    }}

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

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

思路:
如果两个链表出现一个公共节点,那么之后的节点都相同。这道题可以先计算两个链表的长度,计算出长度的差n,然后从头遍历两个链表,让较长的链表先走n步。那么两个链表将会在第一个公共节点相遇。
也可以利用空间换时间,利用一个set统计一个链表中出现的节点,然后遍历另一个链表,找出第一个重复的节点。

import java.util.HashSet;/*public class ListNode {    int val;    ListNode next = null;    ListNode(int val) {        this.val = val;    }}*/public class Solution {    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {        HashSet<ListNode> set=new HashSet<>();        ListNode pNode=pHead1;        while(pNode!=null){            set.add(pNode);            pNode=pNode.next;        }        pNode=pHead2;        while(pNode!=null){            if(set.contains(pNode))                return pNode;            pNode=pNode.next;        }        return null;    }}
1 0
原创粉丝点击