基础算法【算法习题及模板】下

目录

前缀和与差分

前缀和

子矩阵的和

差分

差分矩阵

双指针算法

最长连续不重复子序列

数组元素的目标和

判断子序列

位运算

二进制中1的个数

离散化

区间和

区间合并


前缀和与差分

前缀和

输入一个长度为n的整数序列
接下来再输入 m个询问,每个询问输入一对l,r
对于每个询问,输出原序列中从第 个数到第r个数的和
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列
接下来m行,每行包含两个整数1和r,表示一个询问的区间范围
输出格式
共m行,每行输出一个询问的结果
数据范围
1<l<r<n,
1<n,m < 100000.
-1000 < 数列中元素的值 < 1000

输入样例:

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

输出样例:

3
6
10

思路:先把1-n的每个的前缀和用s数组记录下来(前缀和:就是当前这个数以及前面所有数的和),求l - r区间的总和就是s[r] - s[l] + a[l]; (减去s[l]把a[l]也减去了,所以最后要加上a[l])

解题代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 100100;
int a[N], s[N];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i ++){
        cin >> a[i];
        if(i!=1)s[i] = s[i-1]+a[i];//不是第一个数,那就前面的数之和+当前数
    }
    while(m--){
        int l, r;
        cin >> l >> r;
        cout << s[r] - s[l] + a[l] << "\n";
    }
    return 0;
}

子矩阵的和

输入一个 n 行 m列的整数矩阵,再输入 q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标
对于每个询问输出子矩阵中所有数的和
输入格式
第一行包含三个整数n,m,q。
接下来n行,每行包含 m个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1, x2, y2,表示一组询问
输出格式
共q 行,每行输出一个询问的结果
数据范围
1 <= n,m < =1000
1<= q <= 200000
1 <= x1 <= x2 <= n,
1  <= y1 <= y2 <= m;
-1000 <= 矩阵内元素的值 <= 1000

输入样例:

3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

输出样例:

17
27
21

解题思路:

 

#include <bits/stdc++.h>
using namespace std;
int a[1010][1010], s[1010][1010];
int main()
{
    int n, m, q;
    cin >> n >> m >> q;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            cin >> a[i][j];
            if(i==1 && j== 1)s[i][j] = a[i][j];//第1行第1列
            else if(i==1)s[i][j] = s[i][j-1] + a[i][j];//第1行
            else if(j==1)s[i][j] = s[i-1][j] + a[i][j];//第1列
            else{
                //见图1
                s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
            }
        }
    }
    while(q--){
        int x1, x2, y1, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        //见图2
        cout << (s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1]) <<"\n";
    }
    return 0;
}

差分

输入一个长度为 n的整数序列
接下来输入 m 个操作,每个操作包含三个整数l,r,c,表示将序列中[l, r]之间的每个数加上c。
请你输出进行完所有操作后的序列
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数序列。
接下来 m行,每行包含三个整数l,r,c,表示一个操作。
输出格式
共一行,包含 n 个整数,表示最终序列。
数据范围
1 <= n,m <= 100000.
1<= l <= r <= n,
-1000 <= c <= 1000
-1000<= 整数序列中元素的值 <= 1000

输入样例:

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

输出样例:

3 4 5 3 4 2

思路:a为原数组,题意为在[l,r]区间每个元素+x,用b数组在l位置+x, r+1位置-x,前缀和之后就是题意的意思,现在先m次操作,在b标记,然后再用s数组来前缀和,s数组的元素就是原数组每个元素需要改变的值,将s数组与原数组a相加,之后得到的元素就是改变后的元素。

解题代码

#include <bits/stdc++.h>
using namespace std;
const int N = 100100;
int a[N], b[N], s[N];
int main()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    while(m--){
        int l, r , x;
        cin >> l >> r >> x;
        b[l] += x; b[r+1] -= x;
    }
    for(int i = 1; i <= n; i++)s[i] = s[i-1] + b[i];
    for(int i = 1; i <= n; i++){
        cout << (s[i] + a[i]) << " ";
    }
    return 0;
}

差分矩阵

