常用算法模板

文章目录

一、冒泡排序

  • 冒泡排序的模板

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段距离的数,求剩余数值和的最小的情况
解题步骤:

  1. 先求前缀和
  2. 求区间为k的最大值
for(int i=k;i<n;i++){
	long long res=0;
	res=sum[i]-sum[i-1];
	maxx=max(res,maxx);
}
  1. 最终结果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、差分算法

  1. 求差分数组

void insert(int l, int r, int c)
{
    b[l] += c;
    b[r+1] -= c;
}

  1. 首先计算起始成分数组,for循环遍历每一项
for(int i = 1; i <= n; i ++)
    {
        insert(i,i,a[i]);
    }
  1. 若是需对原数组某个区段[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实现堆

  1. priority_queue默认是大堆
  2. less代表大堆
  3. greater代表小堆
priority_queue<int> q;//默认是从大到小

priority_queue<int, vector<int> ,less<int> >q;//从大到小排序

priority_queue<int, vector<int>, greater<int> >q;//从小到大排序

1.1、自定义排序规则

  1. 创建排序规则对象

使用结构体创建排序规则
注意:return处的小于号代表从大到小排序,大于号代表从小到大排序

struct cmp{                              //priority_queue的排序规则
        bool operator()(pair<char,int> x, pair<char,int> y)
	    {
		    return x.second < y.second;
	    }
    };
  1. 创建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、迷宫问题

参考
思想:

  1. 定义四个变量分别记录入口坐标和目的坐标
  2. 定义三个二维数组——迷宫、入口位置到当前位置的距离len(存在两个用处——记录距离——记录是否经过)、走动方向(如上下左右)
  3. 一个结构体,记录了当前的坐标ij
  4. 递归方法
    • 定义一个队列,类型为结构体类型
    • 遍历二维数组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皇后问题

思路:

  1. 定义五个集合
    • solutions 存储最终返回结果
    • queens 存储皇后的位置(其值代表列,值对应的下标代表行)——vector< int>集合
    • columns 用来记录当前列是否存在皇后
    • diagonals1 用来记录以当前位置为基准其左对角是否存在皇后,使用set集合
    • diagonals2 用来记录以当前位置为基准其右对角是否存在皇后,使用set集合
  2. 调用递归函数
    • 终结条件——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. 线段树的构建
    在这里插入图片描述

  2. 线段树的插入
    在这里插入图片描述

  3. 线段树的查找
    在这里插入图片描述

  4. 线段树的打印
    在这里插入图片描述

小技巧

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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值