Useful Commands

Git

When you are using Git, you may encounter some problems. Here are some useful commands to help you solve them.

  1. Set the postBuffer size to 500MB
    When meeting:
1
2
3
4
5
6
error: RPC failed; HTTP 400 curl 22 The requested URL returned error: 400
send-pack: unexpected disconnect while reading sideband packet
Writing objects: 100% (635/635), 2.07 MiB | 2.53 MiB/s, done.
Total 635 (delta 81), reused 0 (delta 0), pack-reused 0
fatal: the remote end hung up unexpectedly
Everything up-to-date

You can set the postBuffer size to 500MB:

1
git config http.postBuffer 524288000

Add Dark Mode to Your Website

Add Dark Mode to Your Website

Add dark mode to your website using DarkReader. This is a simple way to add dark mode to your website.

1
2
3
4
5
6
7
8
9
{% if jekyll.environment == 'production' -%}
<!-- dark theme using DarkReader -->
<script src="//unpkg.com/darkreader@4.9.40/darkreader.js"></script>
<script type="text/javascript">
DarkReader.setFetchMethod(window.fetch); // Fix to remedy CORS errors in chrome console
//DarkReader.enable();
DarkReader.auto( {brightness: 100, contrast: 90, sepia: 10} );
</script>
{%- endif %}


Reference:
reference the implementation from link

Add comments in blog posts on GitHub Pages websites

  • This, https://alanzeng.com/, is my Github Pages static website and blog, powered by the Jekyll static site generator.
  • I’ve added GitHub-Issue-based comments to the bottom of this website using the plugin called utterances,
    which is totally awesome and makes it trivial to add a beautiful and user-friendly commenting system to your static website or blog.

utteranc config

Visit Utterances main page & installation info.: https://utteranc.es/
image-20240806185908366


repo:设置存放评论的仓库

Utterances 使用 Github Issues 存储评论,所以需要一个仓库。你可以新建一个公开仓库专门用来放评论,也可以使用原有的仓库。要设置存放评论的仓库只需要将 repo=”username/reponame” 这一行中的 username 改为你的 GitHub 用户名,reponame 改为你的仓库名,其它不变。

仓库需满足以下条件:

  • 仓库必须为公开仓库,私有仓库访客无法查看对应 Issues 上的评论。
  • 确保在仓库中安装了 Utterances 的 GitHub App,或是你自己注册的 GitHub App(自托管),否则用户将无法发表评论。
  • 如果你的仓库是派生 (fork) 出的,请在仓库的 Settings 选项确认 Features 区 Issues 已勾选。

issue-term:博客文章和 Issue 映射

Utterances 使用以下几种规则建立博客文章和 GitHub Issues 的映射:

  • Issue 标题包含页面路径名(issue-term=”pathname”)
  • Issue 标题包含页面 URL(issue-term=”url”)
  • Issue 标题包含页面标题(issue-term=”title”)
  • Issue 标题包含页面 og:title(issue-term=”og:title”)
  • 特定的 issue 编号(issue-number=”具体数字”)
  • Issue 标题包含特定项(issue-term=”你设置的特定内容”)

具体细节参考上图


label:Issue 标签

如果你使用原有的仓库,但是担心 Issues 页面评论和问题混杂在一起,Utterances 支持设置标签(Label)来区分它们。设置 label=”你的标签内容”,Utterances 将在创建 issue 时使用你设置的标签。

  • 标签名区分大小写。
  • 标签必须存在于你的仓库中(须提前在 GitHub Issues 页面创建好,不能使用不存在的标签)。
  • 标签名支持 Emoji。例如:label=”💬”

theme:主题

Utterances 有多种主题,其中包括多款夜间模式主题。

  • GitHub Light:theme=”github-light”
  • GitHub Dark:theme=”github-dark”
  • GitHub Dark Orange:theme=”github-dark-orange”
  • Icy Dark:theme=”icy-dark”
  • Dark Blue:theme=”dark-blue”
  • Photon Dark:theme=”photon-dark”

配置

1
2
3
4
5
6
7
<script src="https://utteranc.es/client.js"
repo="[ENTER REPO HERE]"
issue-term="pathname"
theme="github-light"
crossorigin="anonymous"
async>
</script>

将这部分代码粘贴进_layout/post.html中, 大功告成!



