5道巧妙位操作的算法题。

***第一道***

题目描述

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1] 输出: 1

示例 2:

输入: [4,1,2,1,2] 输出: 4

题目解析

根据题目描述,由于加上了时间复杂度必须是O(n),并且空间复杂度为O(1)的条件,因此不能用排序方法,也不能使用map数据结构。

小吴想了一下午没想出来,答案是使用 位操作Bit Operation 来解此题。

将所有元素做异或运算,即a[1] ⊕ a[2] ⊕ a[3] ⊕ …⊕ a[n],所得的结果就是那个只出现一次的数字,时间复杂度为O(n)。

异或

异或运算A ⊕ B的真值表如下:

AB⊕FFFFTTTFTTTF

动画演示

进阶版

有一个 n 个元素的数组,除了两个数只出现一次外,其余元素都出现两次,让你找出这两个只出现一次的数分别是几,要求时间复杂度为 O(n) 且再开辟的内存空间固定(与 n 无关)。

示例 :

输入: [1,2,2,1,3,4] 输出: [3,4]

题目再解析

根据前面找一个不同数的思路算法,在这里把所有元素都异或,那么得到的结果就是那两个只出现一次的元素异或的结果。

然后,因为这两个只出现一次的元素一定是不相同的,所以这两个元素的二进制形式肯定至少有某一位是不同的,即一个为 0 ,另一个为 1 ,现在需要找到这一位。

根据异或的性质 任何一个数字异或它自己都等于 0,得到这个数字二进制形式中任意一个为 1 的位都是我们要找的那一位。

再然后,以这一位是 1 还是 0 为标准,将数组的 n 个元素分成两部分。

  • 将这一位为 0 的所有元素做异或,得出的数就是只出现一次的数中的一个
  • 将这一位为 1 的所有元素做异或,得出的数就是只出现一次的数中的另一个。

这样就解出题目。忽略寻找不同位的过程,总共遍历数组两次,时间复杂度为O(n)。

动画再演示

***第二道***

题目来源于 LeetCode 上第 231 号问题:2 的幂。题目难度为 Easy,目前通过率为 45.6% 。

题目描述

给定一个整数,编写一个函数来判断它是否是 2 的幂次方。

示例 1:

  1. 输入: 1
  2. 输出: true
  3. 解释: 20 = 1

示例 2:

  1. 输入: 16
  2. 输出: true
  3. 解释: 24 = 16

示例 3:

  1. 输入: 218
  2. 输出: false

题目解析

首先,先来分析一下 2 的次方数的二进制写法:

仔细观察,可以看出 2 的次方数都只有一个 1 ,剩下的都是 0 。根据这个特点,只需要每次判断最低位是否为 1 ,然后向右移位,最后统计 1 的个数即可判断是否是 2 的次方数。

代码很简单:

  1. class Solution {
  2. public:
  3. bool isPowerOfTwo(int n) {
  4. int cnt = 0;
  5. while (n > 0) {
  6. cnt += (n & 1);
  7. n >>= 1;
  8. }
  9. return cnt == 1;
  10. }
  11. };

该题还有一种巧妙的解法。再观察上面的表格,如果一个数是 2 的次方数的话,那么它的二进数必然是最高位为1,其它都为 0 ,那么如果此时我们减 1 的话,则最高位会降一位,其余为 0 的位现在都为变为 1,那么我们把两数相与,就会得到 0。

比如 2 的 3 次方为 8,二进制位 1000 ,那么 8 - 1 = 7,其中 7 的二进制位 0111。

图片描述

代码实现

利用这个性质,只需一行代码就可以搞定。

  1. class Solution {
  2. public:
  3. bool isPowerOfTwo(int n) {
  4. return (n > 0) && (!(n & (n - 1)));
  5. }
  6. };

***第三道***

### 题目描述

给定一个整数 (32 位有符号整数),请编写一个函数来判断它是否是 4 的幂次方。

示例 1:

  1. 输入: 16
  2. 输出: true

示例 2:

  1. 输入: 5
  2. 输出: false

进阶:
你能不使用循环或者递归来完成本题吗?

题目解析

这道题最直接的方法就是不停的去除以 4 ,看最终结果是否为 1 ,参见代码如下:

  1. class Solution {
  2. public boolean isPowerOfFour(int num) {
  3. while ( (num != 0) && (num % 4 == 0)) {
  4. num /= 4;
  5. }
  6. return num == 1;
  7. }
  8. }

