演算法-Z字形變換

zeng沐白發表於2018-07-07

題目:

將字串 "PAYPALISHIRING" 以Z字形排列成給定的行數:

P   A   H   N
A P L S I I G
Y   I   R
複製程式碼

之後從左往右,逐行讀取字元:"PAHNAPLSIIGYIR"

實現一個將字串進行指定行數變換的函式:

string convert(string s, int numRows); 示例 1:

輸入: s = "PAYPALISHIRING", numRows = 3 輸出: "PAHNAPLSIIGYIR" 示例 2:

輸入: s = "PAYPALISHIRING", numRows = 4 輸出: "PINALSIGYAHRPI" 解釋:

P     I    N
A   L S  I G
Y A   H R
P     I
複製程式碼

思路1:按行排序

  1. 題目要求有兩個,一個是字元是按Z字形排序,一個由上而下按行輸出。

  2. 分析下Z字形的排序規律。z字形先是由上而下,到了底部再向上,每到底部或者頂部就會反彈,程式中使用bool變數lineUp記錄方向。遍歷字元的同時,使用一個二維陣列int a[row][strlen(s)]來儲存行與元素原始下標的關係。遍歷完畢後,按行拼接字串並輸出。

  3. 元素的下標與輸出後所在的行有這樣的對應關係。

    #row #數字對應元素下標。
     0   0P      6I       12N
     1   1A   5L 7S   11I 13G
     2   2Y 4A   8H 10R
     3   3P      9I     
    複製程式碼
  4. 程式碼

    char* convert(char* s, int numRows) {
    if (numRows == 1) {
        return s;
    }
    
    bool lineUp = true;
    int len = strlen(s);
    //記錄字元的二維陣列。其實可以使用int的二維陣列,因為字元能自動轉成asscii對應的int數字。
    char **pArr = (char**)malloc(sizeof(char*) * numRows);
    
    //這個陣列是為了記錄當前行最大下標,用於後面遍歷。其實可以不用,在記錄字串的時候末尾新增'\0'用於判斷就行。
    int *indexArr = (int*)malloc(sizeof(int) * numRows);
    memset(indexArr, -1, sizeof(int) * numRows);
    
    for (int i = 0; i < numRows; i++ ) {
    //開始的時候設定長度為len 。在leetcode上執行報超出記憶體限制。分析後改為len /2 + 1,因為兩行的時候最多為len / 2 + 1;超過三行字元被分散到其他行行,也不會超過len /2 + 1。修改後執行通過。
        char *a = (char*)malloc(sizeof(char*) * (len / 2 + 1));
        memset(a, '#', sizeof(char*) * (len / 2  + 1));
        pArr[i] = a;
    }
    
    int j = 0;
    for (int i = 0; i < len; i ++) {
        int currentIndex = indexArr[j] + 1;
        indexArr[j] = currentIndex;
        pArr[j][currentIndex] = s[i];
        if (lineUp) {
            if (j == numRows - 1) {
                j --;
                lineUp = false;
            }
            else
            {
                j ++;
            }
        }
        else
        {
            if (j == 0) {
                j ++;
                lineUp = true;
            }
            else
            {
                j --;
            }
        }
    }
    
    char *p = (char*)malloc(sizeof(char*) * len);
    memset(p, 0, sizeof(char*) * len);
    int jj = 0;
    for (int ii = 0; ii < numRows ; ii ++){
        char *temp = pArr[ii];
        for (int k = 0; k <= indexArr[ii]; k++) {
            p[jj++] = temp[k];
        }
    }
    free(pArr);
    free(indexArr);
    
    return p;
    }
    複製程式碼
  5. 複雜度分析

    1. 時間複雜度 O(n), n = strlen(s);
    2. 空間複雜度 O(n)。

思路2:按行訪問

  1. 這個是對字元輸入的方式分析後得出的數學規律。首尾兩行成等比關係,中間的行除了等比的位置填充元素外,另外還需要在等比距離倒退一定長度offset的位置填充,offset = currentRow。

  2. 程式碼

     ```
     char* convert(char* s, int numRows) {
     int len = strlen(s);
     //特殊情況特殊處理
     if (len == 0 || numRows == 0 || numRows == 1 || numRows == len)
     {
         return s;
     }
     
     char *p = (char*)malloc(sizeof(char) * len);
     //等比間隔
     int cycleLen = 2 * numRows - 2;
     int pIndex = 0;
     for (int i = 0 ; i < numRows; i ++) {
         for (int j = 0; j + i < len ; j += cycleLen) {
             //獲取等比位置的字元
             p[pIndex ++] = s[i + j];
             //獲取中間行非等比位置的字元
             if (i != 0 && i != numRows - 1 &&  j + cycleLen - i < len) {
                 p[pIndex ++] = s[ j + cycleLen - i];
             }
         }
     }
     return p;
     }
     
     ```
    複製程式碼
  3. 複雜度分析

    1. 時間複雜度 O(n), n = strlen(s);
    2. 空間複雜度 O(n)。如果返回字元不視為額外空間,那麼空間複雜度為O(1)。

小結:

  1. 這道題開始也是用分析規律的方法去做,但舉的例子少了點,寫出來執行才發現規律是錯的。後面也就沒繼續找數學規律。 為了避免下次犯錯,找規律需要論證不少於3個例子。同時規律需要分情況討論,例如該題的首尾兩行作為一類,中間行作為一類。另外別想著用簡單的規律就能處理全部問題,一般這類問題都是二級或者多級的,例如多種情況對應不同的規律,或者同一個規律要處理的多個問題。還有如果一類問題按,一般會存在規律。
  2. 注意的地方:二維陣列初始化問題;meset的使用;字串、ASCII碼、int之間的關係等。
  3. 部分程式碼可以寫的更簡潔,但為了更好的描述過程,暫時還是保持這個風格,有利於學習和理解。

待完善L('ω')┘三└('ω')」....

相關文章