程序员进阶之路之面试题与笔试题集锦(一)

一、数据结构-算法的时间复杂度和空间复杂度

在编程题之前,首先我们先来聊聊时间复杂度:

算法复杂度分为时间复杂度和空间复杂度。其作用: 时间复杂度是指执行算法所需要的计算工作量;而空间复杂度是指执行这个算法所需要的内存空间。(算法的复杂性体现在运行该算法时的计算机所需资源的多少上,计算机资源最重要的是时间和空间(即寄存器)资源,因此复杂度分为时间和空间复杂度)。
简单理解:
(1)时间复杂度:执行这个算法需要消耗多少时间。
时间复杂度:在计算机科学中,算法的时间复杂度是一个函数,它定量描述了该算法的运行时间。这是一个关于代表算法输入值的字符串的长度的函数。时间复杂度常用大O符号表述,不包括这个函数的低阶项和首项系数。
(2)空间复杂度:这个算法需要占用多少内存空间。
空间复杂度(Space Complexity) 是对一个算法在运行过程中临时占用存储空间大小的量度,记做 S(n)=O(f(n)) ,其中n为问题的规模。利用算法的空间复杂度,可以对算法的运行所需要的内存空间有个预先估计。
  一个算法执行时除了需要存储本身所使用的指令、常数、变量和输入数据外,还需要一些对数据进行操作的工作单元和存储一些计算所需的辅助空间。算法执行时所需的存储空间包括以下两部分。  
(1)固定部分。这部分空间的大小与输入/输出的数据的个数、数值无关。主要包括指令空间(即代码空间)、数据空间(常量、简单变量)等所占的空间。这部分属于静态空间。
(2)可变空间,这部分空间的主要包括动态分配的空间,以及递归栈所需的空间等。这部分的空间大小与算法有关。

1.1 算法的时间复杂度

(1)语句频度T(n): 一个算法执行所花费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。而且一个算法花费的时间与算法中的基本操作语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度,记为T(n)。

(2)时间复杂度: 在刚才提到的语句频度中,n称为问题的规模,当n不断变化时,语句频度T(n)也会不断变化。但有时我们想知道它的变化呈现什么规律。为此,我们引入时间复杂度概念。 一般情况下,算法中的基本操作语句的重复执行次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n) / f(n) 的极限值为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作 T(n)=O( f(n) ),称O( f(n) ) 为算法的渐进时间复杂度,简称时间复杂度。
  T(n) 不同,但时间复杂度可能相同。 如:T(n)=n²+5n+6 与 T(n)=3n²+3n+2 它们的T(n) 不同,但时间复杂度相同,都为O(n²)。

