🌈hello,你好鸭,我是Ethan,一名不断学习的码农,很高兴你能来阅读。
✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。
🏃人生之义,在于追求,不在成败,勤通大道。加油呀!
🔥个人主页:Ethan Yankang
🔥专栏:面试八股文||Java项目
🔥温馨提示:划到文末发现专栏彩蛋
🔥本篇概览:整数篇、二进制数底层使用等
宗旨:
我们真正需要的是系统学习并真正理解不同数据结构和算法的特性及适用场景
目录
2.❖ 递归——根据“i&(i-1)”计算i的二进制形式中1的个数
题目1:整数除法
输入2个int型整数,它们进行除法计算并返回商。要求不得使用乘号'*'、除号'/'及求余符号'%'。当发生溢出时,返回最大的整数值。假设除数不为0。例如,输入15和2,输出15/2的结果,即7。
public class Divide {
public int divide(int dividend, int divisor) {
//先进行特殊情况处理0和1
if (dividend == 0) {
return 0;
}
if (divisor == 1) {
return dividend;
}
//先将所有的数都转化为负数,这里的最小整数是处理溢出的,因为其没有对应的正数
if(dividend==0x80000000&&divisor==-1)
{
return Integer.MAX_VALUE;
}
//负负得正的思想,标记变量法
int negative = 2;
if(dividend>0)
{
dividend=-dividend;
negative--;
}
if(divisor>0)
{
divisor=-divisor;
negative--;
}
int result=divideCore(dividend,divisor);
result=negative==1?-result:result;
return result;
}
//除法核心步骤
private int divideCore(int dividend, int divisor) {
int result=0;
while(dividend<=divisor)
{
int temp=divisor;
int quotient=1;
while(dividend<=temp<<1)
{
//自增乘2的写法,优良之
temp<<=1;
quotient<<=1;
}
result+=quotient;
dividend-=temp;
}
return result;
}
}
总结:
1.一个变量在使用时在声明。
2.多使用integer.MAX_VALUE之类的枚举类型
3.多使用移位操作>>1
题目2:二进制加法
输入两个表示二进制的字符串,请计算它们的和,并以二进制字符串的形式输出。例如,输入的二进制字符串分别是"11"和"10",则输出"101"。
思路:排除先转化为int,在化为二进制的想法有不少人看到这个题目的第一反应是将二进制字符串转换 成int型整数或long型整数,然后把两个整数相加得到和之后再将和转 换成二进制字符串。例如,将二进制字符串"11"转换成3,"10"转换成 2,两个整数的和为5,将之转换成二进制字符串就得到"101"。这种解 法可能会导致溢出。这个题目没有限制二进制字符串的长度。当二进 制字符串比较长时,它表示的整数可能会超出int型整数或long型整数 的范围,此时不能直接将其转换成整数。可以模拟十进制的加法,这里使用逢二进一的思想。
public class addBinary {
public static String addBinary1(String a, String b) {
StringBuilder result = new StringBuilder();
int i = a.length() - 1;
int j = b.length() - 1;
// 标记进位的变量
int carry = 0;
while (i >= 0 || j >= 0) {
// 很有意思的操作,这里的a.charAt(i--) - '0'代表两者的顺序的差值,是数字
int digitA = i >= 0 ? a.charAt(i--) - '0' : 0;
int digitB = j >= 0 ? b.charAt(j--) - '0' : 0;
// 一位一位地相加
int sum = digitA + digitB + carry;
// 进位的数值大小
carry = sum >= 2 ? 1 : 0;
// 该位的和的大小(除去进位后)
sum = sum >= 2 ? sum - 2 : sum;
// 注意这里是反序的,后面一定会增加倒转操作
result.append(sum);
}
// 如果最后还有进位,则需要添加进位
if (carry == 1) {
result.append(carry);
}
// 反序操作
return result.reverse().toString();
}
}
总结:
1.很有意思的操作,这里的a.charAt(i--) - '0'代表两者的顺序的差值,是数字
2.对于会变化的字符,使用StringBuilder result = new StringBuilder();来声明更好
3.本题将三目运算符 ?:使用的淋漓精致,重点留意
题目3:前n个数字二进制形式中1的个数
输入一个非负数 n ,请计算0到 n 之间每个数字的二进制形式中1的个数,并输出一个数组。例如,输入的 n 为4,由于0、1、2、3、4的二进制形式中1的个数分别为0、1、1、2、1,因此输出数组[0,1,1,2,1]。
分析:既然能够表示出来整数,说明可以表示出来二进制,不会溢出。很多人在面试的时候都能想到直观的解法,使用一个for循 环来计算从0到n的每个整数i的二进制形式中1的个数。于是问题转换 成如何求一个整数i的二进制形式中1的个数。
应该有简便方法,而且是位操作。这里提供一个关键信息:
计算整数 i 的二进制形式中1的个数有多种不同的方法,其中一种比较高效的方法是每次用 “i&(i-1)”将整数i的最右边的1变成0” 。(既然知道了1的个数,那么0的个数也就知道了,不过一般都是1的,因为0的可以无限长)整数 i 减去1,那么它最右边的1变成0。如果它的右边还有0,则右边所有的0都变成1,而其左边所有位都保持不变。下面对 i 和 i -1进行位与运算,相当于将其最右边的1变成0。以二进制的1100为例,它减去1的结果是1011。1100和1011的位与运算的结果正好是1000。二进制的1100最右边的1变为0,结果刚好就是1000。
下面提供三种方法来解决这个问题
1.❖ 简单计算每个整数的二进制形式中1的个数
public class countBits {
public int[] countBits1(int num)
{
int [] result =new int[num+1];
for (int i = 0; i <= num; i++) {
int j=i;
// 直至将j变为0,里面的所有1就都被遍历了
while (j!=0)
{
result[i]++;
j=j&(j-1);
}
}
return result;
}
}
2.❖ 递归——根据“i&(i-1)”计算i的二进制形式中1的个数
根据前面的分析可知,“i&(i-1)”将 i 的二进制形式中最右边的1变成0,也就是说,整数i的二进制形式中1的个数比“i&(i-1)”的二进制形式中1的个数多1。我们可以利用这个规律写出如下代码:
public class countBits2 {
// 使用递归法
public int[] countBits(int num)
{
int[] result = new int[num+1];
// 注意这里默认的result[0]=0;也就是递归条件了
for (int i = 1; i < num; i++) {
// 递归简直就是神了!
result[i]=result[i&(i-1)]+1;
}
return result;
}
}
总结:
如果能够挖掘出当前条件与上一个条件的关系,那么递归就是显而易见的了
3.❖ 递归——根据“i/2”计算i的二进制形式中1的个数
如果i是偶数,那么两者(i与i/2)的中的二进制1就是个数相同的,如果是奇数,显然就是多一个1.
则基于以上关系,可以得到递归关系:
public class countBits3 {
public int[] countBits(int num)
{
int[] result = new int[num+1];
for(int i = 0; i <= num; i++)
{
// 这里的i&1就是等价于i%2,只不过这样写效率更高。也更高级。
result[i] = result[i>>1] + (i&1);
}
return result;
}
}
总结:i&1等价于i%2.
题目4:只出现一次的数字
输入一个整数数组,数组中只有一个数字出现了一次,而其他数字都出现了3次。请找出那个只出现一次的数字。例如,如果输入的数组为[0,1,0,1,0,1,100],则只出现一次的数字是100。
public class singleNumber {
public int singleNumber(int[] nums)
{
int[] bitSums = new int[32];
//这种数组遍历方法很好
for (int num : nums) {
for (int i = 0; i < 32; i++) {
//这里使用反向遍历的原因是方便以后再往回倒
bitSums[i] += (num>>(31-i)) & 1;
}
}
int res = 0;
for (int i = 0; i < 32; i++) {
res = (res << 1)+bitSums[i] % 3;
}
//对位进行操作后,底层会根据数据类型直接解释成整数的
return res;
}
}
举一反三:m次与n次
res = (res << 1)+bitSums[i] % 3;
的3改成m就好。
本章总结:
💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖
热门专栏推荐
🌈🌈计算机科学入门系列 关注走一波💕💕
🌈🌈CSAPP深入理解计算机原理 关注走一波💕💕
🌈🌈微服务项目之黑马头条 关注走一波💕💕
🌈🌈redis深度项目之黑马点评 关注走一波💕💕
🌈🌈Java面试八股文系列专栏 关注走一波💕💕
🌈🌈算法leetcode+剑指offer 关注走一波💕💕
总栏
🌈🌈JAVA后端技术栈 关注走一波💕💕
🌈🌈JAVA面试八股文 关注走一波💕💕
🌈🌈JAVA项目(含源码深度剖析) 关注走一波💕💕
🌈🌈计算机四件套 关注走一波💕💕
🌈🌈算法 关注走一波💕💕
🌈🌈必知必会工具集 关注走一波💕💕
🌈🌈书籍网课笔记汇总 关注走一波💕💕
🌈🌈考试复习资料 关注走一波💕💕
🌈🌈C/C++技术栈 关注走一波💕💕
🌈🌈GO技术栈 关注走一波💕💕
分栏
🌈🌈JAVA后端技术栈
🌈🌈spring 关注走一波💕💕
🌈🌈redis 关注走一波💕💕
🌈🌈MySQL 关注走一波💕💕
🌈🌈mybatis 关注走一波💕💕
🌈🌈mybatisplus 关注走一波💕💕
🌈🌈MQ 关注走一波💕💕
🌈🌈微服务 关注走一波💕💕
🌈🌈设计模式 关注走一波💕💕
🌈🌈分布式锁 关注走一波💕💕
🌈🌈JAVA八股文JAVA面试八股文(redis、MySQL、框架、微服务、MQ、JVM、设计模式、并发编程、JAVA集合、常见技术场景) 关注走一波💕💕
🌈🌈JAVA项目(含源码深度剖析)
🌈🌈黑马头条(微服务) 关注走一波💕💕
🌈🌈黑马点评(redis) 关注走一波💕💕
🌈🌈计算机四件套
🌈🌈计算机基础 关注走一波💕💕
🌈🌈计算机基础 关注走一波💕💕
🌈🌈计算机网络 关注走一波💕💕
🌈🌈数据结构与算法 关注走一波💕💕
🌈🌈算法
🌈🌈leetcode 关注走一波💕💕
🌈🌈剑指offer 关注走一波💕💕
🌈🌈必知必会工具集 关注走一波💕💕
🌈🌈书籍网课笔记汇总
🌈🌈CSAPP笔记 关注走一波💕💕
🌈🌈计算机科学速成课 关注走一波💕💕
🌈🌈CS自学指南 关注走一波💕💕
🌈🌈读书笔记与每日记录 关注走一波💕💕
🌈🌈考试复习资料 关注走一波💕💕
🌈🌈C/C++技术栈 关注走一波💕💕
🌈🌈GO技术栈 关注走一波💕💕
📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤收藏✅ 评论💬,大佬三连必回哦!thanks!!!
📚愿大家都能学有所得,功不唐捐!