文章目录
一、冒泡排序
- 冒泡排序的模板
void maopao(int b[],int len){
for(int i=0;i<len-1;i++){
for(int j=0;j<len-i-1;j++){
if(b[j]>b[j+1]){
int t=b[j];
b[j]=b[j+1];
b[j+1]=t;
}
}
}
}
1、给定交换次数求最短字符串
- 按字典序的通用模板
#include <iostream>
using namespace std;
int main()
{
// 请在此输入您的代码
string str;
int k=100; //交换次数
int n=0; //字符串长度
for(;k>n*(n-1)/2;n++); //求n的大小 冒泡排序的交换次数公式n*(n-1)/2
str+='a'+k-(n-1)*(n-2)/2; //求第一个字符
for(int i='a'+n-1;i>='a';i--){
if(i!=str[0]){ //完全逆序————当等于第一个字符时跳过
str+=i;
}
}
cout<<str;
return 0;
}
二、公约数
1、给定值n求abc之积=n的个数
- 排列——即考虑顺序
- 思路:现求n的所有约数,用数组存储,之后遍历数组即可
#include <iostream>
using namespace std;
int main()
{
// 请在此输入您的代码
long long n=2021041820210418;
int res=0;
int cnt=0;
long long arr[1000000];
for(long long i=1;i*i<=n;i++){ //求n的所有约数,并用数组arr保存,注意此处的循环结束条件——i*i
if(n%i==0){
arr[cnt++]=i;
if(n/i!=i){ //一次存放除数和被除数
arr[cnt++]=n/i;
}
}
}
for(long long i=0;i<cnt;i++){ //遍历存放约数的数组
for(long long j=0;j<cnt;j++){
for(long long k=0;k<cnt;k++){
if(arr[i]*arr[j]*arr[k]==n)res++; //将约数组合,若之积为n则res+1
}
}
}
cout<<res;
return 0;
}
注意:在求n的约数时,循环结束的条件是 i * i
2、求最大公约数
递归版辗转相除法
int gcd(int a,int b)
{
if(a<b) //保证a大于b
{
swap(a,b);
}
if(a%b == 0)
{
return b;
}
return gcd(b,a%b); //除数和余数作为参数继续运算
}
三、素数
1、判断是否为素数
//判断是否是素数
int f(int x){
for(int i=2;i<x;i++){
if(x%i==0)
return 0;
}
return 1;
}
2、给定长度求等差素数的公差
#include <iostream>
using namespace std;
bool check(int n){ //判断素数
for(int i=2;i<n;i++){
if(n%i==0)return false;
}
return true;
}
int main()
{
// 请在此输入您的代码
int cnt=1; //等差数列的长度
int nn=10; //规定的等差数列长度
for(int i=2;i<10000;i++){ //先确定等差数列的第一个值
if(check(i)){ //若为素数,便将其假设为结果的第一个素数
for(int d=1;d<1000;d++){ //尝试公差,由大到小依次尝试
while(check(i+cnt*d)){ //以当前公差,尝试求连续的10个素数
cnt++;
if(cnt==nn){
cout<<d;
return 0;
}
}
cnt=1; //若不满足便有效等差素数回到原值
}
}
}
return 0;
}
四、前缀异或和
1、求子数组两两减去2的k次方后变为0
- 由于每次操作要减去两个数的 2的k次方,那么所有数中,2的k次方出现的次数之和必须是偶数。换一种说法,就是所有数的异或和必须是 0
例题:给你一个下标从 0 开始的整数数组nums 。每次操作中,你可以:
选择两个满足 0 <= i, j < nums.length 的不同下标 i 和 j 。
选择一个非负整数 k ,满足 nums[i] 和 nums[j] 在二进制下的第 k 位(下标编号从 0 开始)是 1 。
将 nums[i] 和 nums[j] 都减去 2k 。
如果一个子数组内执行上述操作若干次后,该子数组可以变成一个全为 0 的数组,那么我们称它是一个 美丽 的子数组。
请你返回数组 nums 中 美丽子数组 的数目。
子数组是一个数组中一段连续 非空 的元素序列。
class Solution {
public:
long long beautifulSubarrays(vector<int>& nums) {
int n = nums.size();
unordered_map<int, int> cnt;
cnt[0] = 1;
int now = 0;
long long ans = 0;
for (int i = 0; i < n; i++) {
now ^= nums[i]; //求前缀异或
ans += cnt[now]; //有几个相等的前缀异或值,便有几个满足要求的子数组
cnt[now]++; //记录前缀异或相等的值的个数
}
return ans;
}
};
四、前缀和和差分数组
1、前缀和
1.1、一维前缀和
使用前缀和的情况:
求连续的和
解决办法:使用一维数组s[i]记录前i项和,若是求[L,R]区间的值,则s[R]-s[L-1];
前i项和的代码
for(int i=1;i<n){
cin>>s[i];
s[i]+=s[i-1];
}
例题:给定连续的数值,允许跳过k段距离的数,求剩余数值和的最小的情况
解题步骤:
- 先求前缀和
- 求区间为k的最大值
for(int i=k;i<n;i++){
long long res=0;
res=sum[i]-sum[i-1];
maxx=max(res,maxx);
}
- 最终结果s[n-1]-maxx;
1.2、二维前缀和
- s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+s[i][j];
- 求某个子矩阵所有数值的和
- 所求=s[x2][y2]-s[x1][y2]-s[x2][y1]+s[x1][y1]
- 子矩阵的边长=x2-x1,y2-y1;
- 例题:此题规定了边长长度为c
- 例题二:不固定边长求子矩阵累加和最大的那个
2、差分算法
- 求差分数组
void insert(int l, int r, int c)
{
b[l] += c;
b[r+1] -= c;
}
- 首先计算起始成分数组,for循环遍历每一项
for(int i = 1; i <= n; i ++)
{
insert(i,i,a[i]);
}
- 若是需对原数组某个区段[L,R]加减值时,只需让差分数组第L位加上该值,第R+1位减去该值,再求前缀和便可得到改变后的数组
while(m--) //m代表操作m次
{
int l,r,c; //l和r代表区间的左右边界——c代表操作的值
scanf("%d%d%d",&l,&r,&c);
insert(l,r,c);
}
for(int i = 1; i <= n; i++)
{
b[i] += b[i-1]; //求差分数组每项的前缀和,便得到对应下标数组的值
printf("%d ",b[i]);
}
例题:
- 关键:比较列出关系式——
min(A\*n,B*n+c)
- n为对应路段经过的次数——将先后输出的城市序号当作区间的左右边界,其中经过的路段在原基础上+1——刚好满足差分思想
#include<bits/stdc++.h>
using namespace std;
int a[1005]; //存储原数组——可有可无
int b[1005]; //记录差分数组——此处使用一个数组,先表示差分数组,进行前缀和后表示经过变化后的数组
void insert(int l,int r,int c){
b[l]+=c; //差分数组规律:修改左边界的值和右边界后一个值
b[r+1]-=c;
}
int main(){
int n,m;
cin>>n>>m;
int x,y; //起始点和目的点
if(m>0)cin>>x; //由于修改差分数组需给出左右边界和修改的值,所以提前输出一个值
for(int i=2;i<=m;i++){
cin>>y;
if(x>y){ //由于题目给定的值有顺序,存在左大右小的情况
insert(y,x-1,1); //将每个经过的区段整体+1,注意右边界,此处记录的是路段经过次数右边界要减1,若是点则无需-1
}else{
insert(x,y-1,1);
}
x=y; //修改起始点
}
int A,B,C; //未买卡的价格A,买卡后的价格,卡的价格
for(int i=1;i<n;i++){ //注意城市有10个但是路段只有9个
b[i]=b[i]+b[i-1]; //求差分数组前缀和得对应下标经过变化后的数组值——此处的意义是:每个路段各自经过了几次
}
long long sum=0;
for(int i=1;i<n;i++){
cin>>A>>B>>C;
sum+=min(A*b[i],B*b[i]+C); //关键:两种情况1、选择不买卡价格为a,2、选择买卡价格为b同时加上卡费
}
cout<<sum<<endl;
return 0;
}
五、堆思想
1、priority_queue实现堆
- priority_queue默认是大堆
- less代表大堆
- greater代表小堆
priority_queue<int> q;//默认是从大到小
priority_queue<int, vector<int> ,less<int> >q;//从大到小排序
priority_queue<int, vector<int>, greater<int> >q;//从小到大排序
1.1、自定义排序规则
- 创建排序规则对象
使用结构体创建排序规则
注意:return处的小于号代表从大到小排序,大于号代表从小到大排序
struct cmp{ //priority_queue的排序规则
bool operator()(pair<char,int> x, pair<char,int> y)
{
return x.second < y.second;
}
};
- 创建priority_queue对象
模板: priority_queue<存入pq的数据类型,vector<数据类型>,排序规则名>pq;
priority_queue<pair<char,int>,vector<pair<char,int>>,cmp>pq;
2、给字符串求经打乱后是否存在相邻元素不等
思想:
1、字符串中最多数量的字符<(字符串长度+1)/2
2、构建比较规则
3、创建priority_queue
4、考虑堆中存储什么类型计算简单——(如:pair,char,int),根据选择类型的不同所需的辅助集合也不一样
5、将赋值好的类型数据存入堆中
6、循环拿去堆顶的两个数,数量减一,将数量不为0的数据对象存入堆中
class Solution {
public:
/**
满足相邻元素不相同————则数目最多的元素<=(总长度+1)/2
大堆思想————使用priority_queue来实现——自定义排序
利用pair来将字符和数量绑定在一起——将最终值存入堆中
设置排序规则cmp
字符串并接的过程————使用循环————循环结束条件堆的大小<1
每次从堆顶取出两个元素,first拼接到字符串中,second减一
若减一后second大于0便又加入堆中
若堆的大小不等于0,便将最后一个元素加入堆————返回字符串
**/
struct cmp{ //priority_queue的排序规则
bool operator()(pair<char,int> x, pair<char,int> y)
{
return x.second < y.second;
}
};
string reorganizeString(string s) {
int len=s.size();
vector<int>t(26,0); //先用vector记录每个字符对应的数量
for(int i=0;i<len;i++){
int ch=s[i]-'a';
t[ch]++;
}
// auto cmp=[&](const pair<char,int>a,pair<char,int>b){
// retrun a.second<b.second;
// }
pair<char,int>p;
priority_queue<pair<char,int>,vector<pair<char,int>>,cmp>pq; //创建大堆的队列
for(int i=0;i<26;i++){
if(t[i]==0)continue; //排除数量为0的字符
p.first='a'+i; //将最终字符对应的数量赋值给pair
p.second=t[i];
pq.push(p); //将p添加到pq中自动排序
}
if(pq.top().second>(len+1)/2)return "";
string sb="";
while(pq.size()>1){
pair<char,int> t1=pq.top();pq.pop(); //拿去
pair<char,int> t2=pq.top();pq.pop(); //拿去
sb+=t1.first;
sb+=t2.first;
t1.second--;
t2.second--;
if(t1.second>0)pq.push(t1); //存入
if(t2.second>0)pq.push(t2); //存入
}
if(pq.size()>0){ //将剩余的字符拼接到字符串
sb+=pq.top().first;
}
//cout<<pq.top().second<<" "<<pq.top().first;
return sb;
}
};
六、查询方法
1、二分查找
- 模板
//返回下标
int search(int nums[], int size, int target) //nums是数组,size是数组的大小,target是需要查找的值
{
int left = 0;
int right = size - 1; // 定义了target在左闭右闭的区间内,[left, right]
while (left <= right) { //当left == right时,区间[left, right]仍然有效
int middle = left + ((right - left) / 2);//等同于 (left + right) / 2,防止溢出
if (nums[middle] > target) {
right = middle - 1; //target在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; //target在右区间,所以[middle + 1, right]
} else { //既不在左边,也不在右边,那就是找到答案了
return middle;
}
}
//没有找到目标值
return -1;
}
七、排序算法
1、快速排序
模板
从小到大
void quickSort(int arr[],int l,int r)//left和right的首字母
{
if(l>=r) return;
int base,temp;int i=l,j=r;
base = arr[l]; //取最左边的数为基准数
while(i<j)
{
while(arr[j]>=base&&i<j)j--;//顺序很重要
while(arr[i]<=base&&i<j) i++;
if(i < j)
{temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
}//基准数归位
arr[l]=arr[i];arr[i]=base;
quickSort(arr,l,i-1);//递归左边
quickSort(arr,i+1,r);//递归右边
}
2、堆排序
模板——调整堆算法+堆排序算法
调整对算法
void HeapAdjust(int *array,int i,int length){ //调整堆
int leftChild=2*i+1; //定义左右孩子
int rightChild=2*i+2;
int max=i; //初始化,假设左右孩子的双亲节点就是最大值
if(leftChild<length&&array[leftChild]>array[max]){
max=leftChild;
}
if(rightChild<length&&array[rightChild]>array[max]){
max=rightChild;
}
if(max!=i){ //若最大值不是双亲节点,则交换值
swap(array[max],array[i]);
HeapAdjust(array,max,length); //递归,使其子树也为堆
}
}
堆排序算法
void HeapSort(int *array,int length){ //堆排序
for(int i=length/2-1;i>=0;i--){ //从最后一个非叶子节点开始向上遍历,建立堆
HeapAdjust(array,i,length);
}
for(int j=length-1;j>0;j--){ //调整堆 ,此处不需要j=0
swap(array[0],array[j]);
HeapAdjust(array,0,j); //因为每交换一次之后,就把最大值拿出(不再参与调整堆),第三个参数应该写j而不是length
Print(array,length);
}
}
3、归并排序
模板
void MergeSort(int q[], int l, int r)
{
if (l >= r)
{
return;
}
int mid = l + r >> 1;
MergeSort(q, l, mid);
MergeSort(q, mid + 1, r);
int i = l;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= r)
{
if (q[i] <= q[j])
{
tmp[k++] = q[i++];
}
else
{
tmp[k++] = q[j++];
}
}
while (i <= mid)
{
tmp[k++] = q[i++];
}
while (j <= r)
{
tmp[k++] = q[j++];
}
for (int i = l, j = 0; i <= r; i++)
{
q[i] = tmp[j++];
}
}
4、基数排序
模板
void radix_sort(int k){
int mod = 0;
int t, c;
for(int r = 0; r < k; ++ r){
memset(cnt, 0, sizeof cnt);
for(int i = 0; i < n; ++ i){
t = (a[i] >> (mod * 4)) & 15;
buck[t][cnt[t] ++ ] = a[i];
}
mod ++;
for(int i = 0, c = 0; i < l; ++ i)
for(int j = 0; j < cnt[i]; ++ j)
a[c ++] = buck[i][j];
}
}
————————————————
版权声明:本文为CSDN博主「爱学习的图灵机」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/AKAPinkman/article/details/128680733
5、希尔排序
模板
void hill_sort(int *arr) {
/*希尔排序函数(递减)*/
int length = get_length(arr), step = length / 2, t1, t2,
t3, i = 1; // 获取长度和偏移值
while (step >= 1) { // 只要在合理范围内,就可以一直遍历下去
for (int j = step; j < length; j++) {
t1 = j; // 临时存储j的值
while (t1 - step >= 0) {
if (arr[t1] > arr[t1 - step]) {
// 交换位置
t2 = arr[t1];
t3 = arr[t1 - step];
arr[t1] = t3;
arr[t1 - step] = t2;
t1 -= step; // 更新下标值
} else {
break;
}
}
}
step /= 2; // 继续细分
/*
// 输出调试结果
printf("第 %d 次排序后的结果; ", i);
for (int n = 0; n < length; n++) {
cout << arr[n] << " ";
}
cout << endl;
*/
i++; // 计数
}
}
————————————————
版权声明:本文为CSDN博主「yangjincheng_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yangjincheng_/article/details/126176441
八、sort排序
sort(t,t+len,cmp);
思路:先创建比较方法cmp,然后使用sort,再打印
代码实现:
实现思路:
先构造一个结构体将姓名与分值绑定起来
分析要比较的元素——分数和名字
结构体:
struct Student{
char name[105]; //名字
int score[4]; //四门课的分数
}; //注意此处的分号
Student stu[105];
比较函数——先比分再比名字
bool cmp1(Student a,Student b){
if(a.score[0]!=b.score[0]){
return a.score[0]>b.score[0] //从大到小
}
return strcmp(a.name,b.name)<0; //字符串比较,从小到大
}
最后使用sort方法得到的结果为按分数从大到小排序,同分数字典序小的名字排前面
九、回溯法
1、组合问题
1.1、求元素各不相同数组的子集
模板
此处未包含空子集
若是在idx==nums.size()判断语句中添加ans.push_back(t);则包括空集
//ans存储结果
//t临时存储子集元素
//idx为下标
void dfs(vector<int> nums,int idx,vector<int>t){
if(idx==nums.size())return;
t.push_back(nums[idx]);
ans.push_back(t);
dfs(nums,idx+1,t);
t.pop_back();
dfs(nums,idx+1,t);
}
规定长度的子集——子集长度要求未k
void dfs(vector<vector<int>>res,vector<int>t,int idx,int k){
if(t.size()+(nums.size()-idx)<k){
return;
}
if(t.size()==k){
res.push_back(t);
return;
}
t.push_back(nums[idx]);
dfs(res,t,idx+1,k);
t.pop_back();
dfs(res,t,idx+1,k);
}
1.2、求存在相同元素的子集
模板:
- ff 代表是否访问过 ans存储最终结果
- 终结条件l==数组长度
- 分为两种情况:将当前值加入存储子集的res集合中,或不加入(后一个值和其相同便跳过)
注意使用此方法之前nums要经过排序变成有序
void f(bool ff,vector<int>&nums,int l){
if(l==nums.size()){
ans.push_back(res);
return;
}
f(false,nums,l+1); //不将当前值加入子集
if(!ff&&l>0&&nums[l-1]==nums[l])return; //由于前一个未加入,则若当前值等于之前的值便直接跳过,起到去重的作用
res.push_back(nums[l]); //选择添加,false变true
f(true,nums,l+1); //继续递归
res.pop_back(); //回溯
}
1(添)、求字符串的子串
该问题区别于求组合问题,首先该问题,他的元素顺序和位置不能变,求该问题的子串只能切割(切割的长度0~字符串长度)。
1.0、求子串的简单方法
求所有子串是包含重复子串的
void zichuan(){
for(int i=0;i<len;i++){
for(int j=i;j<len;j++){
for(int k=j;k<i+j+1;k++){
cout<<s[k];
} //一个完整的子串遍历完,换行,进行下一个
cout<<endl;
}
}
}
1.1、字符串中不含重复字符(求所有子串包括相同的子串)
- 求区间l~len之间的子串
void dfs(int l,int len,vector<char> t){
for(int i=l;i<len;i++)//根据有相同长度字符的子字符串的数量来用一次循环。
{
for(int j=l;j<len-i;j++)//根据每一个相同长度的子字符串的首字符不同用第二个循环
{
for(int k=j;k<i+j+1;k++)//根据每个子字符串的长度来用第三次循环。
t.push_back(s[k]); //t临时存储每个子串的字符
res.push_back(t); //将完整的子串存储到res中
t.clear(); //清空t
}
}
}
1.2、字符串中存在重复字符(求不相同的子串)
上面代码改进:在添加完整子串的位置前进行判断该子串是否已经存在
关键方法: find(res.begin(),res.end(),t)
if(find(res.begin(),res.end(),t)!=res.end()){
t.clear();
continue;
}
1、排列问题
1.1、全排列(杂序)
int fullpermutation(int k) {
if(k==n) {
for(int i=0;i<k;i++)
printf("%c",str[i]);
printf("\n");
}
for(int i=k;i<n;i++) {//很多个分支,用循环!
swap(str[k],str[i]);//每个当前分支,当前拥有有的字母都要打一次头
fullpermutation(k+1);//下个老板
swap(str[i],str[k]);//回溯,因为上一层老板要保证分发下去什么样,收回来就得是什么样。
/*注意和上一题硬币分配的区别,硬币那题上一层老板要得到下一层老板的方法数往上汇总,层层累加
这题最底层老板产生的就是其中一个答案,直接保存或输出,而上一层老板要保证分发下去什么样,
收回来就得是什么样。以便于每个统计老板的分配都是公平的,不会出错的,也就是排序不重复。
*/
}
}
1.2、全排列(字典序输出)
int fullpermutation(int k) {
if(k==n) {
for(int i=0;i<k;i++)
printf("%c",ans[i]);
printf("\n");
}
for(int i=0;i<n;i++) {
if(!vis[i]) {
vis[i] = 1;//标记数组
ans[p++] = str[i];//未被标记纳入拼凑行列
fullpermutation(k+1);
p--;//恢复原状态
vis[i] = 0;
}
}
}
2、迷宫问题
参考
思想:
- 定义四个变量分别记录入口坐标和目的坐标
- 定义三个二维数组——迷宫、入口位置到当前位置的距离len(存在两个用处——记录距离——记录是否经过)、走动方向(如上下左右)
- 一个结构体,记录了当前的坐标
i
和j
- 递归方法
- 定义一个队列,类型为结构体类型
- 遍历二维数组len对其赋初值(初值最好不属于结果中拥有的值)
- 向队列中存入起始坐标,len赋值为0
- while循环队列,为空时跳出循环
- for循环来遍历走向
- 加入走向后判断是否越界,是否走过,道路是否可通
- 未越界且未走过且可通
- 则经移动后的坐标位置入队列,len等于前一坐标的距离+1
- 判断是否到达目的坐标——成立便break
- 代码实现见参考
3、求最短路径和最少步骤问题
- a二维数组记录地图——0可通行
- b二维数组记录是否经过——0未经过,1经过
void dfs(int x,int y,int step)
{
int tx,ty;
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
if(x==endx&&y==endy)
{
if(step<v)//判断是不是最小的路径
{
v=step;
return;
}
}
for(int k=0;k<=3;k++)
{
tx=x+dx[k];
ty=y+dy[k];
if(tx<1||tx>n||ty<1||ty>m) continue;
if(a[tx][ty]==0&&b[tx][ty]==0)
{
b[tx][ty]=1;
dfs(tx,ty,step+1);
b[tx][ty]=0;//别忘了dfs后要变成0,要不然没法回退(回溯)了
}
}
return;
}
4、N皇后问题
思路:
- 定义五个集合
- solutions 存储最终返回结果
- queens 存储皇后的位置(其值代表列,值对应的下标代表行)——vector< int>集合
- columns 用来记录当前列是否存在皇后
- diagonals1 用来记录以当前位置为基准其左对角是否存在皇后,使用set集合
- diagonals2 用来记录以当前位置为基准其右对角是否存在皇后,使用set集合
- 调用递归函数
- 终结条件——row==n行数等于n时表明n个皇后都放好了
- 否则循环遍历每一列(每次调用函数都要遍历所有列)
- 查找当前列是否存在皇后
columns.find(i) != columns.end()
存在便continue
- 查找当前左对角是否存在皇后
diagonals1.find(diagonal1) != diagonals1.end()
此处的diagonal1=row-i
(特别注意),存在便continue
- 查找当前右对角是否存在皇后
diagonals2.find(diagonal2) != diagonals2.end()
,此处的diagonal2 = row + i
(特别注意),存在便continue
- --------------放置皇后-------------
queens[row] = i;
在不满足以上条件时,在当前坐标放置皇后- 同时修改当前列、左对角集合和右对角集合数值
columns.insert(i);
diagonals1.insert(diagonal1);
diagonals2.insert(diagonal2);
- 调用递归函数
backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);
- ------------恢复原值--------------
- queens[row] = -1;
columns.erase(i);
diagonals1.erase(diagonal1);
diagonals2.erase(diagonal2);
- 查找当前列是否存在皇后
新函数的使用:string row = string(n, ‘.’);意为长度为n全为
.
组成的字符串
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
auto solutions = vector<vector<string>>();
auto queens = vector<int>(n, -1);
auto columns = unordered_set<int>();
auto diagonals1 = unordered_set<int>();
auto diagonals2 = unordered_set<int>();
backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);
return solutions;
}
void backtrack(vector<vector<string>> &solutions, vector<int> &queens, int n, int row, unordered_set<int> &columns, unordered_set<int> &diagonals1, unordered_set<int> &diagonals2) {
if (row == n) {
vector<string> board = generateBoard(queens, n);
solutions.push_back(board);
} else {
for (int i = 0; i < n; i++) { //遍历列
if (columns.find(i) != columns.end()) {
continue;
}
int diagonal1 = row - i;
if (diagonals1.find(diagonal1) != diagonals1.end()) {
continue;
}
int diagonal2 = row + i;
if (diagonals2.find(diagonal2) != diagonals2.end()) {
continue;
}
//----------------第二块放置皇后-----------------//
queens[row] = i;
columns.insert(i);
diagonals1.insert(diagonal1);
diagonals2.insert(diagonal2);
//---------------第三块递归---------------------//
backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);
//---------------第四块回溯将值复原--------------//
queens[row] = -1;
columns.erase(i);
diagonals1.erase(diagonal1);
diagonals2.erase(diagonal2);
}
}
}
vector<string> generateBoard(vector<int> &queens, int n) {
auto board = vector<string>();
for (int i = 0; i < n; i++) {
string row = string(n, '.');
row[queens[i]] = 'Q';
board.push_back(row);
}
return board;
}
};
十、滑动窗口
1、固定窗口大小
模板
cin>>k; //窗口大小
int r=0,l=0; //窗口的左右边界
int max=0; //记录最大值
vector<int>p; //存储每个窗口的最大值
priority_queue<int>q; //排序默认从大到小,优先队列(大堆)
while(r<n) { //规定右边界的范围
if(num[r]>max) { //最大值的转换
max=num[r];
}
q.push(num[r]); //添加元素
if(r-l+1>=k) { //超过窗口大小
p.push_back(max); //添加每个窗口的最大值
if(num[l]==max) { //最大值刚好为左边界的值
q.pop(); //出队列
max=q.top(); //最大值替换
}
l++; //左边界递加
}
r++; //右边界递加
}
2、非固定窗口大小
模板
- win存储窗口数据
- r右边界,l左边界
while (r < s.size()) {
win.add(s[r]);//增大窗口
r++;
//窗口数据更新
if(右边界值不符合条件了){
while (题目条件) {//通常借助此收缩窗口,如:窗口大小,值要大于多少
win.remove(s[l]); //移出元素
l++; //左边界右移
}
}
}
十一、并查集
并查集分为查和合并两个操作,查是查找每个节点的根节点,合并是将两个根节点不同的树连接到一起
1、使用并查集判断是否存在环
- 条件:已知个节点的边
- 只需根据模板书写查找和合并(即构造树)的方法,再遍历所有边(即给定的两个节点)作为x,y调用合并方法
查找方法
合并方法
十二、快速幂
- 关键代码
const long long m=1e9+7;
long long quickpow(long long a,long long b){
long long sum=1;
while(b){
if(b&1)//与运算,可判断奇偶,详细见注释
sum=sum*a%m;//取模运算
a=a*a%m;
b>>=1;//位运算,右移,相当于除以2
}
return sum;
}
十三、01背包问题
- w[i]为价值,c[i]为体重,v为最大重量
二维数组存放数据
for (int i = 1; i <= n; i++)
for (int j = 1; j <= v; j++)
if (j >= w[i])//背包容量够大
f[i][j] = max(f[i - 1][j - w[i]] + c[i], f[i - 1][j]);
else//背包容量不足
f[i][j] = f[i - 1][j];
优化
for (i = 1; i <= n; i++)
for (j = v; j >= c[i]; j--)//在这里,背包放入物品后,容量不断的减少,直到再也放不进了
f[i][j] = max(f[i - 1][j], f[i - 1][j - c[i]] + w[i]);
降为一维v[i]体重,w[i]价值
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)//注意此处不能从v[i]到m
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
例题:
01背包+魔法
1、多重背包
多重背包的二进制优化
2、完全背包
十四、线段树
1、线段树的定义
用一维数组来存储一个树
2、线段树的应用
3、线段树的实现
-
线段树的构建
-
线段树的插入
-
线段树的查找
-
线段树的打印
小技巧
1、求多个大数之积后末尾连续0的个数
- 可将其转换成求每个数能被2和5连续整除的次数
- while (x%2==0) cnt2++,
x/=2
; - while (x%5==0) cnt5++,
x/=5
; - 结果:
min(cnt2,cnt5)
;
注意:整除2和整除5的顺序可以颠倒,还要注意的是x并不是从原值开始,,而是经历了第一步的多次循环计算后的值。
2、一串数分成n份,求每份中位数的中位数(尽可能大)
- 解题思路: 画图法,使用二维数组来表示,可知最大中位数成立的条件是
- 中位数是排序后中间的值,因此当前这份其右边的值要大于他,后面几份的中位数以及每份中位数右边的值也要大于他,从而计算出需要大于最大中位数的个数,用总大小减去该个数便是最大中位数。
/*
* [][][][a][][][]
* [][][][b][][][]
* [][][][c][][][]
* [][][][max][][][]
* [][][][d][][][]
* [][][][e][][][]
* [][][][f][][][]
*
* 此题意思为将1至49分为7组数字,求取七组数字中每组数字的中位数所构成的数列的中位数的最大值
* 即如图所示,最大化[max]
* 49个数字中需要比[max]大的有[max]行的后三位,d、e、f行的后四位,共3+3*4=15位
* 结果为:49-15=34
* */
3、给定一个值n求最少需要多少砝码可称出0~n所有重量
两边多可以放砝码
- 规律:以3为公比的等比数列,求n位于哪两项之间,所需砝码数便是这两项较大的(即i+1)
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int i, k;
cin >> k;
for (i = 1; ; i++)
{
if (k == 1)
{
cout << i;
return 0;
}
int t = (pow(3, i) - 1) / 2;
int r = (pow(3, i + 1) - 1) / 2;
if (t < k && r >= k )
break;
}
cout << i + 1;
return 0;
}
4、给定一组数据每经过一次便减一(可跳跃)求跳到对面最少具备跳跃多远的距离
- 要求区段数值和要大于来回次数(从最左移到最右为一次);
集合的一些重要特性和易错点
1、map
map的键key会自动按字典序排号
- 遍历需用到迭代器iterator
例题:
- 将要按字典序的值作为键
#include<bits/stdc++.h>
using namespace std;
map<string,map<string,int>>mp;
string s1,s2;
int d;
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>s1>>s2>>d;
mp[s2][s1]+=d;
}
for(map<string,map<string,int>>::iterator it=mp.begin();it!=mp.end();it++){
cout<<it->first<<endl; //自动按字典序排好了序
for(map<string,int>::iterator i=it->second.begin();i!=it->second.end();i++){
cout<<" |----"<<(i->first)<<"("<<(i->second)<<")"<<endl;
}
}
return 0;
}