LeetCode 190.颠倒二进制位 | 从暴力解法到位运算魔法

LeetCode 190.颠倒二进制位 | 从暴力解法到位运算魔法

前言

这道题的核心是32 位二进制位反转,我最初用数组 + 短除法实现,虽然能跑但有符号、精度、前导 0 隐患;学习官方位运算解法后,才真正理解二进制操作的精髓

题目 ★★☆☆☆

190. 颠倒二进制位 颠倒给定的 32 位有符号整数的二进制位。

例如:

输入:00000000 00000000 00000000 00000001

输出:10000000 00000000 00000000 00000000

我的思路

我最先想到的思路是利用进制转换和数组来实现,其核心步骤为:十进制数转换为二进制数 → 颠倒二进制数 → 二进制数转换为十进制数。

  1. 十进制数转换为二进制数:可以利用短除法实现
  2. 颠倒二进制数:利用数组实现,将短除法得到的二进制的每一位存放在数组中,再从不同方向遍历数组即可得到颠倒后的二进制数
  3. 二进制数转换为十进制数:直接利用数学上的转换方式

代码

class Solution { public: int reverseBits(int n) { int arr[32]; int index = 0; int a = n; // 被除数 // 短除法:十进制转换为二进制 while (a != 0) { arr[index++] = a % 2; a /= 2; } index = 31; // 从0下标遍历数组得到颠倒后的二进制数 // 二进制转换为十进制 int res = 0; // 颠倒后的十进制数 int b = 0; // 次分 while (index >= 0) { res += arr[index--] * pow(2, b++); } return res; } };

复杂度

时间复杂度:O(1)。固定的循环次数。

空间复杂度:O(1)。固定的数组大小。

存在问题

虽然通过了所有测试用例(能通过的原因:测试用例友好 + 数组自动补 0),但仍存在以下问题:

  • int 是有符号数,负数会出错
  • 短除法无法处理高位 0,会丢失前导 0,导致结果错误
  • 使用 pow(2,b) 浮点运算,会产生精度错误

官方题解

方法一:逐位颠倒

核心思想

把 32 位整数一位一位取出来,再一位一位放到反转后的位置

位运算

利用 & 和 | 符号进行操作:

  • &:实现取数操作。n & 1——取最后一位:n = 101101,1 = 000001,n & 1 → 000001 → 只保留最后一位
  • |:实现放数操作。rev |=:rev = 100000,新位= 001000,两种相 | → rev= 101000 → 直接拼进去,不覆盖原来的

逐位颠倒步骤

  1. 结果 rev 初始化为 0
  2. 循环 32 次(固定 32 位)
  3. 取出 n 最低位:bit = n & 1
  4. 把这一位移到它反转后的位置bit << (31 - i)
  5. | 把这一位合并到 rev
  6. n 右移一位,删掉已处理的最低位
  7. 循环结束,rev 就是答案

代码

class Solution { public: uint32_t reverseBits(uint32_t n) { uint32_t rev = 0; // 最终反转结果 for (int i = 0; i < 32; i++) { // 1. 取出 n 的最低位(0 或 1) int bit = n & 1; // 2. 将这一位放到反转后的位置(第i位 → 第31-i位) rev |= bit << (31 - i); // 3. n 右移一位,处理下一位 n = n >> 1; } return rev; } };

复杂度

时间复杂度:O(1)

空间复杂度:O(1)

方法二:位运算分治

核心思想

不循环,直接分组交换,把 32 位分成:16 位 ↔ 16 位 → 8 位 ↔ 8 位 → 4 位 ↔ 4 位 → 2 位 ↔ 2 位 → 1 位 ↔ 1 位

每一步都用掩码 + 位移完成交换,最终实现整体反转。

分治过程

  1. 交换相邻 1 位(奇偶位交换)
  2. 交换相邻 2 位
  3. 交换相邻 4 位
  4. 交换相邻 8 位
  5. 交换相邻 16 位

经过 5 步交换,32 位全部反转

掩码含义

