【算法训练营学习笔记-Week07】位运算的妙用

位运算

如何从十进制转换为二进制

位运算符号

含义 运算符 示例
左移 << 0011 ->0110
右移 >> 0110 -> 0011
按位或 | 0011 | 1011 ->1011
按位与 & 0011|1011 -> 0011
按位取反 ~ 0011->1100
按位异或(不同为1) ^ 0011^1011 -> 1000

异或(XOR): 不进位加法,高阶操作(记住就好)

x^0 = x;
x^1s = ~x; // 1s= ~ 0, 表示全为1
x^(~x) = 1s;
x^x = 0;
c = a ^b; a^c=b;b^c=a //两数 交换
a^b^c=a^(b^c)=(a^b)^c; //交换律

任意的对二进制数的部分位进行 清零,置一,移动操作

  1. 清零x的最右边n位: x&(~0<<n)
  2. 获取x的第n位置:(x>>n)&1
  3. 获取x的第n位的幂值: x&(1<<(n-1))
  4. 仅将第n位替换为1: x|(1<<n)
  5. 仅将第n为替换为0: x&(~(1<<n))
  6. 将x最高位至第n位(含)清零: x&((1<<n)-1)
  7. 将第n位到第0位(含)清零: x&(~((1<<(n+1))-1))

实战要点,常用技巧

  1. 奇偶判断: (x&1)
  2. 除2: x>>1
  3. 清零最低位: (x&(x-1))
  4. 得到最低位的1: x&-x
  5. 清零:x&~x=0

补充知识,原码、反码和补码

原码是二进制的原始表示,例如+2是000..010, -2是100...010

在涉及到负数的运算时,不能直接用负数的原码表示, 例如0010 + 1010 = 1100(-4的原码), 而不是0。

因此负数在运算时会用到它的补码,也就是负数的反码加一,反码指的是除符号位外按位取反,也就是1010 -> 1101(反码)-> 1110(补码)。另外规定正数的反码,补码都和原码相同。

于是,实际运算是正数的补码和负数的补码进行相加, 即 0010 + 1110 = 0000

实战题目

191. 位1的个数: x & (x-1) 不断打掉最右边的1

231. 2的幂: x & (x-1)直接打掉最右边的1,看是否为0

190. 颠倒二进制位: 答案每次先左移一位,然后把原数字的右边第一位加入到答案中,接着原数字右移,循环32次

338. 比特位计数: DP方程为 res[i] = res[i & (i-1)]

重点题目,利用位运算处理N皇后问题,分别是51. N皇后52. N皇后 II

以4皇后,8位整型为例讲解里面的位运算。一开始初始化: row, cols, pie,na都是 00000000

下面语句的目的是获取可以放皇后的位置(二进制表示的1)

bits = (~(cols | pie | na)) & ((1 << n) — 1) # 得到当前所有的空位

~(cols | pie | na) 按位或,然后按位取反,也就是先得到所有1占据的位置,接着把1变为0,把0变成1,这里结果就是11111111.

((1 << n) — 1) 首先将00000001左移n位(n=4), 得到 00010000, 然后-1得到00001111,

将两者按位与,就得到00001111。1就是可以放皇后的位置。

注意,如果没有& ((1 << n) — 1)操作,就无法清空高位部分的1。

下面语句的目的是获取最低位的1,也就是0000111(1)

p = bits & —bits # 取到最低位的1

bits是00001111, -bits是10001111, 但是在运算过程中, -bits会以补码形式出现,也就是11110001, 因此实际是00001111 & 11110001 = 00000001

下面的语句是在bits中p的位置上放上皇后, 00001111 - 00000001 = 00001110

bits = bits - p # 表示在p位置上放入皇后
# 或
bits = bits & (bits — 1) # 也就是把最后一位变为0

递归一层,在原来的位置上加上p, 对于左下角要左移,右下角要右移(不需要再考虑数组的边界了)

self.DFS(n, row + 1, cols | p, (pie | p) << 1, (na | p) >> 1) 

于是新的row, cols, pie, na是 00000001, 00000001, 00000010, 00000000. (是原来棋盘的镜像翻转)

接着计算bits, 就是 00001100, 只剩下两个位置可以放皇后了。

感想: 之前看到位运算相关知识点的时候,一般我都会直接跳过,觉得我以后也用不上。但在八皇后问题中,位运算绝对是最优选择,位的左移和右移能够自动处理使用数组会出现的边界判断问题,同时也比使用set更加节约空间。八皇后所需要的复杂位运算只有三个而已,就是最高位到第n清零,获取最后一位1的位置和打掉最后一位的1。

布隆过滤器

哈希表是一个没有误差的数据结构,包括了元素的所有信息。

布隆过滤器是一个很长的二进制向量和一些列随机的映射函数组成。

优点:空间效率和查询效率极高

缺点:删除困难,且存在会情况

布隆过滤器是一个存在误差的数据结构,如果布隆过滤器返回不存在,那么百分百不存在。但是如果返回存在,那么未必真的有该元素。

原理图解

布隆过滤的意义是高效缓存,如果查不到,就不需要继续查询数据库,如果查到了,我们才考虑去检索数据库。

案例:

  1. 比特币网络
  2. 分布式系统
  3. 使用布隆过滤器解决缓存击穿、垃圾邮件识别、集合判重

