算法初步

算法

what?

有穷性、确定性、可行性、输入&输出

How?

穷举(万能算法)

  • 求N个数的全排列
  • 8皇后问题

分而治之(减而治之)

  • 二分查找——减而治之
  • 归并排序——分而治之

贪心

  • 最小生成树
  • 单源最短路

动态规划

  • 背包
  • 士兵路径

复杂度

时空复杂度

使用大O记号(最坏情况,算法的上界。忽略常数,O(n)和O(2n)一样)
时间:基本操作次数(如加减乘除,汇编指令条数)
空间:占用内存字节数
(区别:空间可以再利用)
时空互换(Hash表)

  • 例:快速排序的时间复杂度O(n^2)(最坏情况),平均情况的时间复杂度/期望复杂度O(nlogn)。

常见复杂度:

  • O(1):基本运算,+,-,*,/,%,寻址
  • O(logn):二分查找
  • O(n^1/2):枚举约数
  • O(n):线性查找
  • O(n^2):朴素最近点对、冒泡排序、选择排序
  • O(n^3): Floyd最短路、普通矩阵乘法
  • O(nlogn):归并排序、快速排序的期望复杂度、基于比较排序的算法下界 (比较排序原本有n!种排法,最好情况下每次排序排除一半可能,k次排序排完,则 n!/2k=1 n ! / 2 k = 1 ,又因为 log(n!)<log(nn) l o g ( n ! ) < l o g ( n n ) ,可得 k<nlogn k < n l o g n
  • O(2^n):枚举全部的子集(2^n是集合的全部子集的数量)
  • O(n!):枚举全排列

优秀:O(1) < O(logn) < O(n^1/2) < O(n) < O(nlogn)
可能可以优化O(n^2) < O(n^3) < O(2^n) < O(n!)

算法复杂度的下限:输入输出的时间复杂度
一行代码可忽略输入输出,整段代码不可以
估算:计算机一秒可以约跑1亿条高级指令,看n!=1亿,n有多大。

常见时间复杂度分析方法

  • 输入输出
  • 数循环次数
  • 均摊分析

均摊分析
多个操作,一起算时间复杂度

  • multipop的队列,可以一次性出队k个元素

每个元素只出入队列一次
ki/n=1 ∑ k i / n = 1
均摊时间复杂度为O(1)

  • 动态数组尾部插入操作(vector)

一旦元素超过容量限制,则扩大一倍,再复制

数组:一段连续的内存空间
read O(1)
add O(1)
在不提前指定数组大小的情况下,如果数组长度超过内存分配数,则重新开一个两倍于原空间的内存,将数组拷贝过来。
有空间时 O(1)
没空间时,插n个,则数组不断倍增变大直到长度大于或等于n,所以复杂度为O(1)+O(2)+O(4)+···+O(n),n与问题的规模有关,所以不能视为常数,项数为logn。k项等比数列求和,约为2^k,即2^(logn)=n,所以O(n)插入n个数字,均摊下来复杂度为O(1).

例题:最大子数组和

给定数组a[1…n],求最大子数组和,即找出1<=i<=j<=n,使a[i]+a[i+1]+…+a[j]最大。

介绍三个算法

暴力枚举 O(n3)

三重循环(枚举n个起点和n个终点)
for i <- 1 to n
for j <- i to n
sum <- a[i]+..+a[j]
ans <- max(ans, sum)
时间复杂度 O(n3) 附加空间复杂度 O(1)
附加空间复杂度:除了输入输出其他新开的空间的复杂度,常数个则为O(1),因为sum、ans均为常数。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        int ans = -2147483647;
        for (int st=0;st<n;++st)
            for (int ed=st+1;ed<=n;++ed) {
                int sum=0;
                for (int i=st;i<ed;++i)
                    sum += nums[i];
                if (sum > ans)
                    ans = sum;
            }
        return ans;
    }
};

注意:
1. 起始数问题:int ans = -2147483647;(int的最小数字:-2147483648)
2. 边界问题:for (int ed=st+1;ed**<=**n;++ed)
结果:超时,O(n^3),n超过1000则不行,leedcode的test cases超过1000。

