王清欢Randy 王清欢Randy
首页
  • 编程语言

    • C/C++ 学习笔记
    • Golang 学习笔记
  • 算法分析

    • LeetCode 刷题笔记
  • 操作系统

    • Linux 基础
    • Vim 实用技巧
    • Shell 脚本编程
    • GDB 学习笔记
  • 开发工具

    • Git 学习笔记
  • 分布式理论

    • 共识算法
    • 分布式事务
  • 数据库内核

    • PostgreSQL
    • Postgres-XL
  • hidb
  • pgproxy
  • 实用技巧
  • 学习方法
  • 资源分享
GitHub (opens new window)
首页
  • 编程语言

    • C/C++ 学习笔记
    • Golang 学习笔记
  • 算法分析

    • LeetCode 刷题笔记
  • 操作系统

    • Linux 基础
    • Vim 实用技巧
    • Shell 脚本编程
    • GDB 学习笔记
  • 开发工具

    • Git 学习笔记
  • 分布式理论

    • 共识算法
    • 分布式事务
  • 数据库内核

    • PostgreSQL
    • Postgres-XL
  • hidb
  • pgproxy
  • 实用技巧
  • 学习方法
  • 资源分享
GitHub (opens new window)
  • 基础算法

    • 双指针

      • 双指针基础
      • 碰撞指针
      • 快慢指针
      • 滑动窗口
    • 二分查找

      • 基础应用
      • 边界收缩
      • 局部有序
    • 排序算法

      • 01八大排序
      • 02快速排序
      • 03归并排序
      • 04桶排序
      • 05堆排序
    • 优先搜索

      • 01递归
      • 02网格结构深度优先搜索
      • 03树结构深度优先搜索
      • 04图结构深度优先搜索
      • 05网格结构广度优先搜索
      • 06树结构广度优先搜索
      • 07图结构广度优先搜索
    • 回溯算法

      • 01递归
      • 02 子集问题
      • 03 全排列问题
      • 04 组合问题
      • 05 回溯搜索问题
  • 基础数据结构

    • 线性表与哈希表

      • 01数组
      • 02栈和队列
      • 03单调栈
      • 04优先队列
      • 05双端队列
      • 06哈希表
      • 07多重集合
      • 08前缀和
      • 09数据结构设计
    • 字符串

      • 01字符串比较
      • 02回文字符串
      • 03字符串匹配
      • 04字符串算术表达式
    • 单链表

      • 01链表基础操作
      • 02链表遍历
    • 二叉树

      • 01二叉树的属性
      • 02二叉树的操作
      • 03层次遍历
      • 04前中后序遍历
      • 05二叉搜索树的属性
      • 06二叉搜索树的操作
      • 07字典树
      • 08二叉搜索树BST
    • 图

      • 01二分图
      • 02拓扑排序
      • 03并查集
      • 04最小生成树
      • 05最短路径
  • 进阶算法

    • 贪心算法

      • 01跳跃游戏
      • 02分配问题
      • 03区间问题
    • 分治策略

    • 动态规划

      • 01一维动态规划
      • 02二维动态规划
      • 03分割型动态规划
      • 04子序列问题
      • 05背包问题
      • 06字符串编辑问题
      • 07股票交易问题
  • 其他内容

    • 数学问题

      • 01公倍数与公因数
      • 02质数问题
      • 03进制转换问题
      • 04数字字符串求和问题
      • 05众数问题
      • 06中位数问题
      • 07数字处理问题
      • 08随机数问题
    • 位运算

      • 01位运算基础
      • 02妙用异或运算
      • 03二进制特性
        • 03 二进制特性
          • 342 4的幂
          • 318 最大单词长度乘积
          • 338 比特位计数
  • LeetCode刷题笔记
  • 其他内容
  • 位运算
王清欢
2023-03-24
目录

03二进制特性

# 03 二进制特性

​ 利用二进制的一些特性,可以把位运算使用到更多问题上。例如,可以利用二进制和位运算输出一个数组的所有子集。假设有一个长度为 n 的数组,可以生成长度为 n 的所有二进制,1 表示选取该数字,0 表示不选取。这样就获得了 2^n 个子集。

# 342 4的幂 (opens new window)

给定一个整数,判断它是否是 4 的次方。

输入是一个整数,输出是一个布尔值,表示判断结果。

输入:n = 16
输出:true

解析:

​ 本题可以采用按位与运算简单实现判断数是否为4的幂。如果 n 是 4 的幂,那么 n 的二进制表示中有且仅有一个 1 ,且其位置必须为奇数位,例如16 = (10000),从右向左且从1开始计数的话,16的二进制表示中 1 位于第5位。根据此规律可以将 n 和二进制01010101010101010101010101010101或者十六进制的0x55555555做按位与,如果得到的结果不为零,那么说明这个数可能是 4 的幂。

​ 为什么说是可能呢?可以看出上述条件是充分条件而非充要条件,1 与0x55555555做按位与的结果也是非0的。

