【C#实现漫画算法系列】-判断 2 的乘方
微信上关注了算法爱好者这个公众号,有一个漫画算法系列的文章生动形象,感觉特别好,给大家推荐一下(没收过广告费哦),原文链接:漫画算法系列。也看到了许多同学用不同的语言来实现算法,作为一枚C#资深爱好的小学生,我在这个系列就用C#来实现一下里面的算法。欢迎大佬来点评!
下面我来引述一下-判断 2 的乘方这个算法。
题目1:实现一个方法,判断一个正整数是否是2的乘方(比如16是2的4次方,返回True;18不是2的乘方,返回False)。要求性能尽可能高。
作为一枚工科生,我们首先想到的是从数学角度来解决这个问题,那么稍微想一下,就有思路了,数学解法的套路是:
解法1:设置一个中间变量temp,来存放2的n次方。方法中建立一个循环,在循环中将temp与该正整数x比较,若相等则说明x为2的乘方,否则temp数值乘2,在循环中继续与x比较。当temp大于x时,说明在小于x的2的乘方的数中没有与x相等的,x不为2的乘方,退出循环。实现代码为:
using System; namespace powerof
{
class Program
{
static void Main(string[] args)
{
try
{
int entry = int.Parse(Console.ReadLine());
bool result = isPowerOfTwo(entry);
Console.WriteLine("是否是2的乘方:{0}", result);
}
catch
{
Console.WriteLine("你输入的数字有误,请重新启动程序输入!");
}
Console.ReadKey();
}
public static bool isPowerOfTwo(int number)
{
int temp = ;
while (temp <= number)
{
if (temp == number)
{
return true;
}
temp = temp * ;
}
return false;
}
}
}
这就是典型的用数学方法来解决问题的方式,这样做得到的结果绝对是正确的,但是效率并不高,需要多次在循环中让中间变量temp比较并乘2,算法的时间复杂度为O(logN),空间复杂度为O(1)。
那么如何用程序员的思维、更加高效的解决这个问题呢?我们知道在C#中有一种运算叫做位运算,位运算的特点是:算术左移即操作数乘2,算数右移即操作数除2(快速记忆:左乘右除),按位与0可将操作数按位清0,按位与1得到原操作数,按位或1可将操作数按位置1,按位异或0即得到原操作数,按位异或1即实现原操作数按位取反。还有等等一系列的用处就不一一列举了。
这里我们可以用位运算符来高效的解决有关二进制的问题。
解法2:实现方法大部分与上述方法一样,不同的是这里不需要让temp乘2,而是在循环中让temp左移一位实现乘2的效果。实现代码为:
public static bool isPowerOfTwo(int number)
{
int temp = ;
while (temp <= number)
{
if (temp == number)
{
return true;
}
temp = temp << ;
}
return false;
}
其实这里虽然用了位运算,但是解题的思路仍然和第一个解法是一样的,本质上讲只是换了一个运算temp的方式而已。时间复杂度和空间复杂度没有变。
那么到底怎样的思路才能最高效的解决这个问题呢?大家想一想,那些是2的乘方的正整数,例如1、2、4、8、16、18它们在二进制上都有什么样的特点呢?如下:
1: 00000001
2: 00000010
4: 00000100
8: 00001000
16:
18:
我们可以看到,它们的二进制数只有一位1。这条规律将会帮助我们实现一个时间复杂度为O(1)的算法,那么知道这个之后还要怎么办呢?其实由这条规律我们可以推出,当这些数减1之后:
1-1: 00000000
2-1: 00000001
4-1: 00000011
8-1: 00000111
16-1:
18-1: 00010001
即原数值最高位为0,低位全为1,这个时候我们让这个正整数和它减1后的结果按位相与,即x&x-1,得到的结果是:
1&1-1:
2&2-1:
4&4-1:
8& 8-1:
16&16-1:
18&18-1:
也就是说,在执行完上述操作后,若正整数为2的乘方,结果为0,否则结果非0。
解法3:当x&x-1后的结果为0时,该数为2的乘方。否则若结果不为0,该数不是2的乘方。我们用代码描述:
public static bool isPowerOfTwo(int number)
{ return (number & number - ) == ;
}
时间复杂度为O(1),不需要额外空间。由于计算机中数的形式都为二进制,用二进制的方法来解决二进制问题往往比用十进制的数学方法要高效。
题目2 :思考题 实现一个方法,求出一个正整数转换成二进制后的数字“1”的个数。要求性能尽可能高。
同题目1一样,我们首先会想到数学方法解决这道题,Talk is cheap ,show you the code !
解法1:设置一个变量count存放正整数x的二进制中数字“1”的个数,将x对2取余数。若余数为1,则可得到x二进制的第一个位为1,count加1;若余数为0,x二进制第一个的个位为0,count不变。然后将x除2,相当于将二进制数右移一位。在循环中重复上述步骤,当x不大于0时结束循环。此时count值为x二进制中1的个数。
using System; namespace powerof
{
class Program
{
static void Main(string[] args)
{
try
{
int entry = int.Parse(Console.ReadLine());
int result = counterOfOne(entry);
Console.WriteLine("{0}转换成二进制后的数字“1”的个数为{1}", entry,result);
}
catch
{
Console.WriteLine("你输入的数字有误,请重新启动程序输入!");
}
Console.ReadKey();
}
public static int counterOfOne(int number)
{
int count = ;
while (number > )
{
if(number%==)
count++;
number = number / ;
}
return count;
}
}
}
解法2:设置一个变量count存放正整数x的二进制中数字“1”的个数,判断x二进制最低位是否为“1”,若为1,count加1,否则count不变。将x向右移一位。在循环中重复上述步骤,当x不大于0时结束循环。此时count值为x二进制中1的个数。
using System; namespace powerof
{
class Program
{
static void Main(string[] args)
{
try
{
int entry = int.Parse(Console.ReadLine());
int result = counterOfOne(entry);
Console.WriteLine("{0}转换成二进制后的数字“1”的个数为{1}", entry,result);
}
catch
{
Console.WriteLine("你输入的数字有误,请重新启动程序输入!");
}
Console.ReadKey();
}
public static int counterOfOne(int number)
{
int count = ;
while (number > )
{
if ((number&)==)
count++;
number = number >> ; }
return count;
}
}
}
事实上这段代码的思想和上一个解法的思想是相同的,但是上一个解法用了数学方式解决,而这种解法用了位运算来解决。在这里,同样只有一个判断条件,就是循环条件。在循环中,count不停的加上x同1按位与的值,即加上x二进制最低位的值。最低位是1就加1,不是1就加0,相当于不变。然后,将x右移一位。在计算机中,运算速度:移位>乘法>除法。因此这段代码的运行时间要比上一个解法还要短。
解法3:
看起来似乎已经是最快的解法了,没错,但这仅仅是这个解题思路中的最快解法。我们总结一下,我们从开始看这个问题的开始,就一直用这一种思路:建立循环来将二进制移位,每次循环对当前最低位是否为1进行判断并计数,每次循环移一位。那么如果我们每次循环不止移一位呢?我们看上面描述的所有算法,无论二进制数中有几个1,都要一位一位的来判断。我们只有一次就跳到存有1的位置,才可以让算法的效率有质的飞跃。
可是我们每次移多位的话就会漏掉某一位,这样得到的结果是不正确的。怎么办?还记得我们的第一个题目吗?判断一个数是否为2的乘方,将其二进制x和x-1按位与。如果是2的乘方,结果为0,那么如果不是呢?举个例子:
18:
18-1:
18&18-1: 00010000=16(十进制)
与运算后的结果为16,那么:
16:
16-1:
16&16-1:
与运算后的结果为0,我们这时候可以发现,18的二进制数中有2个1,进行了两次x&x-1的与运算后为0。
也就是说,每执行一次x&x-1,实际上消除了一个二进制数中从最低位开始数的第一个的1。这样的话,我们就可以将算法改为:
using System; namespace powerof
{
class Program
{
static void Main(string[] args)
{
try
{
int entry = int.Parse(Console.ReadLine());
int result = counterOfOne(entry);
Console.WriteLine("{0}转换成二进制后的数字“1”的个数为{1}", entry,result);
}
catch
{
Console.WriteLine("你输入的数字有误,请重新启动程序输入!");
}
Console.ReadKey();
}
public static int counterOfOne(int number)
{
int count = ;
while (number > )
{
count++;
//number=number & number - 1;
number &= number - ;
}
return count;
}
}
}
由于我们规定输入的是正整数,所以一开始就可以++count(如果输入的是0或负数,那么结果就不正确了,小心这个bug,看清题目要求)。执行x&x-1,将结果放入x。循环条件判断逗号后边的表达式,也就是x的值是否为0,若不是0继续循环,否则停止。
这种算法不需要二进制数一位一位的挪,可以一步就将低位开始的第一个1找到,时间复杂度大部分情况下远小于O(logN)。只有当二进制数为全1时,时间复杂度为O(logN)。
那么再说一个二进制中老生常谈的问题吧。
题目3:输出一个正整数的二进制数。
其实C#里面有一个方法直接转化, 详见点击这个博客
但是我用另一个方法来实现:
using System; namespace powerof
{
class Program
{
static void Main(string[] args)
{
try
{
string entry = Console.ReadLine();
string result = DecToBin(entry);
Console.WriteLine("{0}转换成二进制后的数字为{1}", entry,result);
}
catch
{
Console.WriteLine("你输入的数字有误,请重新启动程序输入!");
}
Console.ReadKey();
} public static string DecToBin(string x)
{
string z = null;
int X = Convert.ToInt32(x);
int i = ;
double a, b = ;
while (X > )
{
a = X % ;
X = X / ;
b = b + a * Math.Pow(, i);
i++;
}
z = Convert.ToString(b);
return z;
} }
}
参考文章:MSDN 、castle_kao

