目录
整数中1出现的次数(从1到n整数中1出现的次数)(剑指欧肥儿)
-
数组中只出现一次的数字(剑指欧肥儿)
题目描述:一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。
解题代码:
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
//排除不可能
if(data.size() <= 3) return;
int res = 0;
//得出所有数异或的结果
for(unsigned long i = 0; i < data.size(); i++){
res ^= data[i];
}
int countbit = FindFirst1(res);
for(unsigned long i = 0; i < data.size(); i++){
if(Is1(data[i], countbit) ){
*num1 ^= data[i];
}else{
*num2 ^= data[i];
}
}
}
int FindFirst1(int num){
int countbit = 0;
while( ((num & 1) == 0) && (countbit < 8*sizeof(int)) ){
num = num >> 1;
++countbit;
}
return countbit;
}
bool Is1(int n, int count){
n = n >> count;
if(n & 1 == 1) return true;
else return false;
}
};
解题思路:
- 这道题的解题关键是很难想到的,关键在于异或操作
- 相同的数字异或操作后会得到0,而不同的数字异或得到的数字一定不为0;
- 将数组中所有数字异或得到两个不同的数字的异或值,可以肯定的是这个异或值中若在某一位n上有1,那么两个不同的数之中肯定有一个数在该位上是1,另一个数在该位上为0
- 于是我们可以利用这一位为0,和为1把数组分为两个数组,且两个不相同的数字分别在这两个数组中。
- 最后我们将两个数组里的数,分别全部异或,每个数组得到的数字就是在整个数组中出现一次的数字。
- (num & 1) == 0 判断num的最后一位数是否是1
- num = num >> 1 ;num的二进制数字整体向右移动一位
- countbit < 8*sizeof(int) ; countbit的值小于8位(一个字节大小)乘上int类型的字节数(int是4个字节的)
- n = n >> count;n向右移动count位
- (n & 1 == 1);判断 n的最后一位是否为1
-
二进制中1的个数(剑指欧肥儿)
题目描述:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
解题代码一:
class Solution {
public:
int NumberOf1(int n) {
//不考虑正负,求的是1的个数
//直接得出数里的1的个数
int count = 0;
int k = 8*sizeof(int);
while(k--){
if((n&1) == 1){
count++;
}
n = n >> 1;
}
return count;
}
};
解题思路:
- 这个方法很容易想到。就是将数字n的最右一位和1作比较,然后每次把n的二进制往右移一位,直到移动了一个int的长度位。
- 注意,(n&1 == 1)这样写是不对的,不报错但会得到错误的值,因为优先级不明确,必须写成((n&1) == 1)。
- while里写(k--)。而不是用32--或者8*sizeof(int)--,这样会报错的。
解题代码二:
class Solution {
public:
int NumberOf1(int n) {
//不考虑正负,求的是1的个数
//直接得出数里的1的个数
int count = 0;
while(n){
count++;
n = n&(n-1);
}
return count;
}
};
解题思路:
- 这个代码比代码一更简单和巧妙。
- 如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
- 记下这个方法,挺经典的不容易想到。
-
求1+2+3+...+n(剑指欧肥儿)
题目描述:求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
解题代码:
class Solution {
public:
int Sum_Solution(int n) {
int res = n;
res && (res += Sum_Solution(n-1)); //若res = 0则&&后语句的不会再执行
return res;
}
};
解题思路:
- 这道题用了两个点 ,&&的短路性和递归。
- 若一个数在&&的前面,则前面的数为0或false或NULL时,&&后面就不用看了,直接返回false。这样直接短路了小于0的数
- 递归这个思路比较常见,要熟悉。如果可以用if,则代码可以这样写
class Solution { public: int Sum_Solution(int n) { int res = n; if (n < 1) { return n; } res += Sum_Solution(n - 1); //若res = 0则&&后语句的不会再执行 return res; } };
-
整数中1出现的次数(从1到n整数中1出现的次数)(剑指欧肥儿)
题目描述:求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
解题代码一:
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
int count = 0;
for(int i = 1; i <= n; i++){
int temp = i;
while(temp){
if(temp%10 == 1){
count++;
}
temp /= 10;
}
}
return count;
}
};
解题思路:
- 这个代码是最常规和最容易想到的答案。
- 遍历0~n的数字,同时分析每个数字有多少个1,然后全部加起来。
解题代码二:
解题思路:
-
和为S的连续正数序列(剑指欧肥儿)
题目描述:小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列?
输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
解题代码:
class Solution {
public:
vector<vector<int> > FindContinuousSequence(int sum) {
vector<vector<int>> v;
int left_index = 1;
int right_index = 2;
while(left_index < right_index){
int res = (left_index + right_index)*(right_index - left_index + 1)/2;
if(res == sum){
vector<int> ans;
for(int i = left_index; i <= right_index; i++){
ans.push_back(i);
}
v.push_back(ans);
left_index++;
}else if(res < sum){
right_index++;
}else{
left_index++;
}
}
return v;
}
};
解题思路:
- 首先要明确连续几个数的和,怎么算。连续几个数是等差数列,所以有通项公式(S = (a0+aN)*N/2)。
- 然后用1开始,两个指针(left, right)一个指向低位的数字,一个指向高位的数字。
- 分三种可能, 当前序列(left, right之间的数)和等于sum,那么将数字序列加入向量,然后指向低位的指针(left)向前移动;如果当前序列和小于sum,则指向高位的指针(right)向右移动一位;如果当前序列和大于sum,则指向低位的指针向左移动一位。当低位指针和高位指针一致时,就意味着,两个指针往后的没有数字序列的和等于S了。
- 这道题的思想和“编程题——时间空间效率的平衡“”里的“丑数”那道题思维有相似之处。
-
和为S的两个数字(剑指欧肥儿)
题目描述: 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
输出描述:对应每个测试案例,输出两个数,小的先输出。
解题代码一(自己,常规解):
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
vector<int> ans;
int left = 0;
int right = 1;
int size = array.size();
if(size <= 1) return ans;
while(left != right){
if((array[left] + array[right]) == sum){
ans.push_back(array[left]);
ans.push_back(array[right]);
return ans;
}else if((array[left] + array[right]) < sum){
if(right + 1 < size)
right++;
else{
left++;
if(left + 1 < size){
right = left + 1;
}
}
}else{
left++;
if((left + 1) < size)
right = left + 1;
}
}
return ans;
}
};
解题思路:
- 受到上一道题的启发,这道题我们也用两个指针,一低(left)一高(right)指针。
- 低指针对应的值加高指针对应的值大于sum,我们就移动低指针;低指针对应的值加高指针对应的值小于sum,我们就移动高指针。如果低指针对应的值加高指针对应的值等于sum则直接输出低和高两个指针对应的值。
- 由于是输出满足要求且乘积最小的值,我们根据数学规律,靠近两端的数乘积总是小于都在中间的两个数的乘积,比如(1,2,3,4,5,8,9)这个数组 sum = 7 ,其中2*5 <3*4,所以取(2,5)。所以这个算法中第一次遇到低指针对应的值加高指针对应的值等于sum,就说明两个数的乘积就是满足要求最小的乘积了。
解题代码二(由一得到规律):
class Solution {
public:
vector<int> FindNumbersWithSum(vector<int> array,int sum) {
vector<int> ans;
int size = array.size();
int left = 0;
int right = size - 1;
if(size <= 1) return ans;
while(left < right){
if((array[left] + array[right]) == sum){
ans.push_back(array[left]);
ans.push_back(array[right]);
return ans;
}else if((array[left] + array[right]) < sum){
left++;
}else{
right--;
}
}
return ans;
}
};
解题思路:
- 这个方法比上个方法书写起来更简单。
- 根据上一代码解题思路3的规律,我们直接设置两个指针,一个指向数组头,一个指向数组尾巴,根据两个指针指向的值的和,来判断,左还是右指针往中间靠拢
- 当两个指针靠拢(指向同一个数时),就跳出循环,此时返回的ans,是没有赋值过的ans。
-
不用加减乘除做加法(剑指欧肥儿)
题目描述:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
解题代码一:
class Solution {
public:
int Add(int num1, int num2)
{
int temp;
while(num2){
temp = num1 ^ num2;
num2 = (num1 & num2) << 1;
num1 = temp;
}
return num1;
}
};
解题思路:
- 想是很难想到这个方法的,就背下来叭~
首先看十进制是如何做的: 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为最终结果。
解题代码二:
class Solution {
public:
int Add(int num1, int num2)
{
return num2 ? Add(num1^num2, (num1 & num2) << 1) : num1;
}
};
解题思路:
- 和上一个代码的思路一模一样。
- 但和上个代码的实现方式不一样,上一个代码是用循环,这个代码是才用递归方式,代码更简洁,但是很难想到。