1. 程式人生 > >牛逼!一行程式碼居然能解決這麼多曾經困擾我半天的演算法題

牛逼!一行程式碼居然能解決這麼多曾經困擾我半天的演算法題

春節假期這麼長,幹啥最好?當然是折騰一些演算法題了,下面給大家講幾道一行程式碼就能解決的演算法題,當然,我相信這些演算法題你都做過,不過就算做過,也是可以看一看滴,畢竟,你當初大概率不是一行程式碼解決的。

學會了一行程式碼解決,以後遇到面試官問起的話,就可以裝逼了。

一、2 的冪次方

問題描述:判斷一個整數 n 是否為 2 的冪次方

對於這道題,常規操作是不斷這把這個數除以 2,然後判斷是否有餘數,直到 n 被整除成 1 。

我們可以把 n 拆成二進位制看待處理的,如果 n 是 2 的冪次方的話,那麼 n 的二進位制數的最高位是 1,後面的都是 0,例如對於 16 這個數,它的二進位制表示為 10000。

如果我們把它減 1,則會導致最高位變成 0,其餘全部變成 1,例如 10000 - 1 = 01111。

然後我們把 n 和 (n - 1)進行與操作,結果就會是 0,例如(假設 n 是 16)

n & (n-1) = 10000 & (10000 - 1) = 10000 & 01111 = 0

也就是說,n 如果是 2 的冪次方,則 n & (n-1) 的結果是 0,否則就不是,所以程式碼如下

int isPow(n){
    return (n & (n - 1)) == 0;
}

二、一行程式碼搞定經典的約瑟夫環

約瑟夫環問題,我相信大家在大一大二的時候就接觸過了,很多人也都會拿來作為環形連結串列的一個應用,然而環形連結串列並非最優的解決方法,今天我就用一行程式碼幹掉它,並且幾乎算是最優解了。

鑑於有些人把這道題忘了,我還是把這道題的描述貼出來一下吧

問題描述:編號為 1-N 的 N 個士兵圍坐在一起形成一個圓圈,從編號為 1 的士兵開始依次報數(1,2,3…這樣依次報),數到 m 的 士兵會被殺死出列,之後的士兵再從 1 開始報數。直到最後剩下一士兵,求這個士兵的編號。

先給出程式碼,後面在解釋。

int f(int n, int m){
    return n == 1 ? n : (f(n - 1, m) + m - 1) % n + 1;
}

原理是這樣的:

如果我們把士兵刪除後,重新給這些士兵編號的話,那麼刪除前和刪除後,這些編號存在某種數學關係,我們只需要找出這個關係即可。

我們定義遞迴函式 f(n,m) 的返回結果是存活士兵的編號,顯然當 n = 1 時,f(n, m) = 1。假如我們能夠找出 f(n,m) 和 f(n-1,m) 之間的關係的話,我們就可以用遞迴的方式來解決了。我們假設人員數為 n, 報數到 m 的人就自殺。則剛開始的編號為


1

m - 2

m - 1

m

m + 1

m + 2

n

進行了一次刪除之後,刪除了編號為 m 的節點。刪除之後,就只剩下 n - 1 個節點了,刪除前和刪除之後的編號轉換關係為:

刪除前 --- 刪除後

… --- …

m - 2 --- n - 2

m - 1 --- n - 1

m ---- 無(因為編號被刪除了)

m + 1 --- 1(因為下次就從這裡報數了)

m + 2 ---- 2

… ---- …

新的環中只有 n - 1 個節點。且刪除前編號為 m + 1, m + 2, m + 3 的節點成了刪除後編號為 1, 2, 3 的節點。

假設 old 為刪除之前的節點編號, new 為刪除了一個節點之後的編號,則 old 與 new 之間的關係為 old = (new + m - 1) % n + 1。

這樣,我們就得出 f(n, m) 與 f(n - 1, m)之間的關係了,而 f(1, m) = 1.所以我們可以採用遞迴的方式來做。程式碼如下:

注:有些人可能會疑惑為什麼不是 old = (new + m ) % n 呢?主要是因為編號是從 1 開始的,而不是從 0 開始的。如果 new + m == n的話,會導致最後的計算結果為 old = 0。所以 old = (new + m - 1) % n + 1.

int f(int n, int m){
    if(n == 1)   return n;
    return (f(n - 1, m) + m - 1) % n + 1;
}

怎麼不是一行而是兩行?如果你經常刷題,那肯定希望自己的程式碼看起來越短越簡介越好,至於會不會變的更難理解?我懶的理,所以程式碼如下

int f(int n, int m){
    return n == 1 ? n : (f(n - 1, m) + m - 1) % n + 1;
}

當然,我之前寫過一篇文章,用了三種方法來解決約瑟夫環,感興趣的也可以看:記一道阿里筆試題:我是如何用一行程式碼解決約瑟夫環問題的

只出現一次是數

問題描述:給你一個整型陣列,陣列中有一個數只出現過一次,其他數都出現了兩次,求這個只出現了一次的數。

