C++位运算总结[力扣LeetCode新年第一题][只有穷举法,分治法与滑动窗口还待编辑]

今天在力扣上遇到一道题没有见过这样的用法,所以需要查漏补缺😭

题目引入请添加图片描述请添加图片描述

感觉就是一道字符串问题确定起始位置与延伸长度,位运算的优化我很不解,就参考书籍看一下

c++教程书中的描述

C++中提供了6种位运算符:
双目位运算符: 按位与 & 按位或 | 按位异或 ^ 按位右移 >> 按位左移 <<
单目位运算符:按位取反 ~
(1)&按位合取运算。
(2)| 按位析取
(3)^ 异或:相同为0,不同为1
(4)<< 举个例子00011110为a=30,a<<2为01111000为120
(5)>> 举个例子b=120 01111000,b>>2变成00011110
(6)~取反
课本上例子都是整数用8位2进制数表示

位运算的高级用法

这一部分参考的一位博主的文章点击进入原博客

1.判断奇偶数

if(a%2){cout<<"a为奇数";}

下面是进行优化,进行位运算,效率也会更高

if(a&1){cout<<"a为奇数";}

2.不用额外变量空间或者函数来完成交换

 x=x^y;
 y=x^y;
 x=x^y;

证明只需要带入第二行开始
第一行你会发现得不到最终答案,后两行可以得到最后答案

y=(x ^ y) ^ y
两个相同的数异或之后结果会等于 0,即 n ^ n = 0
并且任何数与 0 异或等于它本身,即 n ^ 0 = n
而且异或运算支持交换律与结合律
化简之后就是y=x;

第三行
x=(x^y) ^ x=y

3、找出没有重复的数

给你一组整型数据,这些数据中,其中有一个数只出现了一次,其他的数都出现了两次,让你来找出一个数 。

这道题可能很多人会用一个哈希表来存储,每次存储的时候,记录 某个数出现的次数,最后再遍历哈希表,看看哪个数只出现了一次。这种方法的时间复杂度为 O(n),空间复杂度也为 O(n)了。

然而我想告诉你的是,采用位运算来做,绝对高逼格!

我们刚才说过,两个相同的数异或的结果是 0,一个数和 0 异或的结果是它本身,所以我们把这一组整型全部异或一下,例如这组数据是:1, 2, 3, 4, 5, 1, 2, 3, 4。其中 5 只出现了一次,其他都出现了两次,把他们全部异或一下,结果如下:

由于异或支持交换律和结合律,所以:

123451234 = (11)(22)(33)(44)5= 00005 = 5。

也就是说,那些出现了两次的数异或之后会变成0,那个出现一次的数,和 0 异或之后就等于它本身。就问这个解法牛不牛逼?所以代码如下

int find(int[] arr){
    int tmp = arr[0];
    for(int i = 1;i &lt; arr.length; i++){
        tmp = tmp ^ arr[i];
    }
    return tmp;
}

时间复杂度为 O(n),空间复杂度为 O(1),而且看起来很牛逼。

4、m的n次方

如果让你求解 m 的 n 次方,并且不能使用系统自带的 pow 函数,你会怎么做呢?这还不简单,连续让 n 个 m 相乘就行了,代码如下:

int pow(int n){
    int tmp = 1;
    for(int i = 1; i &lt;= n; i++) {
        tmp = tmp * m;
    }
    return tmp;
}

不过你要是这样做的话,我只能呵呵,时间复杂度为 O(n) 了,怕是小学生都会!如果让你用位运算来做,你会怎么做呢?

我举个例子吧,例如 n = 13,则 n 的二进制表示为 1101, 那么 m 的 13 次方可以拆解为:

m^1101 = m^0001 * m^0100 * m^1000。

我们可以通过 & 1和 >>1 来逐位读取 1101,为1时将该位代表的乘数累乘到最终结果。直接看代码吧,反而容易理解:

int pow(int n){
    int sum = 1;
    int tmp = m;
    while(n != 0){
        if(n &amp; 1 == 1){
            sum *= tmp;
        }
        tmp *= tmp;
        n = n &gt;&gt; 1;
    }
    
    return sum;
}