(3)常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n),线性对数阶O(nlog2n),平方阶O(n2),立方阶O(n3), k次方阶O(nk),指数阶O(2n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。

(4)平均时间复杂度和最坏时间复杂度:

    平均时间复杂度是指所有可能的输入实例均以等概率出现的情况下,该算法的运行时间。

 最坏情况下的时间复杂度称最坏时间复杂度。一般讨论的时间复杂度均是最坏情况下的时间复杂度。 这样做的原因是:最坏情况下的时间复杂度是算法在任何输入实例上运行时间的界限,这就保证了算法的运行时间不会比最坏情况更长。
(5)如何求时间复杂度: 
【1】如果算法的执行时间不随着问题规模n的增加而增长,即使算法中有上千条语句,其执行时间也不过是一个较大的常数。此类算法的时间复杂度是O(1)。

    public static void main(String[] args) {
        int x = 91;
        int y = 100;
        while (y > 0) {
            if (x > 100) {
                x = x - 10;
                y--;
            } else {
                x++;
            }
        }
    }

该算法的时间复杂度为:O(1)
这个程序看起来有点吓人,总共循环运行了1100次,但是我们看到n没有?
没。这段程序的运行是和n无关的,
就算它再循环一万年,我们也不管他,只是一个常数阶的函数
【2】当有若干个循环语句时,算法的时间复杂度是由嵌套层数最多的循环语句中最内层语句的频度f(n)决定的。

         int x = 1;
         for (int i = 1; i <= n; i++) {
             for (int j = 1; j <= i; j++) {
                 for (int k = 1; k <= j; k++) {
                     x++;
                 }
             }
         }

该算法的时间复杂度为:O(n3)
该程序段中频度最大的语句是第5行的语句,内循环的执行次数虽然与问题规模n没有直接关系,但是却与外层循环的变量取值有关,而最外层循环的次数直接与n有关,因此该程序段的时间复杂度为 O(n3)

1         int i = n - 1;
2         while (i >= 0 && (A[i] != k)) {
3             i--;
4         }
5         return i;

该算法的时间复杂度为:O(n)   
此算法中第3行语句的频度不仅与问题规模n有关,还与输入实例A中的各元素取值和k的取值有关:如果A中没有与k相等的元素,那么第3行语句的频度为 f(n)=n ,该程序段的时间复杂度为 O(n)
 (6)用时间复杂度来评价算法的性能
    用两个算法A1和A2求解同一问题,时间复杂度分别是O(100n2),O(5n3)
    (1) 5n3/100n2=n/20 ,当输入量n<20时,100n2 > 5n3 ,这时A2花费的时间较少。
    (2)随着问题规模n的增大,两个算法的时间开销之比 5n3/100n2=n/20 也随着增大。即当问题规模较大时,算法A1比算法A2要高效的多。它们的渐近时间复杂度O(n2)和O(n3) 评价了这两个算法在时间方面的性能。在算法分析时,往往对算法的时间复杂度和渐近时间复杂度不予区分,而经常是将渐近时间复杂度 O(f(n)) 简称为时间复杂度,其中的f(n)一般是算法中频度最大的语句频度。
 (7)小结
算法的时间复杂度和两个因素有关:算法中的最大嵌套循环层数;最内层循环结构中循环的次数。

一般来说,具有多项式时间复杂度的算法是可以接受的;具有指数(不是对数)时间复杂度的算法,只有当n足够小时才可以使用。一般效率较好的算法要控制在O(log2n) 或者 O(n)
通常:容易计算的方法是看看有几重for循环,只有一重则时间复杂度为O(n),二重则为O(n^2),依此类推,如果有二分则为O(logn),二分例如快速幂、二分查找,如果一个for循环套一个二分,那么时间复杂度则为O(nlogn)

1.2 算法的空间复杂度计算

举例分析算法的空间复杂度:
public void reserse(int[] a, int[] b) {
int n = a.length;
for (int i = 0; i < n; i++) {
b[i] = a[n - 1 - i];
}
}
参考前面的空间复杂度定义,上方的代码中,当程序调用 reserse() 方法时,要分配的内存空间包括:引用a、引用b、局部变量n、局部变量i
因此 f(n)=4 ,4为常量。所以该算法的空间复杂度 S(n)=O(1)

二、数组方面

注意*****以下程序在python3上进行执行
1. 数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组【2,3,1,0,2,5,3】,那么对应的输出是第一个重复的数字2。
相似题型解答:统计一个数字在排序数组中出现的次数。

#官方答案
# -*- coding:utf-8 -*-
import collections
class Solution:
    # 这里要特别注意~找到任意重复的一个值并赋值到duplication
    # 函数返回True/False
    def duplicate(self, numbers, duplication):
        # write code here
        flag=False
        c=collections.Counter(numbers)
        for k,v in c.items():
            if v>1:
                duplication=k
                flag=True
                break
        return flag,duplication

执行形式

numbers=[2,3,1,0,2,5,3]
duplication=-1
Solution().duplicate(numbers,duplication)

在python中也可以对numbera进行处理

b=[]
for x in list(set(numbers)):
       b.append(numbers.count(x))
 print(max(b))
2. 连续子数组的最大和

Kadane算法又被称为扫描法,为动态规划(dynamic programming)的一个典型应用。我们用DP来解决最大子数组和问题:对于数组a,用ci标记子数组a[0..i]的最大和,那么则有

c i = max { a i , c i 1 + a i }

# -*- coding:utf-8 -*-

#法一:动态规划 O(n),并输出了最大和元素所在的索引
class Solution:
    def FindGreatestSumOfSubArray(self, array):
        if not array:
            return 0
        start=[]
        res,cur=array[0],array[0]
        for i in range(1,len(array)):

            cur+=array[i]
            #每次对比计算和和原来数值的大小
            res=max(res,cur)
            #判断新的累加和是否小于0,小于则赋予0
            if cur<0:
               start=[]
               cur=0
            else:
                #记录构成最大元素和的索引
                start.append(i+1)

        return res,start

测试

array=[1,2,-4,2,6,8]
Solution().FindGreatestSumOfSubArray(array)

法二:穷举法
i, j的for循环表示x[i..j],k的for循环用来计算x[i..j]之和。

maxsofar = 0
for i = [0, n)
    for j = [i, n)
        sum = 0
        for k = [i, j]
            sum += x[k]
        /* sum is sum of x[i..j] */
        maxsofar = max(maxsofar, sum)

有三层循环,穷举法的时间复杂度为 O ( n 3 )

猜你喜欢

转载自blog.csdn.net/hhtnan/article/details/80279965