一.双指针算法
- 时间复杂度:双指针算法通常能将时间复杂度从O(n^2)降低到O(n),因为它们只需遍历数据结构一次。
- 空间复杂度:双指针算法通常不需要额外的空间,空间复杂度为O(1)。
1.从输入的字符串中提取每个单词并逐行输出:
变量 i
和 j
的作用
i
:用于遍历整个字符串。每次循环开始时,i
指向当前未处理的字符。j
:用于找到当前单词的结尾。j
从i
开始,向后移动直到遇到空格或字符串末尾。
通过这种方式,代码可以逐个提取并输出字符串中的每个单词。
#include <iostream>
#include <string.h>
using namespace std;
int main() {
char str[1000];
gets(str); // 从输入中获取字符串
int n = strlen(str); // 获取字符串的长度
for (int i = 0; i < n; i++) { // 遍历整个字符串
int j = i; // 初始化 j 为 i 的值
// 找到从 i 开始的第一个空格或者字符串的结尾
while (j < n && str[j] != ' ')
j++;
// 打印从 i 到 j 之间的字符,即一个单词
for (int k = i; k < j; k++)
cout << str[k];
cout << endl; // 打印单词后换行
i = j; // 更新 i 为 j 的值,继续处理下一个单词
}
return 0;
}
2.输出最长连续不重复子序列:
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int a[N], s[N];
int main()
{
cin >> n; // 输入数组的长度
for (int i = 0; i < n; i++) cin >> a[i]; // 输入数组元素
int res = 0;
for (int i = 0, j = 0; i < n; i++) // 使用双指针遍历数组
{
s[a[i]]++; // 记录当前元素的出现次数
while (s[a[i]] > 1) // 如果当前元素出现次数超过1,说明有重复
{
s[a[j]]--; // 将左指针指向的元素出现次数减1
j++; // 左指针右移
}
res = max(res, i - j + 1); // 更新最长子序列长度
}
cout << res << endl; // 输出结果
return 0;
}
int res = 0;
:用于存储最长子序列的长度。for (int i = 0, j = 0; i < n; i++)
:使用双指针遍历数组,i
是右指针,j
是左指针。s[a[i]]++;
:记录当前元素a[i]
的出现次数。while (s[a[i]] > 1)
:如果当前元素a[i]
出现次数超过1,说明有重复元素。s[a[j]]--;
:将左指针指向的元素出现次数减1。j++;
:左指针右移。
res = max(res, i - j + 1);
:更新最长子序列的长度。
-
这段代码通过双指针和哈希表的方式高效地找出了数组中最长的不包含重复元素的子序列,其时间复杂度为 (O(n))。具体步骤如下:
-
使用双指针
i
和j
遍历数组。 - 使用数组
s
记录每个元素的出现次数。 - 当发现重复元素时,通过移动左指针
j
来缩小窗口,直到没有重复元素。 - 在每次移动右指针
i
时,更新最长子序列的长度
二.位运算
1.按位与
按位与运算的规则是:
- 如果两个对应的位都是
1
,结果位为1
。 - 否则,结果位为
0
。 - 举例:·100&1
逐位比较:
- 第一位(从右数第一位):
0 & 1 = 0
- 第二位:
0 & 0 = 0
- 第三位:
1 & 0 = 0
因此,结果是 000
,即二进制的 0
。
(1)n的二进制表示中第k位是几
步骤
- 右移
k
位:n >> k
:将n
右移k
位,此时第k
位移动到最低位。
- 按位与操作:
(n >> k) & 1
:将右移后的结果与1
进行按位与操作,结果要么是0
要么是1
。
示例
假设我们有一个整数 n = 13
,其二进制表示为 1101
。现在我们要找出第 2
位(从右边数)。
-
右移操作:
n >> 2
:13
的二进制表示是1101
,右移两位后得到11
(即3
的二进制表示)。
-
按位与操作:
(n >> 2) & 1
:11 & 1
结果为1
。
所以,13
的二进制表示中第 2
位是 1
。
代码实现
#include <iostream>
#include <string.h>
using namespace std;
int main()
{
int n = 10;
for (int k = 3; k >= 0; k--)
cout << (n >> k & 1) << endl;
return 0;
}
(2)lowbit(x)
计算输入的每个整数中二进制表示中 1
的个数:
lowbit
函数用于获取整数 x
的二进制表示中最低位的 1
#include <iostream>
using namespace std;
int lowbit(int x)
{
return x & -x;
}
int main()
{
int n;
cin >> n; // 输入整数的数量
while (n--)
{
int x;
cin >> x; // 输入一个整数
int res = 0;
while (x) x -= lowbit(x), res++; // 每次减去 x 的最后一位 1
cout << res << ' ';
}
return 0;
}
示例
假设输入的整数是 13
,其二进制表示为 1101
。我们来逐步分析 lowbit
和 while
循环的执行过程:
-
第一次循环:
x = 13
,二进制表示为1101
。lowbit(13)
:13 & -13
=1101 & 0011
=0001
。x -= lowbit(13)
:13 - 1
=12
,二进制表示为1100
。res++
:res = 1
。
-
第二次循环:
x = 12
,二进制表示为1100
。lowbit(12)
:12 & -12
=1100 & 0100
=0100
。x -= lowbit(12)
:12 - 4
=8
,二进制表示为1000
。res++
:res = 2
。
-
第三次循环:
x = 8
,二进制表示为1000
。lowbit(8)
:8 & -8
=1000 & 1000
=1000
。x -= lowbit(8)
:8 - 8
=0
,二进制表示为0000
。res++
:res = 3
。
循环结束,x
已经变为 0
,最终 res = 3
,表示 13
的二进制表示中有 3
个 1
。
总结
这段代码通过不断减去整数 x
的二进制表示中最低位的 1
,计算出了每个输入整数中 1
的个数,并将结果输出。该方法利用了 lowbit
函数,以逐位去除最低位的 1
来实现这一目标。
三.离散化(整数)
代码分解
1. 定义并初始化一个整数向量 alls
vector<int> alls; // 存储所有待离散化的值
alls
是一个 vector<int>
类型的变量,用于存储所有需要离散化的整数值。
2. 对 alls
进行排序
sort(alls.begin(), alls.end()); // 将所有值排序
这行代码使用 sort
函数对 alls
中的元素进行升序排序。
3. 去除 alls
中的重复元素
alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
unique
函数会将alls
中的重复元素移到向量的末尾,并返回一个指向第一个重复元素的迭代器。erase
函数将alls
中从unique
返回的迭代器到alls.end()
的元素删除,从而去除重复元素。
经过这两步处理后,alls
中只剩下不重复且有序的元素。
4. 二分查找函数 find
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;
}
find
函数用于在 alls
中查找某个值 x
的离散化结果。其具体步骤如下:
- 初始化左右边界
l
和r
,分别为 0 和alls
的最后一个索引。 - 进入
while
循环,循环条件为l < r
。 - 计算中间位置
mid
,使用l + r >> 1
进行位运算来求中间值。 - 判断
alls[mid]
是否大于等于x
:- 如果是,则将右边界
r
移动到mid
。 - 如果不是,则将左边界
l
移动到mid + 1
。
- 如果是,则将右边界
- 循环结束后,返回
r + 1
,即x
在alls
中对应的离散化值。
总结
这段代码首先对一个整数数组进行排序并去重,然后提供了一个二分查找函数 find
,用来查找某个整数在离散化后的数组中的位置。
四.区间合并
1.按区间的左端点进行排序
2.扫描整个区间,把所有有交集的区间进行合并
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
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() {
cin >> n;
for (int i = 0; i < n; i++) {
int l, r;
cin >> l >> r;
segs.push_back({l, r});
}
merge(segs);
cout << segs.size() << endl;
for (auto seg : segs) cout << seg.first << " " << seg.second << endl;
return 0;
}
详细解释:
-
头文件和命名空间:
#include <iostream> #include <vector> #include <algorithm> using namespace std;
引入了必要的头文件,并使用
std
命名空间。 -
常量和变量声明:
const int N = 100010; int n; typedef pair<int, int> PII; vector<PII> segs;
N
是一个常量,表示可能的最大区间数。n
用于存储区间的数量。PII
是一个类型定义,表示一个整型对(l, r)
。segs
是一个存储区间的向量。
-
合并区间函数
merge
: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; }
res
用于存储合并后的区间。sort(segs.begin(), segs.end())
对区间按起点进行排序。st
和ed
初始化为一个非常小的值,用于表示当前合并区间的起点和终点。- 遍历每个区间
seg
:- 如果当前区间的起点大于已合并区间的终点
ed
,则当前区间与已合并区间不重叠:- 如果
st
不等于初始值,表示有一个有效的已合并区间,加入res
。 - 更新
st
和ed
为当前区间的起点和终点。
- 如果
- 否则,更新
ed
为当前区间终点和已合并区间终点的最大值。
- 如果当前区间的起点大于已合并区间的终点
- 最后,如果
st
不等于初始值,加入最后一个已合并区间到res
。 - 将合并后的区间赋值回
segs
。
-
主函数
main
:int main() { cin >> n; for (int i = 0; i < n; i++) { int l, r; cin >> l >> r; segs.push_back({l, r}); } merge(segs); cout << segs.size() << endl; for (auto seg : segs) cout << seg.first << " " << seg.second << endl; return 0; }
- 读取区间数
n
。 - 读取每个区间的起点
l
和终点r
,并加入segs
。 - 调用
merge
函数合并区间。 - 输出合并后的区间数。
- 输出每个合并后的区间的起点和终点。
- 读取区间数
这个程序的核心功能是合并重叠的区间,并输出合并后的区间列表。