王禹 406130917327

對編程之法中算法的實現

王禹 406130917327






摘要:將編程之美中的15個題目調試成功,並且對其做了詳細的報告,報告中包括該數據結構,以及這個題目的簡單應用。還有這些代碼的出處,和測試情況。並且根據這些調試以及應用的情況,思考這些代碼的巧妙之處以及可能會有哪些不足。並針對這些地方提出一些自己的看法。






關鍵字:編程之美,調試,應用。




1.數塔問題

這裏寫圖片描述
在上面的數字三角形中尋找一條從頂部到底邊的路徑,使得路徑上所經過的數字之和最大。路徑上的每一步都只能往左下或 右下走。只需要求出這個最大和即可,不必給出具體路徑。 三角形的行數大於1小於等於100,數字爲 0 - 99

解法一
剛拿到這道題目想到的是用遞歸的方法來解決題目

具體代碼爲:
這裏寫圖片描述
運行結果爲:
這裏寫圖片描述

但是遞歸算法的時間複雜度較爲高,並且由於是遞歸的方式,會對一個子問題進行多次計算。於是我們可以想到是否可以將子問題的答案保存起來。這樣就不必多次計算了。

解法2

這裏寫圖片描述

編譯成功,但是未產生合理的值。

這裏寫圖片描述

經過調試發現第10行應該是maxsum[i][j]!=-1代表已經i,j位置上的值已經被計算出來了。才能夠返回。

調試完畢 成功得出想要的結果
這裏寫圖片描述

但是可否能夠寫出一個不需要遞歸的算法呢?應該是可以的。

解法3

選擇如下算法
這裏寫圖片描述

編譯結果爲:
這裏寫圖片描述
編譯成功,但是沒有得到想要的解。