> referenced: > - [link](https://gabrielstaples.com/github-pages-comments/#gsc.tab=0) > - [link](https://blog.njilc.com/post/self-hosted-utterances-tutorial)

Common Docker Operations and Commands

Common Docker Operations and Commands

Create Docker Image for Python Projects

  1. Configure Dependencies
1
2
3
pip freeze > requirements.txt
touch Dockerfile
vim Dockerfile
  1. Create and Edit Dockerfile
1
2
3
4
5
6
FROM python:3.10.12
ADD . /code
WORKDIR /code
# Install Dependencies
RUN pip install -r requirements.txt
CMD ["python", "/hello.py"]
  1. Build Docker Image & run
1
2
3
docker build -t demo .
docker run -it demo
docker run -itd --name demo -p 5000:5000 demo

Pull or Push Docker Images

Docker Hub

login to Docker Hub;

1
2
3
4
5
6
7
8
9
[root@localhost ~]
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username:
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

Edit Tag

  • Push Command docker push docker_username/REPOSITORY:TAG
  • We need to edit the tag and repository name:
1
2
3
4
5
6
7
8
9
[root@localhost ~]# docker images 
REPOSITORY TAG IMAGE ID CREATED SIZE
demo latest 1e5f6711d527 3 days ago 178MB
[root@localhost ~]# docker tag demo:latest ***/demo:v1
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
demo latest 1e5f6711d527 3 days ago 178MB
***/demo v1 1e5f6711d527 3 days ago 178MB
[root@localhost ~]#

Push

1
2
3
4
5
6
7
8
[root@localhost ~]# docker push ***/demo:v1
The push refers to repository [docker.io/***/demo:v1]
833a0t6a6ff9: Pushed
10bfe4y2500e: Pushed
d43sfdd7d594: Mounted from library/nginx
c2adabsadfed: Mounted from library/nginx
v1: digest: sha256:67dcdae5578c0374019bdc899731543cfd7c48fe5780e84233a258f2bf7d2ceda size: 1155
[root@localhost ~]#

Pull

1
2
3
4
5
[root@localhost ~]# docker pull ***/demo:v1
v1: Pulling from ***/demo:v1
Digest: sha256:67dcdae5578c0374019bdc899731543cfd7c48fe5780e84233a258f2bf7d2ceda
Status: Downloaded newer image for ***/demo:v1
docker.io/llxxyy/nginx-io:v1

Basic Algorithms

Chapter1 基础算法

快速排序 Quick Sort

785.快速排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
using namespace std;

const int N = 1e6 + 10;

int n;
int q[N];

void quick_sort(int q[],int l,int r)
{
if(l>=r) return;
int i = l-1;
int j = r+1;
int x = q[(l+r)/2];

while(i<j){
do i++; while(q[i]<x);
do j--; while(q[j]>x);
if(i<j) swap(q[i],q[j]);
}

quick_sort(q,l,j);
quick_sort(q,j+1,r);
}


int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&q[i]);

quick_sort(q,0,n-1);

for(int i=0;i<n;i++) printf("%d ",q[i]);

return 0;
}

注意边界问题

quick_sort(q,l,j);
quick_sort(q,j+1,r);//此时x不能取q[r]

可以更改为

quick_sort(q,l,i-1);
quick_sort(q,i,r);//此时x不能取q[l]

归并排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<iostream>
using namespace std;

const int N = 1e6+10;
int n;
int q[N],tmp[N];

void merge_sort(int q[],int l,int r)
{
if(l>=r)return;
int mid = l+r >> 1;
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
int k = 0;
int i = l,j = mid+1;
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(i=l,j=0;i<=r;i++,j++) q[i]=tmp[j];
}

int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&q[i]);

merge_sort(q,0,n-1);

for(int i=0;i<n;i++) printf("%d ",q[i]);

return 0;
}

二分排序

整数二分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
例题:

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。

对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 00 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数 n 和 q,表示数组长度和询问个数。

第二行包含 n 个整数(均在 1∼100001∼10000 范围内),表示完整数组。

接下来 q 行,每行包含一个整数 k,表示一个询问元素。

输出格式

共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

1≤n≤100000

1≤q≤10000

1≤k≤10000

输入样例

1
2
3
4
5
6 3
1 2 2 3 3 4
3
4
5

输出样例

1
2
3
3 4
5 5
-1 -1

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
using namespace std;

const int N = 100010;
int arr[N];