這道題可能很多人會用一個雜湊表來儲存,每次儲存的時候,記錄 某個數出現的次數,最後再遍歷雜湊表,看看哪個數只出現了一次。這種方法的時間複雜度為 O(n),空間複雜度也為 O(n)了。

然而這道題其實可以採用異或運算來解決,兩個相同的數異或的結果是 0,一個數和 0 異或的結果是它本身,並且異或運算支援交換律,基於這個特點,我們只需要把這一組整型全部異或一下,最後的結果就是我們要找的數了。

例如這組資料是:1, 2, 3, 4, 5, 1, 2, 3, 4。其中 5 只出現了一次,其他都出現了兩次,把他們全部異或一下,結果如下:

由於異或支援交換律和結合律,所以:

1^2^3^4^5^1^2^3^4 = (1^1)^(2^2)^(3^3)^(4^4)^5= 0^0^0^0^5 = 5。

通過這種方法,可以把空間複雜度降低到 O(1),而時間複雜度不變,相應的程式碼如下

int find(int[] arr){
    int tmp = arr[0];
    for(int i = 1;i < arr.length; i++){
        tmp = tmp ^ arr[i];
    }
    return tmp;
}

說好的一行程式碼的呢?

這不是為了先讓你看的懂嗎?一行程式碼解決方案如下:

// 例如使用這個函式的時候,我們最開始傳給 i 的值是 1,傳給 result 的是 arr[0]
//例如 find(arr, 1, arr[0])
int find(int[] arr,int i, int result){
    return arr.length <= i ? result : find(arr, i + 1, result ^ arr[i]);
}

實不相瞞,這道題用了一行程式碼之後,更加複雜 + 難懂了,,,,,,不好意思,我錯了,不該把簡單的問題搞複雜了再扔給面試題的。

四、n 的階乘

問題描述:給定一個整數 N,那麼 N 的階乘 N! 末尾有多少個 0?例如: N = 10,則 N!= 3628800,那麼 N! 的末尾有兩個0。

我先給出個程式碼讓大家品嚐一下,在細細講解

int f(n){
    return n == 0 ? 0 : n / 5 + f(n / 5);
}

對於這道題,常規操作是直接算 N!的值再來除以 10 判斷多少個 0 ,然而這樣肯定會出現溢位問題,並且時間複雜度還大,我們不妨從另一個思路下手:一個數乘以 10 就一定會在末尾產生一個零,那麼,我們是否可以從哪些數相乘能夠得到 10 入手呢?

答是可以的,並且只有 2 * 5 才會產生 10。

注意,4 * 5 = 20 也能產生 0 啊,不過我們也可以把 20 進行分解啊,20 = 10 * 2。

於是,問題轉化為 N! 種能夠分解成多少對 2*5,再一步分析會發現,在 N!中能夠被 2 整除的數一定比能夠被 5 整除的數多,於是問題近似轉化為求 1…n 這 n 個數中能夠被 5 整除的數有多少個,

注意,像 25 能夠被 5整除兩次,所以25是能夠產生 2 對 2 * 5滴。有了這個思路,程式碼如下:

int f(int n){
    int sum = 0;
    for(int i = 1; i <= n; i++){
        int j = i;
        while(j % 5 == 0){
            sum++;
            j = j / 5;
        }
    }
    return sum;
}

然而進一步拆解,我們發現

當 N = 20 時,1~20 可以產生幾個 5 ?答是 4 個,此時有 N / 5 = 4。

當 N = 24 時,1~24 可以產生幾個 5 ?答是 4 個,此時有 N / 5 = 4。

當 N = 25 時,1~25 可以產生幾個 5?答是 6 個,主要是因為 25 貢獻了兩個 5,此時有 N / 5 + N / 5^2 = 6。

可以發現 產生 5 的個數為 sum = N/5 + N/5^2 + N/5^3+….

於是,一行程式碼就可以搞定它了

int f(n){
    return n == 0 ? 0 : n / 5 + f(n / 5);
}

總結

有木覺得很牛逼?以後面試官問你這些題,你就把這行程式碼扔給他!!!

當然,想要一直保持牛逼,還得多看一些演算法書,我也有整理了一些

在此貢獻給大家,都是一些值得看的演算法書

大家可以在我的微信公眾號『帥地玩程式設計』獲取『我要學演算法』獲取下載連結。

老鐵,要不點個贊再走可好?麼麼噠

1、給俺點個讚唄,可以讓更多的人看到這篇文章,順便激勵下我,嘻嘻。

2、老鐵們,關注我的原創微信公眾號「帥地玩程式設計」,專注於寫演算法 + 計算機基礎知識(計算機網路+ 作業系統+資料庫+Linux)。

儲存讓你看完有所收穫,不信你打我。後臺回覆『電子書』送你一份精選電子書大禮包,包含各類技能的優質電子書。

作者簡潔

作者:大家好,我是帥地,從大學、校招一路走來,深知演算法,計算機基礎知識的重要性,所以申請了一個微星公眾號『帥地玩程式設計』,專業於寫這些底層知識,提升我們的內功,帥地期待你的關注,和我一起學習。 轉載說明:未獲得授權,禁止轉載