不过这段代码使用了 循环 ,逼格不够高。

对于一个整数而言,如果这个数是 4 的幂次方,那它必定也是 2 的幂次方。

我们先将 2 的幂次方列出来找一下其中哪些数是 4 的幂次方。

十进制二进制2104100 (1 在第 3 位)810001610000(1 在第 5 位)32100000641000000(1 在第 7 位)12810000000256100000000(1 在第 9 位)5121000000000102410000000000(1 在第 11 位)

找一下规律: 4 的幂次方的数的二进制表示 1 的位置都是在奇数位

之前在小吴的文章中判断一个是是否是 2 的幂次方数使用的是位运算 n & ( n - 1 )。同样的,这里依旧可以使用位运算:将这个数与特殊的数做位运算。

这个特殊的数有如下特点:

  • 足够大,但不能超过 32 位,即最大为 1111111111111111111111111111111( 31 个 1)
  • 它的二进制表示中奇数位为 1 ,偶数位为 0
    符合这两个条件的二进制数是:
  1. 1010101010101010101010101010101

如果用一个 4 的幂次方数和它做与运算,得到的还是 4 的幂次方数

将这个二进制数转换成 16 进制表示:0x55555555 。有没有感觉逼格更高点。。。

代码实现

  1. class Solution {
  2. public boolean isPowerOfFour(int num) {
  3. if (num <= 0)
  4. return false;
  5. //先判断是否是 2 的幂
  6. if ((num & num - 1) != 0)
  7. return false;
  8. //如果与运算之后是本身则是 4 的幂
  9. if ((num & 0x55555555) == num)
  10. return true;
  11. return false;
  12. }
  13. }

***************************第四道*******************************

题目描述

喜羊羊和灰太狼用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i]

游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。

喜羊羊和灰太狼轮流进行,喜羊羊先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。

假设喜羊羊和灰太狼都发挥出最佳水平,当喜羊羊赢得比赛时返回 true ,当灰太狼赢得比赛时返回 false

题目分析

举两个例子来帮助理解题意。

例子一:

输入:[ 5,3,4,5 ]

输出:true

解释

喜羊羊先开始,只能拿前 5 颗或后 5 颗石子 。

假设他取了前 5 颗,这一行就变成了 [ 3 ,4,5 ] 。

如果灰太狼拿走前 3 颗,那么剩下的是 [ 4,5 ],喜羊羊拿走后 5 颗赢得 10 分。

如果灰太狼拿走后 5 颗,那么剩下的是 [ 3,4 ],喜羊羊拿走后 4 颗赢得 9 分。

这表明,取前 5 颗石子对喜羊羊来说是一个胜利的举动,所以我们返回 true 。

例子二:

输入:[ 5,10000,2,3 ]

输出:true

解释

喜羊羊先开始,只能拿前 5 颗或后 3 颗石子 。

假设他取了后 3 颗,这一行就变成了 [ 5,10000,2 ]。

灰太狼肯定会在剩下的这一行中取走前 5 颗,这一行就变成了 [ 10000,2 ]。

然后喜羊羊取走前 10000 颗,总共赢得 10003 分,灰太狼赢得 7 分。

这表明,取后 3 颗石子对喜羊羊来说是一个胜利的举动,所以我们返回 true 。

这个例子表明,并不是需要每次都挑选最大的那堆石头

题目回答

涉及到最优解的问题,那么肯定要去尝试一下使用 **动态规划 **来解决了。

先看一下力扣的正规题解:

让我们改变游戏规则,使得每当灰太狼得分时,都会从喜羊羊的分数中扣除。

dp(i, j) 为喜羊羊可以获得的最大分数,其中剩下的堆中的石子数是 piles[i], piles[i+1], ..., piles[j]。这在比分游戏中很自然:我们想知道游戏中每个位置的值。

我们可以根据 dp(i + 1,j)dp(i,j-1) 来制定 dp(i,j) 的递归,我们可以使用动态编程以不重复这个递归中的工作。(该方法可以输出正确的答案,因为状态形成一个DAG(有向无环图)。)

当剩下的堆的石子数是 piles[i], piles[i+1], ..., piles[j] 时,轮到的玩家最多有 2 种行为。

可以通过比较 j-iN modulo 2 来找出轮到的人。