友情提示
作者:mhq_martin
博客园地址:http://www.cnblogs.com/mhq-martin/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【C#实现漫画算法系列】-判断 2 的乘方的更多相关文章
- JAVA算法系列 冒泡排序
java算法系列之排序 手写冒泡 冒泡算是最基础的一个排序算法,简单的可以理解为,每一趟都拿i与i+1进行比较,两个for循环,时间复杂度为 O(n^2),同时本例与选择排序进行了比较,选择排序又叫直 ...
- javascript实现数据结构与算法系列:栈 -- 顺序存储表示和链式表示及示例
栈(Stack)是限定仅在表尾进行插入或删除操作的线性表.表尾为栈顶(top),表头为栈底(bottom),不含元素的空表为空栈. 栈又称为后进先出(last in first out)的线性表. 堆 ...
- 数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解
数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...
- 数据结构与算法系列2 线性表 链表的分类+使用java实现链表+链表源码详解
数据结构与算法系列2.2 线性表 什么是链表? 链表是一种物理存储单元上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表的链接次序实现的一系列节点组成,节点可以在运行时动态生成,每个节点包括两个 ...
- Leetcode算法系列(链表)之删除链表倒数第N个节点
Leetcode算法系列(链表)之删除链表倒数第N个节点 难度:中等给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点.示例:给定一个链表: 1->2->3->4-&g ...
- Leetcode算法系列(链表)之两数相加
Leetcode算法系列(链表)之两数相加 难度:中等给出两个 非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字.如果,我们将 ...
- 简答一波 HashMap 常见八股面试题 —— 算法系列(2)
请点赞,你的点赞对我意义重大,满足下我的虚荣心. Hi,我是小彭.本文已收录到 GitHub · Android-NoteBook 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注 ...
- C#算法之判断一个字符串是否是对称字符串
记得曾经一次面试时,面试官给我电脑,让我现场写个算法,判断一个字符串是不是对称字符串.我当时用了几分钟写了一个很简单的代码. 这里说的对称字符串是指字符串的左边和右边字符顺序相反,如"abb ...
- JAVA算法系列 快速排序
java算法系列之排序 手写快排 首先说一下什么是快排,比冒泡效率要高,快排的基本思路是首先找到一个基准元素,比如数组中最左边的那个位置,作为基准元素key,之后在最左边和最右边设立两个哨兵,i 和 ...
随机推荐
- Python使用np.c_和np.r_实现数组转换成矩阵
# -*- coding: utf-8 -*-"""Created on Sat Jun 30 14:49:22 2018 @author: zhen"&quo ...
- FastReport脚本把数据绑定到文本控件上
public class ReportScript { private void Data25_BeforePrint(object sender, EventArgs e)//Data25是指需要绑 ...
- Maven将依赖包、jar/war包及配置文件输出到指定目录
使用Maven插件将依赖包 jar包 war包及配置文件输出到指定目录 写在前面 最近遇到一个朋友遇到一个项目需要将 maven 的依赖包和配置文件分开打包然后用脚本执行程序.这样的好处在于可以随 ...
- 基于Redis的INCR实现一个限流器
模式:计数器 计数器是 Redis 的原子性自增操作可实现的最直观的模式了,它的想法相当简单:每当某个操作发生时,向 Redis 发送一个 INCR 命令. 比如在一个 web 应用程序中,如果想知道 ...
- mysql排序索引优化
为排序使用索引 KEY a_b_c (a,b,c) order by 能使用索引最左前缀 -order by a -order by a,b -order by a,b,c -order by a d ...
- go标准库的学习-database/sql
参考:https://studygolang.com/pkgdoc 导入方式: import "database/sql" sql包提供了保证SQL或类SQL数据库的泛用接口. 使 ...
- Oracle 11g rac 添加新节点测试
[转]https://blog.csdn.net/shiyu1157758655/article/details/60877076 前期准备: 操作系统设置OS版本必须相同,检查内核参数,系统内存.C ...
- Linux系统学习之字符处理
管道 管道是一种使用非常频繁的通信机制,我们可以使用管道符"|"来连接进程,由管道连接起来订单进程可以自动运行,如同有一个数据流一样,所以管道表现为输入输出重定向的一种方法,它可以 ...
- mac版本idea使用(二)-如何安装PlantUML画时序图、类图
在跟踪spring源码的时候,看见网上的博客好多使用了idea自带的展示类继承关系图的功能,这个地方使用了idea的diagrams-show diagram,就可以显示类的继承图,很神奇的样子,记录 ...
- Gazebo仿真
1.建议在本地Ubuntu 16.04下运行仿真程序.目前Gazebo模拟器的兼容性是一大问题,在虚拟机或配置较低的电脑上可能无法运行.如果你的显卡是N卡,建议安装Ubuntu下的显卡驱动. 2.运行 ...