LintCode刷题,各题思路记录------待完善

1、给出两个整数a和b, 求他们的和, 但不能使用 + 等数学运算符。

难点:使用逻辑求两数的和

分析:先不考虑进位的相加得到的结果a,再只考虑进位有哪些将其左移一位得到b,最后将a、b相加(即重载)。

以3 + 5为例 3的二进制为 1 1,5的二进制为 1 0 1

可以这样做:1先给这两个数加起来不考虑进位,这样得到的结果为 1 1 0,会发现与^(异或符号)得到的结果相同,于是先给两个数做^运算;

2、接下来考虑进位,两个二进制数相加会有这么几种情况 1 1,0 0, 1 0, 0 1除第一种情况外其他情况均不产生进位,而1 1两数相加进1,结果得0,可以这样做先将两个数做&运算,再将结果左移1位,这样就模拟了进位 

 3、将第1步得到的没进位的和 和第2步的进位相加便是结果,下面是代码

{
    if (num2==0) retur num1; //注意:这里的if条件语句是判断如果没有进位后,那么num1即为最后的结果,因此返回num1即可。它是终止条件。
    int num3=num1^num2;
    int carry=(num1&num2)<<1;
    return add(num3,carry);
因此最后方法为如下,被注释的三行为网上的答案。
class Solution {
public:
    /**
     * @param a: An integer
     * @param b: An integer
     * @return: The sum of a and b
     */
    int aplusb(int a, int b) {
        if (b==0) return a;
        
        int c=a^b;
        int carry=(a&b)<<1;
        return aplusb(c,carry);
        
       // int c=a&b;
       // int d=a^b;
       // return c==0?d:aplusb(c<<1,d);
    }
};

2、尾部的零。

分析:最后尾数为零的数都是5和偶数相乘的结果,因此只需计算有n里面有多少个5即可。

程序代码如下:

class Solution {
public:
    /*
     * @param n: A long integer
     * @return: An integer, denote the number of trailing zeros in n!
     */
    long long trailingZeros(long long n) {
        long long result=0;
        while(n/5>0)
        {
            result=result+n/5;
            n/=5;
        }
        return result;
    }
};

3、统计数字

class Solution {
public:
    /*
     * @param : An integer
     * @param : An integer
     * @return: An integer denote the count of digit k in 1..n
     */
    int digitCounts(int k, int n) {

       int a[n+1];int result=0;    //使用数组存储0~n的每个数,方便枚举测试,result用于记录结果
       for(int i=0;i<=n;i++) a[i]=i;  //将0~n存入a[n+1]数组
       if(k==0) result=1;  //当k==0时,如果按照下面的算法,会漏下a[i]是0的情况,因此在这里先加一
       for(int i=0;i<=n;i++)  //1~n每个数都与k值比较
       {
           int number=a[i];
           while(number!=0)
           {
               if ((number%10)==k) result++;
                number/=10;
           }
       }
       return result;
    }
};

4、丑数Ⅱ

首先最容易想到的方法就是暴力破解,思路非常简单,首先除2,直到不能整除为止,然后除5到不能整除为止,然后除3直到不能整除为止。最终判断剩余的数字是否为1,如果是1则为丑数,否则不是丑数。

代码如下:

[cpp]  view plain  copy
  1. class Solution {  
  2. public:  
  3.     /* 
  4.      * @param n an integer 
  5.      * @return the nth prime number as description. 
  6.      */  
  7.     int nthUglyNumber(int n) {  
  8.         // write your code here  
  9.         int countN = 0;  
  10.         int m = 0;  
  11.         int lastNumber = 2;  
  12.         while(countN < n)  
  13.         {  
  14.             m++;  
  15.             int number = m;  
  16.             while(number % 2 == 0)  
  17.                 number = number / 2;  
  18.             while(number % 3 == 0)  
  19.                 number = number / 3;  
  20.             while(number % 5 == 0)  
  21.                 number = number / 5;  
  22.             if(number == 1)  
  23.             {  
  24.                 countN++;  
  25.             }  
  26.         }  
  27.         return m;  
  28.     }  
  29. };  
但是这样做在LintCode中最后一个测试样例中会因超时提交失败。

第二种方法:

直接寻找丑数,由丑数的定义可知,任何一个丑数都是2^i * 3^j * 5^m这种形式的,因此不断寻找丑数,将他们按从小到大的顺序进行排列,直到第n个即为结果。

首先定义一个数组存放丑数,认为1是丑数,则初始化数组num[0] = 1,然后从2,3,5这三个种子中挑选,选择num[0]*2,num[0]*3,num[0]*5中最小的数为新的丑数,显然应该选择2,即num[1] = 2,然后在从2,3,5中选择,这时应该是从num[1]*2,num[0]*3,num[0]*5中进行选择,显然选择3,即num[2] = 3,然后再从num[1]*2,num[1]*3,num[0]*5中选择最小的,选择2,即num[3] = 4,依次进行如下操作,得到最终的结果。

[cpp]  view plain  copy
  1. class Solution {  
  2. public:  
  3.     /* 
  4.      * @param n an integer 
  5.      * @return the nth prime number as description. 
  6.      */  
  7.     int nthUglyNumber(int n) {  
  8.         // write your code here  
  9.         int *ugly = new int[n];  
  10.         ugly[0] = 1;  
  11.         int num_2 = 0;  
  12.         int num_3 = 0;  
  13.         int num_5 = 0;  
  14.         for(int i = 1;i<n;i++)  
  15.         {  
  16.             ugly[i] = min(min(ugly[num_2]*2,ugly[num_3]*3),ugly[num_5]*5);  
  17.             if(ugly[i] / ugly[num_2] == 2)  
  18.                 num_2 ++;  
  19.             if(ugly[i] / ugly[num_3] == 3)  
  20.                 num_3 ++;  
  21.             if(ugly[i] / ugly[num_5] == 5)  
  22.                 num_5 ++;  
  23.         }  
  24.         return ugly[n-1];  
  25.     }  
  26. };  
代码如上所示,这里需要注意的是

[cpp]
  view plain  copy
  1. if(ugly[i] / ugly[num_2] == 2)  
  2.     num_2 ++;  
  3. if(ugly[i] / ugly[num_3] == 3)  
  4.     num_3 ++;  
  5. if(ugly[i] / ugly[num_5] == 5)  
  6.     num_5 ++;  

这几段代码的意思是找出到底是2,3,5中哪个种子计算出的ugly[i],当然,有可能有多个种子,比如ugly[num_2]*2 == ugly[num_3]*3时,需要把num_2++,并且要使num_3++。因此这里不能使用if-else,要全部使用if进行判断。

本题中主要记住的是方法二中,模拟2^i * 3^j * 5^m的程序

  1.         ugly[0] = 1;  
  2.         int num_2 = 0;  
  3.         int num_3 = 0;  
  4.         int num_5 = 0;  
  5.         for(int i = 1;i<n;i++)  
  6.         {  
  7.             ugly[i] = min(min(ugly[num_2]*2,ugly[num_3]*3),ugly[num_5]*5);  
  8.             if(ugly[i] / ugly[num_2] == 2)  
  9.                 num_2 ++;  
  10.             if(ugly[i] / ugly[num_3] == 3)  
  11.                 num_3 ++;  
  12.             if(ugly[i] / ugly[num_5] == 5)  
  13.                 num_5 ++;  
  14.         }  


5、第K大元素查找

方法一:直接排序查找法(时间效率低,不符合O(n)的要求,因此到77%数据通过后,显示超时

最简单的想法是直接进行排序,算法复杂度是O(N*logN)。这么做很明显比较低效率,因为不要求别的信息只要计算出第K大的元素。当然,如果在某种情况下需要频繁访问第K大的元素就可以先进行一次排序在直接得出结果。

第一种方式是这样,用选择排序,冒泡法,或者交换排序这类的排序,对前K个元素进行排序。这三种算法也许不是最快的排序算法。但是都有个性质:计算出最大(小)的元素的算法复杂度是O(N)。这个过程不能中断,要计算第三大的元素必须建立在已经算出第二大的元素的基础上(因为每次都是计算当前数组最大)。所以它的算法复杂度是O(N*K);程序如下:

 int result=1;
    int kthLargestElement(int n, vector<int> &nums) {
        // write your code here
        //空间复杂度O(1)表示该程序所占用的空间和所用数据量无关。
        /*时间复杂度O(n),即当数组元素为n个时,算法要执行的步数为C*n,C为常数
          但是类似于冒泡排序法,时间复杂度就位n^2了  
        */
        //方法1、枚举比较
        /******************************
        int result=0;
        int m=nums.size();
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<m;j++)
          {
            if(j==i) continue;
            if (nums[i]<nums[j]) result++;
          }
          if (result==n-1) {return nums[i];}
          else result=0;
        }
        //错误原因:超时了,数据只通过了77%
        ************************************/

方法二:(快速排序过程中判断第k大元素在左侧还是右侧,从而每次处理的数都是减半)

第二种方法是用快速排序的思想。快速排序每次把一个元素交换到正确的位置,同时把左边的都方上大的,右边都放上小的。这个算法每一次选取一个枢纽元,排序之后,查看枢纽元的位置。如果它的位置大于K,就说明,要求出前面一个子序列的第K大的元素。反之,如果小于K,就说明要求出在后面一个序列的第K - 前一个序列的长度个元素。

如此,就把这个问题改变成了一个可以用快排思想解决的问题。对于快速排序,算法复杂度是O(N*logN)。而这个算法的算法复杂度是O(N)。为什么呢?

其实这个地方的算法复杂度分析很有意思。第一次交换,算法复杂度为O(N),接下来的过程和快速排序不同,快速排序是要继续处理两边的数据,再合并,合并操作的算法复杂度是O(1),于是总的算法复杂度是O(N*logN)(可以这么理解,每次交换用了N,一共logN次)。但是这里在确定枢纽元的相对位置(在K的左边或者右边)之后不用再对剩下的一半进行处理。也就是说第二次插入的算法复杂度不再是O(N)而是O(N/2),这不还是一样吗?其实不一样,因为接下来的过程是1+1/2+1/4+........ < 2,换句话说就是一共是O(2N)的算法复杂度也就是O(N)的算法复杂度。

程序如下所示:

class Solution {
public:
    /*
     * @param n: An integer
     * @param nums: An array
     * @return: the Kth largest element
     */
     int result=1;
    int kthLargestElement(int n, vector<int> &nums) {
      
        int N=nums.size();
        getnumber(n,0,N-1,nums);
        return result;
    }
    
   void getnumber(int m,int s, int t, vector<int> &nums)
    {
        int i,j;
        if(s<t)
        {
            i=s; j=t+1;
            while(1)
            {
                do i++; while(nums[i]>=nums[s] && i!=t);
                do j--; while(nums[j]<=nums[s] && j!=s);
                
                if(i<j) swap(nums[i],nums[j]);
                else break;
            }
            swap(nums[j],nums[s]);
            if(j==m-1)  {result=nums[j];return;}
            else if(j>m-1) {getnumber(m,s,j-1,nums);}
            else if(j<m-1) {getnumber(m,j+1,t,nums);}
        }
        else { result=nums[s];return;}  
    //之前一直错误,是因为没有加这句话,这句话是补充了查找元素是两端的
    }
};








猜你喜欢

转载自blog.csdn.net/NeverLate_gogogo/article/details/79644752