时间复杂度近为 O(logn),而且看起来很牛逼。

这里说一下,位运算很多情况下都是很二进制扯上关系的,所以我们要判断是否是否位运算,很多情况下都会把他们拆分成二进制,然后观察特性,或者就是利用与,或,异或的特性来观察,总之,我觉得多看一些例子,加上自己多动手,就比较容易上手了。所以呢,继续往下看,注意,先别看答案,先看看自己会不会做。

5、找出不大于N的最大的2的幂指数

传统的做法就是让 1 不断着乘以 2,代码如下:

int findN(int N){
    int sum = 1;
   while(true){
        if(sum * 2 &gt; N){
            return sum;
        }
        sum = sum * 2;
   }
}

这样做的话,时间复杂度是 O(logn),那如果改成位运算,该怎么做呢?我刚才说了,如果要弄成位运算的方式,很多时候我们把某个数拆成二进制,然后看看有哪些发现。这里我举个例子吧。

例如 N = 19,那么转换成二进制就是 00010011(这里为了方便,我采用8位的二进制来表示)。那么我们要找的数就是,把二进制中最左边的 1 保留,后面的 1 全部变为 0。即我们的目标数是 00010000。那么如何获得这个数呢?相应解法如下:

1、找到最左边的 1,然后把它右边的所有 0 变成 1

2、把得到的数值加 1,可以得到 00100000即 00011111 + 1 = 00100000。

3、把 得到的 00100000 向右移动一位,即可得到 00010000,即 00100000 >> 1 = 00010000。

那么问题来了,第一步中把最左边 1 中后面的 0 转化为 1 该怎么弄呢?我先给出代码再解释吧。下面这段代码就可以把最左边 1 中后面的 0 全部转化为 1,

n |= n &gt;&gt; 1;
n |= n &gt;&gt; 2;
n |= n &gt;&gt; 4;

就是通过把 n 右移并且做运算即可得到。我解释下吧,我们假设最左边的 1 处于二进制位中的第 k 位(从左往右数),那么把 n 右移一位之后,那么得到的结果中第 k+1 位也必定为 1,然后把 n 与右移后的结果做或运算,那么得到的结果中第 k 和 第 k + 1 位必定是 1;同样的道理,再次把 n 右移两位,那么得到的结果中第 k+2和第 k+3 位必定是 1,然后再次做或运算,那么就能得到第 k, k+1, k+2, k+3 都是 1,如此往复下去…

最终的代码如下

int findN(int n){
    n |= n &gt;&gt; 1;
    n |= n &gt;&gt; 2;
    n |= n &gt;&gt; 4;
    n |= n &gt;&gt; 8 // 整型一般是 32 位,上面我是假设 8 位。
    return (n + 1) &gt;&gt; 1;
}

这种做法的时间复杂度近似 O(1),重点是,高逼格。

回归题解

其实这道题的第一个官方方法就是不用哈希表用位运算来检验
第一个方法官方称为枚举其实就是穷举

class Solution {
public:
    string longestNiceSubstring(string s) {
        int n = s.size();
        int maxPos = 0;//起始位置
        int maxLen = 0;//长度
        for (int i = 0; i < n; ++i) {//起始位置
            int lower = 0;//更换起始位置lower和upper的数值就要更新
            //lower和upper是利用位运算来存储的
            int upper = 0;
            for (int j = i; j < n; ++j) {//往后延伸
                if (islower(s[j])) {//s[j]是小写字母
                    lower |= 1 << (s[j] - 'a');
                    //  00000001<<字母序号假设是‘a’那么字母序号就是0
                    //  lower=lower丨00000001 即 00000000丨00000001=00000001
                } else 
                {upper |= 1 << (s[j] - 'A');}//为大写字母
                if (lower == upper && j - i + 1 > maxLen) {
                    maxPos = i;
                    maxLen = j - i + 1;
                }
            }
        }
        return s.substr(maxPos, maxLen);
    }
};

我是真的觉得这个题目复杂,后面还有两个方法需要慢慢理解

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值