输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数 x1, y1, x2, y2, c,其中(x1,y1)和(x2,y2)表示一个子矩阵的左上角坐标和右下角坐标
每个操作都要将选中的子矩阵中的每个元素的值加上c。
请你将进行完所有操作后的矩阵输出
输入格式
第一行包含整数n,m, q
接下来n行,每行包含m个整数,表示整数矩阵
接下来 q 行,每行包含 5 个整数 x1, y1, x2, y2, c,表示一个操作。
输出格式
共n行,每行m个整数,表示所有操作进行完毕后的最终矩阵
数据范围
1 ≤ n,m ≤ 1000
1≤ 4 ≤ 100000
1 ≤ x1 ≤ x2 ≤  n,
1 ≤  y1 ≤ y2 ≤ m,
-1000 ≤ c≤ 1000,
-1000≤ 矩阵内元素的值 ≤ 1000

输入样例:

3 4 3
1 2 2 1
3 2 2 1
1 1 1 1
1 1 2 2 1
1 3 2 3 2
3 1 3 4 1

输出样例:

2 3 4 1
4 3 4 1
2 2 2 2

思路:差分矩阵与差分类似,先逐行差分(将二维数组化为一维),每行都标记b数组,并进行前缀和,求出每个元素要改变的值,最后与原数组相加,得到改变后的数组。

解题代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N][N], b[N][N], s[N][N];
int main()
{
    int n, m, q;
    cin >> n >> m >> q;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            cin >> a[i][j];
        }
    }
    while(q--){
    	int x1, y1, x2, y2, c;
    	cin >> x1 >> y1 >> x2 >> y2 >> c;
    	for(int i = x1; i <= x2; i++){//在x1 ~ x2每行都标记b数组
    		b[i][y1] += c;
    		b[i][y2 + 1] -= c;
		}
	}
	for(int i = 1; i <= n; i++){//对每一行都前缀和,求每个元素要改变的值
		for(int j = 1; j <= m; j++){
			s[i][j] = s[i][j-1] + b[i][j];
		}
	}
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++){
			cout << s[i][j] + a[i][j] << " ";//原数组+改变值 = 现数组
		}
		cout << "\n";
	}
    
    return 0;
}

双指针算法

最长连续不重复子序列

给定一个长度为n的整数序列,请找出最长的不包含重复的数的连续区间,输出它的长度。
输入格式
第一行包含整数n。
第二行包含n个整数(均在0~ 10^5范内),表示整数序列
输出格式
共一行,包含一个整数,表示最长的不包含重复的数的连续区间的长度。
数据范围
1 ≤ n ≤ 10^5
输入样例:

5
1 2 2 3 5

输出样例:

3

思路:构造以a[i]为结尾的连续不重复区间,遍历a[i],如果s[a[i]] > 1,说明a[i]重复了,用另一个指针 j, 从头开始,走一步这个区间就删掉一个数,知道把重复的a[i]删掉,剩下的就是不重复且连续的,每次操作取最大值,最终得到最长连续不重复区间。

tips: 双指针的核心思想是把O(N^2)的复杂度降为O(N)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], s[N];
int main()
{
    int n;
    cin >> n;
    for(int i = 0; i < n; i++)cin >> a[i];
    int mx = 0;
    //构造以 a[i]为结尾的最长连续不重复区间 
    for(int i = 0, j = 0; i < n; i++){
    	s[a[i]]++;//记录数组中a[i]出现的次数 
    	//从头开始,把前面的元素一个一个排掉,直到排到与a[i]重复的 
        while(j < i && s[a[i]] > 1)s[a[j++]]--;
        mx = max(mx, i - j + 1);//最大值 
    }
    cout << mx;
    return 0;
}

数组元素的目标和

给定两个升序排序的有序数组 A和B,以及一个目标值 
数组下标从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
输入样例:

4 5 6
1 2 4 7
3 4 6 8 9

输出样例:

1 1

 思路:
方法1:通过遍历a数组,再通过二分找到要达到目标值的b数组元素。
方法2:双指针,i 指针从前往后遍历 a数组, j指针从后往前遍历b数组


方法3:直接暴力枚举,复杂度 O(N^2),这里就不写代码了

解题代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int n, m, x;
//遍历 + 二分 
void solve1()
{
	for(int i = 0; i < n; i++){
        //二分找b[j]
		int l = 0, r = m -1;
		while(l < r){
			int mid = (l + r + 1) >> 1;
			if(b[mid] + a[i] > x){//两个数的和超过了x,第二个数往小的找
				r = mid - 1; 
			}
			else{
				l = mid;
			}
		}
		if(a[i] + b[l]==x){
	        cout << i <<" " << l <<"\n";
			break; 
		}
	}
}
//双指针 
void solve2()
{
	int i = 0, j = m-1;
	for(; i < n; i++){
        while(j >= 0 && b[j] + a[i] > x)j--;
        
        if(a[i]+b[j]==x)break;
	}
	cout << i << " " << j;
}
int main()
{
	cin >> n >> m >> x;
	for(int i = 0; i < n; i++)cin >> a[i];
	for(int i = 0; i < m; i++)cin >> b[i];
//	solve1();
	solve2();
	return 0;
}

