一、递归
1.1递归
一个函数直接或间接调用自己为递归函数;
递归算法:把问题转化为规模缩小了的同类问题的子问题,然后调用递归函数表示问题的解,其思想是将一个大型而复杂的问题层层简化,转化为一个与 原问题相似的规模较小且简单的子问题,通过多次调用子问题得到最终复杂问题的解。
在递归调用的过程中,系统为每一层的返回点、局部量等开辟了栈来存储,为了避免栈溢出,递归需要有边界条件,必须有明确的递归出口。
1.2分治
把一个复杂的问题分解成若干简单子问题去求解,对于每个子问题再继续分成更小的子问题去求解,对于每个子问题再继续分成更小的子问题,一直分下去,直到最后的子问题可以直接求解时结束。然后将这些子问题合并,得到原问题的解。
需要注意的时,分解的子问题需要满足相互之间相互独立且互不影响,且子问题与原问题形式相同规模不同担忧受原问题的影响。
1.3例题
递归例题
1.母牛的故事
有一头母牛,它每年年初生一头小母牛。每头小母牛从第四个年头开始,每年年初也生一头小母牛。请编程实现在第n年的时候,共有多少头母牛?
解法:直接递归
直接递归存在多次调用,会出现TLE,所以需要用数组进行记忆化
#include<bits/stdc++.h>
using namespace std;
int Cow[999];
int CowNum(int n){
if(Cow[n]){
return Cow[n];
}
if(n < 4){
return Cow[n] = n;
}
else{
return Cow[n] = CowNum(n - 1) + CowNum(n - 3);
}
}
int main(){
int n;
while(cin >> n){
if(n != 0){
cout << CowNum(n) << endl;
}
}
}
数组版
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
while(cin >> n){
int num[n + 1];
if(n == 0){
num[0] =0;
continue;
}
for(int i = 1; i <= n; i++){
if(i < 4){
num[i] = i;
}
else{
num[i] = num[i - 3] + num[i - 1];
}
}
if(num[n] != 0){
cout << num[n] << endl;
}
}
}
2.数的计算
题目描述
我们要求找出具有下列性质数的个数(包含输入的正整数 n)。先输入一个正整数 n(n≤1000),然后对此正整数按照如下方法进行处理:
1. 不作任何处理;
2. 在它的左边加上一个正整数,但该正整数不能超过原数的一半;
3. 加上数后,继续按此规则进行处理,直到不能再加正整数为止。
输入格式
1 个正整数 n(n≤1000)
输出格式
1 个整数,表示具有该性质数的个数。
二、二分
2.1二分搜索
二分搜索又称为折半搜索。使用二分时,要确保数列是具有有序性的,通过比较中间值,不断将搜索范围缩小为原来的一半,大大缩短了查找的时间,时间复杂度为O(logn);
⭐函数lower_bound()在begin和end中的左闭右开区间进行二分查找,返回大于或等于val的第一个元素位置(迭代器)。如果所有元素都小于val,则返回last的位置。(数组必须时排好序的顺序)
注意STL中设计区间都是左闭右开,即[begin,end)。
upper_bound函数:函数upper_bound()在begin和end中的左闭右开区间进行二分查找,返回的是被查序列中第一个大于查找值的位置(迭代器)。
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int a[10];
for(int i = 0; i < 10; i++){
a[i] = i;
}
?
pos = lower_bound(a,a + 10, 6) - a; //第一个大于等于6的
pos2 = upper_bound(a,a + 10, 6) - a; //第一个大于6的 ,下标从0开始
cout << pos << endl;
cout << pos2;
}
vector和set自带lower_bound(first,last,a);
#include<bits/stdc++.h>
#include<vector>
#include<set>
using namespace std;
int main(){
vector<int> v;
set<int> st;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
st.insert(1);
st.insert(2);
st.insert(3);
st.insert(4);
vector<int>::iterator t = lower_bound(v.begin(),v.end(),3);
set<int>::iterator s = lower_bound(st.begin(),st.end(),3);
cout << *s <<" " << *s;
}
set 自带的 lower_bound 和 upper_bound 的时间复杂度为 O(logn)。
但使用 algorithm 库中的 lower_bound 和 upper_bound 函数对 set 中的元素进行查询,时间复杂度为 0(n)。
总结:对于可随机访问的有序容器使用 algorithm 库中的 lower_bound 和 upper_bound 函数时间复杂度为O(logn),
但对于set,multiset这种不能随机访问的有序容器,要用其自带的 lower_bound 和 upper_bound 的时间复杂度才为 O(logn)。
2.2从有序数组中查找值
常见问题:判断某个值是否出现在数组中,如果出现求出坐标;X值打一次在数组中的位置等;
题目一:
一个长度为N的有序且不重复的数组,请判断X是否出现在数组中。
输入待查序列有序
❤️1.查找等于某个值的最小下标
#include<bits/stdc++.h>
#include<vector>
#include<set>
using namespace std;
const int N = 1e6 + 10;
int arr[N];
int binsearch(int arr[],int n,int key){
int ans = -1;
int l = 0,r = n - 1;
while(l <= r){
int mid = (l + r)/2;
if(arr[mid] == key){
ans = mid;
r = mid - 1; //此时的中间值等于key,有可能前面更小的下标值也为这个
}
else{
if(arr[mid] > key){
r = mid - 1;
}else{
l = mid + 1;
}
}
}return ans;
}
int main(){
int n,k;
cin >> n >> k;
for(int i = 0; i < n; i++){
arr[i] = i;
}
cout << binsearch(arr,n,k);
}
❤️2.如果要查找第一个大于或等于某个值的下标
#include<bits/stdc++.h>
#include<vector>
#include<set>
using namespace std;
const int N = 1e6 + 10;
int arr[N];
int binsearch(int arr[],int n,int key){
int ans = -1;
int l = 0,r = n - 1;
while(l <= r){
int mid = (l + r)/2;
🧡if(arr[mid] >= key){
ans = mid;
r = mid - 1; }
else{
if(arr[mid] > key){
r = mid - 1;
}else{
l = mid + 1;
}
}
}return ans;
}
int main(){
int n,k;
cin >> n >> k;
for(int i = 0; i < n; i++){
arr[i] = i;
}
cout << binsearch(arr,n,k);
}
❤️3.如果要查找第一个大于某个值的下标
#include<bits/stdc++.h>
#include<vector>
#include<set>
using namespace std;
const int N = 1e6 + 10;
int arr[N];
int binsearch(int arr[],int n,int key){
int ans = -1;
int l = 0,r = n - 1;
while(l <= r){
int mid = (l + r)/2;
if(arr[mid] > key){
ans = mid;
r = mid - 1; }
else{
if(arr[mid] > key){
r = mid - 1;
}else{
l = mid + 1;
}
}
}return ans;
}
int main(){
int n,k;
cin >> n >> k;
for(int i = 0; i < n; i++){
arr[i] = i;
}
cout << binsearch(arr,n,k);
}
❤️4.求解在数组arr中找出值为key的元素个数
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int a[10];
for(int i = 0; i < 10; i++){
cin >> a[i];
}
int num = upper_bound(a, a + 10, 2) - a - (lower_bound(a,a + 10, 2) - a);
cout << num;
}
如果要求arr中key的个数,只需要调用upperBound - lowerBound,求它们的差就可以了!是不是超简单!
三、前缀和
3.1前缀和
前缀和就是从位置1到位置i这个区间内的所有的数字之和。
for (int i = 1; i <= n; i ++ )
{ cin >> a[i]; s[i] = s[i - 1] + a[i]; }
缀和的优势:以(o1)的时间复杂度得到某块区间的总和
3.2前缀和例题
输入
一个长度为n的整数序列。 接下来再输入m个询问,每个询问输入一对l, r。 对于每个询问,输出原序列中从第l个数到第r个数的和
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列。
接下来m行,每行包含两个整数l和r,表示一个询问的区间范围。
输出格式
共m行,每行输出一个询问的结果。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e6 + 10;
int n,m;
int a[N],s[N];
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
while(m--){
int l,r;
cin >> l >> r;
cout << s[r] - s[l - 1] << endl;
}
}