判断子序列
问题描述
给定两个整数序列 a 和 b,长度分别为 n 和 m。判断 a 是否为 b 的子序列。子序列的定义是原序列中一部分项按原有次序排列的序列,例如 {a1, a3, a5} 是 {a1, a2, a3, a4, a5} 的一个子序列。
解题思路
使用双指针法遍历两个序列。指针 i 指向序列 a,指针 j 指向序列 b。通过比较 a[i] 和 b[j] 的值,逐步移动指针,最终判断 a 是否完全匹配 b 的某个子序列。
代码实现
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int a[N], b[N];
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < m; i++) scanf("%d", &b[i]);
int i = 0, j = 0;
while (i < n && j < m)
{
//n <= m,i指向子串,j指向原串
//匹配时i,j都向后移动;
//不匹配时,j向后移动
if (a[i] == b[j]) i++;
j++;
}
if (i == n) cout << "Yes";
else cout << "No";
return 0;
}
代码解析
-
输入处理
- 读取序列
a的长度n和序列b的长度m。 - 分别读取序列
a和b的所有元素。
- 读取序列
-
双指针遍历
- 初始化指针
i和j为 0,分别指向序列a和b的起始位置。 - 在循环中比较
a[i]和b[j]:- 如果相等,移动指针
i(表示匹配到一个元素)。 - 无论是否匹配,移动指针
j(继续检查b的下一个元素)。
- 如果相等,移动指针
- 初始化指针
-
结果判断
- 如果
i等于n,说明a的所有元素都在b中按顺序匹配成功,输出 “Yes”。 - 否则,输出 “No”。
- 如果
复杂度分析
- 时间复杂度:O(m),因为需要遍历整个序列
b。 - 空间复杂度:O(1),仅使用了常数个额外变量。
示例测试
示例 1
输入:
3 5
1 3 5
1 2 3 4 5
输出:
Yes
解释:序列 {1, 3, 5} 是 {1, 2, 3, 4, 5} 的子序列。
示例 2
输入:
4 6
1 4 5 7
1 2 3 4 5 6
输出:
No
解释:序列 {1, 4, 5, 7} 不是 {1, 2, 3, 4, 5, 6} 的子序列(7 不存在于 b 中)。
二进制位操作的原理与实现
在计算机科学中,二进制位操作是一种基础且重要的技术。理解如何提取二进制数的某一位对于处理底层数据结构和算法至关重要。以下内容将详细解释二进制位操作的原理,并提供相应的代码实现。
二进制位的基本概念
一个二进制数由多个位组成,每个位只能是0或1。例如,二进制数1101可以分解为:
- 第3位:1
- 第2位:1
- 第1位:0
- 第0位:1
提取二进制某一位的方法
要提取二进制数的第k位,可以通过以下步骤实现:
- 将二进制数右移k位,使得目标位移动到最低位(第0位)。
- 对移动后的数与1进行按位与(AND)运算。这将屏蔽其他位,仅保留最低位的值。
数学表达式为:
第k位的值 = (n >> k) & 1
代码实现
以下是一个完整的C++示例,展示如何提取一个数的每一位并打印其二进制表示:
#include <iostream>
using namespace std;
const int N = 100010;
int a[N];
int main() {
int n = 10; // 要转换的数字
// 输出每一位,从最高位开始
for (int k = 3; k >= 0; k--) {
printf("%d", (n >> k) & 1);
}
return 0;
}
代码解析
- 变量定义:
n为待处理的十进制数,这里以10为例。 - 循环结构:循环从最高位(第3位)开始,逐步处理到最低位(第0位)。
- 位操作:每次循环中,将
n右移k位后与1进行按位与运算,结果即为第k位的值。 - 输出:直接打印每一位的值,最终得到
n的二进制表示。
应用场景
这种方法在以下场景中非常有用:
- 二进制数的解析与转换。
- 位掩码的设计与实现。
- 硬件编程中的寄存器操作。
总结
通过右移和按位与运算,可以高效地提取二进制数的任意一位。这种方法不仅简单,而且计算速度快,适合在各种编程环境中使用。
二进制中1的个数
问题描述
给定一个长度为n的数列,要求计算每个数的二进制表示中1的个数。
解决方法
方法一:逐位检查法
通过移位操作逐位检查每一位是否为1。对于32位整数,最多需要检查32次。
int countBits(int num) {
int count = 0;
for (int i = 0; i < 32; ++i) {
if (num & (1 << i)) {
count++;
}
}
return count;
}
方法二:lowbit优化法
利用二进制补码特性快速定位最右侧的1,每次减去该位直到数值归零,统计操作次数即为1的个数。
int lowbit(int x) {
return x & (-x); // 等价于x & (~x + 1)
}
int countBits(int x) {
int count = 0;
while (x) {
x -= lowbit(x);
count++;
}
return count;
}
方法三:Brian Kernighan算法
通过n & (n-1)直接消除最右侧的1,循环次数等于1的个数,效率更高。
int countBits(int n) {
int count = 0;
while (n) {
n &= (n - 1);
count++;
}
return count;
}
方法四:查表法(空间换时间)
预处理0-255所有数的1的个数,分段查表减少计算次数,适合大规模数据。
int table[256];
void initTable() {
for (int i = 0; i < 256; ++i) {
table[i] = (i & 1) + table[i >> 1];
}
}
int countBits(int n) {
return table[n & 0xFF] + table[(n >> 8) & 0xFF] +
table[(n >> 16) & 0xFF] + table[(n >> 24) & 0xFF];
}
性能对比
| 方法 | 时间复杂度 | 适用场景 |
|---|---|---|
| 逐位检查法 | O(32) | 简单通用 |
| lowbit优化法 | O(k), k为1的个数 | 需要快速定位1 |
| Brian Kernighan法 | O(k) | 高效消除1 |
| 查表法 | O(1) | 大规模数据预处理 |
完整代码示例
结合lowbit方法实现输入输出:
#include<iostream>
using namespace std;
const int N = 100010;
int a[N];
int lowbit(int x) {
return x & -x;
}
int main() {
int n;
scanf("%d", &n);
while (n--)
{
int x, count = 0;
scanf("%d", &x);
while (x)
{
x -= lowbit(x);
count++;
}
printf("%d ", count);
}
return 0;
}
扩展思考
- 负数处理:上述方法均适用于补码表示的负数。
- 并行计算:可通过位操作并行统计多个位的1的个数。
- 硬件指令:部分CPU提供
POPCNT指令可直接获取1的个数。
unique函数的实现
unique函数的分析
实现一个高效的去重函数是算法和数据处理中的常见需求。以下是一个针对已排序容器的unique函数实现,其核心思想是将不重复元素移动到容器前部,并返回无重复元素部分的结束迭代器。
实现代码
#include <iostream>
#include <vector>
using namespace std;
vector<int>::iterator unique(vector<int> &a)
{
//j <= i
//j指向的是不重复元素的下一个位置
//i是用来遍历整个数组的
int j = 0;
for (int i = 0; i < a.size(); i++)
{
//如果是第一个元素或你和前面的元素不等,那么你就是不重复元素
if (i == 0 || a[i] != a[i - 1])
{
a[j] = a[i];
j++;
}
//[0, j - 1]是不重复元素
}
//返回的是最后一个不重复元素的下一个位置
return a.begin() + j;
}
int main()
{
vector<int> a = {1, 1, 1, 2, 3, 4, 4, 4, 4, 5, 5, 6};
auto iter = unique(a);
for (auto it = a.begin(); it < iter; it++)
{
cout << *it << " ";
}
cout << endl;
return 0;
}
实现原理
- 输入要求:容器必须是已排序的,否则无法保证正确去重。
- 双指针法:使用变量
j指向不重复元素的下一个位置,i用于遍历整个数组。 - 条件判断:当前元素是第一个元素或与前一个元素不相同时,将其移动到位置
j并递增j。 - 结果范围:
[0, j - 1]为不重复元素的范围,函数返回a.begin() + j作为结束迭代器。
示例输出
输入容器为{1, 1, 1, 2, 3, 4, 4, 4, 4, 5, 5, 6}时,输出为:
1 2 3 4 5 6
复杂度分析
- 时间复杂度:O(n),仅需一次遍历。
- 空间复杂度:O(1),原地操作,无需额外空间。
扩展说明
- 与STL的
std::unique对比:STL的实现原理类似,但返回的是逻辑上无重复的结束迭代器,原容器剩余部分的值未定义。 - 非排序容器的去重:需先排序(O(nlogn)),再使用此方法或哈希表法(O(n)空间)。
注意事项
- 确保容器已排序,否则结果错误。
- 函数不改变容器大小,仅逻辑上标记无重复部分,可通过
erase清理剩余部分。
算法内容来自AcWing算法基础课,感谢AcWing老师的详细讲解。
2098

被折叠的 条评论
为什么被折叠?