判断子序列

给定一个长度为n的整数序列 a1,a2...,an 以及一个长度为 m的整数序列 b1,b2,..·.,bm。
请你判断 a序列是否为b序列的子序列
子序列指序列的一部分项按原有次序排列而得的序列,例如序列[a1,a3,a5]是序列{a1,a2,a3,a4,a5}的一个子序列。
输入格式
第一行包含两个整数n,m。
第二行包含n 个整数,表示 a1,a2,..·,an。
第三行包含 m 个整数,表示 bl,b2,...,bm。
输出格式
如果a序列是6序列的子序列,输出一行 Yes
否则,输出 Noo
数据范围
1≤n≤m≤10^5,
−10^9≤ai,bi≤10^9

输入样例:

3 5
1 3 5
1 2 3 4 5

输出样例:

Yes

思路:用两个指针,i 在a数组里面找,j 在b数组里面找;如果数组a是b的子序列,它的元素出现的次序也和在b出现的次序一样,当a数组的元素当前指针 i 在b中当前指针 j 匹配 就往后找a数组下下一个元素;不匹配,指针j就要往后找;当a数组的每个元素都匹配到了,则a序列是b序列的子序列。

解题代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], b[N];
int main()
{
	int n, m;
	cin >> n >> m;
	for(int i = 0; i < n; i++)cin >> a[i];
	for(int i = 0; i < m; i++)cin >> b[i];
	int i = 0, j = 0;
	for(; j < m && i < n;){
		if(a[i]==b[j]){//相等两个指针都往后走
		    i++; j++;
		}else{//j往b数组后面找
		    j++;
		}
	}
	if(i == n)//a数组每个数都能在b找到
	    puts("Yes");
	else
	    puts("No");
	return 0;
}

位运算

求 n 的第 k 位数字: n >> k & 1;   返回n的最后一位1: lowbit(n) = n & -n;

将n二进制的最低位1移除:n & (n - 1);

0 & 0 = 0; 0 & 1 = 0; 1 & 1 = 1;

0 | 0 = 0; 0 | 1 = 1; 1 | 1 = 1

0 ^ 0 = 0; 0 ^ 1 = 1; 1 ^ 1 = 0

对于任何数:n ^ n = 0; n ^ 0 = n

自反性: a ^ b ^ b = a ^ 0 = a;

二进制中1的个数

给定一个长度为n的数列,请你求出数列中每个数的二进制表示中1的个数。
输入格式
第一行包含整数n。
第二行包含n个整数,表示整个数列。
输出格式
共一行,包含n个整数,其中的第个数表示数列中的第个数的进制表示中1的个数。
数据范围
1 ≤ n ≤  100000,
0 ≤  数列中元素的值 ≤  10^9

输入样例:

5
1 2 3 4 5

输出样例:

1 1 2 1 2
#include <bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    cin >> n;
    while(n--)
    {
        long long x;
        int s = 0;
        cin >> x;
        while(x){
            if(x & 1) s ++;//当前元素二进制的最后一位是否为1
            x >>= 1;//x 右移一位,相当于 / 2
        }
        cout << s << " ";
    }
    return 0;
}

练习题:

136. 只出现一次的数字 - 力扣(LeetCode)

231. 2 的幂 - 力扣(LeetCode)

离散化

离散化: 把一个值域很大,个数很少的数组 映射 到 较小 的数组里面。即把大而分散的一段稀疏区间,整合映射到连续的较小的稠密区间。

区间和

假定有一个无限长的数轴,数轴上每个坐标上的数都是0。现在,我们首先进行n次操作,每次操作将某一位置上的数加c
接下来,进行m次询问,每个询问包含两个整数 l 和r,你需要求出在区间[l, r]之间的所有数的和。
输入格式
第一行包含两个整数n和m。
接下来 n 行,每行包含两个整数a 和c再接下来 m行,每行包含两个整数 l 和r。
输出格式
共m行,每行输出一个询问中所求的区间内数字和。
数据范围
−10^9≤x≤10^9
1≤n,m≤10^5,
−10^9≤l≤r≤10^9,
−10000≤c≤10000
输入样例:

