文章目录
补码
正数的补码是其本身;负数的补码是其反码+1。例如:0000 0001的补码0000 0001;1000 0001的补码是1111 1111
1.二进制中1的个数
题目
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
答案
思路:
一个二进制数如果 - 1,则他最右面的 1 会变成 0 ,最右面的 1 的后面的所有 0 会变成 1,比如:1010 1100 - 1 = 1010 0011。
所以,n&(n-1)的结果就是去掉最右边的 1 的值,如果用上面的数的话,结果就是1010 1000,也就是每运算一次,去掉一个1。
如果求二进制中1或0的个数的话,还可以每次右移一位,判断是否为零!
/**
* 二进制中1的个数
* @param n
* @return
*/
public static int NumberOf1(int n) {
int count = 0;
while(n != 0) {
n = n & (n - 1);
count++;
}
return count;
}
2.求1+2+3+…+n
题目
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
答案
思路:知道&&运算符左边不成立,不会运算右边就很好做。
/**
* 用if条件判断实现
* @param n
* @return
*/
public static int f(int n) {
if(n == 0) {
return 0;
}else {
return f(n -1) + n;
}
}
/**
* 三元条件判断符实现
* @param n
* @return
*/
public static int f(int n) {
return n > 0 ? (f(n - 1) + n) : 0;
}
/**
* 采用&&运算符
* @param n
* @return
*/
public static int Sum_Solution(int n) {
int sum = n;
boolean sign = n > 0 && (sum += Sum_Solution(n - 1)) != 0;
return sum;
}
3.不用加减乘除做加法
题目
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
答案
思路:看讨论区大佬说的!
首先看十进制是如何做的: 5+7=12,三步走
第一步:相加各位的值,不算进位,得到2。
第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。
第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。
同样我们可以用三步走的方式计算二进制值相加: 5-101,7-111 第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。
第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。
第三步重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010)<<1。
继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。
/**
* 递归实现
* @param num1
* @param num2
* @return
*/
public static int Add(int num1, int num2) {
if (num2 == 0)
return num1;
else
return Add(num1 ^ num2, (num1 & num2) << 1);
}
/**
* 循环实现
* @param num1
* @param num2
* @return
*/
public static int Add(int num1, int num2) {
int sum = num1;
while(num2 != 0) {
sum = num1 ^ num2;
num2 = (num1 & num2) << 1;
num1 = sum;
}
return sum;
}
4.旋转数组的最小数字
题目
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
答案
思路:剑指Offer中有这道题目的分析。这是一道二分查找的变形的题目。
旋转之后的数组实际上可以划分成两个有序的子数组:前面子数组的大小都大于后面子数组中的元素
注意到实际上最小的元素就是两个子数组的分界线。本题目给出的数组一定程度上是排序的,因此我们试着用二分查找法寻找这个最小的元素。
思路:
(1)我们用两个指针left,right分别指向数组的第一个元素和最后一个元素。按照题目的旋转的规则,第一个元素应该是大于最后一个元素的(没有重复的元素)。
但是如果不是旋转,第一个元素肯定小于最后一个元素。
(2)找到数组的中间元素。
中间元素大于第一个元素,则中间元素位于前面的递增子数组,此时最小元素位于中间元素的后面。我们可以让第一个指针left指向中间元素。
移动之后,第一个指针仍然位于前面的递增数组中。
中间元素小于第一个元素,则中间元素位于后面的递增子数组,此时最小元素位于中间元素的前面。我们可以让第二个指针right指向中间元素。
移动之后,第二个指针仍然位于后面的递增数组中。
这样可以缩小寻找的范围。
(3)按照以上思路,第一个指针left总是指向前面递增数组的元素,第二个指针right总是指向后面递增的数组元素。
最终第一个指针将指向前面数组的最后一个元素,第二个指针指向后面数组中的第一个元素。
也就是说他们将指向两个相邻的元素,而第二个指针指向的刚好是最小的元素,这就是循环的结束条件。
到目前为止以上思路很耗的解决了没有重复数字的情况,这一道题目添加上了这一要求,有了重复数字。
因此这一道题目比上一道题目多了些特殊情况:
我们看一组例子:{1,0,1,1,1} 和 {1,1, 1,0,1} 都可以看成是递增排序数组{0,1,1,1,1}的旋转。
这种情况下我们无法继续用上一道题目的解法,去解决这道题目。因为在这两个数组中,第一个数字,最后一个数字,中间数字都是1。
第一种情况下,中间数字位于后面的子数组,第二种情况,中间数字位于前面的子数组。
因此当两个指针指向的数字和中间数字相同的时候,我们无法确定中间数字1是属于前面的子数组(绿色表示)还是属于后面的子数组(紫色表示)。
也就无法移动指针来缩小查找的范围。
/**
* 旋转数组的最小数字
* @param array
* @return
*/
public int minNumberInRotateArray(int [] array) {
int left = 0;
int right = array.length - 1;
int mid;
while(left < right) {
if(right - left == 1)
break;
mid = (left + right) / 2;
if(array[mid] < array[left])
right = mid;
else
left = mid;
}
return array[right];
}
5.整数中1出现的次数
题目
求出1 - 13 的整数中1出现的次数,并算出100 - 1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
答案
思路:剑指讨论区大佬的解释,直接借用过来,方便以后复习,啦啦啦。