代码实现案例:

  1. 布隆过滤器 Python 代码示例
  2. 布隆过滤器 Java 实现示例 1

LRU缓存

缓存两个要素:大小,替换策略。缓存的大小通常是有限的,因此需要将缓存外更重要的东西替换到缓存中

替换算法:LRU(最近最少使用),以哈希表+双链表实现, O(1)查询和O(1)的查询和更新

[图片上传失败...(image-4cb8df-1584940239942)]


image.png

替换算法总揽

面试的时候,介绍LRU是什么,它的三个API是什么?

实现方式有两种:

  1. 有序字典 (简单)
  2. 哈希表 + 双向链表(复杂)

Python提供了有序词典(根据插入顺序进行存放元素),Java提供了LinkedHashMap 链式的哈希表都可以用于实现第一种策略。C++的map是以红黑树进行存放元素,因此顺序是基于key,value对。

排序

分为比较类排序和非比较排序

算法总览
复杂度

表格中快排时间复杂度描述不当,快排是原地排序方法,不用额外申请空间。

一定要看的三篇文章:

看完动画发现,归并排序始终很稳定,而快排则和数据集关系很大(可以通过随机化标杆来优化),简单排序某些时候表现效果好于高级排序算法。由于快排平均情况能够达到O(n log n), 且是原地排序,是大部分程序标准库的第一选择。

初级排序: 插入排序,选择排序和冒泡排序

高级排序:

  • 快速排序(分治): 数组取标杆(pivot), 将小元素放在pivot左边,大元素放在pivot放在右侧
  • 归并排序(分治): 需要额外空间。
  • 快排和归并具有相似性,但步骤相反,归并是排序左右子数组然后合并,快排是分开左右数组,然后对左右数组排序
  • 堆排序: 先建立小顶堆(C++的优先队列),然后以此取出元素

特殊排序(要求数据都是整型,因此使用范围有限)

  • 计数排序: 要求输入数据必须是有确定范围(例如成绩)
  • 桶排序: 假设输入数据均匀分布,然后将数组分到优先数量的同,每个桶单独排序。 桶排序可以是计数排序的升级
  • 基数排序: 先排个位,再排十位,再排百位

242. 有效的字母异位词:排序比较即可

56. 合并区间: 高频,看排序+一次扫描 逐行解释好理解 python3

493. 翻转对: 面试的时候需要注意逆序对,归并排序

如果是普通的逆序对,在归并排序的合并步骤时,检查顺序

图解

如果arr[i] > arr[j] (i <j) 就说明存在一个逆序对, 此外如果左侧元素还没有使用完,就说明存在左侧元素存在比较大的情况,还需要统计。

更改处

对于重要翻转对,C++实现如下, 排序部分直接调用了自带的排序方法,导致时间复杂度从 n log(n) 变为, n log(n) log(n)

class Solution {
public:
    int reversePairs(vector<int>& nums) {

        return mergeSort(nums, 0, nums.size() -1);

    }
    //s: start, e: end
    int mergeSort(vector<int>& nums, int s, int e){
        if ( s >= e) return 0;
        int mid = s + (e-s)/2;
        int cnt = mergeSort(nums, s, mid) + mergeSort(nums, mid+1, e);
        for (int i = s, j = mid + 1; i <= mid; i++){
            while ( j <= e && nums[i] / 2.0 > nums[j]) j++;
            cnt += j - (mid+1);
        }
        sort(nums.begin()+s, nums.begin()+e+1);
        return cnt;
    }
};

程序分为三个部分:

  1. 一分为二,分别统计左右区间内的重要翻转对
  2. 然后统计,左右两个区间之间的重要翻转对
  3. 最后对左右区间合并(可以偷懒调用系统sort)

PS: 1244题目是会员才能做

其他

构造函数与成员初始化器列表: 初始化列表对成员变量初始化, 初始化列表比构造函数先执行

struct S {
    int n;
    S(int); // 构造函数声明
    S() : n(7) {} // 构造函数定义。
                  // ": n(7)" 为初始化器列表
                  // ": n(7) {}" 为函数体
};
S::S(int x) : n{x} {} // 构造函数定义。": n{x}" 为初始化器列表
int main()
{
    S s; // 调用 S::S()
    S s2(10); // 调用 S::S(int)
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 计算机的源头 日常生活中,我们广泛采用十进制计数法。10是十进制计数法的基数,这也是十进制中“十”的由来。 十进制...
    Jack_Cui阅读 2,560评论 0 3
  • 原码 原码是电脑运算的名词,是指“未经更改”的码。为了便于ALU(算术逻辑单元)的设计,又发展出反码、补码等转换过...
    __RY__阅读 5,879评论 0 2
  • C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程...
    C语言学习阅读 3,739评论 5 29
  • 2020年元月4日 早课点滴 1.拜名师。听君一席话胜读十年书。书读百遍其义自见,也需要名师指点。密码需要问本人。...
    魏嘉彤阅读 123评论 0 0
  • 总想给你送去问候 然后告诉你 你安好,我也无恙 风来雨落,秋来叶归 生活就是一场花开花落 人生就是一场无悔穿越 恍...
    平语心声阅读 909评论 2 23