acwing c++基础算法笔记(4)

位运算

AcWing 801. 二进制中1的个数

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

法一: 使用lowbit 时间复杂度为O(nlogn),使用lowbit操作,每次操作截取一个数字的最后一个1后面的所有位,每次减去lowbit得到的数字,直到数字减到0,就得到了最终1的个数。

#include <iostream>
using namespace std;
int lowbit(int x)
{
    return x&-x;
}
int main()
{
    int n;
    cin>>n;
    for(int i=0; i<n; i++)
    {
        int x,sum=0;
        cin>>x;
        while (x)
        {
            x=x-lowbit(x);
            sum++;
        }
        cout<<sum<<" ";
    }
}

法二: 暴力枚举 对于每个数字,我们将其与1相与,就得到了该数字的最后一位,之后将a右移一位,直到为0,就得到了1的个数。

#include <iostream>
using namespace std;
int main()
{
    int n;
    cin>>n;
    for(int i=0; i<n; i++)
    {
        int x,sum=0;
        cin>>x;
        while (x)
        {
            if(x&1)
            {
                sum++;
            }
            x=x>>1;
        }
        cout<<sum<<" ";
    }
}

双指针算法

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

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

#include<iostream>
using namespace std;
const int N = 100010;
int q[N],s[N];
int main()
{
    int n;
    cin >> n;
    for (int i = 0; i <n; i ++ )
    {
        cin >> q[i];
    }
    int res = 0;
    for (int i = 0,j=0; i < n; i ++ )
    {
        s[q[i]]++;
        while(j<i&&s[q[i]]>1)//有重复元素时,j右移直到无重复元素
        {
        s[q[j]]--;
        j++;
        }
        res = max(res,i-j+1);
    }
    cout << res << endl;
    return 0;
}

声明了两个整型数组qs,数组q用于存储输入的整数序列,数组s则起到记录元素出现次数的作用,用来辅助判断元素是否重复。

进入main函数后,先是通过cin >> n;语句从标准输入读取一个整数,并将其赋值给变量n,这个n表示输入的整数序列的长度。

然后是一个for循环,循环从i = 0开始,到i < n结束,在每次循环中,通过cin >> q[i];语句读取一个整数,并将其存储到数组q对应的q[i]位置中,如此便完成了整个整数序列的输入操作。

接着初始化一个变量res0,这个变量是用于记录最终的结果,也就是满足特定条件的子序列的最大长度。

之后进入一个关键的for循环,这里通过两个指针ij来对数组q进行遍历操作,其中i0开始逐步往后移动,j也从0开始,并且会随着循环中的情况动态往后移动。在每次循环中,首先执行s[q[i]]++;,这表示数组q中当前位置i对应的元素q[i]在数组s中的出现次数加1,也就是记录元素q[i]出现的频次。

随后进入一个while循环,其判断条件是j < i && s[q[i]] > 1,这个条件意味着当指针j小于指针i(保证窗口合理移动,不会出现不合理回溯等情况)并且当前元素q[i]在数组s中的出现次数大于1(即出现重复元素了)时,就需要对窗口进行调整。此时执行s[q[j]]--;,也就是把窗口最左边(由指针j指向)的元素q[j]在数组s中的出现次数减1,表示这个元素在当前考虑的子序列中的出现次数减少了,然后执行j++;,将指针j往后移动一位,相当于缩小窗口,去除掉重复的元素,使得当前考虑的子序列中每个元素最多只出现一次。

在每次调整完窗口后,通过res = max(res, i - j + 1);来更新res的值,这里的i - j + 1计算的是当前窗口(也就是当前满足条件的子序列)的长度,然后使用max函数将它与之前记录的最大长度res进行比较,把较大的值赋给res,这样就能始终记录下满足条件(即子序列中每个元素最多出现一次)的子序列的最大长度。

最后,通过cout << res << endl;输出最终记录的最大长度值,然后main函数通过return 0;语句结束,表明程序正常执行完毕。

2.AcWing 800. 数组元素的目标和

给定两个升序排序的有序数组 A 和 B,以及一个目标值 x。

数组下标从 0 开始。

请你求出满足 A[i]+B[j]=x 的数对 (i,j)。

数据保证有唯一解。

#include <iostream>
using namespace std;
 
const int N = 100010;
 
int a[N], b[N];
 
int main()
{
    int n, m, x;
    cin >> 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 = 0, j = m - 1;
    for (; i < n; i ++)//  第一个数组依次遍历
    {
        while (a[i] + b[j] > x && j > 0){//当大于x是下一个i相加仍大于不用再管后面的j
            j --;
        }
        if(a[i] + b[j] < x) continue;// 此时i,找不到对应的j
        if(a[i] + b[j] == x) break;// 找到后退出
    }
    printf("%d %d",i, j);
    return 0;
}

使用两个 for 循环分别接收用户输入的数组 ab 的各个元素,将它们依次存储到对应的数组中。

接下来进入核心的查找逻辑部分。初始化两个指针变量 i0jm - 1(也就是让 j 指向数组 b 的最后一个元素)。随后进入一个外层的 for 循环,这个循环以 i 遍历数组 a,在每次循环中,会有一个内层的 while 循环,它会不断判断当前 a[i]b[j] 的和是否大于 x,如果大于且 j 大于 0(保证不越界),就将 j 的值减 1,也就是让 b 数组的指针往前移动,尝试找到更小的 b 数组中的元素,使得和有可能等于 x

之后通过 if 语句进行判断,如果 a[i] + b[j] 的和小于 x,那就执行 continue,直接进入下一次外层 for 循环,去尝试下一个 a 数组中的元素;而如果恰好 a[i] + b[j] 的和等于 x,就执行 break 跳出外层的 for 循环,意味着找到了满足条件的元素组合。

最后,通过 cout 将此时的 ij 的值输出,也就是输出找到的满足条件的两个数组元素对应的索引(这里需要注意,如果没有找到满足条件的元素组合,直接输出此时的 ij 可能不符合预期,原代码在这方面缺乏相应的错误处理机制),最后 main 函数返回 0,表示程序正常结束。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值