这里写目录标题
算法类
1.合并数组
先写合并两个有序数组,接着写合并n个有序的数组,假设总共m个数
解法:N个数组进行两两合并,合并后的数组再继续执行合并过程,最后合成NM的有序数组。可以认为合并这个递归过程发生了logN次,每一次合并的过程都是NM个数合并,所以每一次合并的时间复杂度为NM,总的时间复杂度就是NM*logN
vector<int> num[maxn];//用vector<int> num[n]存储n个长度为m的数组,maxn>=n其实即可
vector<int> res[maxn];//整个原始数组两两合并合并的结果数组
vector<int> helper;//两个数组合并的时候辅助
//合并n个长度为m的数组,
vector<int> merge_all(vector<int> num)
int cnt=num.size();//目前有几个数组待合并
if(cnt==1) return num[0];//如果只剩一个数组直接返回
res.clear();//清空之前的合并结果,准备再次合并
for(int i=0;i<cnt;i+=2)
{
res[i/2]=mergetwoarray(ori[i],ori[i+1]);
}
return merge(res);//继续合并,直到合并成一个
}
//两个数组合并(归并排序的合并部分)
vector<int> mergetwoarray(vector<int> num1,vector<int> num2)
{
int len1=num1.size();
int len2=num2.size();
int p=0,q=0;
while(p<len1||q<len2)
{
if(q>=len2||p<len1&&num1[p]<=num2[q])helper.push_back(num1[p++]);
else helper.push_back(num2[q++]);
}
return helper;
}
2.把字符串转化成整数
题目描述
请你写一个函数StrToInt,实现把字符串转换成整数这个功能。
当然,不能使用atoi或者其他类似的库函数。
越是简单的题目越是需要考虑 特殊情况的处理 比如,非数字字符,整数溢出,正负号等
INT_MAX=2^31-1;
INT_MIN=-2^31;
int str_to_int(string str)
{
int flag,i=0;//flag记录正负,i记录当前字符下标
long long int res=0;//res记录当前数值,因为有可能整数溢出,所以用long long
int n=str.size();
//先处理前面的空格
while(str[i]==' '&&i<n)) i++;
if(i>=n) return 0;//没有数字
//只有第一个非空字符是正负号或者数字才有可能完成有效的转换
if(str[i]='-'){flag=-1;i++;}
else if(str[i]<'0'||str[i]>'9') return 0;
else {flag=1;}
while(str[i]>='0'&&str[i]<='9')
{
res=res*10+str[i]-'0';//每读入一位,就向前移动原来的数字(=*10)
i++;
if(res>INT_MAX) return INT_MAX;
if(res<INT_MIN) return INT_MIN;
}
return res;
}
3.查找数组中的重复元素
方法一:哈希表
map<int,int> hashmap;
for(int i=0;i<nums.size();i++)
{
if(!hashmap.count(nums[i])) hashmap.insert(pair<int,int>(nums[i],nums[i]));
else return nums[i];
}
return -1;
T ( n ) = O ( n ) T(n)=O(n) T(n)=O(n),遍历数组,每次查找这个元素是否在哈希表中需要 O ( 1 ) O(1) O(1)
S ( n ) = O ( n ) S(n)=O(n) S(n)=O(n),需要存储一个哈希表
方法二:原地交换
for(int i=0;i<nums.size();i++)
{
if(i==nums[i]) continue;
else if(nums[i]==nums[nums[i]]) return nums[i];
else swap(nums[i],nums[nums[i]]);
}
return -1;
}
T
(
n
)
=
O
(
n
)
T(n)=O(n)
T(n)=O(n),遍历数组,每次交换操作需要
O
(
1
)
O(1)
O(1)
S
(
n
)
=
O
(
1
)
S(n)=O(1)
S(n)=O(1),在原数组上交换,不需要额外的空间
方法三:不改变原数组
数字都在1~n之间,如果没有重复的数字,那么数字总数应该是在n以内的,如果有重复的数字,总数就会大于n(当1到n全都出现的时候),那么可以用二分查找的思路,用2/n分成两部分,[1,2/n)和[2/n,n),统计[1,2/n)在原数组中出现的次数,如果出现的次数大于n/2,那么重复的数字就在[1,2/n)中,再继续二分直到找到某一个重复的数字
eg:[1,2,6,5,5,3,4,7,8]
先分别统计1,2,3,4,在数组中出现的次数
1:1次
2:1次
3:1次
4:1次
正好出现了4次,那么重复的数字就在[5,8]中,再把[5,8]分成[5,6]和[7,8]两部分,分别统计
T
(
n
)
=
O
(
n
l
o
g
n
)
T(n)=O(nlogn)
T(n)=O(nlogn),统计这个元素在指定区间内出现了几次的函数总共被调用
O
(
l
o
g
n
)
O(logn)
O(logn)次,
S
(
n
)
=
O
(
1
)
S(n)=O(1)
S(n)=O(1),在原数组上交换,不需要额外的空间
4.字符串替换
方法一:插入删除
string replaceSpace(string s) {
int p=0;
while(s.find(" ",p)!=string::npos)
{
p=s.find(" ",p);
s.insert(p,"%20");
s.erase(p+3,1);
}
return s;
}
while遍历字符串O(n),字符串查找函数find的时间复杂度O(n)因此,这个方法时间复杂度度为:$O(n^2)$
方法二:指针帮助复制
用两个指针p,q
首先可以遍历字符串,统计出空格个数k,这样就知道替换后的串长度,s.size()+2k,
(1)如果不要求空间复杂度,可以建立一个空串snew来存放结果字符串,
p指向原数组当前要复制到新数组的字符,q指向新数组当前可写入的下标,从前向后(或者从后向前遍历数组),
不是空格的时候,snew[q]=s[p];p++;q++
当遇到空格的时候,p++;snew[q]=‘2’;snew[++q]=‘0’;snew[++q]=’%’;q++;
直到遍历完整个数组
(2)如果只能在原字符串上操作,必须从后往前遍历字符串,因为这样不会让替换后的结果覆盖掉原来的串。
让p指向s的末尾,q指向替换后的字符串的末尾
p=s.size(); q=s.size()+2k;
从后往前遍历,
不是空格的时候,s[q]=s[p];p–;q–;
是空格的时候,是p–;s[q]=’%’;s[–q]=‘0’;s[–q]=‘2’;q–;
直到遍历整个字符串
这里用到s.resize()重置字符串长度
string replaceSpace(string s) {
int i=0,blankcon=0;
while(i<s.size())
{
if(s[i]==' ') blankcon++;
i++;
}
int p=s.size()-1;
int q;
s.resize(s.size()+2*blankcon);
q=s.size()-1;
while(p>=0)
{
if(s[p]!=' '){s[q]=s[p];q--;p--;}
else{
p--;
s[q]='0';
s[--q]='2';
s[--q]='%';
q--;
}
}
return s;
}
存在需要对数组整体进行多次移动的题目,考虑用一个辅助数组、两个指针,分别指向原数组和辅助数组进行操作
T
(
n
)
=
O
(
n
)
T(n)=O(n)
T(n)=O(n),
S
(
n
)
=
O
(
1
)
S(n)=O(1)
S(n)=O(1)
数据结构类
1.优先队列什么实现的?
优先队列什么实现的?堆。自己写一个堆实现优先队列
小顶堆(最小的数由最高的优先级)
定义:priority_queue<int,vector<int>,greater<int> > pq;
大顶堆(最大的数先出队)
定义:priority_queue<int,vector<int>,less<int> > pq;
能够使用二叉树高效的解决上述问题,树的节点从上到下,从左到右的顺序紧凑排列的。最重要的是,儿子节点的值一定>=父亲节点的值
插入操作:自底向上提升,直到没有大小颠倒
出堆操作:自顶向下(堆顶元素出去,把最后一个元节点提到堆顶,删除最后一个节点,然后不断向下交换直到没有大小颠倒,交换的时候选择子节点中较小的一个交换)
时间复杂度:和树的高度有关,如果有n个节点,那么就是 O ( l o g n ) O(logn) O(logn)
堆的实现:
用数组存储堆的元素,而不用指针表示左右子树
父节点的编号是i
左孩子节点的编号是:2i+1
右孩子节点的编号是:2i+2
求节点i的父节点编号用 (i-1)/2,因为C++除法的关系,不论i是左孩子还是右孩子,都会得到相同的父节点
int heap[MAX_N],sz=0;//初始堆的大小为0
//插入操作
void push(int x)
{
int i=sz++;//i表示这个值x要插入的位置
while(i>0)
{
int p=(i-1)/2;//父节点的编号
if(heap[p]<=x) break;//没有大小颠倒,直接退出循环
heap[i]=heap[p];//把父节点的值往下移,放到i的位置
i=p;//这个值要插入的位置从最后移动到父节点的位置
}
heap[i]=x;
}
//出堆操作
int pop()
{
int ret=heap[0];//堆顶元素出堆
int x=heap[--sz];//最后一个元素提到堆顶,因为sz是堆的大小,所以先减一
int i=0;//i是原本的最后一个节点所在的位置(目前已经被提到堆顶)
while(i*2+1<sz)//因为sz-1是原来最后一个节点的编号,现在背提到堆顶了,那么现在最后一个节点的编号应该是<=堆栈的size-2
//同时因为让左孩子是两个孩子里比较小的,所以比较2i+1就可以了
{
int a=i*2+1,b=2*i+2;//当前节点的左右孩子
if(b<sz&&heap[b]<=heap[a]) a=b;//让a是左右孩子中小的那个节点的编号
if(heap[a]>=x) break;
heap[i]=heap[a];//把小的孩子节点提上去
i=a;//现在要比较x和小的孩子节点的左右孩子的大小
}
heap[i]=x;
}