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

下面的例题来自剑指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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值