int main()
{
int n,q,k;
scanf("%d%d",&n,&q);
for(int i=0;i<n;i++) scanf("%d",&arr[i]);
for(int i=0;i<q;i++) {
scanf("%d",&k);
int l=0,r=n-1;
while(l<r)
{
int mid = l+r >> 1;
if(arr[mid]>=k) r = mid;
else l = mid+1;
}
if(arr[l]!=k) cout<<"-1 -1"<<endl;
else{
cout<<l<<" ";
int l=0,r=n-1;
while(l<r)
{
int mid = l+r+1 >> 1;
if(arr[mid]<=k) l = mid;
else r = mid-1;
}
cout<<r<<endl;
}
}
}

浮点数二分

不考虑边界问题更简单

例题:

给定一个浮点数 n,求它的三次方根。

输入格式

共一行,包含一个浮点数 n。

输出格式

共一行,包含一个浮点数,表示问题的解。
注意,结果保留 66 位小数。

数据范围

−10000≤n≤10000

输入样例

1
1000.00

输出样例

1
10.000000

答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
double n;
cin>>n;
bool isNegtive = false;
if(n<0) {
isNegtive = true;
n = n*-1.0;
}
double l=0,r=max(1.0,n);
while((r-l)>1e-8)
{
double mid = (l+r)/2;
if(mid*mid*mid>=n){
r = mid;
}
else l = mid;
}
if(!isNegtive) printf("%lf\n",l);
else printf("%lf\n",-1*l);

return 0;
}

高精度算法

高精度加法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <vector>

using namespace std;

const int N = 1e6+10;

vector<int> add(vector<int>&A,vector<int>&B)
{
vector<int> C;
int t = 0;
for (int i = 0; i<A.size()||i<B.size(); i ++ ){
if(i<A.size()) t+=A[i];
if(i<B.size()) t+=B[i];
C.push_back(t%10);
t/=10;//上一位的进位
}
if(t) C.push_back(1);
return C;
}

int main()
{
string a,b;
vector<int> A,B;

cin>>a>>b;
for(int i = a.size()-1;i>=0;i--){
A.push_back(a[i]-'0');
}
for(int i = b.size()-1;i>=0;i--){
B.push_back(b[i]-'0');
}

auto C = add(A,B);

for(int i = C.size()-1;i>=0;i--){
printf("%d",C[i]);
}
return 0;
}

高精度减法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <iostream>
#include <vector>

using namespace std;

bool cmp(vector<int>&A,vector<int>&B){
if(A.size()!=B.size()) return A.size()>B.size();
else {
for(int i=A.size()-1;i>=0;i--){
if(A[i]!=B[i]) return A[i]>B[i];
}
}
return true;
}

vector<int> sub(vector<int>&A,vector<int>&B)
{
//要求——A>=B
//if A>=B 直接计算A-B
//else 计算-(B-A)

vector<int> C;
int t = 0;
for (int i = 0; i<A.size(); i ++ ){
t = A[i] - t;
if(i<B.size()) t -= B[i];
C.push_back((t+10)%10);
if(t<0) t = 1;
else t = 0;
}
while(C.size()>1 && C.back()==0) C.pop_back();
return C;
}

int main()
{
string a,b;
vector<int> A,B;

cin>>a>>b;
for(int i = a.size()-1;i>=0;i--){
A.push_back(a[i]-'0');
}
for(int i = b.size()-1;i>=0;i--){
B.push_back(b[i]-'0');
}
if(cmp(A,B)){
auto C = sub(A,B);
for(int i = C.size()-1;i>=0;i--){
printf("%d",C[i]);
}
}
else{
auto C = sub(B,A);
printf("-");
for(int i = C.size()-1;i>=0;i--){
printf("%d",C[i]);
}
}

return 0;
}

高精度乘法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <vector>

using namespace std;

vector<int> mul(vector<int> A , int b)
{
vector<int> C;
int t = 0;
for(int i=0;i < A.size() || t ; i++){
if(i<A.size()){
t += A[i] * b;
}
C.push_back(t % 10);
t /= 10;
// C.push_back((A[i] * b + t) % 10);
// t = (A[i] * b + t) / 10;
}
while(C.size()>1 && C.back()==0) C.pop_back();
// 去除前导0
return C;
}

int main()
{
string a;
int b;
vector<int> A;
cin>>a>>b;
for(int i = a.size()-1;i>=0;i--){
A.push_back(a[i]-'0');
}

auto C = mul(A,b);

for(int i = C.size()-1;i>=0;i--){
printf("%d",C[i]);
}
}