如果玩家是喜羊羊,那么它将取走 piles[i]piles[j] 颗石子,增加它的分数。之后,总分为 piles[i] + dp(i+1, j)piles[j] + dp(i, j-1);我们想要其中的最大可能得分。

如果玩家是灰太狼,那么它将取走 piles[i]piles[j] 颗石子,减少喜羊羊这一数量的分数。之后,总分为 -piles[i] + dp(i+1, j)-piles[j] + dp(i, j-1);我们想要其中的最小可能得分。

代码如下:

上面的代码并不算复杂,当然,如果你看不懂也没关系,不影响解决问题,请看下面的数学分析。

数学分析

因为石头的数量是奇数,因此只有两种结果,输或者赢。

喜羊羊先开始拿石头,随便拿!然后比较石头数量:

  1. 如果石头数量多于对手,赢了;
  2. 如果石头数量少于对手,自己拿石头的顺序和对手拿石头的顺序对调,还是赢。

所以代码如下:

  1. class Solution {
  2. public boolean stoneGame(int[] piles) {
  3. return true;
  4. }
  5. }

看完之后,你的心情是怎么样的?

***第五道***

题目来源于 LeetCode 上第 172 号问题:阶乘后的零。题目难度为 Easy,目前通过率为 38.0% 。

题目描述

给定一个整数 n,返回 n! 结果尾数中零的数量。

示例 1:

  1. 输入: 3
  2. 输出: 0
  3. 解释: 3! = 6, 尾数中没有零。

示例 2:

  1. 输入: 5
  2. 输出: 1
  3. 解释: 5! = 120, 尾数中有 1 个零.

说明: 你算法的时间复杂度应为 O(log n) 。

题目解析

题目很好理解,数阶乘后的数字末尾有多少个零。

最简单粗暴的方法就是先乘完再说,然后一个一个数。

事实上,你在使用暴力破解法的过程中就能发现规律: 这 9 个数字中只有 2(它的倍数) 与 5 (它的倍数)相乘才有 0 出现

所以,现在问题就变成了这个阶乘数中能配 多少对 2 与 5

举个复杂点的例子:

10! = 【 2 *( 2 * 2 )* 5 *( 2 * 3 )*( 2 * 2 * 2 )*( 2 * 5)】

在 10!这个阶乘数中可以匹配两对 2 * 5 ,所以10!末尾有 2 个 0。

可以发现,一个数字进行拆分后 2 的个数肯定是大于 5 的个数的,所以能匹配多少对取决于 5 的个数。(好比现在男女比例悬殊,最多能有多少对异性情侣取决于女生的多少)。

那么问题又变成了 统计阶乘数里有多少个 5 这个因子

需要注意的是,像 25,125 这样的不只含有一个 5 的数字的情况需要考虑进去。

比如 n = 15。那么在 15! 中 有 35 (来自其中的5, 10, 15), 所以计算 n/5 就可以 。

但是比如 n=25,依旧计算 n/5 ,可以得到 55,分别来自其中的5, 10, 15, 20, 25,但是在 25 中其实是包含 25 的,这一点需要注意。

所以除了计算 n/5 , 还要计算 n/5/5 , n/5/5/5 , n/5/5/5/5 , ..., n/5/5/5,,,/5直到商为0,然后求和即可。

代码实现

  1. public class Solution {
  2. public int trailingZeroes(int n) {
  3. return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5);
  4. }
  5. }
  1. 参考:程序员吴师兄 https://www.zhihu.com/question/33776070/answer/685253646
  1.  