200 / 202 test cases passed.
Status: Time Limit Exceeded

优化枚举 O(n2)

两层循环
优化时先找内层循环,因为内层循环执行次数最多。
去冗余:内层循环加的重复次数太多了
for i <- 1 to n
sum <- 0
for j <- i to n
sum <- sum + a[j]
ans <- max(ans, sum)
时间复杂度 O(n^2) 附加空间复杂度 O(1)

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        int ans = -2147483647;
        for (int st=0;st<n;++st) { //注意++st是先
            int sum = 0; //注意sum的位置是在外层循环里的
            for (int ed=st+1;ed<=n;++ed) {  //注意<=
                // int sum=0;
                // for (int i=st;i<ed;++i)
                sum += nums[ed-1];
                if (sum > ans)
                    ans = sum;
            }
        } 
        return ans;
    }
};

结果:

202 / 202 test cases passed.
Status: Accepted
Runtime: 252 ms

贪心法 O(n)

一重循环

sum <- 0 ans <- 0 for i <- 1 to n
sum <- sum + a[i]
ans <- max(sum, ans)
if (sum < 0) sum <- 0

时间复杂度 O(n) 附加空间复杂度 O(1)
上述算法正确但太难懂

maxi,j(a[i],...,a[j])s[i]=a0+...+aimaxi,j(s[j]s[i1])js[j]=PPmini<j(s[i1]) 原 问 题 等 价 于 : m a x i , j ( a [ i ] , . . . , a [ j ] ) 令 s [ i ] = a 0 + . . . + a i 则 问 题 变 为 : m a x i , j ( s [ j ] − s [ i − 1 ] ) 固 定 j , 令 s [ j ] = P 则 问 题 变 为 : P − m i n i < j ( s [ i − 1 ] )

增量的性质去冗余

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        int ans = -2147483647;
        int sj=0;
        int si=0;
        int minSi=0;
        for (int j = 0;j < n; ++j) { // min[0,6] = min (min[0,5],[6])
            sj += nums[j];
            if (si < minSi)
                minSi = si;
            if (sj - minSi > ans) {
                ans = sj - minSi;
            }
            si += nums[j];
        }
        return ans;
    }
};

202 / 202 test cases passed.
Status: Accepted
Runtime: 8 ms

变量替换:si和sj形式类似,把sj用si表示

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        int ans = -2147483647;

        int si=0;
        int minSi=0;
        for (int j = 0;j < n; ++j) {
            if (si < minSi)
                minSi = si;
            if (si + nums[j] - minSi > ans) //注意此处是+nums[j]而不是-nums[j]
                ans = si + nums[j] - minSi;        
            si += nums[j];
        }
        return ans;
    }
};

202 / 202 test cases passed.
Status: Accepted
Runtime: 4 ms

这次提交告诉我:Your runtime beats 100.00 % of cpp submissions.

把si-minSi替换成sum(此处意义上不是很好理解),再调整代码顺序,则变成如下形式

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        int ans = -2147483647;

        int sum = 0;//si - minSi;
        for (int j = 0;j < n; ++j) {
            sum += nums[j];
            if (sum > ans) 
                ans = sum; 
            if (sum < 0)   //注意两个if的顺序
                sum = 0;
        }
        return ans;
    }
};

思想:
把求和变为求差,使求和复杂度从O(n^3)变为O(n);
把最大的差的问题转化为求最小的减数的问题(求最小值时不用for循环,用if判断)

python简明版:
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        ans = -2147483647
        n = len(nums)

        sj = 0
        si = 0
        minSi = 0
        for i in range(0,n):
            sums = 0
            sj +=nums[i]
            if si < minSi:
                minSi = si
            sums = sj - minSi
            if sums > ans:
                ans = sums
            si +=nums[i]
        return ans
python优化版:
class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        ans = -2147483647
        n = len(nums)

        sums = 0
        for i in range(0,n):
            sums +=nums[i]
            if sums > ans:
                ans = sums
            if sums < 0:
                sums = 0         

        return ans
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值