- 第一节 枚举+优化套路(1)-
枚举的基本思想:
枚举算法的基本思想非常朴素:就是利用计算机强大的运算能力,一个一个试,把答案试出来。
举个例子:我们要求1到100万里有多少个质数。这个答案我没办法用公式或者什么办法直接算出来,只好一个一个试1是不是质数、2是不是质数、3是不是质数……一直试到100万。这就是枚举
枚举的要点:
如果我一个算法根据数据范围、时限我知道要超时,我们怎么减少复杂度?
方法不外乎就是
1减少枚举的变量,原来我要枚举4个变量,能不能只枚举3个?
2就是缩小枚举范围,我能不能通过改变枚举的变量,减少范围?
确定需要的枚举变量
确定枚举的范围(不重不漏)
1是二分,二分查找、二分搜索非常有效,一般是复杂度从O(N)降到O(logN)。使用范围也很广,我们会在后面专门拿出一节时间来讲。
2是用Hash,空间换时间。
3此外还有一些常用的套路:比如双指针,Leetcode上对应的分类是two pointer,直译过来就是双指针,大概的思想就是滑动窗口。
4还比如前缀后缀和,也是空间换时间的思路,后面我们会讲到。
二分
哈希
双指针
前缀、后缀和
优化
我们先来看一道题,叫做平方十位数。是蓝桥杯2017年决赛题。
标题:平方十位数
由0~9这10个数字不重复、不遗漏,可以组成很多10位数字。
这其中也有很多恰好是平方数(是某个数的平方)。
比如:1026753849,就是其中最小的一个平方数。
请你找出其中最大的一个平方数是多少?
注意:你需要提交的是一个10位数字,不要填写任何多余内容。
当然这是一道填空题,还不是编程题。不过我们可以从这个题来讨论一下枚举的优化。首先我们的一个思路可能就是这个样子:
思路1:
1、直接从大到小枚举可能的答案。最大的是9876543210,最小的是题目中给的1026753849。
2、然后我们去判断是不是恰好包含0~9十个数字。
3、再判断是不是完全平方数。
1)令Y=int(sqrt(x))
2)判断Y*Y==X?
这个算法当然正确。不过它的时间复杂度大概是10^10到10^11次方量级。因为首先枚举的范围就大约是10^10,然后分离各位数字也有一个O(10)的复杂度。
我们能不能优化这个算法?
当然是可以,我们不去枚举这个答案X是多少,改成枚举X的平方根Y是多少。因为X要求是完全平方数,所以Y也应该是整数嘛。
思路2:
1、枚举平方根,十位数的平方根,最小是30000,最大我们这里设的是100000,绰绰有余。
2、然后我们根据Y算出X,这样X一定是完全平方数,X=Y*Y
3、只用检查X是不是恰好0-9十个数字。
这个算法的复杂度就比之前的算法低了很多。枚举大概是10^5量级,判断是不是恰好十个数字还有O(10)的复杂度,总共大概是10^6量级。
主要来看一下判断是不是包含0~9的代码:
bool contains0_9(int x){
if(x==0)return false;
set<int> s;
while(x){
int d=x%10;
s.insert(d);
x/=10;
}
return s.size()==10;
}
这个while循环是在分离x的各位数字。每次把x的末位数字,也就是个位数字通过模10分离出来,存在变量d里。这个分离各位数字的方法是很常用的。
另外我们这里用了set来统计哪些数字出现过。Set是STL里非常好用的工具,由于它内部是平衡树实现的,所有插入、删除、查找等都是很优秀的复杂度O(logN)
STL-set用法
set跟vector差不多,它跟vector的唯一区别就是,set里面的元素是有序的且唯一的,只要你往set里添加元素,它就会自动排序,而且,如果你添加的元素set里面本来就存在,那么这次添加操作就不执行。要想用set先加个头文件set。
十位数 用long long
#include <iostream>
#include <set>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
using namespace std;
bool contains0_9(long long x){
if(x==0)return false;//这里没什么用,算法的严谨性
set<long long> s;
//分离x的各位数字,每次把x的末尾数字分离出去
while(x){
int d=x%10;//d是x末位数字
s.insert(d);//把d插入s中
x/=10;//更新x
}
return s.size()==10;
}
int main(int argc, char *argv[]) {
//c语言中基本整型int占据两个字节,取值范围-2^15--2^15-1(-32768--32767)肯定不够
//用 long long
for(long long i=100000;i--;i>30000){//找最大,倒着来
long long x=i*i;
if(contains0_9(x)){
cout << x << endl;
return 0;
}
}
return 0;
}
结果为:9814072356。