/**
* 整数中1的个数
* @param n
* @return
*/
public static int NumberOf1Between1AndN_Solution(int n) {
if(n <= 0)
return 0;
int count = 0;
for(int i = 1; i <= n; i *= 10)
count += (n / (i * 10)) * i + Math.max(Math.min(n % (i * 10) - i + 1, i), 0);
return count;
}
6.扑克牌顺子
题目
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张_)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
答案
思路:
先不考虑有大小王的情况:
五个数的顺子,所以数组只能有五个数;不能出现对子(重复数字);顺子的最大数减顺子的最小数只能等于四;只要满足这三个条件的数组,都可以组成顺子。
现在考虑有大小王的情况:
数组只能有五个数;不能有对子(除0之外的重复数字);最大数减最小数小于等于四;
/**
* 扑克牌顺子
* @param numbers
* @return
*/
public boolean isContinuous(int[] numbers) {
if(numbers.length != 5)
return false;
HashMap< Integer, Integer> map = new HashMap<>();
int max = 1;
int min = 13;
for(int i = 0; i < numbers.length; i++) {
if(numbers[i] == 0) {
continue;
}else if(map.containsKey(numbers[i])) {
return false;
}else {
map.put(numbers[i], 0);
min = Math.min(min, numbers[i]);
max = Math.max(max, numbers[i]);
}
}
if(max - min < 5)
return true;
return false;
}
7.孩子们的游戏(圆圈中最后剩下的数)
题目
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
答案
思路:
用循环链表,模仿游戏的整个过程,可以自己实现各循环链表,但是这样太麻烦,所以就用单链表或者双链表取余的方式达到循环链表的效果。
主要想说的是这行代码sd = (sd + m - 1) % list.size();,第一次删掉的位置是从0开始数m-1个位置, 以后每次从删掉的下一个节点开始取, 所以每次要在sd的索引处加上m-1,因为是模仿环,所以加了以后对链表长度取余,从而达到想要的效果。
还有一点,你可能会有疑问,报数是从0 - (m-1),总共m个数,为什么上面那行代码要加m-1,而不是m,因为list.remove(sd);删除是从0 - sd删除的,删除的是第sd + 1个元素,元素的索引是sd,所以这里用m-1,而不是m。
/**
* 孩子们的游戏(圆圈中最后剩下的数)
* @param n
* @param m
* @return
*/
public static int LastRemaining_Solution(int n, int m) {
LinkedList<Integer> list = new LinkedList<>();
for (int i = 0; i < n; i++)
list.add(i);
// 每一次开始的下标,也是下一次删除的索引
int sd = 0;
while (list.size() > 1) {
sd = (sd + m - 1) % list.size();
list.remove(sd);
}
return list.size() == 1 ? list.get(0) : -1;
}
8.替换空格
题目
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
答案
/**
* 替换空格
* @param str
* @return
*/
public static String replaceSpace(StringBuffer str) {
StringBuffer newSB = new StringBuffer();
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) != ' ') {
newSB.append(str.charAt(i));
} else {
newSB.append("%20");
}
}
return newSB.toString();
}
9.斐波那契数列
题目
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39
斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963年起出版了以《斐波纳契数列季刊》为名的一份数学杂志,用于专门刊载这方面的研究成果。
答案
思路:
(1)递归实现
因为只知道第一项和第二项,后面的都是前两项的和,所以使用递归来做就很简单了,但是使用递归的效率很感人,比如你想知道 f(10) ,就要算出 f(9) 和 f(8),想要知道 9 就要算出 8 和 7, 依次往下计算,会出现很多重复的计算,就很难受,所以只要明白递归的实现方法,和为什么不这么用就ok,不推荐用递归去解决这道题。
/**
* 斐波那契
* @param n
* @return
*/
public static int Fibonacci(int n) {
if(n == 0)
return 0;
if(n == 1)
return 1;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
(2)循环实现
采用循环实现就很奈斯,时间发复杂度为O(n),也很简单。
/**
* 斐波那契数列循环实现
* @param n
* @return
*/
public static int Fibonacci(int n) {
if(n == 0)
return 0;
if(n == 1)
return 1;
int temp = 0;
int n1 = 0;
int n2 = 1;
for(int i = 3; i <= n; i++) {
temp = n2;
n2 = n2 + n1;
n1 = temp;
}
return n1 + n2;
}
10.跳台阶
题目
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
答案
思路:
只要台阶数超过三层,那么跳上该层台阶n只有两种可能,要么是该台阶的下一层n-1,要么是下下一层n-2,所以铁子,你是不是已经想到用“斐波那契数列”数列来解决了啊!!!
/**
* 跳台阶
* @param target
* @return
*/
public static int JumpFloor(int target) {
if(target <= 0)
return 0;
if(target == 1)
return 1;
if(target == 2)
return 2;
int tar1 = 1;
int tar2 = 2;
int temp = 0;
for(int i = 4; i <= target; i++) {
temp = tar2;
tar2 += tar1;
tar1 = temp;
}
return tar1 + tar2;
}
11.变态跳台阶
题目
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
答案
思路:
假设第n层的跳法为f(n)
则
f(1)= 1;因为只有从第0层跳上去
f(2)= f(1)+1 = 2;只有从第1层、第0层跳上去
f(3)= f(2)+ f(1)+1 = 4;只有从第2层、第1层、第0层跳上去。
f(4)= f(3)+f(2)+f(1)+ 1 = 8;
……
很容易发现,f(n)是一个以1为第一项,2为公比的等比数列,f(n)= 2 * f(n-1)
这不就出来了嘛,铁子!!!
/**
* 变态跳台阶
*
* @param target
* @return
*/
public static int JumpFloorII(int target) {
if(target < 1)
return 0;
int res = 1;
for(int i = 2; i <= target; i++)
res = res * 2;
return res;
}
12.矩形覆盖
题目
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
答案
思路:
刚开始,我还想着,给一个n,求出被若干个1、若干个2的和组成的可能数,但是想了一下,这个和跳台阶的题很像啊,我做那个题的时候,刚开始的思路也是这样的,然后就想到了斐波那契数列。
如果n=7,第7个格子是竖着放的(第二个表格),则这样的摆放可能数等于前面6个格子的可能数。
第7个格子是横着放的(第一个表格),这样的摆放可能数等于前面5个格子的可能数,所以,前7个格子摆放的可能总数等于前6个的总数和前5个的总数之和。斐波那契有没有!!!0
/**
* 矩形覆盖
* @param target
* @return
*/
public int RectCover(int target) {
int n1 = 1;
int n2 = 2;
int temp = 0;
if(target < 1)
return 0;
if(target == 1)
return n1;
if(target == 2)
return n2;
for(int i = 4; i <= target; i++) {
temp = n2;
n2 = n1 + n2;
n1 = temp;
}
return n1 + n2;
}
13.数值的整数次方
题目
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
答案
思路:
这道题感觉没什么好说的,就是防止分母不能为零就OK!
/**
* 数值的整数次方
* @param base
* @param exponent
* @return
*/
public static double Power(double base, int exponent) {
double res = 1;
int n = exponent;
if(n < 0 && base != 0)
n = -n;
if(n < 0 && base == 0)
throw new RuntimeException( "分母不能为零!");
if(n == 0 && base == 0)
return 1;
for(int i = 1; i <= n; i++)
res *= base;
res = exponent > 0 ? res : 1 / res;
return res;
}
14.顺时针打印矩阵
题目
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
本文解析了包括二进制中1的个数、求1+2+3+...+n、不用加减乘除做加法等在内的14道经典编程题目,提供了详细的解题思路与代码实现。

被折叠的 条评论
为什么被折叠?



