编程题——关于数字

 

目录

 

数组中只出现一次的数字(剑指欧肥儿)

二进制中1的个数(剑指欧肥儿)

求1+2+3+...+n(剑指欧肥儿)

整数中1出现的次数(从1到n整数中1出现的次数)(剑指欧肥儿)

和为S的连续正数序列(剑指欧肥儿)

和为S的两个数字(剑指欧肥儿)

不用加减乘除做加法(剑指欧肥儿)


  • 数组中只出现一次的数字(剑指欧肥儿)

题目描述:一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。

解题代码:

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;
    }
};

解题思路:

  1. 这道题的解题关键是很难想到的,关键在于异或操作
  2. 相同的数字异或操作后会得到0,而不同的数字异或得到的数字一定不为0;
  3. 将数组中所有数字异或得到两个不同的数字的异或值,可以肯定的是这个异或值中若在某一位n上有1,那么两个不同的数之中肯定有一个数在该位上是1,另一个数在该位上为0
  4. 于是我们可以利用这一位为0,和为1把数组分为两个数组,且两个不相同的数字分别在这两个数组中。
  5. 最后我们将两个数组里的数,分别全部异或,每个数组得到的数字就是在整个数组中出现一次的数字。
  1. (num & 1) == 0 判断num的最后一位数是否是1
  2.  num = num >> 1 ;num的二进制数字整体向右移动一位
  3. countbit < 8*sizeof(int)  ; countbit的值小于8位(一个字节大小)乘上int类型的字节数(int是4个字节的)
  4. n = n >> count;n向右移动count位
  5. (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;
     }
};

解题思路:

  1. 这个方法很容易想到。就是将数字n的最右一位和1作比较,然后每次把n的二进制往右移一位,直到移动了一个int的长度位。
  2. 注意,(n&1 == 1)这样写是不对的,不报错但会得到错误的值,因为优先级不明确,必须写成((n&1) == 1)。
  3. 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;
     }
};

解题思路:

  1. 这个代码比代码一更简单和巧妙。
  2. 如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
  3. 记下这个方法,挺经典的不容易想到。

  • 求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;
    }
};

解题思路:

  1. 这道题用了两个点 ,&&的短路性和递归。
  2. 若一个数在&&的前面,则前面的数为0或false或NULL时,&&后面就不用看了,直接返回false。这样直接短路了小于0的数
  3. 递归这个思路比较常见,要熟悉。如果可以用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;
    }
};

解题思路:

  1. 这个代码是最常规和最容易想到的答案。
  2. 遍历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;
    }
};

解题思路:

  1. 首先要明确连续几个数的和,怎么算。连续几个数是等差数列,所以有通项公式(S = (a0+aN)*N/2)。
  2. 然后用1开始,两个指针(left, right)一个指向低位的数字,一个指向高位的数字。
  3. 分三种可能, 当前序列(left, right之间的数)和等于sum,那么将数字序列加入向量,然后指向低位的指针(left)向前移动;如果当前序列和小于sum,则指向高位的指针(right)向右移动一位;如果当前序列和大于sum,则指向低位的指针向左移动一位。当低位指针和高位指针一致时,就意味着,两个指针往后的没有数字序列的和等于S了。
  4. 这道题的思想和“编程题——时间空间效率的平衡“”里的“丑数”那道题思维有相似之处。

  • 和为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;
    }
};

解题思路:

  1. 受到上一道题的启发,这道题我们也用两个指针,一低(left)一高(right)指针。
  2. 低指针对应的值加高指针对应的值大于sum,我们就移动低指针;低指针对应的值加高指针对应的值小于sum,我们就移动高指针。如果低指针对应的值加高指针对应的值等于sum则直接输出低和高两个指针对应的值。
  3. 由于是输出满足要求且乘积最小的值,我们根据数学规律,靠近两端的数乘积总是小于都在中间的两个数的乘积,比如(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;
    }
};

解题思路:

  1. 这个方法比上个方法书写起来更简单。
  2. 根据上一代码解题思路3的规律,我们直接设置两个指针,一个指向数组头,一个指向数组尾巴,根据两个指针指向的值的和,来判断,左还是右指针往中间靠拢
  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;
    }
};

解题思路:

  1. 想是很难想到这个方法的,就背下来叭~
首先看十进制是如何做的: 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; 
    }
};

解题思路:

  1. 和上一个代码的思路一模一样。
  2. 但和上个代码的实现方式不一样,上一个代码是用循环,这个代码是才用递归方式,代码更简洁,但是很难想到。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值