java 状态机 位运算_位运算的应用

本文介绍了如何利用Java中的位运算解决特定计算问题,如不用加减乘除做加法,以及在数组中寻找只出现一次的数字。通过位运算实现的状态机模型,可以高效地处理这类问题,例如在数组中唯一只出现一次的数字,可以通过状态转移方程进行求解。
摘要由CSDN通过智能技术生成

位运算的应用

1、不用加减乘除做加法

写一个函数, 求两个整数之和, 要求在函数体内不得使用+、-、×、÷ 四则运算符号

两个数相加的和 $=$ 异或和 $+$ 进位

即 $a + b = a ^ b + (a $ & $ b) << 1 $

其中, $a$ & $b$ 具有 $ \leq min(a, b) $ 的性质, 依据此式, 可迭代求出两数的和(类似迭代求$gcd$)

classSolution {public:int add(int num1, intnum2) {int ans = 0, carry = 0; //异或和 以及 进位

while(num2) { //当进位不为0时

ans = num1 ^num2;

carry= (num1 & num2) << 1;

num1=ans;

num2=carry;

}returnans;

}

};

2、找到数组中只出现一次的两个数字

一个整型数组里除了两个数字之外, 其他的数字都出现了两次, 找出这两个只出现一次的数字

异或的性质:

1.异或两次等于没异或

2.相当于不进位加法, 即不产生进位

可求出所有数的异或和, 出现两次的等于没异或, 即最终等于只出现一次的两个数的异或和

因为该两个数不相同, 所以其异或和在二进制下一定存在一位为"1", 记该位为第 $pos$ 位, 且该位1是其中一个数特有的(否则两个都等于1异或完了等于0)

然后求出数组中所有 $pos$ 位上为1的数的异或和, 即其中一个数(其余的数被异或了两遍等于没异或), 最终另一个数就是两个数的异或和再异或一遍第一个数

classSolution {public:

vector findNumsAppearOnce(vector&nums) {int tot = 0; //所有数的异或和

for(int i = 0; i < nums.size(); i++) tot ^= nums[i]; //求出异或和

int pos = 0, tot1 = 0;while(!(tot >> pos & 1)) pos++; //求出两个数异或和tot在二进制表示下的第一位1

for(int i = 0; i < nums.size(); i++)if(nums[i] >> pos & 1) //求出数组中pos位上是1的数的异或和, 即其中的一个数(其余的数被异或了两遍等于没异或)

tot1 ^=nums[i];return vector{tot1, tot ^ tot1}; //另一个数就是两个数的异或和再异或一遍第一个数

}

};

3、数组中唯一只出现一次的数字

在一个数组中除了一个数字只出现一次之外, 其他数字都出现了三次, 请找出那个只出现一次的数字

法一($O(32n)$, 其实和$nlogn$差不多...)

按位确定那个数, 因为数组中其他数都出现了三次, 除去这个数后每一位上为1或0的数的个数都是3的倍数, 而加上这个数后有一边的个数会多1

所以可按位枚举, 遍历数组, 记录每位上是1和是0的个数为(1为 $tot1$, 最终根据 $tot1 % 3 == $ 0还是1来确定所求的数的该位

classSolution {public:int findNumberAppearingOnce(vector&nums) {int ans = 0; //确定答案

for(int i = 0; i < 32; i++) { //按位枚举

int tot0 = 0, tot1 = 0; //数组中第i位为0和1的数的个数//求出个数

for(int j = 0; j < nums.size(); j++)if(nums[j] >> i & 1) tot1++;else tot0++;if(tot1 % 3 == 1) ans |= 1 << i; //如果1的个数 % 3后多了一个, 那么该位就是1

}returnans;

}

};

法二(状态机模型, $O(n)$)

本题与前一题思路类似, 前一题中, 其他数都出现了两次, 因此需要的状态转移方式是, 如果出现两个1就抵消为0, 用一个变量和异或运算即可实现

而本题是需要1出现三次时才会抵消, 因此有三种状态, 即1出现的次数为 $3k, 3k + 1, 3k + 2$ 次

逐位来看, 要设计一个两位的状态转移, 出现三个1时抵消, 出现0时不变

一个变量只能表示两种状态, 要用两个变量来表示, 用 $one$ 和 $two$ 两个变量来记录1出现次数, 00表示1出现 $3k$ 次, 01表示1出现 $3k + 1$ 次, 10表示1出现 $3k + 2$ 次

真值表

(one, two)二元组

x(当前位, 0/1)

(one, two)二元组

(0, 0)

0

(0, 0)

(0, 0)

1

(0, 1)

(0, 1)

0

(0, 1)

(0, 1)

1

(1, 0)

(1, 0)

0

(1, 0)

(1, 0)

1

(0, 0)

可以看到, 该状态机有三种状态, 碰到0不发生改变, 碰到1进入下一个状态

$one$ 的状态转移方程

$ one = ($~$one$ & ~$ two $ & $ x) $ | $ (one $ & $ ~two $ & $ ~x) $

$ = $~$two$ & $(($~$one$ & $x)$ | $(one$ & ~$x))$

$ = $~$two$ & $(one$ ^ $x)$

同理, 再用转移后的 $one$ 来求 $two$ 的状态转移方程

其中 $one$ 为1当且仅当1出现次数为 $3k + 1$, $tow$ 为1当且仅当1出现次数为 $3k + 2$

因此如果题目改为, 有一个数出现了两次, 则返回 $two$ 即可

classSolution {public:int findNumberAppearingOnce(vector&nums) {int ones = 0, twos = 0; //状态机的两个变量(需要表示出3种状态)

for(int i = 0; i < nums.size(); i++) { //迭代求出最终的状态(即答案)

int x =nums[i];

ones= (ones ^ x) & ~twos;

twos= (twos ^ x) & ~ones;

}returnones;

}

};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值