首页 » 算法

有时候算法的复杂度跟数据的排列由很大关系,这种复杂度不能简单的用一个复杂度来分析,而是由最好,最坏,平均复杂度来分析

1.如下面的,如果x在第0个位置,就是O(1),如果在第n-1个位置,就是O(n),这里就带表了最好和最坏


// n 表示数组 array 的长度,
int find(int[] array, int n, int x) {
  int i = 0;
  int pos = -1;
  for (; i < n; ++i) {
    if (array[i] == x) {
       pos = i;
       break;
    }
  }
  return pos;
}

2.最好最坏的都是极端的,大概率不会发生,那么就引入了平均(情况)时间复杂度,那么x的位置,由n+1种情况,即在0~n-1的位置,还有不在数组种

  • 我们把每种情况的时间复杂度相加,除以可能出现的情况个数,就是平均时间复杂度---即1+2+3+4+...+ n 除以n+1,平均情况时间复杂度就是:n*(n+3)/2(n+1) 分子分母,逐步忽略常量,就是O(n)

3.加权平均时间复杂度,第二步中有点问题,就是没有考虑各种情况的概率,如果x出现在某个位置的概率非常大,那么上面的计算是把所有复杂度的情况都认为是1/(n+1)。

  • 实际上我们可以考虑,x在数组中的概率是1/2,x在每个位置的概率是1/n,那么x在某个位置的概率退化为1/2n,不在数组中的概率为1/2
  • 所以加权平均值是11/2n + 21/2n +...+ n1/2n + n1/2 = (3n+1)/4
  • 第二步的算法,是理想化了,每个情况的权重都看成了1/(n+1)

4.均摊时间复杂度---摊还分析,均摊时间复杂度就是一种特殊的平均时间复杂度,即只有1/n的情况复杂度是n(这里出现的概率很规律,比如数组插入n个数后必然发生扩容,不需要概率),那么均摊下来是O(1),而不需要通过平均计算

big o是算法分析的常用手段,简单的说就是估计算法的执行时间,我们假设每行代码的时间是一样的,所以就可以理解为代码的执行次数.用n表示数据规模,f(n)表示执行次数,那么执行时间T(n) = O(f(n))表示Tn和执行次数fn成正比

1.看个简单的例子,1+ 1 + n + n ,即时间复杂度 (2n+2)*unit_time



 int cal(int n) {
   int sum = 0;//执行1次
   int i = 1;//执行1次
   for (; i <= n; ++i) {//执行n次
     sum = sum + i;//执行n次
   }
   return sum;
 }

2.再看一个例子,T(n) = O(2n2+2n+3),即O表示随着n变化时间的趋势的法则,对于函数的概念,可以理解为映射,这里大O表示次数和时间的映射

int cal(int n) {
   int sum = 0;//1
   int i = 1;//1
   int j = 1;//1
   for (; i <= n; ++i) {//n
     j = 1;//n
     for (; j <= n; ++j) {//n*n
       sum = sum +  i * j;//n*n
     }
   }
 }

3.总结+ 计算手段(下面列的都是): * 大 O 时间复杂度实际上并不具体表示代码真正的执行时间,而是表示代码执行时间随数据规模增长的变化趋势,所以,也叫作渐进时间复杂度(asymptotic time complexity),简称时间复杂度

  • 计算手段1:只关注循环执行次数最多的一段代码---我们通常会忽略掉公式中的常量、低阶、系数,只需要记录一个最大阶的量级就可以了
  • 计算手段2:加法法则,如果两段代码是满足加法法则(即没有嵌套),就是次数是相加的,那么最终取最大的----如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)+T2(n)=max(O(f(n)), O(g(n))) =O(max(f(n), g(n))).
  • 如下满足加法法则,即n方
int cal(int n) {
   int sum_1 = 0;//sum_1 是100次
   int p = 1;
   for (; p < 100; ++p) {
     sum_1 = sum_1 + p;
   }

   int sum_2 = 0;//sum_2是n次
   int q = 1;
   for (; q < n; ++q) {
     sum_2 = sum_2 + q;
   }

   int sum_3 = 0;//sum_3是n的平方
   int i = 1;
   int j = 1;
   for (; i <= n; ++i) {
     j = 1;
     for (; j <= n; ++j) {
       sum_3 = sum_3 +  i * j;
     }
   }

   return sum_1 + sum_2 + sum_3;
 }
  • 计算手段3:如果两段代码是嵌套的,那么满足乘法法则---如果 T1(n)=O(f(n)),T2(n)=O(g(n));那么 T(n)=T1(n)T2(n)=O(f(n))O(g(n))=O(f(n)*g(n)).

    • 如下满足乘法法则,即n方
