文章目录

  • 常用技巧
    • 1. 散列
    • 2. 递归
      • 2.1 全排列问题
      • 2.2 n皇后问题
      • 2.3 回溯法优化n皇后问题
    • 3. 贪心
      • 3.1 简单贪心
      • 3.2 区间贪心
    • 4. 二分
      • 4.1 二分查找
      • 4.2 二分法拓展(以求根号2近似值为例)
      • 4.3 快速幂
    • 5. two pointers(双指针)
      • 5.1 求递增数列中的A+B=M
      • 5.2 序列合并问题
      • 5.3 [归并排序](https://blog.csdn.net/zxc0074869/article/details/124560144)
      • 5.4 [快速排序](https://blog.csdn.net/zxc0074869/article/details/124560144)
    • 6. 打表
    • 7. 活用递推
    • 8. 随机选择算法

常用技巧

1. 散列

原理是把一个元素key通过散列函数H转换为H(key)。

最常见的散列法:把key作为数组下标。

散列函数:直接定址法,平方取中法,除留余数法。

解决冲突的方法:线性探测法,平方探测法,链地址法。

字符串hash:把26个字母看做26进制(0~25)的数,将26进制转换为10进制,如BCD看做731。


2. 递归

  • 递归边界:返回最简单底层的结果。
  • 递归式:减少数据规模并向下一次递归。

2.1 全排列问题

#include <cstdio>const int maxn = 11;
//输出1~n的全排列,P为当前排列,hash记录整数x是否已经在P中
int n, P[maxn], hash[maxn] = {false};void fullP(int index) { //当前处理排列的第index号位if (index == n + 1) { //递归边界,已经处理完排列的1~n位for (int i = 1; i <= n; i++) {printf("%d", P[i]); //输出当前排列}printf("\n");return;}for (int x = 1; x <= n; x++) {if (hash[x] == false) {P[index] = x;hash[x] = true;fullP(index + 1); //递归式hash[x] = false;}}
}int main() {n = 3; //输出1~3的全排列fullP(1); //从P[1]开始填return 0;
}

2.2 n皇后问题

#include <cstdio>
#include <cmath>const int maxn = 11;
// n皇后问题,P为当前排列,hash记录整数x是否已经在P中,count记录合法排列个数
int n, P[maxn], hash[maxn] = {false}, count = 0;void fullP(int index) { //当前处理排列的第index号位if (index == n + 1) { //递归边界,已经处理完排列的1~n位bool flag = true;for (int i = 1; i <= n; i++) { //遍历任意两个皇后for (int j = i + 1; j <= n; j++) {if (abs(i - j) == abs(P[i] - P[j])) { //在一条对角线上flag = false;}}}if (flag) count++;return;}for (int x = 1; x <= n; x++) {if (hash[x] == false) {P[index] = x;hash[x] = true;fullP(index + 1); //递归式hash[x] = false;}}
}int main() {n = 8; //八皇后问题fullP(1); //从P[1]开始填printf("%d", count);return 0;
}

2.3 回溯法优化n皇后问题

#include <cstdio>
#include <cmath>const int maxn = 11;
// n皇后问题,P为当前排列,hash记录整数x是否已经在P中,ans数组记录每种情况,cnt记录合法排列个数
int n, P[maxn], hash[maxn] = {false}, ans[1000] = {0}, cnt = 0;void fullP(int index) { //当前处理排列的第index号位if (index == n + 1) { //递归边界,能到达这里的一定是合法的for (int i = 1; i <= n; i++) {ans[cnt] = ans[cnt] * 10 + P[i]; //将结果数组转化为整数}cnt++;return;}for (int x = 1; x <= n; x++) {if (hash[x] == false) {bool flag = true;for (int pre = 1; pre < index; pre++) { //遍历之前的皇后//第index列皇后的行号为x,第pre列皇后的行号为P[pre]if (abs(index - pre) == abs(x - P[pre])) {flag = false;break;}}if (flag) {P[index] = x;hash[x] = true;fullP(index + 1); //递归式hash[x] = false;}}}
}int main() {n = 8; //八皇后问题fullP(1); //从P[1]开始填printf("%d\n", cnt);for (int i = 0; i < cnt; i++) {printf("%d\n", ans[i]);}return 0;
}

3. 贪心

局部最优推全局最优。

3.1 简单贪心

3.2 区间贪心

N个开区间,选择尽可能多的开区间,使这些开区间两两没有交集。

  • 若开区间1被开区间2包含,则选开区间2。
  • 去除开区间包含的情况,先选所有开区间左端点最大的区间。
#include <cstdio>
#include <algorithm>
using namespace std;const int maxn = 110;
struct Inteval {int x, y; //开区间左右段点
} I[maxn];
bool cmp(Inteval a, Inteval b) {if (a.x != b.x)return a.x > b.x; //先按左端点从大到小排序elsereturn a.y < b.y; //左端点相同的按右端点从小到大
}int main() {int n;while (scanf("%d", &n), n != 0) {for (int i = 0; i < n; i++) {scanf("%d%d", &I[i].x, &I[i].y);}sort(I, I + n, cmp);// ans记录不相交区间个数,lastX记录上一个被选中区间的左端点int ans = 1, lastX = I[0].x;for (int i = 1; i < n; i++) {if (I[i].y <= lastX) { //如果该区间右端点在lastX左边lastX = I[i].x; //以I[i]作为新选中的区间ans++;}}printf("%d\n", ans);}return 0;
}

4. 二分

4.1 二分查找

严格单调递增数列中查找元素

int binarySearch(int arr[], int left, int right, int key) {while (left <= right) {//下列句子可以优化为mid = left + (right - left) / 2,防止溢出int mid = (left + right) / 2; //取中点if (arr[mid] == key){ //找到return mid;} else if (arr[mid] > key) { //中间的数大于keyright = mid - 1;} else { //中间的数小于keyleft = mid + 1;}}return -1; //查找失败,返回-1
}

查找第一个满足条件C的元素

int binarySearch(int arr[], int left, int right, int key) {while (left < right) { //对[left,right]来说,left=right意味着到唯一位置int mid = left + (right - left) / 2;//下面if的括号内填入找的条件,如下是找第一个大于等于key的元素if (arr[mid] >= key) { //条件成立,第一个满足条件的元素的位置<=midright = mid;} else { //条件不成立,则第一个满足条件的元素的位置>midleft = mid + 1;}}return left; //返回夹出的位置
}

查找最后一个满足条件C的元素

先求第一个满足==“条件!C”==的元素,然后将该位置减1。

4.2 二分法拓展(以求根号2近似值为例)

#include <cstdio>const double eps = 1e-5;
double f(double x) {return x * x;
}
double calSqrt() {double left = 1, right = 2, mid;while (right - left > eps) {mid = (left + right) / 2;if (f(mid) > 2) {right = mid;} elseleft = mid;}return mid;
}int main() {printf("%f", calSqrt());return 0;
}

4.3 快速幂

给定三个正整数 a 、 b 、 m ( a < 1 0 9 , b < 1 0 6 , 1 < m < 1 0 9 ) a、b、m(a<10^9,b<10^6,1<m<10^9) a、b、m(a<109,b<106,1<m<109),求解 a b % m a^b\%m ab%m

递归写法

  • 若 b b b是奇数,则 a b = a ∗ a b − 1 a^b =a*a^{b-1} ab=a∗ab−1
  • 若 b b b是偶数,则 a b = a b / 2 ∗ a b / 2 a^b=a^{b/2}*a^{b/2} ab=ab/2∗ab/2
typedef long long LL;
//求a^b%m,递归写法
//若初始时a大于m,则在进入函数前先让a对m取模
//若m=1,则直接判定为0
LL binaryPow(LL a, LL b, LL m) {if (b == 0) return 1;// b为奇数,转换为b-1if (b % 2 == 1) { //可用if (b & 1)替代return a * binaryPow(a, b - 1, m) % m;} else { // b为偶数,转换为b/2LL mul = binaryPow(a, b / 2, m);return mul * mul % m;}
}

迭代写法

可以证明,任意的 a b a^b ab都可以表示为 a 2 k 、 … a 4 、 a 2 、 a 1 a^{2k}、…a^4、a^2、a^1 a2k、…a4、a2、a1中若干项的乘积。若 b b b的二进制的 i i i位为1,则 a 2 i a^{2i} a2i就选中,注意到 a 2 k 、 … a 4 、 a 2 、 a 1 a^{2k}、…a^4、a^2、a^1 a2k、…a4、a2、a1序列中的前一项总是等于后一项的平方。

typedef long long LL;
//求a^b%m,递归写法
LL binaryPow(LL a, LL b, LL m) {LL ans = 1;while (b > 0) {if (b & 1) { //如果b的二进制末尾为1,相当于b为奇数ans = ans * a % m; //令ans累积上a}a = a * a % m; //令a平方,相当于a^2i序列b >>= 1; // b二进制右移一位,相当于b/=2}return ans;
}

5. two pointers(双指针)

5.1 求递增数列中的A+B=M

void add(int arr[], int n, int m) {int i = 0, j = n - 1;while (i < j) {if (arr[i] + arr[j] == m) { //找到满足的情况printf("%d+%d=%d\n", arr[i], arr[j], m);i++;j--;} else if (arr[i] + arr[j] < m) { // i往右移动i++;} else // j往左移动j--;}
}

5.2 序列合并问题

将两个递增序列A与B合并为一个递增序列C。

int merge(int A[], int B[], int C[], int n, int m) {int i = 0, j = 0, index = 0; // i指向A[0],j指向B[0]while (i < n && j < m) {if (A[i] <= B[j]) { //选择序列A和B中最小的元素加入序列CC[index++] = A[i++];} else {C[index++] = B[j++];}}while (i < n) C[index++] = A[i++]; //将序列A中剩余元素直接加入序列Cwhile (j < m) C[index++] = B[j++]; //将序列B中剩余元素直接加入序列Creturn index; //返回序列C的长度
}

5.3 归并排序

5.4 快速排序


6. 打表

  • 在程序中一次性计算出所有需要的结果,之后的查询直接取这些结果,如素数表。
  • 在程序B中分一次或多次计算出所有需要的结果,手工把结果写在程序A的数组中,然后在程序A中就可以直接使用这些结果,如提前计算出n皇后问题的方案数。
  • 对没有思路的题,先用暴力程序计算小范围内的结果,然后找规律。

7. 活用递推

找到F(n)与F(n-1)的递推关系,简化计算。


8. 随机选择算法

找到一个无序数组中第K大的数(数组中元素各不相同)

int randPartition(int A[], int left, int right) {//生成[left,right]内的随机数int p = (int)(round(1.0 * rand() / RAND_MAX * (right - left) + left));swap(A[p], A[left]); //交换A[p]和A[left]int temp = A[left];while (left < right) {while (left < right && A[right] > temp) right--; //反复左移rightA[left] = A[right]; //将A[right]移到A[left]while (left < right && A[left] < temp) left++; //反复右移leftA[right] = A[left]; //将A[left]移到A[right]}A[left] = temp; //把temp放到left和right相遇的地方return left; //返回相遇的下标
}int randSlect(int A[], int left, int right, int K) {if (left == right) return A[left]; //递归边界int p = randPartition(A, left, right); //划分后主元的最终位置为pint M = p - left + 1; // A[p]是A[left,right]中的第M大if (K == M) return A[p]; //找到第K大数if (K < M) { //第K大数在主元左侧return randSlect(A, left, p - 1, K);} else //第K大数在主元右侧return randSlect(A, p + 1, right, K - M);
}

应用举例

给定一个由整数组成的集合,集合中的整数各不相同,现在要将它分为两个子集合,使得这两个子集合的并为原交集、交为空集,同时在两个子集合的元素个数 n 1 n_1 n1​与 n 2 n_2 n2​之差的绝对值 ∣ n 1 − n 2 ∣ \lvert n_1-n_2 \rvert ∣n1​−n2​∣尽可能小的前提下,要求它们各自的元素之和 S 1 S_1 S1​与 S 2 S_2 S2​之差的绝对值 ∣ S 1 − S 2 ∣ \lvert S_1-S_2 \rvert ∣S1​−S2​∣尽可能大,并求 ∣ S 1 − S 2 ∣ \lvert S_1-S_2 \rvert ∣S1​−S2​∣的最大值。

该问题等价于求原集合中第n/2大元素,同时根据这个元素把集合划分为两部分,使得其中一个子集合中的元素都不小于这个数,而另一个子集合中的元素都大于这个数。因此使用随机选择算法较为简便。

#include <cstdio>
#include <ctime>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;const int maxn = 100010;
int A[maxn], n;int randPartition(int A[], int left, int right) {//生成[left,right]内的随机数int p = (int)(round(1.0 * rand() / RAND_MAX * (right - left) + left));swap(A[p], A[left]); //交换A[p]和A[left]int temp = A[left];while (left < right) {while (left < right && A[right] > temp) right--; //反复左移rightA[left] = A[right]; //将A[right]移到A[left]while (left < right && A[left] < temp) left++; //反复右移leftA[right] = A[left]; //将A[left]移到A[right]}A[left] = temp; //把temp放到left和right相遇的地方return left; //返回相遇的下标
}int randSlect(int A[], int left, int right, int K) {if (left == right) return A[left]; //递归边界int p = randPartition(A, left, right); //划分后主元的最终位置为pint M = p - left + 1; // A[p]是A[left,right]中的第M大if (K == M) return A[p]; //找到第K大数if (K < M) { //第K大数在主元左侧return randSlect(A, left, p - 1, K);} else //第K大数在主元右侧return randSlect(A, p + 1, right, K - M);
}int main() {//初始化随机数种子srand((unsigned)time(NULL));// sum和sum1记录所有整数之和与切分后前n/2个元素之和int sum = 0, sum1 = 0;scanf("%d", &n); //整数个数for (int i = 0; i < n; i++) {scanf("%d", &A[i]);sum += A[i];}randSlect(A, 0, n - 1, n / 2);for (int i = 0; i < n / 2; i++) {sum1 += A[i];}printf("%d", (sum - sum1) - sum1); //求两个子集合的元素和之差return 0;
}

《算法笔记》第4章常用技巧相关推荐

  1. 《算法笔记》第4章常用技巧及排序算法

    文章目录 二. 常用技巧 1. 散列 2. 递归 2.1 全排列问题 2.2 n皇后问题 2.3 回溯法优化n皇后问题 3. 贪心 3.1 简单贪心 3.2 区间贪心 4. 二分 4.1 二分查找 4 ...

  2. OpenSSL密码库算法笔记——第5章 椭圆曲线

    从这一章开始,将讨论椭圆曲线密码.椭圆曲线密码系统实现之前,有很多东西需要学习研究,包括选择椭圆曲线的域参数(有限域.有限域的表示.椭圆曲线).域算法.椭圆曲线算法.协议算法等等.在这里我们不准备详解 ...

  3. 算法笔记(1)-常用推荐算法总结

    常用推荐算法包括以下几种 1.协同过滤算法 1)基于用户的协同过滤算法 2)基于项的协同过滤算法 2.基于矩阵分解的推荐算法 矩阵分解是将一个矩阵分解成两个或多个矩阵的乘积.基于矩阵分解的推荐算法分为 ...

  4. 《c语言从入门到精通》看书笔记——第5章 常用的数据输入输出函数

    字符数据的输入/输出 printf()输出,scanf()获取键盘的输入 字符数据的输出putchar(),但是使用该函数时头文件要添加stdio.h,且,putchar函数自能输出一个字符,当输出字 ...

  5. 算法笔记之狄克斯特拉算法

    算法笔记第七章: 1.狄克斯特拉算法->找出加权图中前往X的最短路径: 2.加权图:(带权重的图) 3.迪克斯特拉算法步骤: x.找出最便宜的节点,即可最短时间内到达的节点:x.更新该节点的邻居 ...

  6. 基于聚类的推荐算法笔记——以豆瓣电影为例(二)(附源代码)

    基于聚类的推荐算法笔记--以豆瓣电影为例(二)(附源代码) 第一章 聚类算法介绍 基于聚类的推荐算法笔记一 第二章 数据介绍 基于聚类的推荐算法笔记二 第三章 实现推荐算法 基于聚类的推荐算法笔记三 ...

  7. 基于聚类的推荐算法笔记——以豆瓣电影为例(三)(附源代码)

    基于聚类的推荐算法笔记--以豆瓣电影为例(三)(附源代码) 第一章 聚类算法介绍 基于聚类的推荐算法笔记一 第二章 数据介绍 基于聚类的推荐算法笔记二 第三章 实现推荐算法 基于聚类的推荐算法笔记三 ...

  8. 关于“labuladong的算法小抄”的学习笔记---第0章核心框架汇总的后半部分技巧(c++版)

    目录 前言 一.回溯算法秒杀所有排列/组合/子集问题 回溯和DFS之间区别---遍历树枝or遍历节点 1.子集(元素无重不可复选) 2.组合(元素无重不可复选) 3.排列(元素无重不可复选) 4.子集 ...

  9. 关于“labuladong的算法小抄”的学习笔记---第0章核心框架汇总的前半部分框架(c++版)

    目录 作者给自己的话 一.学习算法和刷题的框架思维 1.数据结构的存储方式 2.数据结构的基本操作 3.算法刷题指南 二.labuladong的刷题心得 1.算法的本质 2.数组/单链表系列算法 3. ...

最新文章

  1. “AI界漫威” 深度学习超级英雄联盟漫画:吴恩达,李飞飞…
  2. 安卓开发笔记(二十六):Splash实现首页快速开屏功能
  3. 剑指Offer 56 数组中数字出现的次数
  4. ASP.NET MVC:通过 FileResult 向 浏览器 发送文件
  5. 支付宝支付—沙箱环境使用
  6. UDP/TCP网络传输方式
  7. 浅谈BFS模板思路(一)
  8. 笔记本电脑怎样连接打印机_怎样使用打印机,打印机的基础知识
  9. Spring 多线程
  10. flow control
  11. GIVE root password for maintenance
  12. Linux 运维必备的13款实用工具,拿好了~
  13. SpringCloudAlibaba电商项目实战
  14. mysql 二次方函数_MySQL SQRT函数:求二次方根
  15. 通过修改window本地hosts文件修改域名指向
  16. 医院管理源码 排队叫号管理源码
  17. windows server 2012 安装 VC++ 安装失败0x80240017解决方法
  18. 概述、 BGP AS 、BGP 邻居、 BGP 更新源 、BGP TTL 、BGP路由表、 BGP 同步
  19. html网易云客堂,网易云课堂 HTML5 播放器样式调整
  20. DOC与DOCX区别【100字】【原创】

热门文章

  1. 字符编码、Unicode原理、数据流压缩Zlib与Miniz的实现
  2. C/C++超市收银系统
  3. Python入门(一) —— 编程基础
  4. 一文读懂图像信号中的高频和低频
  5. 解决IDEA的图标消失
  6. 浅谈分布式架构搭建-理论知识
  7. PXC高可用集群(MySQL)
  8. java二叉树计算深度
  9. 关于英语的大小写规则
  10. 3GPP TS 23501-g51 中英文对照 | 4.1 General concepts