Bit Operation妙解算法题的更多相关文章

  1. 位操作Bit Operation算法题

    一道让你拍案叫绝的算法题   这是一道看完答案会觉得很简单,但做之前很难想到答案的题目!!! 不信? Let us go ! 题目描述 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均 ...

  2. 解决一道leetcode算法题的曲折过程及引发的思考

    写在前面 本题实际解题过程是 从 40秒 --> 24秒 -->1.5秒 --> 715ms --> 320ms --> 48ms --> 36ms --> ...

  3. 奇妙的算法之LCS妙解

    LCS算法妙解 LCS问题简述:最长公共子序列 一个数列 S,如果分别是两个或多个已知数列的子序列,且是所有符合此条件序列中最长的,则S 称为已知序列的最长公共子序列. LCS问题的分支:最长公共子串 ...

  4. Newtonsoft.Json C# Json序列化和反序列化工具的使用、类型方法大全 C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数 C# 算法题系列(一) 两数之和、无重复字符的最长子串 DateTime Tips c#发送邮件,可发送多个附件 MVC图片上传详解

    Newtonsoft.Json C# Json序列化和反序列化工具的使用.类型方法大全   Newtonsoft.Json Newtonsoft.Json 是.Net平台操作Json的工具,他的介绍就 ...

  5. 高盛昂赛 算法题先写corner case

    [方法] 字写大点,先注释框架 链表:指针走就行了,最多是两个同时一起走. 两个链表求交点 //corner case if (headA == null || headB == null) { re ...

  6. 经典算法题每日演练——第十一题 Bitmap算法

    原文:经典算法题每日演练--第十一题 Bitmap算法 在所有具有性能优化的数据结构中,我想大家使用最多的就是hash表,是的,在具有定位查找上具有O(1)的常量时间,多么的简洁优美, 但是在特定的场 ...

  7. 【每天一道算法题】时间复杂度为O(n)的排序

    有1,2,……一直到n的无序数组,求排序算法,并且要求时间复杂度为O(n),空间复杂度为O(1),使用交换,而且一次只能交换两个数. 这个是以前看到的算法题,题目不难.但是要求比较多,排序算法中,时间 ...

  8. Kotlin实现LeetCode算法题之Two Sum

    LeetCode介绍 LeetCode是算法练习.交流等多功能网站,感兴趣的同学可以关注下(老司机请超车).页面顶部的Problems菜单对应算法题库,附带历史通过滤.难易程度等信息. 未来计划 打算 ...

  9. [2]十道算法题【Java实现】

    前言 清明不小心就拖了两天没更了-- 这是十道算法题的第二篇了-上一篇回顾:十道简单算法题 最近在回顾以前使用C写过的数据结构和算法的东西,发现自己的算法和数据结构是真的薄弱,现在用Java改写一下, ...

随机推荐

  1. Android商城开发系列(十)—— 首页活动广告布局实现

    在上一篇博客当中,我们讲了频道布局的实现,接下来我们讲解一下活动广告布局的实现,效果如下图: 这个是用viewpager去实现的,新建一个act_item.xml,代码如下所示: <?xml v ...

  2. 访问FTP站点下载文件,提示“当前的安全设置不允许从该位置下载文件”的解决方案

    访问FTP站点下载文件,提示“当前的安全设置不允许从该位置下载文件”的解决方案: 打开客戶端浏览器--工具---internet-安全-自定义级别-选择到低到中低. 然后点受信任站点,把你要访问的站点 ...

  3. SharePoint Survey – Custom Action

    <?xml version="1.0" encoding="utf-8" ?> <Elements xmlns="http://sc ...

  4. 富文本编辑器Ueditor的使用

    1.下载:http://ueditor.baidu.com/website/download.html. 2.解压,并放到项目webapp下. 3.jsp页面的配置. 4.配置根路径. 5.页面展示: ...

  5. POJ 1769 Minimizing maximizer (线段树优化dp)

    dp[i = 前i中sorter][j = 将min移动到j位置] = 最短的sorter序列. 对于sorteri只会更新它右边端点r的位置,因此可以把数组改成一维的,dp[r] = min(dp[ ...

  6. Android(java)学习笔记88:BaseAdapter适配器重写之getView()

    1. BaseAdapter适配器重写 之getView(): (1)View getview(int position, View convertview, ViewGroup parent ) 第 ...

  7. C++之RAII惯用法

    http://blog.csdn.net/hunter8777/article/details/6327704 C++中的RAII全称是“Resource acquisition is initial ...

  8. Shuffle Cards

    C: Shuffle Cards 时间限制: 1 Sec  内存限制: 128 MB提交: 3  解决: 3[提交] [状态] [讨论版] [命题人:admin] 题目描述 Eddy likes to ...

  9. Windows环境下在Oracle VM VirtualBOX下克隆虚拟机镜像(克隆和导入)

    Windows环境下在Oracle VM VirtualBOX下克隆虚拟机镜像: 注:直接复制一个.vdi 虚拟硬盘再挂上去就可以,但Virtualbox居然提示UUID重复,无法使用. 则,可以通过 ...

  10. Java Web Application使Session永不失效(利用cookie隐藏登录)

    在做 Web Application 时,因为 Web Project 有 session 自动失效的问题,所以如何让用户登录一次系统就能长时间运行三个月,就是个问题. 后来,看到 session 失 ...