力扣——位运算系列

ops/2024/9/20 3:53:51/ 标签: leetcode, java, 算法, 位运算
LeetCode 137. 只出现一次的数字 II

在这里插入图片描述

这题有两个思路:

1)逐位考虑;

2)状态机;

先来看逐位考虑的思路:

如果对于 nums 中的每个数只观察某一位,因为除了一个数(后用 a 来代称)只出现了一次,其余的数都出现了三次,一位上的数字只有 0 或 1 ,对该位求和。该位上为 0 的数对和没有影响,该位上为 1 的数,会使得 1 加三次,那么如果不算 a 的该位,求出的和应该是 3 的倍数。那么怎么得到 a 的该位呢?显然就是考虑上 a 的该位进行求和,该和 % 3 的值就是 a 该位的值。

接下来考虑一些实现细节。因为逐位考虑,一定会用到移位。题目给出的条件(提示里)说明 a 至多三十二位,初始化为 0 ,这样只需要让是 1 的位变成 1 即可。每一位的处理分两步,先让 x (auto x : nums)右移 i 位( i 从 0 开始递增),每次右移一位,只看从左往右最后一位的数字的累加情况( 也就是移位操作后与上 1 )。每次累加得到的数字对 3 取模,左移 i 位(对应原先在 a 中的位置),与初始化后的 a( 三十二位全 0 )相或,这样逐位将 a 中为 1 的位置赋为 1 。

代码实现:

初始化只出现了一次的数字每一位为 0 ;

        int res = 0;

每位处理时,初始化和为 0 ,累加;

            cnt = 0;for (auto x : nums) {cnt += (x >> i) & 1;}

还原原先在 res 中的位置(之前右移几位,现在就左移几位);

            res |= (cnt % 3) << i;

完整循环展示:

        for (int i = 0, cnt; i < 32; i++) {cnt = 0;for (auto x : nums) {cnt += (x >> i) & 1;}res |= (cnt % 3) << i;}

返回 32 位处理结束后的 res;

        return res;

提交总览:

在这里插入图片描述

至此,逐位考虑法结束。

接下来,来看一看状态机是怎么实现的!

首先,这个状态机是干什么的?在逐位考虑这种方法里面,res 一开始被初始化为 0 ,从本质上而言,只需要找到 res 中为 1 的位就可以了,而逐位考虑法将 0 也考虑了,其实是冗余的。设计状态机的目的就是可不可以只考虑什么情况下可以筛出 res 该位为 1 ,其余无需考虑。

显然,对于这个状态机的每个状态有两种输入:0 和 1 。对于这个状态机的所有状态而言,输入 0 都不会发生状态转移,因为无需考虑 0 的影响;而对于输入 1 ,状态转移就很有意思了。初态 0 输入 1 ,说明有一个新的数字 x 来了,状态转移至状态一(诞生新状态);状态一 又输入 1 ,连续输入的两个 1 说明这是一个出现三次的数字 x 出现的第二次,状态转移至 状态二(诞生新状态);状态二又输入 1 ,连续输入的三个 1 说明这是一个出现三次的数字 x 出现的第三次,状态转移至状态三(诞生新状态);状态三又输入 1 ,同一个数字 x 至多出现三次,这个新到的 1 说明是一个新的数字 y 来了,状态转移至状态一(无新状态产生);结束。

在这里插入图片描述

不难发现找到新数字的总是状态一,而状态二和状态三得到的数字一定是出现三次的数字。是不是有思路了?先等一等,刚刚分析出的这个状态机的状态 0 和状态三是可以合并的,先化简一下。

在这里插入图片描述

前面说到状态 1 会找到每个新出现的数字,出现在状态 2 的数字则都是会出现三次的数字,状态机某一时刻只会出现在一种状态,需要考虑的就是怎么用表达式更新状态 1 和状态 2 的值。

处在状态 1 有两种方式和一个前提:

方式: 1)状态 0 状态输入 1 转移过来的(当前状态机不在状态 1 );2)状态 1 输入 0 回到状态 1

这两种方式总结一下,即状态 1 异或输入值为 1 ;