高精度除法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<int> div(vector<int> &A, int b ,int &r){
vector<int> C;// 商
r = 0;// 余数
// 从最高位开始算--不同于其他几种计算
for(int i = A.size() - 1;i >= 0 ; i--){
r = r * 10 + A[i];
C.push_back(r / b);
r %= b;
}

reverse(C.begin() ,C.end());

while(C.size()>1 && C.back()==0) C.pop_back();
// 去除前导0
return C;
}

int main()
{
string a;
int b;
vector<int> A;
cin>>a>>b;
for(int i = a.size()-1;i>=0;i--){
A.push_back(a[i]-'0');
}

int r;
auto C = div(A,b,r);

for(int i = C.size()-1;i>=0;i--){
printf("%d",C[i]);
}
cout<<endl;
cout<<r<<endl;
}

前缀和

一维前缀和

  • 更新 S[i] = S[i-1] + a[i]
  • 计算a[l,r] ans = S[r] - S[l-1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;

const int N = 100010;
int a[N],S[N];

int main()
{
int n,m;
scanf("%d%d", &n, &m);

for (int i = 1;i<=n;i++) scanf("%d", &a[i]);

S[0] = 0;
for(int i = 1;i<=n;i++){
S[i] = S[i-1] + a[i];
}
for (int i = 0;i < m;i++){
int l,r;
scanf("%d%d", &l, &r);
printf("%d\n",S[r]-S[l-1]);
}

return 0;

}

二维前缀和(子矩阵的和)

  • 更新 S[i][j] =a[i][j] + S[i][j-1] + S[i-1][j] - S[i-1][j-1]
  • 计算(x1,y1)(x2,y2) ans = S[x2][y2] - S[x1-1][y2] - S[x2][y1-1] + S[x1-1][y1-1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <cstring>
#include <algorithm>

const int N = 1010;
int a[N][N],S[N][N];

int main()
{
int n,m,q;
scanf("%d%d%d", &n, &m, &q);
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
scanf("%d",&a[i][j]);
}
}
S[0][0] = 0;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
S[i][j] =a[i][j] + S[i][j-1] + S[i-1][j] - S[i-1][j-1];
}
}

int x1,x2,y1,y2;
while(q--){
scanf("%d%d%d%d", &x1, &y1 , &x2, &y2);
int ans = S[x2][y2] - S[x1-1][y2] - S[x2][y1-1] + S[x1-1][y1-1];
printf("%d\n",ans);
}

return 0;
}