3 3
1 2
3 6
7 5
1 3
4 6
7 8

输出样例:

8
0
5

思路:每个下标存起来,新的下标为离散化下标,把每个下标排序去重,插入操作要用离散后的下标进行标记,然后进行前缀和,再进行查询操作; 查询操作中要用下标找到离散后的下标,最后得到结果。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
ll a[N], s[N];
vector<ll> alls;
vector<pair<ll, ll> > add, query;

// 返回输入坐标的离散化下标 
ll find(ll x)
{
	ll l = 0, r = alls.size() - 1;
	while(l < r)
	{
		ll mid = l + r >> 1;
		if(alls[mid] >= x){
			r = mid;
		}else{
			l = mid + 1;
		}
	}
	return r + 1;
 } 

int main()
{
	int n, m;
	cin >> n >> m;
	for(int i = 0; i < n; i++){
		ll x, c;
		cin >> x >> c;
		alls.push_back(x);//位置下标存进去 
		add.push_back({x, c});//位置x + c 
	}
	for(int i = 0; i < m; i++){
		ll l, r;
		cin >> 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 e: add){
		ll x = find(e.first);//找到下标对应的离散下标 
		a[x] += e.second; 
	}
	//前缀和
	for(int i = 1; i <= alls.size(); i++)s[i] = s[i-1] + a[i]; 
	//查找 
	for(auto e: query){
		ll l = find(e.first);//找到下标的离散下标 
		ll r = find(e.second); 
		cout << s[r] - s[l-1] << "\n";
	}
	return 0; 
 } 

升级版:利用set,map数组,不用再写一个函数找离散化下标

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
ll a[N], s[N];
vector<ll>alls;
vector<pair<ll, ll> > add, query;
int main()
{
    int n, m;
    cin >> n >> m;
    set<ll> sidx;//下标去重+排序
    for(int i = 0; i < n; i++){
        ll x, c;
        cin >> x >> c;
        add.push_back({x, c});
        sidx.insert(x);
    }
    for(int i = 0; i < m; i++){
        ll l, r;
        cin >> l >> r;
        query.push_back({l, r});
        sidx.insert(l);
        sidx.insert(r);
    }
    map<ll, ll>midx;//每个下标给一个离散下标
    ll cnt = 1;
    for(auto e: sidx){
        midx[e] = cnt++;
    }
    for(auto e: add){
        ll x = midx[e.first];
        a[x] += e.second;
    }
    for(int i = 1; i <= cnt; i++){
        s[i] = s[i-1] + a[i];
    }
    for(auto e: query){
        ll l = midx[e.first];
        ll r = midx[e.second];
        cout << s[r] - s[l-1] << "\n";
    }
    
    
    return 0;
}

区间合并

给定n个区间[li,ri],要求合并所有有交集的区间
注意如果在端点处相交,也算有交集
输出合并完成后的区间个数例如: [1,3]和[2,6]可以合并为一个区间[1,6]
输入格式
第一行包含整数n。
接下来n行,每行包含两个整数 l 和 r。
输出格式
共一行,包含一个整数,表示合并区间完成后的区间个数
数据范围
1≤n≤100000,
−10^9≤li≤ri≤10^9
输入样例:

5
1 2
2 4
5 6
7 8
7 9

输出样例:

3

思路: 记录左右端点,对左端点进行排序,记录当前合并区间的右端点(一开始是第一个区间[~, pre])pre,如果左端点比pre大,说明不能和[~, pre]合并,另开一个合并区间res++; 否则和当前合并区间合并,更新合并区间的右端点(注:如果当前i区间的右端点比pre大,说明合并区间要扩大,更新pre,否则不需要扩大合并区间,不用动pre)。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<pair<ll, ll> > all;
int main()
{
	int n;
	cin >> n;
	while(n--){
		ll l, r;
		cin >> l >> r;
		all.push_back({l, r});
	}
	sort(all.begin(), all.end());
	ll pre = all[0].second, res = 1;
	for(int i = 1; i < all.size(); i++)
	{
		if(all[i].first > pre){
			res ++;
			pre = all[i].second;
		}
		if(all[i].second > pre){
			pre = all[i].second;
		}
	}
	cout << res; 
	return 0;
 } 

 本篇包括了前缀和与差分,双指针算法,位运算,离散化,区间合并算法。每天学一点,离目标更近了一点,加油吧,少年~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值