  • M1 = 0x55555555 → 奇偶位交换(1 位一组)
  • M2 = 0x33333333 → 2 位一组交换
  • M4 = 0x0f0f0f0f → 4 位一组交换
  • M8 = 0x00ff00ff → 8 位一组交换

代码

class Solution { private: const uint32_t M1 = 0x55555555; // 01010101010101010101010101010101 const uint32_t M2 = 0x33333333; // 00110011001100110011001100110011 const uint32_t M4 = 0x0f0f0f0f; // 00001111000011110000111100001111 const uint32_t M8 = 0x00ff00ff; // 00000000111111110000000011111111 public: uint32_t reverseBits(uint32_t n) { n = n >> 1 & M1 | (n & M1) << 1; n = n >> 2 & M2 | (n & M2) << 2; n = n >> 4 & M4 | (n & M4) << 4; n = n >> 8 & M8 | (n & M8) << 8; return n >> 16 | n << 16; } };

复杂度

时间复杂度:O(1)

空间复杂度:O(1)​​​​​​

Read more

Flutter 三方库 flutter_dropzone 的鸿蒙化适配指南 - 掌握万物皆可拖拽的资源流转技术、助力鸿蒙大屏与 Web 应用构建极致直观的文件导入与交互体系

Flutter 三方库 flutter_dropzone 的鸿蒙化适配指南 - 掌握万物皆可拖拽的资源流转技术、助力鸿蒙大屏与 Web 应用构建极致直观的文件导入与交互体系

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 flutter_dropzone 的鸿蒙化适配指南 - 掌握万物皆可拖拽的资源流转技术、助力鸿蒙大屏与 Web 应用构建极致直观的文件导入与交互体系 前言 在 OpenHarmony 鸿蒙应用全场景覆盖、特别是适配鸿蒙桌面模式(Desktop Mode)、折叠屏大屏交互及鸿蒙 Web 版推送的工程实战中,“文件拖拽(Drag and Drop)”已成为提升生产力效率的标配功能。用户希望能够像在 PC 上一样,直接将图片或文档拖入应用窗口即可完成上传。如何实现这种跨越边界的直观交互?flutter_dropzone 作为一个专注于“拖放区域感知与文件流提取”的库,旨在为鸿蒙开发者提供一套标准的拖放治理方案。本文将详述其在鸿蒙端的实战技法。 一、原原理分析 / 概念介绍 1.1 基础原理 flutter_dropzone

By Ne0inhk
【数据结构指南】深入二叉树遍历

【数据结构指南】深入二叉树遍历

前言:         在前文中,我们探讨了完全二叉树和满二叉树的概念与性质,并基于完全二叉树实现了堆这一数据结构。然而,对于普通二叉树的认识仍有待深入,本文将系统性地介绍普通二叉树的遍历相关内容。                   一、前置说明          一般而言,对于一棵普通二叉树是通过链式结构定义,即每个节点包含三个部分:          1.数据域(data):用于存储节点的值          2.左指针(left):用于指向左子节点          3.右指针(right):用于指向右子节点                  typedef int BTDataType;//此处将 (int)整形 作为节点存储的内容 typedef struct BinaryTree { BTDataType data; struct BinaryTree* left; struct BinaryTree* right; }BTNode;                  在学习二叉树的基本操作前,需先要先创建一棵二叉树,然后才能学习

By Ne0inhk
【数据结构】长幼有序:树、二叉树、堆排序与TOP-K问题的层次解析(含源码)

【数据结构】长幼有序:树、二叉树、堆排序与TOP-K问题的层次解析(含源码)

为什么我们要学那么多的数据结构?这是因为没有一种数据结构能够去应对所有场景。我们在不同的场景需要选择不同的数据结构,所以数据结构没有好坏之分,而评估数据结构的好坏要针对场景,就如我们已经学习的结构而言,如果在一种场景下我们需要频繁地对头部进行插入删除操作,那么这个时候我们用链表;但是如果对尾部进行插入删除操作比较频繁,那我们用顺序表比较好。 因此,不同的场景我们选择不同的数据结构 文章目录 * 一、树 * 1.树的基本概念 * 2.树相关术语 * 3.树的表示 * 4.树形结构实际运用场景 * 二、二叉树 * 1. 概念与结构 * 现实中的二叉树 * 特殊的二叉树 * 二叉树的性质 * 二叉树存储结构 * 三、手动模拟实现顺序二叉树——堆 * 1.堆的结构 * 2.初始化 * 3.销毁 * 4.向上调整算法 * 5.插入数据 * 6.判空 * 7.求size * 8.向下调整算法

By Ne0inhk
【数据结构】排序算法(下篇·开端)·深剖数据难点

【数据结构】排序算法(下篇·开端)·深剖数据难点

前引:前面我们通过层层学习,了解了Hoare大佬的排序精髓,今天我们学习的东西可能稍微有点难度,因此我们必须学会思想,我很受感慨,借此分享一下:【用1520分钟去调试】,如果我们遇到了任何问题,必须先学会自己能不能解决,调试是每次代码找错的一个途径。通过每次调试,看它的数据变化是否达到了目前应该的预期,然后我们找到了错误,应该如何改进!下面小编通过拆分思想,一步步带你深解这些算法思想的奥妙! 目录  快排(双指针·递归) 算法思想: 实现步骤: 复杂度分析: 代码实现: 单趟实现: 递归实现:  代码优化: 优缺点分析: 快排(双指针·非递归) 算法思想: 实现步骤: 代码实现: 左区间: 右区间:   整体代码展示: 优缺点分析: 小编寄语  快排(双指针·递归) 大家在写部分题目的时候,是否有在评论区看见这样的评论:“双指针秒了”,小编当时见的很多,当时我不知道双指针是哈,但是现在领悟了!双指针是通过两个指针完成!

By Ne0inhk