一维差分

  • 输入一个长度为 n 的整数序列。

    接下来输入 m个操作,每个操作包含三个整数 l,r,c,表示将序列中 [l,r] 之间的每个数加上 c。

    请你输出进行完所有操作后的序列。

  • 前缀和的逆运算

  • 构造 等同于 操作(i,i,a[i])

  • 操作等价于

    • b[l] += c;
    • ``b[r+1] -= c;`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;
int a[N],b[N];

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

int main()
{
int n,m;
scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++){
scanf("%d", &a[i]);
}
for(int i=1;i<=n;i++){
op(i,i,a[i]);
}

/*接下来输入 m个操作,
* 每个操作包含三个整数 l,r,c
* 表示将序列中 [l,r]之间的每个数加上 c
*/
while (m--) {
int l,r,c;
scanf("%d%d%d", &l, &r, &c);
op(l,r,c);
}

for (int i = 1; i <= n; i++){
b[i] += b[i-1];
}
for (int i = 1; i <= n; i++){
printf("%d ",b[i]);
}

return 0;


}

二维差分

  • 输入一个 n 行 m列的整数矩阵,再输入 q 个操作,每个操作包含五个整数 x1,y1,x2,y2,c
    其中 (x1,y1)和 (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标。

    每个操作都要将选中的子矩阵中的每个元素的值加上 c

    请你将进行完所有操作后的矩阵输出。

  • 操作等价于

    • b[x1][y1] += c
    • b[x2+1][y1] -= c
    • b[x1][y2+1] -= c
    • b[x2+1][y2+1] += c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <cstring>
#include <algorithm>

const int N = 1010;
int a[N][N],b[N][N];

void op(int x1,int x2,int y1,int y2,int c){
b[x1][y1] += c;
b[x2+1][y1] -= c;
b[x1][y2+1] -= c;
b[x2+1][y2+1] += c;
}

int main()
{
int n,m,q;
scanf("%d%d%d", &n, &m, &q);

for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
scanf("%d",&a[i][j]);
}
}
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
op(i,i,j,j,a[i][j]);
}
}

int x1,x2,y1,y2,c;
while(q--){
scanf("%d%d%d%d%d", &x1, &y1 , &x2, &y2, &c);
op(x1,x2,y1,y2,c);
}

for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
b[i][j] = b[i][j] + b[i][j-1] + b[i-1][j] - b[i-1][j-1];
}
}

for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
printf("%d ",b[i][j]);
}
puts("");
}


return 0;
}

双指针算法

  • 基本模版

    1
    2
    3
    4
    5
    6
    for(i = 0,j = 0;i < n;i++){
    while(j<i && check(i,j))
    j++;

    // ......
    }
  • 核心思想

    将O(n^2^)算法优化到O(n)

799.最长连续不重复子序列

给定一个长度为 n 的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。

输入格式

第一行包含整数 n。
第二行包含 n 个整数(均在 0∼1050∼105 范围内),表示整数序列。

输出格式

共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。

数据范围

1≤n≤105

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <cstring>
#include <algorithm>


using namespace std;

const int N = 1e5 + 10;
int n;
int a[N],s[N];


int main()
{
scanf("%d", &n);
for(int i = 0;i < n;i++) {
scanf("%d", &a[i]);
}
int maxLen = 0;
for (int i = 0,j = 0; i < n; i++){
s[a[i]]++;
while(s[a[i]] > 1){
s[a[j]]--;
j++;
}
maxLen = max(maxLen,i - j + 1);
}
cout << maxLen;
return 0;
}

800.数组元素的目标和

给定两个==升序排序==的有序数组 A 和 B,以及一个目标值 x。
数组下标从 0 开始。
请你求出满足 A[i]+B[j]=x 的数对 (i,j)。
数据保证有唯一解。

输入格式
第一行包含三个整数 n,m,x ,分别表示 A 的长度,B 的长度以及目标值 x
第二行包含 n 个整数,表示数组 A
第三行包含 m 个整数,表示数组 B

输出格式
共一行,包含两个整数 i 和 j

数据范围
数组长度不超过 10^5^
同一数组内元素各不相同。
1 ≤ 数组元素 ≤ 10^9^

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

int n,m,x;
int A[N],B[N];


int main()
{
scanf("%d%d%d", &n, &m, &x);
for (int i = 0; i < n; i++) {
scanf("%d", &A[i]);
}
for (int i = 0; i < m; i++) {
scanf("%d", &B[i]);
}
int i, j;
for(i = 0, j = m - 1; i < n; i++) {
while (j >= 0 && A[i] + B[j] > x) j--;
if(j >= 0 && A[i] + B[j] == x) {
cout << i << ' ' << j;
return 0;
}
}
return 0;
}

位运算

n的二进制表示中的第k位是几?

1
2
3
4
int n = 10;
for(int k = 3;k >= 0;k--){
cout << (n>>k & 1);
}

lowbit(x) 返回x的最后一位1

x & -x = x & (~x + 1)

eg:

x = 1010……..==1==000……0
x = 0101……..==0==111……1
~x + 1 = 0101……..==1==000……0
x & (
x + 1) = 0000……..==1==000……0

1
2
int x;
cout << (x & -x);

801.二进制中1的个数

给定一个长度为 n 的数列,请你求出数列中每个数的二进制表示中 1 的个数。

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int lowbit(int x) // 返回末尾的1
{
return x & -x;
}

int main()
{
int n;
cin >> n;
while (n--){
int x;
cin>>x;
int res = 0;
while(x){
x -= lowbit(x);
res++;
}
cout<<res<<' ';
}
return 0;
}

(整数)离散化

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vector<int> alls; // 存储所有待离散化的值
sort(alls.begin(), alls.end()); // 将所有值排序
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素

// 二分求出x对应的离散化的值
int find(int x) // 找到第一个大于等于x的位置
{
int l = 0, r = alls.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (alls[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1; // 映射到1, 2, ...n
}

802.区间和 难☹️

假定有一个无限长的数轴,数轴上每个坐标上的数都是 0
现在,我们首先进行 n 次操作,每次操作将某一位置 x 上的数加 c
接下来,进行 m 次询问,每个询问包含两个整数 l 和 r,你需要求出在区间 [l,r] 之间的所有数的和

输入格式

第一行包含两个整数 n 和 m
接下来 n 行,每行包含两个整数 x 和
再接下来 m 行,每行包含两个整数 l 和 r

输出格式

共 m 行,每行输出一个询问中所求的区间内数字和。

数据范围

−10^9^≤x≤10^9^,
1≤n,m≤10^5^
−10^9^≤l≤r≤10^9^
−10000≤c≤10000

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

typedef pair<int, int> PII;

const int N = 300010;
int n,m;
int a[N],s[N];

vector<int> alls;
vector<PII> add, query;

int find(int x) // 二分
{
int l = 0, r = alls.size() - 1;
while(l<r){
int mid = l+r >> 1;
if(alls[mid] >= x){
r = mid;
}
else{
l = mid + 1;
}
}
return r + 1;
}


int main()
{
scanf("%d%d", &n, &m);
while (n--) {
int x,c;
scanf("%d%d", &x, &c);
add.push_back({x,c});

alls.push_back(x);
}
while (m--) {
int l,r;
scanf("%d%d", &l, &r);
query.push_back({l,r});
alls.push_back(l);
alls.push_back(r);
}

// 排序 & 去重
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(), alls.end()),alls.end());
// 处理插入
for(auto item : add){
int x = find(item.first);
a[x] += item.second;
}
// 预处理前缀和
for(int i = 1;i <= alls.size(); i++){
s[i] = s[i-1] + a[i];
}
// 处理询问
for(auto item : query){
int l = find(item.first);
int r = find(item.second);
cout << s[r] - s[l-1] << endl;
}
return 0;
}

注意:

  1. vector<int> alls 中存的是需要离散化的下标

  2. const int N = 300010; 查询最多10^5^次,插入最多10^5^次,最多需要离散化2*查询+插入 = 3*10^5^次

  3. 实现unique函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    vector<int>::iterator unique(vector<int> &a)
    {
    for(int i = 0;i < a.size(); i++){
    if(!i || a[i]!=a[i-1]){
    a[j++] = a[i];
    }
    }
    return a.begin() + j;
    }

    alls.erase(unique(alls.begin(), alls.end()),alls.end());改为

    alls.erase(unique(alls),alls.end());

区间合并

  1. 按区间左端点排序
  2. 扫描整个区间,把所有可能有交集的区间进行合并

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 将所有存在交集的区间合并
void merge(vector<PII> &segs)
{
vector<PII> res;

sort(segs.begin(), segs.end());

int st = -2e9, ed = -2e9;
for (auto seg : segs)
if (ed < seg.first)
{
if (st != -2e9) res.push_back({st, ed});
st = seg.first, ed = seg.second;
}
else ed = max(ed, seg.second);

if (st != -2e9) res.push_back({st, ed});

segs = res;
}

803. 区间合并

给定 n 个区间 [li,ri],要求合并所有有交集的区间。
注意如果在端点处相交,也算有交集。
输出合并完成后的区间个数。

Eg. [1,3] & [2,6] -> [1,6]

输入格式

第一行包含整数 n。
接下来 n 行,每行包含两个整数 l 和 r。

输出格式

共一行,包含一个整数,表示合并区间完成后的区间个数。

数据范围

1 ≤ n ≤ 100000
−10^9^ ≤ li ≤ ri ≤ 10^9^

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;
typedef pair<int,int> PII;

vector<PII> segs;

void merge(vector<PII> &segs){
vector<PII> res;

sort(segs.begin(),segs.end());

int st = -2e9, ed = -2e9;
for(auto seg : segs){
if(ed < seg.first){
if(st!= -2e9) res.push_back({st,ed});
st = seg.first;
ed = seg.second;
}
else ed = max(ed, seg.second);
}
if(st!= -2e9) res.push_back({st,ed});

segs = res;
}

int main()
{
int n;
scanf("%d", &n);
while (n--) {
int l,r;
scanf("%d%d", &l, &r);
segs.push_back({l,r});
}

merge(segs);

cout << segs.size() << endl;

return 0;

}

Chapter2 数据结构

链表

笔试中一般不采用动态链表

单链表

826. 单链表

实现一个单链表,链表初始为空,支持三种操作:

  1. 向链表头插入一个数;
  2. 删除第 k 个插入的数后面的数;
  3. 在第 k 个插入的数后插入一个数。

现在要对该链表进行 M 次操作,进行完所有操作后,从头到尾输出整个链表。

注意: 题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。

输入格式

第一行包含整数 M,表示操作次数。

接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:

  1. H x,表示向链表头插入一个数 x。
  2. D k,表示删除第 k 个插入的数后面的数(当 k 为 0 时,表示删除头结点)。
  3. I k x,表示在第 k 个插入的数后面插入一个数 x(此操作中 k 均大于 00)。

输出格式

共一行,将整个链表从头到尾输出。

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

/* head存储链表头,
e[i]存储节点i的值,
ne[i]存储节点i的next指针,
idx表示当前用到了哪个节点
*/
int head, e[N], ne[N];
int idx;

void init() // 初始化
{
head = -1;
idx = 0;
}

void insert_head(int x) // 插入头节点
{
e[idx] = x;
ne[idx] = head;
head = idx;
idx++;
}

void insert(int k, int x) // 在k坐标后插入节点(值为x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx++;
}

void remove_head() // 删除头节点head
{
head = ne[head];
}

void remove(int k) // 删除下标k后的点
{
ne[k] = ne[ne[k]];
}

int main()
{
int M;
scanf("%d", &M);
init();
while (M--) {
int k, x;
char op;

cin >> op;
if (op == 'H') {
cin >> x;
insert_head(x);
}
else if (op == 'D') {
cin >> k;
if (k == 0) {
remove_head();
}
remove(k - 1);
}
else {
cin >> k >> x;
insert(k - 1,x);
}
}

for(int i = head; i != -1; i = ne[i]){
cout << e[i] << ' ';
}
cout << endl;

return 0;
}

双链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;

// 初始化
void init()
{
//0是左端点,1是右端点
r[0] = 1, l[1] = 0;
idx = 2;
}

// 在节点a的右边插入一个数x
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}

// 删除节点a
void remove(int a)
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
827. 双链表

实现一个双链表,双链表初始为空,支持 55 种操作:

  1. 在最左侧插入一个数;
  2. 在最右侧插入一个数;
  3. 将第 k 个插入的数删除;
  4. 在第 k 个插入的数左侧插入一个数;
  5. 在第 k 个插入的数右侧插入一个数

现在要对该链表进行 M 次操作,进行完所有操作后,从左到右输出整个链表。

注意:题目中第 k 个插入的数并不是指当前链表的第 k 个数。例如操作过程中一共插入了 n 个数,则按照插入的时间顺序,这 n 个数依次为:第 1 个插入的数,第 2 个插入的数,…第 n 个插入的数。

输入格式

第一行包含整数 M,表示操作次数。

接下来 M 行,每行包含一个操作命令,操作命令可能为以下几种:

  1. L x,表示在链表的最左端插入数 x。
  2. R x,表示在链表的最右端插入数 x。
  3. D k,表示将第 k 个插入的数删除。
  4. IL k x,表示在第 k 个插入的数左侧插入一个数。
  5. IR k x,表示在第 k 个插入的数右侧插入一个数。

输出格式

共一行,将整个链表从左到右输出。

数据范围

1 ≤ M ≤ 100000
所有操作保证合法。

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;
int m;
int e[N], l[N], r[N], idx;

//初始化
//0号点:head pointer
//1号点:tail pointer
void init()
{
r[0] = 1;
l[1] = 0;
idx = 2;
}

//在k号点右边边插入值为x的点
void insert_right(int k, int x)
{
e[idx] = x;
l[idx] = k;
r[idx] = r[k];
l[r[k]] = idx;
r[k] = idx;
idx++;
}

//在k号点左边插入值为x的点:直接实现
void insert_left(int k, int x)
{
e[idx] = x;
l[idx] = l[k];
r[idx] = k;
r[l[k]] = idx;
l[k] = idx;
idx++;
}

//在k号点左边插入值为x的点:调用法
void insert_left_call(int k, int x)
{
insert_right(l[k], x);
}

//删除第k个点
void delete_k(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}


int main()
{
scanf("%d", &m);
init();
while (m--){
string op;
cin >> op;
if(op == "L"){
int x;
cin >> x;
insert_right(0, x);
}
else if(op == "R"){
int x;
cin >> x;
insert_left(1, x);
}
else if(op == "D"){
int k;
cin >> k;
delete_k(k + 1);
}
else if(op == "IL"){
int k, x;
cin >> k >> x;
insert_left(k + 1, x);
}
else if(op == "IR"){
int k, x;
cin >> k >> x;
insert_right(k + 1, x);
}
}

for(int i = r[0]; i != 1; i = r[i]){
cout << e[i] << " ";
}
cout << endl;

return 0;
}

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

int stk[N], tt;

int m;

void init()
{
tt = 0;
}

void push(int x)
{
stk[++tt] = x;
}

void pop()
{
tt--;
}

bool isEmpty()
{
if(tt > 0)
return false;
else
return true;
}

int query()
{
return stk[tt];
}

int main()
{
scanf("%d", &m);
init();
while(m--){
string op;
cin >> op;
if(op == "push"){
int x;
cin >> x;
push(x);
}
else if(op == "pop"){
pop();
}
else if(op == "empty"){
string e;
e = isEmpty() ? "YES" : "NO";
cout << e << endl;
}
else if(op == "query"){
cout << query() << endl;
}
}
return 0;
}

队列

队尾插入,队头推出

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

int m;
int q[N], hh, tt;

void init(){
tt = -1;
hh = 0;
}

void push(int x){
q[++tt] = x;
}

void pop(){
hh++;
}

bool isEmpty(){
return hh <= tt ? false : true;
}

int query(){
return q[hh];
}

int main()
{
scanf("%d", &m);
init();
while(m--){
string op;
cin >> op;
if(op == "push"){
int x;
cin >> x;
push(x);
}
else if(op == "pop"){
pop();
}
else if(op == "empty"){
string e;
e = isEmpty() ? "YES" : "NO";
cout << e << endl;
}
else if(op == "query"){
cout << query() << endl;
}
}
return 0;
}

单调栈

830. 单调栈

给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。

输入格式

第一行包含整数 N,表示数列长度。

第二行包含 N 个整数,表示整数数列。

输出格式

共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。

数据范围

1 ≤ N ≤ 10^5^
1 ≤ 数列中元素 ≤ 10^9^

样例

3 4 2 7 5
-1 3 -1 2 2

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>

using namespace std;

const int N = 100010;

int n;
int stk[N], tt;

int main()
{
scanf("%d", &n);
while (n--){
int x;
cin >> x;
while(tt && stk[tt] >= x) tt--;
if(tt)
// cout << stk[tt] << ' ';
printf("%d ",stk[tt]);
else
// cout << -1 << ' ';
printf("-1 ");

stk[++tt] = x;
}
}

单调队列

154. 滑动窗口——难

给定一个大小为 n≤10^6^ 的数组。

有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。
你只能在窗口中看到 k 个数字。
每次滑动窗口向右移动一个位置。

以下是一个例子:

该数组为 [1 3 -1 -3 5 3 6 7],k 为 3。

窗口位置 最小值 最大值
[1 3 -1] -3 5 3 6 7 -1 3
1 [3 -1 -3] 5 3 6 7 -3 3
1 3 [-1 -3 5] 3 6 7 -3 5
1 3 -1 [-3 5 3] 6 7 -3 5
1 3 -1 -3 [5 3 6] 7 3 6
1 3 -1 -3 5 [3 6 7] 3 7

你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式

输入包含两行。

第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度。
第二行有 n 个整数,代表数组的具体数值。

同行数据之间用空格隔开。

输出格式

输出包含两个。

第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。

输入样例

1
2
8 3
1 3 -1 -3 5 3 6 7

输出样例

1
2
-1 -3 -3 -3 3 3
3 3 5 5 6 7

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <cstdio>

using namespace std;

const int N = 1000010;

int n, k;
int a[N], q[N], tt, hh;

int main()
{
scanf("%d%d", &n, &k);

for (int i = 0; i < n; i++) {
scanf("%d", &a[i]);
}

//最小值
hh = 0;
tt = -1;
for (int i = 0; i < n; i++) {

// 判断队头是否已经滑出窗口
if(hh <= tt && i - k + 1 > q[hh]){
hh++;
}
while(hh <= tt && a[q[tt]] > a[i]){
tt--;
}
q[++tt] = i;
if(i >= k-1){
printf("%d ", a[q[hh]]);
}
}
printf("\n");
//最大值
hh = 0;
tt = -1;
for (int i = 0; i < n; i++) {

// 判断队头是否已经滑出窗口
if(hh <= tt && i - k + 1 > q[hh]){
hh++;
}
while(hh <= tt && a[q[tt]] <= a[i]){
tt--;
}
q[++tt] = i;
if(i >= k-1){
printf("%d ", a[q[hh]]);
}
}
return 0;
}

理解

image-20230721205330280

init() : tt = -1, hh = 0; // 此时hh>tt 队空

窗口应该在(i-k+1)到i范围内

  • 出队判定 : 队列不为空& 队头下标 q[hh] ≥ i - k + 1
  • (i-k+1)到i范围内 ,如果==队尾下标对应值比i下标对应值还要大==就丢弃(此时是以(i-k+1)到i为窗口范围,但实际i还未入队)
  • i下标入队 q[++tt] = i;

Leetcode

剑指 Offer 59 - I. 滑动窗口的最大值

KMP算法