算法
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)
上述算法正确但太难懂
用增量的性质去冗余
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