​ 所以需要进一步考虑该数 n 也是 2 的幂,才能保证上述条件判断结果为该数是 4 的幂。如果 n 是 2 的幂,那么 n 的二进制表示中也有且仅有一个 1,假设其位置为 pos ;那么 n - 1 二进制表示由第 1 位到第 pos-1 位全部为 1构成。例如16 = 10000; 15 = 01111 ,所以可以得出:如果 n & (n - 1) == 0,那么 n 是 2 的幂。

​ 最终得到:如果(n&(n-1))==0 && (n & 0x55555555)!=0,那么n 是 4 的幂。

class Solution {
public:
    bool isPowerOfFour(int n) {
        return n > 0 && !(n&(n-1)) && (n&0x55555555);
    }
};

​ 当然,本题也可以采用类似求 326. 3的幂 (opens new window)的方法直接使用对数函数的换底公式构造log4(n),然后使用fmod()函数判断该值是否为整数,进而判断 n 是否是 4 的幂。

class Solution {
public:
    bool isPowerOfFour(int n) {
        return fmod(log10(n)/log10(4),1) == 0;
    }
};

# 318 最大单词长度乘积 (opens new window)

给定多个字母串,求其中任意两个字母串的长度乘积的最大值,且这两个字母串不能含有相同字母。

输入一个包含多个字母串的一维数组,输出一个整数,表示长度乘积的最大值。

输入: ["a","ab","abc","d","cd","bcd","abcd"]
输出: 4 
解释: 这两个单词为 "ab", "cd"。

解析:

​ 怎样快速判断两个字母串是否含有重复数字呢?可以为每个字母串建立一个长度为 26 的二进制数字 mask,用于表示从右到左每个位置表示是否存在a-z对应的字母。例如abc:111, abd:1011,如果两个字母串含有重复数字,那它们的二进制表示的按位与不为 0,例如abc,adbe存在相同的字母,将它们按位与的结果是1011!=0。

​ 怎么实现对字符串的编号记录呢?也可以通过位运算实现:首先将 mask 置为 0,然后遍历字符串的所有字符,每一次遍历中计算当前字符串所处的位置,然后使用算术左移,将标识 1 移动到指定位置,移动完成后将其与mask按位与运算,将位置记录在mask中。

​ 为了求长度乘积的最大值,可以建立一个哈希表记录字符串与其自身的长度。在计算长度乘积最大值时,遍历哈希表将不存在相同字母的字符串长度相乘并取最大值。

class Solution {
public:
    int maxProduct(vector<string>& words) {
        unordered_map<int,int> hash;
        int ans = 0;
        for(const auto& word: words){
            int mask = 0;
            int wordLen = word.length();
            // 为当前字符串编码
            for(const auto c:word){
                // 考虑字符串中每一个字符时,将 1 左移 (c-'a') 位
                int cPos = 1 << (c-'a');
                // 与mask按位与,将当前字符的位置保存到mask中,如果当前字符已经存在不影响mask
                mask = mask | cPos;
            }
            // 更新mask对应的长度时要考虑到:字符串中元素重复出现的情况,这种情况下字符串长度不一致 
            hash[mask] = max(hash[mask],wordLen);
            // 遍历所有已经记录的字符串,如果有不存在相同字符 (mask & h_mask)== 0 的字符串计算当前长度乘积
            for(const auto& [h_mask,h_len]: hash){
                if(!(mask & h_mask)){
                    ans = max(ans,wordLen*h_len);
                }
            }
        }
        return ans;
    }
};

# 338 比特位计数 (opens new window)

给定一个非负整数 n,求从 0 到 n 的所有数字的二进制表达中,分别有多少个 1。

输入是一个非负整数 n,输出是长度为 n + 1 的非负整数数组,每个位置 m 表示 m 的二进制里有多少个 1。

输入:n = 5 输出:[0,1,1,2,1,2] 解释:0 --> 0, 1 --> 1, 2 --> 10, 3 --> 11, 4 --> 100, 5 --> 101

解析:

​ 本题如果使用双层循环逐个统计每个数二进制表示中1的个数会出现超时的情况。为了优化可以考虑使用动态规划来解题。

​ 设置状态:使用一个一维数组dp[i]表示第 i 个数的二进制表示中 1 的个数

​ 状态转移方程:对于第 i 个数字,如果该数为奇数即其二进制表示的末位为1,那么该数二进制表示中 1 的个数直接与前一个偶数相关为dp[i]=dp[i-1]+1;如果该数为偶数即其二进制表示的末位为0,那么该数二进制表示中 1 的个数与其算术右移一位结果相同,因为其算术右移一位将末位0移出后与对应数的二进制表示一致,即dp[i]=dp[i>>1]

​ 边界条件:当 n = 0时,dp[0]=0

class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> dp(n+1,0);
        for(int i=1;i<=n;++i){
            if(i&1){
                dp[i] = dp[i-1] + 1;
            }else{
                dp[i] = dp[i>>1];
            }
        }
        return dp;
    }
};
上次更新: 2023/11/19, 12:55:48
02妙用异或运算

← 02妙用异或运算

Theme by Vdoing | Copyright © 2023-2024 Wang Qinghuan | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式