一、计算时间复杂度(master法)
二、回溯/递归
(1)子集树与排列树框架
子集树:
void Backtrack(int t) { //t 表示当前是树的第t层,即对集合 S 中的第 t 个元素进行判断
if (t > n)
output(x); //大于S中总的元素个数 ,遍历完成
else
for (int i = 0; i < = 1; i++) { // 两种可能 加入或者不加入到解集合
x[t] = i;
if (Constraint(t) && Bound(t)){ //满足约数条件
Backtrack(t + 1); //对 t+1 层进行判断
}
}
}
排列树:
void Backtrack(int t) { //t 表示集合 S 的第 t 个元素
if (t > n)
output(x);
else
for (int i = t; i <= n; i++) { //第t 个元素与其后面的所有元素进行交换位置
swap(x[t], x[i]);
if (constraint(t) && bound(t)){
backtrack(t + 1);
}
swap(x[t], x[i]);
}
}
(2)递归求全排列(排列树)
#include <stdio.h>
#include <string.h>
int count = 0;
void swap(char* a, char* b)
{
char temp;
temp = *a;
*a = *b;
*b = temp;
}
void perm(char list[], int k, int m)
{
if (k == m) //当循环到最后一层时(末尾),输出排列
{
count++;
printf("%s\n", list);
}
for (int i = k; i <= m; i++)
{
swap(&list[k], &list[i]); //交换位置
perm(list, k + 1, m); //递归调用
swap(&list[k], &list[i]);
}
}
int main()
{
char a[10];
scanf("%s", a);
int n = strlen(a);
printf("排序序列为:\n");
perm(a, 0, n - 1);
printf("总排序个数为\n");
printf("%d", count);
return 0;
}
(3)子集和问题(子集树)
void Backtrack(int i){
// 如果已经遍历到元素下标大于 n ,表示一个分支走完
if(i>n){
// 如果找到解,修改标志位
if(sum==c)
found=true;
return;
}
// 如果未找到解
if (found == false) {
// 将当前元素加入和中
sum+=a[i];
// 记录当前元素已加入和中
record[i]=true;
// 继续递归遍历下一个元素
Backtrack(i + 1);
// 如果已经找到解,直接返回
if (found == true)
return;
// 取出当前元素从和中减去
sum-=a[i];
// 记录当前元素未加入和中
record[i]= false;
// 继续递归遍历下一个元素
Backtrack(i+1);
}
}
int main(){
// 读取输入
cin>>n>>c;
for (int i = 1; i <=n ; i++) {
cin>>a[i];
}
// 调用 Backtrack 函数开始递归遍历
Backtrack(1);
// 输出结果
for (int i = 1; i <=n ; i++) {
if(record[i]== true)
cout<<a[i]<<" ";
}
// 判断是否找到解
if(found== false)
cout<<"No Solution";
}
(4)快速排序
#include <iostream>
#include <algorithm>
using namespace std;
int a[1000];
int n;
void print()
{
for(int i=1 ; i<=n ; i++)
{
cout<<a[i]<<' ';
}
cout<<endl;
}
int Partition(int a[],int low,int high)
{
a[0]=a[low];
while(low<high)
{
while(low<high&&a[high]>=a[0]) high--;
a[low]=a[high];
while(low<high&&a[low]<=a[0]) low++;
a[high]=a[low];
}
a[low]=a[0]; //枢轴来到真正属于他的位置
print();
return low;
}
void QSort(int a[],int low,int high)
{
if(low<high) /**记得加这个条件,否则将无限递归*/
{
int pivotloc=Partition(a,low,high);
QSort(a,low,pivotloc-1);
QSort(a,pivotloc+1,high);
}
}
int main()
{
cin>>n;
for(int i=1 ; i<=n ; i++)
cin>>a[i];
QSort(a,1,n);
return 0;
}
(5)归并排序
void Merge(int num[], int bg, int mid, int end)
{
//建立临时数组存两个部分的合并后的数组
int tmp[end - bg]= {0};
int i = bg, j = mid;
int k = 0;
//比较两部分 当i=mid 或j=end 退出
while (i < mid && j < end)
{
//由大小依次排序放到临时数组里 直到一个部分排完退出
if (num[i] > num[j])
{
tmp[k++] = num[j++];
}
else
{
tmp[k++] = num[i++];
}
}
//如果前半部分没存完 依次存进去
while (i < mid)
{
tmp[k++] = num[i++];
}
//如果后半部分没存完 依次存进去
while (j < end)
{
tmp[k++] = num[j++];
}
//把排好的临时数组存到num数组对应下标里
for (int i = 0; i < k; i++)
{
num[bg++] = tmp[i];
}
}
void MergeSort(int sourceArr[], int startIndex, int endIndex) {
int midIndex;
if (startIndex < endIndex) {
midIndex = startIndex + (endIndex - startIndex) / 2;//避免溢出int
MergeSort(sourceArr, startIndex, midIndex);
MergeSort(sourceArr, midIndex + 1, endIndex);
Merge(sourceArr, startIndex, midIndex, endIndex);
}
}
(6)第k小元素选择
int partition(int a[], int low, int high)
{
a[0] = a[low];
while (low < high)
{
while (low < high && a[high] >= a[0]) high--;
a[low] = a[high];
while (low < high && a[low] <= a[0]) low++;
a[high] = a[low];
}
a[low] = a[0];
return low;
}
//p 和 r 为序列的左右边界,k 为要查找的第 k 小元素
int Select(int a[], int p, int r, int k)
{
if (p == r)
return a[p];
int i = partition(a, p, r);
int j = i - p + 1;
if (k <= j)
return Select(a, p, i, k); // 对左段递归 填空1
else
return Select(a, i + 1, r, k - j); // 或者对右段递归 填空2
}
三、动态规划
(1)最长公共子序列
#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
char s1[1001] ,s2[1001];
int dp[1001][1001];
int main()
{
cin>>s1>>s2;
int m=strlen(s1),n=strlen(s2);
//int **dp=new int[m+1][n+1];
int i,j;
for(i=1 ; i<=m ; i++)
{
for(j=1 ; j<=n ; j++)
{
if(s1[i-1]==s2[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
}
else
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
//cout<<dp[i-1][j-1]<<endl;
cout<<dp[m][n]<<endl;
return 0;
}
(2)最大子段和
// 最大子段和.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
using namespace std;
//末尾法,双层dp
//dp数组的含义:以i为结尾num[i]的最大连续子序列和
//dp[i]=max (dp[i-1] + num[i], num[i])
//舍弃负数,与贪心一样
/*
dp五部曲:
//dp含义
//递推公式
//初始化
//遍历顺序
//打印
*/
int num[101];
int dp[101];
int n;
int max_subarray_sum()
{
int result = 0;
dp[0] = num[0];
for (int i = 1; i < n; i++)
{
/*遇到负数不用跳,继续计算?和之前的舍弃负数,不一样?
if (num[i] < 0)
continue;
*/
dp[i] = max(dp[i - 1] + num[i], num[i]); //仅此处是关键,本题较简单
//记录
if (dp[i] > result)
result = dp[i];
}
//return dp[n-1]; //注意返回的不是dp数组最后一个数,是最大的那个数 ※因为//dp数组的含义:以i为结尾num[i]的最大连续子序列和
return result;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> num[i];
}
cout << max_subarray_sum() << endl;
}
(3)最大子矩阵和
int MaxSum(int n, int* num)
{
//初始化dp
int* dp = new int [n + 1];
for (int i = 1; i <= n; i++)
dp[n] = 0;
//初始化
int result = 0;
dp[0] = num[0];
//仅此步关键
for (int i = 1; i < n; i++)
{
dp[i] = max(dp[i - 1] + num[i], num[i]);
}
//记录
for (int i = 1; i < n; i++)
{
if (dp[i] > result)
result = dp[i];
}
return result;
}
int maxSum2(int m, int n, int** a)
{
int sum = 0;
int* b = new int[n + 1]; //新建一个 b[ ]空间,用于保存条状二维矩阵纵向相加的结果
for (int i = 1; i <= m; i++)
{
for (int k = 1; k <= n; k++)
b[k] = 0; //初始化 填空1
for (int j = i; j <= m; j++)
{
for (int k = 1; k <= n; k++)
b[k] += a[j][k]; //累加 填空2
int max = MaxSum(n, b); //调用一维方法 填空3
if (max > sum)
sum = max;
}
}
return sum;
}
(4)最长递增子序列
// 最长递增子序列.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <algorithm>
using namespace std;
int dp[100];
int nums[100];
int n;
int solution()
{
int result = 0;
for (int i = 0; i < n; i++)
{
dp[i] = 1;
}
for (int i = 1; i < n; i++)
{
for (int j = 0; j < i; j++)
{
if (nums[i] > nums[j])
dp[i] = max(dp[j] + 1, dp[i]); //不连续子序列
//注意这⾥不是要dp【i】 与 dp【j】 + 1进⾏⽐较,⽽是我们要取dp【j】 + 1的最⼤值。
}
//if (num[i] > num[i - 1])
// dp[i] = dp[i-1]+1; //连续子序列
}
for (int i = 0; i < n; i++)
{
if (dp[i] > result)
result = dp[i];
}
return result;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> nums[i];
}
cout << solution() << endl;
}
(5)石子合并(区间dp)
//https://www.bilibili.com/video/BV1gz4y1y7Rv
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
int n;
int a[110];
int s[110];
int f[110][110];
int main()
{
cin>>n;
memset(f,0x3f,sizeof(f));
for(int i=1; i<=n; i++)
{
cin>>a[i];
s[i]=s[i-1]+a[i]; //前缀和
f[i][i]=0;
}
//区间dp模板
for(int len=2; len<=n; len++)
{
for(int l=1; l+len-1<=n; l++)
{
int r=l+len-1; //区间终点
for(int k=l; k<r; k++) //决策:枚举分割点
{
f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);
}
}
}
cout<<f[1][n]<<endl;
return 0;
}
//矩阵连乘 矩阵链
(6)矩阵连乘
四、贪心问题
(1)区间相交问题
// 区间相交问题.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <algorithm>
using namespace std;
//在不知道贪心思路的情况下想的:并不是在输入的时候就处理,而是先排序后用贪心
//关键在于贪心:若有重叠,移除右边界大的那个区间,代码上体现在更新当前区间的右边界
int n;
struct MyStruct
{
int s;
int e;
}a[51];
int cmp(MyStruct s1, MyStruct s2)
{
return s1.e < s2.e;
}
int solution()
{
sort(a, a+n,cmp);
int count = 1;
int ccount = 0;
//维护一个最新右边界 ,而不是与前一个比较, 因为可能有多个重叠
int end = a[0].e;
for (int i = 1; i < n; i++)
{
//if (a[i].s >= end) //后一个区间的start >= 前一个区间的end:没重叠
//{
// count++;
// end = a[i].e;
//}
//前面可能有多个,与当前i重叠的区间
if (a[i].s < end)
ccount++;
else
end = max(end, a[i].e); //更新边界
}
//return n - count; //最开始的题解:这里为什么?
return ccount;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> a[i].s >> a[i].e;
}
cout << solution() << endl;
}
(2)多机调度:贪心算法:最短处理时间优先,最长处理时间优先