小皇帝学算法--基础篇(3)

一.双指针算法

  • 时间复杂度:双指针算法通常能将时间复杂度从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位是几

步骤
  1. 右移 k 位
    • n >> k:将 n 右移 k 位,此时第 k 位移动到最低位。
  2. 按位与操作
    • (n >> k) & 1:将右移后的结果与 1 进行按位与操作,结果要么是 0 要么是 1
示例

假设我们有一个整数 n = 13,其二进制表示为 1101。现在我们要找出第 2 位(从右边数)。

  1. 右移操作

    • n >> 213 的二进制表示是 1101,右移两位后得到 11(即 3 的二进制表示)。
  2. 按位与操作

    • (n >> 2) & 111 & 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 循环的执行过程:

  1. 第一次循环

    • x = 13,二进制表示为 1101
    • lowbit(13)13 & -13 = 1101 & 0011 = 0001
    • x -= lowbit(13)13 - 1 = 12,二进制表示为 1100
    • res++res = 1
  2. 第二次循环

    • x = 12,二进制表示为 1100
    • lowbit(12)12 & -12 = 1100 & 0100 = 0100
    • x -= lowbit(12)12 - 4 = 8,二进制表示为 1000
    • res++res = 2
  3. 第三次循环

    • 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 的离散化结果。其具体步骤如下:

  1. 初始化左右边界 l 和 r,分别为 0 和 alls 的最后一个索引。
  2. 进入 while 循环,循环条件为 l < r
  3. 计算中间位置 mid,使用 l + r >> 1 进行位运算来求中间值。
  4. 判断 alls[mid] 是否大于等于 x
    • 如果是,则将右边界 r 移动到 mid
    • 如果不是,则将左边界 l 移动到 mid + 1
  5. 循环结束后,返回 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;
}

详细解释:

  1. 头文件和命名空间

    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    using namespace std;

    引入了必要的头文件,并使用 std 命名空间。

  2. 常量和变量声明

    const int N = 100010;
    
    int n;
    typedef pair<int, int> PII;
    vector<PII> segs;
    • N 是一个常量,表示可能的最大区间数。
    • n 用于存储区间的数量。
    • PII 是一个类型定义,表示一个整型对 (l, r)
    • segs 是一个存储区间的向量。
  3. 合并区间函数 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
  4. 主函数 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 函数合并区间。
    • 输出合并后的区间数。
    • 输出每个合并后的区间的起点和终点。

这个程序的核心功能是合并重叠的区间,并输出合并后的区间列表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值