本系列为胡凡编著的算法笔记当中代码部分的精简版整理,笔者也在同时准备Leetcode刷题和实习面试,希望为有一定编码和数据结构基础的同学提供一份系统型的参考,以方便遗忘时的算法查阅、期末复习总览以及C++学习参照。
目录
[01 贪心](#01 贪心)
[Ⅱ 区间贪心](#Ⅱ 区间贪心)
[02 二分](#02 二分)
[Ⅰ 二分查找](#Ⅰ 二分查找)
[Ⅱ 二分法拓展](#Ⅱ 二分法拓展)
[Ⅲ 快速幂](#Ⅲ 快速幂)
[03 Two Pointers](#03 Two Pointers)
[Ⅰ 什么是 two points?](#Ⅰ 什么是 two points?)
[Ⅱ 归并排序](#Ⅱ 归并排序)
[Ⅲ 快速排序](#Ⅲ 快速排序)
[04 打表](#04 打表)
[05 递推](#05 递推)
[06 随机选择算法](#06 随机选择算法)
01 贪心
Ⅰ简单贪心


cpp
#include <cstdio>
#include <algorithm>
using namespace std;
struct moonCake{
double store;
double sell;
double price;
}moon[1010]
bool cmp(moon a, moon b){
return a.price > b.price;
}
int maxSell(double D){
sort(moon, moon+N, cmp);
double num = 0;
for(int i=0; i<N; i++){
if(moon[i].store > D){ //供大于求
num = num + moon[i].price * D;
return num;
}else{ //供不应求
num = num + moon[i].sell;
D = D - moon[i].store;
}
}
return num;
}
int main(){
int N;
double D;
scanf("%d %lf", &N, &D);
for(int i=0; i<N; i++){
scanf("%lf", &moon[i].store);
}
for(int i=0; i<N; i++){
scanf("%lf", &moon[i].sell);
moon[i].price = moon[i].sell / moon[i].store;
}
printf("%.2f", maxSell(D));
return 0;
}

cpp
#include <cstdio>
int main(){
int count[10];
for(int i=0; i<10; i++){
scanf("%d", &count[i]);
}
//输出第一位非0数字
for(int i=1; i<10; i++){
if(count[i] > 0){
printf("%d", i);
count[i]--;
break;
}
}
//输出剩下的数字
for(int i=0; i<10; i++){
for(int j=0; j<count[i]; j++){
printf("%d", i);
}
}
}
Ⅱ 区间贪心

区间不相交问题:
给出 N 个开区间 (x, y),从中选择尽可能多的开区间,使得这些开区间两两没有交集。
cpp
//区间贪心
//struct
//cmp
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 110;
struct Interval{
int x;
int y;
}I[maxn];
bool cmp(Interval a, Interval b){
if(a.x != b.x) return a.x > b.x;
else return 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);
int ans = 1;
int lastX = I[0].x;
for(int i=1; i<n; i++){
if(I[i].y < lastX){
lastX = I[i].x;
ans++;
}
}
printf("%d\n", ans);
}
return 0;
}
02 二分
Ⅰ 二分查找
cpp
#include <stdio.h>
const int N = 10;
//A[]为严格递增数列,left为二分下界,right为二分上界,x为欲查询的数
//二分区间为左闭右闭的[left, right],传入的初值为[0, N-1]
//函数返回等于 x 的元素位置
int binarySearch(int A[], int left, int right, int x){
int mid;
while(left < right){
mid = (left + right) / 2
if(A[mid] == x){
return mid;
}
else if(A[mid] < x){
left = mid + 1;
}
else{
right = mid - 1;
}
}
return -1;
}
int main(){
int A[N] = {1, 5, 8, 12, 13, 26, 32, 54, 58, 68};
int num = binarySearch(A, 0, N-1, 8);
printf("%d\n", num);
return 0;
}
cpp
#include <stdio.h>
const int N = 10;
//A[]为严格递增数列,left为二分下界,right为二分上界,x为欲查询的数
//二分区间为左闭右闭的[left, right],传入的初值为[0, N-1]
//函数返回第一个大于等于 x 的元素位置
int binarySearch(int A[], int left, int right, int x){
int mid;
while(left < right){
mid = (left + right) / 2
if(A[mid] == x){
return mid;
}
else if(A[mid] < x){
left = mid + 1;
}
else{
right = mid; //mid 可能是第一个大于等于 x 的元素
}
}
return -1;
}
int main(){
int A[N] = {1, 5, 8, 12, 13, 26, 32, 54, 58, 68};
int num = binarySearch(A, 0, N-1, 11);
printf("%d\n", num);
return 0;
}
Ⅱ 二分法拓展
① 计算根号 2 的近似值。
cpp
const double eps = le-5; //精度为 10^-5
double f(double x){
return x * x
}
double calSqrt(double left, double right, double num){ //num
double mid;
while(right - left > eps){ //左右区间长度小于eps
mid = (left + right) / 2;
if(f(mid) < num){
left = mid;
}
else{
right = mid;
}
}
return mid;
}
② 有一个侧面看去是半圆的储水装置,该半圆的半径为 R,要求往里面装入高度为 h 的水,使其在侧面看去的面积与半圆面积的比例恰好为r,现在给定 R 和 r ,求高度 h。
思路:在 [0, R] 范围内对水面高度 h 进行二分,计算在高度下面积比例 r 的值。如果计算得到的r比给定的数值要大,说明高度过高,范围应缩减至较低的一半;如果计算得到的 r 比给定的数值要小,说明高度过低,范围应缩减至较高的一半。

cpp
#include <cstdio>
#include <cmath>
const double PI = acos(-1.0);
const double eps = 1e-5;
double f(double R, double h){
double alpha = 2 * acos(R-h, R);
double L = 2 * sqrt(R * R - (R-h) * (R-h));
double S1 = alpha * R * R / 2 - L * (R-h) / 2;
double S2 = PI * R * R / 2;
return S1 / S2;
}
double calH(double left, double right, double r){ //r
double mid;
while(right - left > eps){
mid = (left + right) / 2;
if(f(R, mid) < r){
left = mid;
}
else{
right = mid;
}
return mid;
}
}
int main(){
double R, r;
scanf("%lf%lf", &R, &r);
printf("%.4f\n", calH(0, R, r)); //保留四位小数
return 0;
}
Ⅲ 快速幂

思路一:二分幂 + 递归

cpp
//快速幂
//递归边界:b == 0
//递归算式:a * binaryPow(a, b-1, m) % m
// binaryPow(a, b/2, m) * binaryPow(a, b/2, m) % m
typedef long long LL;
LL binaryPow(LL a, LL b, LL m){
if(b == 0) return 1;
else if(b % 2 == 1) return a * binaryPow(a, b-1, m) % m;
else return binaryPow(a, b/2, m) * binaryPow(a, b/2, m) % m;
}
思路二:二进制 + 迭代

cpp
typedef long long LL;
LL binaryPow(LL a, LL b, LL m){
double ans = 1;
while(b > 0){
if(b & 1) ans = ans * a % m; //b的二进制末尾为1
//why % m
a = a * a % m;
b = b >> 1; //b向右移动一位
}
return ans;
}
03 Two Pointers
Ⅰ 什么是 two points?
① 给定一个递增的正整数序列和一个正整数 M,求序列中的两个不同位置的数 a 和 b,使得它们的和恰好为 M,输出所有满足条件的方案。例如给定序列 {1, 2, 3, 4, 5, 6} 和正整数 M=8,就存在 2+6=8 与 3+5=8 成立。
cpp
//i A[]
//j B[]
//z temp[]
//暴力法
for(int i=0; i<n; i++){
for(int j=i+1; j<n; j++){
if(A[i] + A[j] == M){
printf("%d %d\n", i, j);
}
}
}
cpp
//two points优化法
while(i < j){
if(A[i] + A[j] == M){
i++;
j--;
}
else if(A[i] + A[j] < M){
i++;
}
else{
j--;
}
}
② 假设有两个递增序列 A 与 B,要求将它们合并为一个递增序列 C。
cpp
int merge(int A[], int B[], int C[], int n, int m){
int i = 0, j = 0, z = 0;
while(i < n && j < m){
if(A[i] <= B[j]){
C[z++] = A[i++];
}else{
C[z++] = B[j++];
}
}
while(i < n){
C[z++] = A[i++];
}
while(j < m){
C[z++] = B[j++];
}
return z;
}
Ⅱ 归并排序

思路一:递归实现
cpp
const int maxn = 100;
void merge(int A[], int L1, int R1, int L2, int R2){
int i = L1, j = L2;
int temp[maxn], z = 0;
while(i <= R1 && j <= R2){
if(A[i] <= A[j]){
temp[z++] = A[i++];
}else{
temp[z++] = A[j++];
}
}
while(i <= R1){
temp[z++] = A[i++];
}
while(j <= R2){
temp[z++] = A[j++];
}
for(i=0; i<z; i++){ //temp为临时数组,数据需要输送回A
A[L1+i] = temp[i];
}
}
void mergeSort(int A[], int left, int right){
int mid;
if(left < right){ //why if not while
mid = (left + right) / 2;
mergeSort(A, left, mid);
mergeSort(A, mid+1, right);
merge(A, left, mid, mid+1, right); //why merge end
}
}
思路二:非递归实现
cpp
//i [1, n]
void mergeSort(int A[]){
for(int step=2; step/2<=n; step*=2){ //step长度
for(int i=1; i<=n; i+=step){ //step第一个元素L1
int mid = i + step / 2 - 1;
if(mid + 1 <= n){
merge(A, i, mid, mid+1, min(i+step-1, n));
}
}
}
}
//i [0, n-1]
void mergeSort(int A[]){
for(int step=2; step/2<=n; step*=2){
for(int i=0; i<n; i+=step){ //改动部分
int mid = i + step / 2 - 1;
if(mid + 1 <= n){
merge(A, i, mid, mid+1, min(i+step-1, n));
}
}
}
}
//sort函数
void mergeSort(int A[]){
for(int step=2; step/2<=n; step*=2){
for(int i=1; i<=n; i+=step){
sort(A+i, A+min(i+step, n+1));
}
}
}
Ⅲ 快速排序

cpp
//快速排序
//while(left<right) right-- left++
//递归边界:left == right
//递归算式:
int Partition(int A[], int left, int right){
int temp = A[left];
while(left < right){
while(left < right && A[right] >= temp) right--;
A[left] = A[right];
while(left < right && A[left] < temp) left++;
A[right] = A[left];
}
A[left] = temp;
return left;
}
void quickSort(int A[], int left, int right){
if(left < right){
int pos = Partition(A, left, right);
quickSort(A, left, pos - 1); //pos - 1
quickSort(A, pos + 1, right); //pos + 1
}
}
拓展:生成随机数
cpp
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand((unsigned)time(NULL)); //生成随机数种子
for(int i=0; i<10; i++){
//rand() % (b-a+1) + a [a, b]
//(int)((double)rand() % 32767 * (b-a+1) + a) [a, k(b-a)+a]
printf("%d", rand());
}
return 0;
}
int Partition(int A[], int left, int right){
//改动部分
int p = (int)(round(1.0 * rand() % (right-left) + left));
swap(A[p], A[left]);
int temp = A[left];
while(left < right){
while(left < right && A[right] >= temp) right--;
A[left] = A[right];
while(left < right && A[left] < temp) left++;
A[right] = A[left];
}
A[left] = temp;
return left;
}
04 打表
① 在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果。
② 在程序B中分一次或多次计算出所有需要用到的结果,手工把结果写在程序A的数组中,然后在程序A中就可以直接使用这些结果。
③ 对一些感觉不会做的题目,先用暴力程序计算小范围数据的结果,然后找规律,或许就能发现一些 "蛛丝马迹"。
05 递推

对于一个确定位置的 A 来说,以它形成的 PAT 的个数等于它左边 P 的个数乘以它右边 T 的个数。
只需要设定一个数组 leftNumP[ ],记录每一位左边 P 的个数(含当前位,下同),接着从左到右遍历字符串,如果当前位 i 是 P,那么 leftNumP[i] = leftNumP [i-1] +1;如果当前位i不是 P,那么leftNumP[i] = leftNumP[i-1]。
cpp
//PAT
//leftNumP[] rightNumP ans%MOD
#include <cstdio>
#include <cstring>
const int MAXN = 100010;
const int MOD = 100000007;;
char str[MAXN];
int leftNumP[MAXN] = {0};
int main(){
gets(str);
int len = strlen(str);
for(int i=0; i<len; i++){
if(i > 0){
leftNumP[i] = leftNumP[i-1];
}
if(str[i] == 'P'){
leftNumP[i] += 1;
}
}
int ans = 0, rightNumT = 0;
for(int i=len-1; i>=0 ;i--){
if(str[i] == 'T'){
rightNumT += 1;
}
if(str[i] == 'A'){
ans = (ans + leftNumP[i] * rightNumT) % MOD;
}
}
printf("%d\n", ans);
return 0;
}
06 随机选择算法
当对 A[left, right] 执行一次 randPartition 函数之后,主元左侧的元素个数就是确定的,且它们都大于主元。假设此时主元是 A[p],那么 A[p] 就是 A[left, right] 中的第 p-left+1 大的数。不妨令 M 表示p-left+1,那么如果 K=M 成立,说明第K大的数就是主元 A[P];如果 K<M 成立,就说明第 K 大的数在主元左侧,即 A[left...(p-1)] 中的第 K 大,往左侧递归即可;如果 K>M 成立,则说明第K大的数在主元右侧,即 A[p+1]...right 中的第 K-M 大,往右侧递归即可。算法以 left==right 作为递归边界,返回A[left]。
cpp
//随机选择算法
//递归边界:left == right
//递归算式:K < M randSelect(A, left, p-1, K)
// K > M randSelect(A, p+1, right, K-M)
//时间复杂度:O(n)
int randSelect(int A[], int left, int right, int K){
if(left == right){
return A[P];
}
int p = partition(A, left, right);
int M = p-left+1;
if(K == M){
return A[p];
}else if(K < M){
return randSelect(A, left, p-1, K);
}else{
return randSelect(A, p+1, right, K-M);
}
}
问:给定一个由整数组成的集合,集合中的整数各不相同,现在要将它分为两个子集合,使得这两个子集合的并为原集合、交为空集,同时在两个子集合的元素个数 n1 与 n2 之差的绝对值尽可能小的前提下,要求它们各自的元素之和 S1 与 S2 之差的绝对值尽可能大。求这个绝对值等于多少。
答:这个问题实际上就是求原集合中元素的第 n/2 大,同时根据这个数把集合分为两部分,使得其中一个子集合中的元素都不小于这个数,而另一个子集合内部元素的顺序则不需要关心