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- class Solution {
- public:
- /*
- * @param n an integer
- * @return the nth prime number as description.
- */
- int nthUglyNumber(int n) {
- // write your code here
- int countN = 0;
- int m = 0;
- int lastNumber = 2;
- while(countN < n)
- {
- m++;
- int number = m;
- while(number % 2 == 0)
- number = number / 2;
- while(number % 3 == 0)
- number = number / 3;
- while(number % 5 == 0)
- number = number / 5;
- if(number == 1)
- {
- countN++;
- }
- }
- return m;
- }
- };
第二種方法:
直接尋找醜數,由醜數的定義可知,任何一個醜數都是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- class Solution {
- public:
- /*
- * @param n an integer
- * @return the nth prime number as description.
- */
- int nthUglyNumber(int n) {
- // write your code here
- int *ugly = new int[n];
- ugly[0] = 1;
- int num_2 = 0;
- int num_3 = 0;
- int num_5 = 0;
- for(int i = 1;i<n;i++)
- {
- ugly[i] = min(min(ugly[num_2]*2,ugly[num_3]*3),ugly[num_5]*5);
- if(ugly[i] / ugly[num_2] == 2)
- num_2 ++;
- if(ugly[i] / ugly[num_3] == 3)
- num_3 ++;
- if(ugly[i] / ugly[num_5] == 5)
- num_5 ++;
- }
- return ugly[n-1];
- }
- };
[cpp] view plain copy
- if(ugly[i] / ugly[num_2] == 2)
- num_2 ++;
- if(ugly[i] / ugly[num_3] == 3)
- num_3 ++;
- if(ugly[i] / ugly[num_5] == 5)
- 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的程式
- ugly[0] = 1;
- int num_2 = 0;
- int num_3 = 0;
- int num_5 = 0;
- for(int i = 1;i<n;i++)
- {
- ugly[i] = min(min(ugly[num_2]*2,ugly[num_3]*3),ugly[num_5]*5);
- if(ugly[i] / ugly[num_2] == 2)
- num_2 ++;
- if(ugly[i] / ugly[num_3] == 3)
- num_3 ++;
- if(ugly[i] / ugly[num_5] == 5)
- num_5 ++;
- }
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;}
//之前一直錯誤,是因為沒有加這句話,這句話是補充了查詢元素是兩端的
}
};