前提: 状态机不可能到达状态 2 ,即非状态 2 ;

处在状态 2 同样有两种方式和一个前提:

方式: 1)状态 1 状态输入 1 转移过来的(当前状态机不在状态 2 );2)状态 2 输入 0 回到状态 2

这两种方式总结一下,即状态 2 异或输入值为 1 ;

前提: 状态机虽然可能出现在状态 1 ,但是当转移到状态 2 后,状态机就不在状态 1 了,即非状态 1;

最终应该返回状态 1 的数字,因为新发现且不在状态 2 中。

代码实现:

申请状态 1 和状态 2 ,都初始化为 0 ;

        int once = 0, twice = 0;

按上述思路写出表达式;

        for (auto x : nums) {once = (x ^ once) & (~twice);twice = (x ^ twice) & (~once);}

返回遍历结束后状态 1 的数字;

        return once;

提交总览:

在这里插入图片描述

至此,状态机的方法结束。

【LeetCode 672. 灯泡开关 Ⅱ】

在这里插入图片描述

在这里插入图片描述

思路:

这道题虽然给出了四种操作,但是每种操作都是规律性地针对某类位置进行反转,也就是说同个操作进行两次等于无效操作。如果给出的操作次数大于等于四次,有效操作最多四次。

第一种操作的周期是一;第二种和第三种操作的周期是二;第四种操作的周期是三。

所以这四种操作的所有搭配方式用长度为六的周期足以展示。也就是说目前将分析的范围缩小到了 n 在一到六之间,presses 在零到四之间。

假设有效操作是四个,观察列举出来的情况。

在这里插入图片描述

可以发现前三个状态确定了,后面三个状态也就确定了,分析范围进一步缩小。此时,只需分析,n 在一到三之间,presses 在零到四之间。

枚举计算:

  • 如果 presses = 0 ,那么就只有 1 种状态(灯都开着)。
  • 否则如果 n = 1,那么有 2 种状态。
  • 否则如果 n = 2 ,若 presses = 1,就有 3 种状态;若 presses >= 2,就有 4 种状态。
  • 否则如果 n >= 3,若 m = 1,就有 4 种状态;若 m = 2,就有 7 种状态;若 m >= 3,就有 种 8 状态。

代码实现:

依照枚举结果写即可:

        if (presses == 0) return 1;if (n == 1) return 2;presses = min(presses , 3);if (n == 2) return vector<int>{3, 4, 4}[presses - 1];return vector<int>{4, 7, 8}[presses - 1];

提交总览:
在这里插入图片描述

【LeetCode 810. 黑板异或游戏】

在这里插入图片描述

在这里插入图片描述

首先,假设现在有 n 个互相不同的数:

1)如果 n 是偶数,alice必胜;

2)如果 n 是奇数,alice必输;

现在考虑含有相同数字的情况:

切片观察,对于某 m 个相同的值,如果 m 为偶数,就都留在存在相同数的阵营里;如果 m 为奇数,就选出一个数去 n 个互相不同的数的阵营里,其余留下。

对于存在相同数的阵营,bob 的最优选择就是不断拿走不重复的数字,因为两个人是回合制的,所以每次 bob 拿完都会剩下偶数个数字或者刚好拿完直接输掉;对于 alice 而言,最优选择就是让剩下的偶数个数字越来越接近偶数个互相不同的数字,这样拿下去,要么首次出现偶数个互相不同的数字,要么 bob 刚好拿完直接获胜;偶数个互相不同的数字前面分析过,alice 必胜。所以存在相同数阵营不可能导致 alice 输掉。

在这里插入图片描述

那么 alice 是否获胜取决于整体的 n 是否是偶数,或者考虑一种边界情况,所有的数一开始异或的结果就为 0 ,alice 开局就获胜。

代码实现:

判断整体的数字个数是否是偶数;

        if (!(nums.size() & 1)) return true;

边界情况;(这里初始化 i 为 0 是因为, 0 遇到 1 变成 1 ,0 遇到 0 还是 0 ,第一次可以加载数组的首个数字,同时后续维持异或逻辑)

        int i = 0;for (auto x : nums) i ^= x;return !i;