觀察到第22行應該爲cout<<maxsum[1[1]纔對修改後的出正確值

2.揹包問題

用動態規劃求解0,1揹包問題代碼如下:

這裏寫圖片描述

這個時候雖然能編譯過,但是報錯如圖所示。

這裏寫圖片描述

通過在
這裏寫圖片描述

加入打印here以及lala語句發現進入這個循環之後就出現錯誤。於是認爲這個地方出現了錯誤。

發現了一個邏輯錯誤。我只在C<0的時候返回了值。但是還有C不小於0的情況沒有考慮。通過添加
else return 0;語句編譯通過。

通過的解爲
這裏寫圖片描述

3.旋轉字符串


問題描述:

給定一個字符串,要求把字符串前面的若干個字符移動到字符串的尾部,如把字符串“abcdef”前面的2個字符’a’和’b’移動到字符串的尾部,使得原字符串變成字符串“cdefab”。請寫一個函數完成此功能,要求對長度爲n的字符串操作的時間複雜度爲 O(n),空間複雜度爲 O(1)。

解法一:暴力移位法

可以逐個將字符串的最前面的元素移動到最後的位置,以達到這種效果。具體代碼爲:
這裏寫圖片描述

並且可以運行成功。

這裏寫圖片描述
但是其中left_shift_one函數需要調用m次每次的時間複雜度爲n。那麼總的時間複雜度就爲O(n*m)遠遠超出了要求。於是可以有下面一種方法。

解法2:三步反轉法

將一個字符串分成X和Y兩個部分,在每部分字符串上定義反轉操作,如X^T,即把X的所有字符反轉(如,X=”abc”,那麼X^T=”cba”),那麼就得到下面的結論:(X^TY^T)^T=YX,顯然就解決了字符串的反轉問題。

代碼分兩個部分第一部分實現反轉功能。第二部分分別對X和Y進行反轉之後對整個字符串進行反轉。於是便實現了功能。

代碼爲:


運行結果爲:


這裏寫圖片描述

4.字符串包含



問題描述


給定兩個分別由字母組成的字符串A和字符串B,字符串B的長度比字符串A短。請問,如何最快地判斷字符串B中所有字母是否都在字符串A裏?

暴力解法

針對B中的每一個字符判斷它是否存在於A串中。
代碼可編寫爲下:
這裏寫圖片描述


代碼運行結果:


這裏寫圖片描述


顯然對於A,B兩個串長度分別爲n和m來說這個算法需要O(n*m)的時間複雜度。

排序解法

如果允許排序的話,我們可以考慮下排序。比如可先對這兩個字符串的字母進行排序,然後再同時對兩個字串依次輪詢。兩個字串的排序需要(常規情況)O(m log m) + O(n log n)次操作,之後的線性掃描需要O(m+n)次操作。


其代碼爲:

這裏寫圖片描述

結果爲:

想法:當n和m相進的時候才適合用這個方法。設n=m則原先的暴力算法複雜度爲 n2 而這個算法的複雜度爲nlogn 。較爲適合使用這個方法。但是一般查詢的時候m都會遠小於n。較長出現的情況爲m<logn 所以使用這個算法並不合適。


運用素數求解

用26個素數分別代表A到Z並且將長字符串中的字母對應的數字相乘。得到一個整數。利用上面字母和素數的對應關係,對應第二個字符串中的字母,然後輪詢,用每個字母對應的素數除前面得到的整數。如果結果有餘數,說明結果爲false。如果整個過程中沒有餘數,則說明第二個字符串是第一個的子集了(判斷是不是真子集,可以比較兩個字符串對應的素數乘積,若相等則不是真子集)


具體步驟如下:
1.按照從小到大的順序,用26個素數分別與字符’A’到’Z’一一對應。
2.遍歷長字符串,求得每個字符對應素數的乘積。
3.遍歷短字符串,判斷乘積能否被短字符串中的字符對應的素數整除。
4.輸出結果。


代碼爲:
這裏寫圖片描述
該算法之中有兩個循環長度分別是n與m其時間複雜度爲O(n+m).。最好情況爲O(n)即遍歷短串的第一個數與長串相除就有餘數。該算法時間複雜度較爲優秀,但是多個素數相乘有可能會產生溢出的情況。

用hash表求解


這種包含問題很容易便能想到hash表求解。並且只有26個字母hash表的表長很短。


其代碼爲:
這裏寫圖片描述


運行結果爲

這裏寫圖片描述


該算法時間複雜度爲O(n+m)並且不會有上溢的風險。


5.字符串轉換爲整數問題

題目描述

輸入一個由數字組成的字符串,把它轉換成整數並輸出。例如:輸入字符串”123”,輸出整數123。

給定函數原型int StrToInt(const char *str) ,實現字符串轉換成整數的功能.

3.2分析與解法

對於一個字符串來說。比如說字符串”123” 。

  • 首先我們掃描到了字符 ‘1’我們知道這是第一位。則令結果爲1。
  • 之後我們掃描到了字符 ‘2’我們知道這是第二位。將原先的結果一往左移動一位並且往後面加入2。
  • 最後我們掃描到了字符’3’按照以前的步驟將原先的結果往前移動一位。之後在後面加入3。

所以我們具有了思路,即對一個字符串我們從左到右的掃描,把以前的數字乘上10,之後加上現在的數字。

具體算法爲:

這裏寫圖片描述

需要加上許多限制條件


6. 尋找數組中最小的K個數

問題描述:

輸入n個整數,輸出其中最小的k個。

解法1

可以先將數組排序,之後將排序號的數組的前k個元素拿過來使用。


該方法雖然簡單,但是其中後n-k個元素並沒有要求你排序,這樣就浪費了時間。所以應該存在更號的方法。

解法2

1、遍歷n個數,把最先遍歷到的k個數存入到大小爲k的數組中,假設它們即是最小的k個數;
2、對這k個數,利用選擇或交換排序找到這k個元素中的最大值kmax(找最大值需要遍歷這k個數,時間複雜度爲O(k));
3、繼續遍歷剩餘n-k個數。假設每一次遍歷到的新的元素的值爲x,把x與kmax比較:如果x < kmax ,用x替換kmax,並回到第二步重新找出k個元素的數組中最大元素kmax‘;如果x >= kmax,則繼續遍歷不更新數組。
每次遍歷,更新或不更新數組的所用的時間爲O(k)或O(0)。故整趟下來,時間複雜度爲n*O(k)=O(n*k)。

其代碼爲

這裏寫圖片描述


運行結果爲:

這裏寫圖片描述

解法3

有一種平均複雜度爲O(n)的算法
選取S中一個元素作爲樞紐元v,將集合S-{v}分割成S1和S2,就像快速排序那樣

如果k <= |S1|,那麼第k個最小元素必然在S1中。在這種情況下,返回QuickSelect(S1, k)。
如果k = 1 + |S1|,那麼樞紐元素就是第k個最小元素,即找到,直接返回它。
否則,這第k個最小元素就在S2中,即S2中的第(k - |S1| - 1)個最小元素,我們遞歸調用並返回QuickSelect(S2, k - |S1| - 1)。


具體代碼如下

這裏寫圖片描述


7.尋找數組中和爲定值的兩個數

輸入一個數組和一個數字,在數組中查找兩個數,使得它們的和正好是輸入的那個數字。

要求時間複雜度是O(N)。如果有多對數字的和等於輸入的數字,輸出任意一對即可。
例如輸入數組1、2、4、7、11、15和數字15。由於4+11=15,因此輸出4和11。

:問題觀察
這個問題相當於對於每個a[i],查找sum-a[i]是否也在原始序列中,每一次要查找的時間都要花費爲O(N),這樣下來,最終找到兩個數還是需要O(N^2)的複雜度。那麼如何減少查找的時間複雜度呢?
:解法2
此時我們相既然時間複雜度都爲O(n*n)了不如先排個序吧。先將數組排序後。排序了之後我們發現確實可以減少查找的時間複雜度了。因爲這個時候我們可以使用二分查找了。使用二分查找的話,相當與對於每個a[i]只用log n的時間複雜度就可以將它找出來。那麼總的時間複雜度就爲nlogn
:解法三
對於數組有序的情況,我們是否有更高效的方法去得到問題的解呢?我們可以這樣:
用兩個指針i,j,各自指向數組的首尾兩端,令i=0,j=n-1,然後i++,j–,逐次判斷a[i]+a[j]?=sum,
如果某一刻a[i]+a[j] > sum,則要想辦法讓sum的值減小,所以此刻i不動,j–;
如果某一刻a[i]+a[j] < sum,則要想辦法讓sum的值增大,所以此刻i++,j不動。
這樣的話找到算法的解的時間複雜度爲O(n)。
在數組有序的情況下總的時間複雜度爲O(n),在數組無序的情況下總的時間複雜度爲O(nlogn).


具體代碼如下:
這裏寫圖片描述


代碼的運行結果
這裏寫圖片描述

8.尋找和爲定值的多個數

6解法1

注意到取n,和不取n個區別即可,考慮是否取第n個數的策略,可以轉化爲一個只和前n-1個數相關的問題。
如果取第n個數,那麼問題就轉化爲“取前n-1個數使得它們的和爲sum-n”,對應的代碼語句就是sumOfkNumber(sum - n, n - 1);
如果不取第n個數,那麼問題就轉化爲“取前n-1個數使得他們的和爲sum”,對應的代碼語句爲sumOfkNumber(sum, n - 1)。

具體代碼爲
這裏寫圖片描述


運行結果爲:
這裏寫圖片描述

9.最大連續子數組和

輸入一個整形數組,數組裏有正數也有負數。數組中連續的一個或多個整數組成一個子數組,每個子數組都有一個和。 求所有子數組的和的最大值,要求時間複雜度爲O(n)。

這裏寫圖片描述
這樣的時間複雜度爲O(n^3) 當然有更好的方法

int MaxSubArray(int* a, int n)
{
int currSum = 0;
int maxSum = a[0]; //全負情況,返回最大數

for (int j = 0; j < n; j++)
{
    currSum = (a[j] > currSum + a[j]) ? a[j] : currSum + a[j];
    maxSum = (maxSum > currSum) ? maxSum : currSum;

}
return maxSum;

}
代碼運行結果爲。
這裏寫圖片描述

10.跳臺階問題

一個臺階總共有n 級,如果一次可以跳1 級,也可以跳2 級。
求總共有多少總跳法,並分析算法的時間複雜度。其可以簡化爲求fibonacci數問題

long long Fibonacci(unsigned int n)
{
int result[3] = {0, 1, 2};
if (n <= 2)
return result[n];

return Fibonacci(n - 1) + Fibonacci(n - 2);

}

這裏寫圖片描述

當然fibonacci是可以優化的可以將其優化爲
int ClimbStairs(int n)
{
int dp[3] = { 1, 1 };
if (n < 2)
{
return 1;
}
for (int i = 2; i <= n; i++)
{
dp[2] = dp[0] + dp[1];
dp[0] = dp[1];
dp[1] = dp[2];
}
return dp[2];
}
這樣就可以降低時間複雜度。
換硬幣問題。想兌換100元錢,有1,2,5,10四種錢,問總共有多少兌換方法

const int N = 100;
int dimes[] = { 1, 2, 5, 10 };
int arr[N + 1] = { 1 };
for (int i = 0; i < sizeof(dimes) / sizeof(int); ++i)
{
for (int j = dimes[i]; j <= N; ++j)
{
arr[j] += arr[j - dimes[i]];
}
}

11.奇偶調序

輸入一個整數數組,調整數組中數字的順序,使得所有奇數位於數組的前半部分,所有偶數位於數組的後半部分。要求時間複雜度爲O(n)。

解法1:
藉助快速排序的想法,用一個主元將這個數組劃分爲兩個堆一堆奇數一堆偶數,具體代碼爲:

這裏寫圖片描述

得到的結果爲:

這裏寫圖片描述

12.荷蘭國旗問題

下面是問題的正規描述: 現有n個紅白藍三種不同顏色的小球,亂序排列在一起,請通過兩兩交換任意兩個球,使得從左至右,依次是一些紅球、一些白球、一些藍球。

荷蘭國旗問題類似於有三個指針的快速排序問題。

具體代碼如下
這裏寫圖片描述
代碼運行結果爲
這裏寫圖片描述

13.完美洗牌算法

有個長度爲2n的數組{a1,a2,a3,…,an,b1,b2,b3,…,bn},希望排序後{a1,b1,a2,b2,….,an,bn},請考慮有無時間複雜度o(n),空間複雜度0(1)的解法。

解法一爲暴力求解法逐次找到b1,b2,的位置之後將其插入進去。其代碼爲:O(n^2);
可以採用利用空間來換取時間複雜度的想法。算法如下:
這裏寫圖片描述
時間空間複雜度均爲O(n);

完美洗牌算法的進階版本:


void CycleLeader(int *a, int from, int mod)
{
    int t,i;

    for (i = from * 2 % mod; i != from; i = i * 2 % mod)
    {
        t = a[i];
        a[i] = a[from];
        a[from] = t;
    }
}
void RightRotate(int *a, int num, int n)
{
    reverse(a, 1, n - num);
    reverse(a, n - num + 1, n);
    reverse(a, 1, n);
}
void reverse(int *a,int i,int j)
{
 while(i<j)
 {
    temp=a[i];
    a[i]=a[j];
    a[j]=temp;
    i++;
    j--;
}
}
void PerfectShuffle2(int *a, int n)
{
    int n2, m, i, k, t;
    for (; n > 1;)
    {
        // step 1
        n2 = n * 2;
        for (k = 0, m = 1; n2 / m >= 3; ++k, m *= 3)
          ;
        m /= 2;
        // 2m = 3^k - 1 , 3^k <= 2n < 3^(k + 1)

        // step 2
        right_rotate(a + m, m, n);

        // step 3
        for (i = 0, t = 1; i < k; ++i, t *= 3)
        {
          cycle_leader(a , t, m * 2 + 1);
        }

        //step 4
        a += m * 2;
        n -= m;

    }
    // n = 1
    t = a[1];
    a[1] = a[2];
    a[2] = t;
} 

程序主體爲:
這裏寫圖片描述

其獲得的結果爲

這裏寫圖片描述

14.最大連續乘積字串問題

給一個浮點數序列,取最大乘積連續子串的值,例如 -2.5,4,0,3,0.5,8,-1,則取出的最大乘積連續子串爲3,0.5,8。也就是說,上述數組中,3 0.5 8這3個數的乘積30.58=12是最大的,而且是連續的。

解法一:暴力解法

double maxProductSubstring(double *a, int length)
{
    double maxResult = a[0];
    for (int i = 0; i < length; i++)
    {
        double x = 1;
        for (int j = i; j < length; j++)
        {
            x *= a[j];
            if (x > maxResult)
            {
                maxResult = x;
            }
        }
    }
    return maxResult;
}


該方法將數組中每一個子串都進行計算。顯然時間複雜度爲O(n^2)。

解法2:

這裏寫圖片描述

運行結果爲:

這裏寫圖片描述

15.交替字符串問題

輸入三個字符串s1、s2和s3,判斷第三個字符串s3是否由前兩個字符串s1和s2交錯而成,即不改變s1和s2中各個字符原有的相對順序,例如當s1 = “aabcc”,s2 = “dbbca”,s3 = “aadbbcbcac”時,則輸出true,但如果s3=“accabdbbca”,則輸出false。

public boolean IsInterleave(String s1, String 2, String 3){
    int n = s1.length(), m = s2.length(), s = s3.length();

    if (n + m != s)
        return false;

    boolean[][]dp = new boolean[n + 1][m + 1];

    //在初始化邊界時,我們認爲空串可以由空串組成,因此dp[0][0]賦值爲true。
    dp[0][0] = true;

    for (int i = 0; i < n + 1; i++){
        for (int j = 0; j < m + 1; j++){
            if ( dp[i][j] || (i - 1 >= 0 && dp[i - 1][j] == true &&
                //取s1字符
                s1.charAT(i - 1) == s3.charAT(i + j - 1)) ||

                (j - 1 >= 0 && dp[i][j - 1] == true &&
                //取s2字符
                s2.charAT(j - 1) == s3.charAT(i + j - 1)) )

                dp[i][j] = true;
            else
                dp[i][j] = false;
        }
    }
    return dp[n][m]
}

main函數代碼爲
這裏寫圖片描述
運行結果爲:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章