1. 程式人生 > >LintCode刷題,各題思路記錄------待完善

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;}  
    //之前一直錯誤,是因為沒有加這句話,這句話是補充了查詢元素是兩端的
    }
};