提交总览:

在这里插入图片描述

【LeetCode 861. 翻转矩阵后的得分】

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

思路:

每一行、每一列要么不翻转,要么翻转一次,再多是等价的,没有意义。

用贪心的思想:

要想最终和最大,第一列必须全为 1 。

证明很简单,对于任意一行,如果它的第一位是 1,那么这一位的二进制数值就是 2 的 n - 1 次方。反之如果这一位是 0 ,那么即使后面所有位全为 1,总数值也只能达到 2 的 n - 1 次方 - 1。所以第一位是一定要为 1 的。

这样就很简单了,每一行的翻转情况其实是确定的。如果第一位是 1,就不翻转,否则就翻转。

然后每一列还是看不翻转的 1 多,还是翻转后 1 多。

那么为啥不把每行第一位全翻转为 0 ,然后翻转第一列使得每行第一位全 1 呢?其实这样是等价的,相当于第一位是 1 翻转该行,第一位是 0 不翻转,每一列还是看不翻转的 1 多,还是翻转后 1 多。

代码实现:

记录矩阵列高和行宽;

        int height = grid.size(), width = grid[0].size();

对第一列进行遍历,遇到 0 ,翻转 0 所在的一整行;(这里翻转一行采用的是逐位异或 1)

        for (int i = 0; i < height; i++) {if (!grid[i][0]) {for (int j = 0; j < width; j++) {grid[i][j] ^= 1;}}}

计算第一列转十进制的数值,从每一行的角度来看最高位对应的数量级就是 width - 1 ,因为最低位对应的 2 的 0 次方。乘以 height 是因为按照上述思路第一列应该满 1 。

        int res = (1 << (width - 1)) * height;

初始化每列的 1 的个数为 0 ,计算每列的 1 的个数(其实就是每列求和,0 不影响)。

            cnt = 0;for (int h = 0; h < height; h++) {cnt += grid[h][k];}

如果 1 的个数少于翻转后的个数,取翻转后的个数进行转十进制的数值计算;

            cnt = max(cnt , height - cnt);res += (1 << (width - 1 - k)) * cnt;

循环整体如下:

        for (int k = 1, cnt; k < width; k++) {cnt = 0;for (int h = 0; h < height; h++) {cnt += grid[h][k];}cnt = max(cnt , height - cnt);res += (1 << (width - 1 - k)) * cnt;}

返回遍历结束后的 res;

        return res;

提交总览:

在这里插入图片描述

LeetCode Offer 65. 不用加减乘除做加法

在这里插入图片描述

思路:

因为不允许采用四则运算,所以只能考虑位运算了。

其实就是用二进制来模拟加法操作。首先将两个数最低位相加,如果都是 1 ,那么就得到 0 ,并且进位 1,然后接着算下一位。

但是这样一位一位模拟不方便实现,更简单的实现方法是直接把两个数对应位相加,不管进位。然后进位单独计算,如果某一位两个数都是 1 ,那么进位就会对下一位产生影响。然后接着算不进位求和加上进位的值,再计算新的进位,依次重复下去,直到进位为 0 为止。

用一个实际的例子来演示一下,计算 3 + 7 的值,其中 s 表示每一步不考虑进位的求和,c 表示每一步的进位,最后得到结果 1010 ,也就是十进制的 10 :

在这里插入图片描述

因为是二进制,所以不考虑进位求和的话,可以直接采用异或运算。而计算进位的话,直接用位与左移一位就行了。

代码实现:

当进位不为 0 时,先于异或计算进位的值,因为异或的值会赋给 a ,如果在异或之后计算进位,此时的 a 已经改变了;

int carry = (unsigned int) (a & b) << 1;

计算异或,并将之前计算的进位值赋给 b ,进入下一轮运算;

            a ^= b;b = carry;

完整循环如下:

        while(b) {int carry = (unsigned int) (a & b) << 1;a ^= b;b = carry;}

返回进位为 0 后(即运算终止后)的 a 值;

        return a;

