面試算法知識梳理(2) - 字符串算法第一部分

面試算法代碼知識梳理系列

面試算法知識梳理(1) - 排序算法
面試算法知識梳理(2) - 字符串算法第一部分
面試算法知識梳理(3) - 字符串算法第二部分
面試算法知識梳理(4) - 數組第一部分
面試算法知識梳理(5) - 數組第二部分
面試算法知識梳理(6) - 數組第三部分
面試算法知識梳理(7) - 數組第四部分
面試算法知識梳理(8) - 二分查找算法及其變型
面試算法知識梳理(9) - 鏈表算法第一部分
面試算法知識梳理(10) - 二叉查找樹
面試算法知識梳理(11) - 二叉樹算法第一部分
面試算法知識梳理(12) - 二叉樹算法第二部分
面試算法知識梳理(13) - 二叉樹算法第三部分


一、概要

本文介紹了有關字符串的算法第一部分的Java代碼實現,所有代碼均可通過 在線編譯器 直接運行,算法目錄:

  • 替換字符串中的空格
  • 輸入一個字符串,打印出該字符串的所有排列
  • 第一個只出現一次的字符
  • 翻轉句子
  • 計算字符串之間的最短距離

二、代碼實現

2.1 替換字符串中的空格

問題描述

實現一個函數,將字符串p中的所有空格都替換成為指定的字符串r

解決思路

  • 遍歷原始的字符串p,統計原先字符串中空格的個數spaceNum
  • 創建一個新的數組n,用于存放替換后的字符串。由于原先字符串中空格也占了一個位置,因此新數組n的長度為p.len + (r.len - 1) * spaceNum
  • 對于p 從后往前遍歷,如果 遇到了空格,那么就將需要替換的字符串r中的字符 從后往前 填入n數組中;如果 遇到了非空格,那么就將p中的字符填入n數組中。

實現代碼

class Untitled {
    
    static char[] replaceSpace(char p[], char r[], int pLen, int rLen){
        int spaceNum = 0;
        int i;
        for(i = 0; i < pLen; i++){
            if(p[i] == ' ')
                spaceNum += (rLen-1);
        }
        int nLen = pLen+spaceNum;
        char n[] = new char[nLen+1];
        i = nLen-1;
        int j = pLen-1;
        while(i >= 0 && j >= 0){
            if (p[j] == ' ') {
                for (int k = rLen-1; k >= 0; k--)
                    n[i--] = r[k];
            } else {
                n[i--] = p[j];
            }
            j--;
        }
        n[nLen] = 0;
        return n;
    } 
    
    public static void main(String[] args) {
        char[] source = "I am sentence with space".toCharArray();
        char[] replace = "%20".toCharArray();
        char[] result = replaceSpace(source, replace, source.length, replace.length);
        System.out.println(result);
    }
}

運行結果

>> I%20am%20sentence%20with%20space

2.2 輸入一個字符串,打印出該字符串的所有排列

問題描述

輸入一個字符串,打印出該字符串中字符的所有排列。例如輸入字符串abc,則輸出由字符abc所能排列出來的所有字符串abcacbbacbcacabcba

解決思路

這是一個 遞歸問題,求一個長度為n的字符串的全排列的方法為:

  • n[0..n.len-1]全排列的計算方法為:將n[0]位置的字符分別和n[1..n.len-1]的每一個字符串交換,n[0]和交換后的n[1..n.len - 1]的全排列進行組合。我們將字符串{s}的全排列表示為{s},那么對于abc來說,其全排列{abc},就等于是a + {bc}b + {ac}c + {ba}
  • 以此類推,n[1..n.len - 1]的全排列,則是將n[1]分別和n[2..n.len - 1]的每一個字符串交換,再求出交換后的n[2..len - 1]的全排列,遞歸結束的條件為n[i..n.len - 1]只有一個字符,例如,bc的全排列為b + {c}c + {b},而{c}{b}的全排列只有一種,因此遞歸結束,這時候就可以打印出結果。

實現代碼

class Untitled {
    
    static void permutationStr(char p[], int depth, int length){
        if (depth == length) {
            System.out.println(p);
            return;
        }
        char c;
        for (int i = depth; i < length; i++){
            c = p[depth]; p[depth] = p[i]; p[i] = c;
            permutationStr(p, depth+1, length);
            c = p[depth]; p[depth] = p[i]; p[i] = c;
        }
    }
    
    public static void main(String[] args) {
        char[] source = "abc".toCharArray();
        permutationStr(source, 0, source.length);
    }
}

運行結果

>> abc
>> acb
>> bac
>> bca
>> cba
>> cab

2.3 第一個只出現一次的字符

問題描述

在字符串中找出第一個只出現一次的字符。如輸入abaccdeff,則輸出b,要求時間復雜度為O(n)

解決思路

這里需要采用 以空間換時間 的思想,也就是創建一個足夠大的數組c,這里為256,然后對原始的數組p進行兩次遍歷:

  • 第一次 從頭開始 遍歷p,以p的值作為數組c的下標,并將c中對應位置的值加1,也就是說c[Integer.valueOf(i)]的值表示的是字符ip中出現的次數。這和HashMap的原理有些類似,只不過是將查找的key值直接簡化成為了value的整型值。
  • 第二次 從頭開始 遍歷p,查找數組c對應位置該值是否為1,如果為1,那么就表示它是第一次只出現一次的字符。

實現代碼

class Untitled {
    
    static char firstNotRepeat(char p[], int len){
        if (len == 1) 
            return p[0];
        int c[] = new int[256];
        int i;
        char r = p[0];
        for (i = 0; i < 256; i++)
            c[i] = 0;
        for (i = 0; i < len; i++)
            c[p[i]] += 1;
        for (i = 0; i < len; i++) {
            if (c[p[i]] == 1) {
                r = p[i];
                break;
            }
        }
        return r;
    } 
    