int cal(int n) {
   int ret = 0;
   int i = 1;
   for (; i < n; ++i) {
     ret = ret + f(i);//n次
   }
 }

 int f(int n) {
  int sum = 0;
  int i = 1;
  for (; i < n; ++i) {
    sum = sum + i;//n 次
  }
  return sum;
 }

4.常见的算法复杂度,复杂度分为两类:多项式量级和非多项式量级.非多项式量级只有两个:O(2^n) 和 O(n!),我们把时间复杂度为非多项式量级的算法问题叫做NP问题

  • O(1) 常数级别的都归为O(1),比如下面的代码执行了3次,也是O(1),一般情况下,只要算法中不存在循环语句、递归语句,即使有成千上万行的代码,其时间复杂度也是Ο(1)。
int i = 8;
 int j = 6;
 int sum = i + j;
  • O(logn)---对数阶算法复杂度,可以简单的理解为一个数除以k(比如k是2,即log2N)多少次可以得到1,如下面的就是

    • 对于log2N,log3N和log10N都记作logN,。因为log3n 就等于 log32 * log2n 而log32是一个常数,所以可以忽略
 i=1;
 while (i <= n)  {
   i = i * 2;
 }
  • 线性阶O(N) O(M+N)
  • 线性对数阶 O(NlogN)
  • 平方阶 O(n^2) ,O(n^3) ...O(n^k)

5.对于空间复杂度,用的最多的是O(1),O(n),O(n^2)。相对简单,不做深入

查找一个字符串中最长的字符串,这个思路就是从左向右找,用i向右找,j标识开始统计的字符。max记录最大长度.如:
Input: "pwwkew"
Output: 3
Explanation: The answer is "wke", with the length of 3.
Note that the answer must be a substring, "pwke" is a subsequence and not a substring.

[1]算法如下,复杂度O(n)


import java.util.HashMap;
import java.util.Map;
class Solution {

    public int lengthOfLongestSubstring(String s) {
        int max = 0;
        Map<Character,Integer> map= new HashMap<Character,Integer>();
        for(int i =0,j=0;i < s.length();i++){
            Character cha = s.charAt(i);
            if(map.containsKey(cha)){
                j = Math.max(j,map.get(cha)+1);
            }
            map.put(cha,i);
            max = Math.max(max,i-j + 1);
        }
        return max;
    }
}

用链表表示10进制的某一位,这样可以计算大数的相加:如
Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8
Explanation: 342 + 465 = 807.

[1]思路:遍历链表,保存进位

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
          int pre= 0;
        
        ListNode r = new ListNode(-1);
        ListNode p0 = r;
        ListNode p_1 = null;
        ListNode p1 = l1;
        ListNode p2 = l2;
        while(p1 != null || p2 != null || pre > 0){
           int v = p1 ==null ? 0 : p1.val;
           v = p2 ==null ? v : v + p2.val;
           v = v + pre;
            pre = v / 10;
           if(p1 != null){
                p1 = p1.next;
           }
           if(p2 != null){
              p2 = p2.next;
           }
          
           p0.val = v % 10;
           p0.next = new ListNode(-1);
           p_1 = p0;
           p0 = p0.next;
            
        }
       p_1.next = null;
       
        return r;
    }
}

已知数组中有两个数的和是一个target,写算法找出这两个数的索引。如nums = [2, 7, 11, 15], target = 9,找出的index是[0, 1]

[1] 算法思路:索引化,类似数据库中的map,找到一个书x,那么寻找target-x是否在数组中。

import java.util.HashMap;
import java.util.Map;
class Solution {
    
    public int[] twoSum(int[] nums, int target) {
        int [] result = new int[2];
        Map<Integer,Integer> map = new HashMap<Integer,Integer>(nums.length);

      for(int j  = 0 ;j < nums.length;j++){
             if(map.containsKey(target-nums[j])){
                        result[1] = j ;
                        result[0] =map.get(target-nums[j]);
                        break;
                    }else{
                          map.put(nums[j] ,j);
                    }
               
         }
        return result;
    }
}

[2] 算法复杂度,O(n)