提交总览:
在这里插入图片描述

至此,位运算系列结束!!


http://www.ppmy.cn/ops/15123.html

相关文章

Docker容器:docker基础

目录 一.docker前言 云计算服务模式 关于 docker 产品 虚拟化产品有哪些&#xff1f; ① 寄居架构 ② 源生架构 虚拟化技术概述 主流虚拟化产品对比 1. VMware系列 2. KVM/OpenStack 3. Xen 4. 其他半/全虚拟化产品 二. docker 的相关知识 1. docker 的概念 doc…

用斐波那契数列感受算法的神奇(21亿耗时0.02毫秒)

目录 一、回顾斐波那契数列 二、简单递归方法 &#xff08;一&#xff09;解决思路 &#xff08;二&#xff09;代码展示 &#xff08;三&#xff09;性能分析 三、采用递归HashMap缓存 &#xff08;一&#xff09;解决思路 &#xff08;二&#xff09;代码展示 &…

基于adb操作安卓手机封装的python库

import re import shlex import subprocessclass ADBClient:def __init__(self, ip, port):"""初始化ADBClient实例。:param ip: 远程设备的IP地址。:param port: 远程设备的端口号。"""self.ip ipself.port portdef is_app_running(self, pac…

git版本库瘦身你知道吗

1.问题缘由 新开发一个git项目时&#xff0c;可能会使用之前的代码库作为基准&#xff0c;并且之前的代码库中可能有许多git提交记录&#xff08;.git文件夹下面有许多提交记录存储&#xff09;。这时需要处理以下2种情况。 1&#xff09;远程代码库中还未上传git提交记录&am…

使用FunASR处理语音识别

FunASR是阿里的一个语音识别工具&#xff0c;比SpeechRecognition功能多安装也很简单&#xff1b; 官方介绍&#xff1a;FunASR是一个基础语音识别工具包&#xff0c;提供多种功能&#xff0c;包括语音识别&#xff08;ASR&#xff09;、语音端点检测&#xff08;VAD&#xff…

Android 水滴屏、全屏适配

Android 水滴屏、全屏适配 何谓刘海屏&#xff1f;何谓水滴屏&#xff1f; 上述两种屏幕都可以统称为刘海屏&#xff0c;不过对于右侧较小的刘海&#xff0c;业界一般称为水滴屏或美人尖。 目前国内流行的手机厂商主要有&#xff1a;vivo、oppo、华为、小米。各厂商对刘海屏的…

GHO文件安装到Vmware的两种姿势

1、使用 Ghost11.5.1.2269 将gho转换为vmdk文件(虚拟机硬盘)&#xff0c;Vmware新建虚拟机自定义配置&#xff0c;然后添加已有的虚拟硬盘文件。 注意ghost的版本&#xff0c;如果你是用Ghost11.5备份的gho文件&#xff0c;再用Ghost12把gho文件转换为vmdk&#xff0c;则vmdk文…

深度学习之视觉特征提取器——VGG系列

VGG 提出论文&#xff1a;1409.1556.pdf (arxiv.org) 引入 距离VGG网络的提出已经约十年&#xff0c;很难想象在深度学习高速发展的今天&#xff0c;一个模型能够历经十年而不衰。虽然如今已经有VGG的大量替代品&#xff0c;但是笔者研究的一些领域仍然有大量工作选择使用VG…

vue2响应式 VS vue3响应式

Vue2响应式 存在问题&#xff1a; 新增属性&#xff0c;删除属性&#xff0c;界面不会更新。 直接通过下标修改数组界面不会自动更新。 Vue2使用object.defineProperty来劫持数据是否发生改变&#xff0c;如下&#xff1a; 能监测到获取和修改属性&#xff1a; 新增的属性…

服务器数据恢复—存储硬盘坏道,指示灯亮黄色的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 一台某品牌EqualLogic PS系列某型号存储&#xff0c;存储中有一组由16块SAS硬盘组建的RAID5磁盘阵列&#xff0c;RAID5上划分VMFS文件系统存放虚拟机文件。存储系统上层一共分了4个卷。 raid5阵列中磁盘出现故障&#xff0c;有2块硬盘…

安全评估报告 项目安全风险评估报告 信息安全评估报告

一、安全评估报告的意义 安全评估报告是对特定环境、设施或系统安全性进行全 面分析、评估和预测的重要工具。它通过对潜在风险的识别、分析和评价&#xff0c;帮助决策者了解当前安全形势&#xff0c;制定科学的安全策略&#xff0c;从而有 效预防和减少安全事故的发生。安全…

Hive安装与配置实战指南

Hive安装与配置实战指南 在大数据领域中&#xff0c;Hive以其类SQL的查询语言HQL、可扩展的数据仓库能力和对Hadoop生态系统的良好集成&#xff0c;成为了数据分析和处理的重要工具。本文将指导您完成Hive的安装与配置&#xff0c;帮助您快速搭建起自己的Hive环境。 一、环境…

C++静态变量

C语言中与“静态”相关的词包括&#xff0c;静态全局变量&#xff0c;静态局部变量和静态函数&#xff0c;关键词是static。C语言中的变量从作用域分&#xff0c;可以分为全局变量和局部变量&#xff1b;从存储方式分&#xff0c;可以分为静态存储方式和动态存储方式。 1. 静态…

C语言工程调用C++库解决方案

本文为C语言工程调用C库的解决方案。 应用场景&#xff1a; 需要C程序编译成的库提供函数接口&#xff0c;来解决C语言工程的需求。 人的出场顺序真的很重要&#xff0c;很多人如果换一个时间认识&#xff0c;换一个时间共处&#xff0c;一切都将是不一样的场景&#xff0c;不…

鸿蒙原生应用元服务-访问控制(权限)开发应用权限列表二

ohos.permission.ACCELEROMETER 允许应用读取加速度传感器的数据。 权限级别 &#xff1a;normal 授权方式 &#xff1a;system_grant ACL使能 &#xff1a;TRUE ohos.permission.GYROSCOPE 允许应用读取陀螺仪传感器的数据。 权限级别 &#xff1a;normal 授权方式 &a…

C++高级特性:异常概念与处理机制(十四)

1、异常的基本概念 异常&#xff1a;是指在程序运行的过程中发生的一些异常事件&#xff08;如&#xff1a;除数为0&#xff0c;数组下标越界&#xff0c;栈溢出&#xff0c;访问非法内存等&#xff09; C的异常机制相比C语言的异常处理&#xff1a; 函数的返回值可以忽略&…

Mongodb支持事务吗?

一、概念 1.1、MongoDB事务简介 MongoDB 是一个非关系型数据库管理系统&#xff0c;最初并不支持事务。然而&#xff0c;随着时间的推移&#xff0c;MongoDB 在其4.0版本中引入了多文档事务支持&#xff0c;使得在单个集合中执行多个操作成为可能。 In MongoDB, an operation…

C#面:阐述什么是泛型,泛型的优点有哪些?

泛型是 C# 中的一种特性&#xff0c;它允许我们编写可以在不同类型上工作的可重用代码。 通过使用泛型&#xff0c;我们可以编写更加灵活和通用的代码&#xff0c;而不需要为每种类型都编写重复的代码。 泛型的优点有以下几个方面&#xff1a; 代码重用&#xff1a;使用泛型可…

装饰器模式

一、实现原理 装饰器设计模式&#xff08;Decorator&#xff09;是一种结构型设计模式&#xff0c;它允许动态地为对象添加新的行为。它通过创建一个包装器来实现&#xff0c;即将对象放入一个装饰器类中&#xff0c;再将装饰器类放入另一个装饰器类中&#xff0c;以此类推&am…

nginx反向代理

简介 Nginx反向代理是一种服务器架构模式&#xff0c;它允许Nginx服务器接收客户端的请求&#xff0c;然后将这些请求转发到上游服务器&#xff08;例如应用服务器&#xff09;进行处理&#xff0c;并将处理后的响应返回给客户端。在这个过程中&#xff0c;Nginx充当了客户端和…