    public static void main(String[] args) {
        char[] source = "abaccdeff".toCharArray();
        char c = firstNotRepeat(source, source.length);
        System.out.println(c);
    }
}

運行結果

>> b

2.4 翻轉句子

問題描述

翻轉句子中單詞的順序,但單詞內字符的順序不變,句子中單詞以空格符隔開。例如I am a original string翻轉后的結果為string original a am I

解決思路

實現過程分為兩步:

  • 第一步,將整個句子中的所有字符都翻轉
  • 第二步,遍歷翻轉后的句子,對于句子內的每一個單詞,將其字符再翻轉一次,就能保證單詞內字符的順序不變。翻轉單詞的時候,通過pStartpEnd記錄每次遇到單詞的起止下標,并使用子方法reverseSub對單詞中的字符進行翻轉。

實現代碼

class Untitled {
    
    static void reverseSub(char p[], int start, int end){
        char c;
        int i = start;
        int j = end;
        while(i < j){
            c = p[i]; p[i] = p[j]; p[j] = c;
            i++; j--;
        }
    }

    static void reverseSentence(char p[], int length){
        //首先翻轉整個具體的所有字符。
        reverseSub(p, 0, length-1);
        int pStart = 0;
        int pEnd = 0;
        //從頭開始遍歷,尋找句子中的單詞,pStart和pEnd分別表示單詞的起止下標。
        while(pStart < length && pEnd < length){
            if(p[pStart] == ' '){
                pStart++;
                pEnd++;
            } else if (p[pEnd] == ' ' || p[pEnd] == '\0') {
                //翻轉單詞中的字符。
                reverseSub(p, pStart, --pEnd);
                pStart = ++pEnd;
            } else {
                pEnd++;
            }
        }
    } 
    
    public static void main(String[] args) {
        char[] source = "I am a original string".toCharArray();
        System.out.println(source);
        reverseSentence(source, source.length);
        System.out.println(source);
    }
}

運行結果為:

>> string original a am I

2.5 計算字符串之間的最短距離

問題描述

假設我們有兩個字符串AB,那么如果想要將字符串A通過以下三種操作變換成B:刪除、新增和修改,操作步驟的次數就稱為 字符串 A 和 B 之間的距離

現在給定兩個字符串,求這兩個字符串之間的最短距離。

解決思路

首先,我們需要先明確一個前提條件:如果A的長度為0,那么AB之間的距離就為B的長度,反之對于B也如此。

下面,我們在來看普通的情況,假如A[0]B[0]相同,那么AB之間的距離就為A[1..A.len-1]B[1..B.len-1]之間的距離;假如A[0]B[0]不相同,那么想要讓AB相同,執行的操作有以下幾種:

  • 刪除A的第一個字符,然后計算A[1..A.len-1]B[0..B.len-1]的距離
  • 刪除B的第一個字符,然后計算A[0..A.len-1]B[1..B.len-1]的距離
  • 修改A的第一個字符為B的第一個字符,然后計算A[1..A.len-1]B[1..B.len-1]的距離
  • 修改B的第一個字符為A的第一個字符,然后計算A[1..A.len-1]B[1..B.len-1]的距離
  • 增加A的第一個字符到B第一個字符之前,然后計算A[1..A.len-1]B[0...B.len-1]的距離
  • 增加B的第一個字符到A第一個字符之前,然后計算A[0...A,len-1]B[1..B.len-1]的距離

對于以上這六種情況,其實最終都可以歸納為 經過一次操作,再加上剩下部分的操作次數,那么我們的接下來的工作就是 求出剩下部分的操作部分的最小值。對于上面的任意一種情況,經過劃分后AB的長度都會減少,那么最終必然會達到我們在一開始談到的 前提條件:如果A的長度為0,那么AB之間的距離就為B的長度,反之對于B也如此。

實現代碼

class Untitled {
    
    static int minValue(int t1, int t2, int t3){
        if (t1 < t2) {
            return t1 < t3 ? t1 : t3;
        } else {
            return t2 < t3 ? t2 : t3;
        }
    }

    static int calStringDis(char p1[], char p2[], int p1Start, int p2Start, int p1Len, int p2Len){
        if (p1Len == 0) {
            if (p2Len == 0)
                return 0;
            else
                return p2Len;
        }
        if (p2Len == 0) {
            if (p1Len == 0)
                return 0;
            else
                return p1Len;
        }
        if (p1[p1Start] == p2[p2Start])
            //A和B的第一個字符相同。
            return calStringDis(p1, p2, p1Start+1, p2Start+1, p1Len-1, p2Len-1);
        else {
            //(1) 刪除B的第一個字符,或者將B的第一個字符放到A之前。
            int t1 = calStringDis(p1, p2, p1Start, p2Start+1, p1Len, p2Len-1);
            //(2) 刪除A的第一個字符,或者將A的第一個字符放到B之前。
            int t2 = calStringDis(p1, p2, p1Start+1, p2Start, p1Len-1, p2Len);
            //(3) 修改A的第一個字符為B的第一個字符,或者修改B的第一個字符為A的第一個字符。
            int t3 = calStringDis(p1, p2, p1Start+1, p2Start+1, p1Len-1, p2Len-1);
            //計算以上三種情況的最小值,再加上這次操作的次數。
            return minValue(t1, t2, t3) + 1;
        }
    } 
    
    public static void main(String[] args) {
        char[] source = "abcde".toCharArray();
        char[] other = "bcd".toCharArray();
        System.out.println("" + calStringDis(source, other, 0, 0, source.length, other.length));
    }
}

運行結果

>> 2

更多文章,歡迎訪問我的 Android 知識梳理系列:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容