前端系列——查找字符串B的字符任意一種組合是不是字符串A的子串

題目要求

這道算法題在前端面試中可能遇到,聽說某條出過這題。javascript

查找字符串B的字符任意一種組合是不是字符串A的子串。
例如 A=abc123,B=cba,則B的其中一種組合abc是A的子串,而後返回true。

算法思路

題目的出處已經無從考究,接下來咱們從JavaScript的角度來封裝這樣一個功能函數。前端

窮舉

一開始看到這道題,你會想到什麼?
我想到的是先列舉出B的全部排列組合,存到數組裏面,而後遍歷,判斷是否有組合包含在A中,若是有返回true,不然返回false。
若是從題目給出的例子來窮舉,一共6種組合,很容易窮舉出來,可是字符串長度很是大的時候,怎麼辦呢?
因此,窮舉的辦法被我排除了。java

標記刪除法

這名字聽起來很奇怪,怎麼個思路呢?面試

一、A的排序確定是不變的,既然可變的咱們很難下手,那麼能夠從不變的地方入手,以不變應萬變。 算法

二、看字符串可能不太習慣,我把A和B都轉換成數組。數組

let a = A.split('') // [a, b, c, 1, 2, 3]
let b = B.split('') // [c, b, a]

三、先過濾數組爲空的狀況,若是a或者b爲空,那麼不須要比較,返回false。前端工程師

if (a.length === 0 || b.length === 0) {
    return false
}

四、只看數組b,能夠有6種排列組合,[c,b,a],[a,b,c],[a,c,b],[b,a,c],[b,c,a],[c,a,b]。還記得第1步說的,咱們無論b有多少種組合,都從a入手。函數

// a = [a, b, c, 1, 2, 3]
for (let j = 0; j < a.length; j++) { 

}

五、遍歷a有什麼做用呢?接下來我爲你們揭曉何爲標記刪除法,容許我小小解釋一下該方法,分爲2個核心,「標記」和「刪除」,「標記」是指標記當前數組a遍歷的位置,「刪除」是指刪除數組b中的元素。測試

這樣說可能不太懂,先不看代碼,我用數組來模擬一下執行過程。code

初始化的值
a = [a, b, c, 1, 2, 3]
b = [c, b, a]
==================================================
當遍歷a的時候,j從0開始,遍歷到a.length-1結束
==================================================
j = 0 // 給a裏的字符加'',作個標記,表示當前遍歷的下標位置
a = ['a', b, c, 1, 2, 3]
==================================================
而後咱們發現數組b存在當前的字符'a',執行刪除操做
b = [c, b]
==================================================
j = 1 // 數組a遍歷到第二個字符
a = [a, 'b', c, 1, 2, 3] // 標記
b = [b] // 刪除
==================================================
j = 1 // 數組a遍歷到第三個字符
a = [a, b, 'c', 1, 2, 3] // 標記
b = [] // 刪除
==================================================
如今咱們看到b數組變成空了,則證實b的任意一種排列存在於a中

六、上一步描述的狀況是最簡單的狀態,恰好在A字符中存在不間斷的字符組合。咱們把A改一下,變成 A = a1b2c3abc。即便變複雜了,咱們依舊可使用標記刪除發來作,只是稍微作一點處理。

初始化的值
a = [a, 1, b, 2, c, 3, a, b, c]
b = [c, b, a]
==================================================
當遍歷a的時候,j從0開始,遍歷到a.length-1結束
==================================================
j = 0 // 給a裏的字符加'',作個標記,表示當前遍歷的下標位置
a = ['a', 1, b, 2, c, 3, a, b, c]
==================================================
而後咱們發現數組b存在當前的字符'a',執行刪除操做
b = [c, b]
==================================================
j = 1 // 數組a遍歷到第二個字符
a = [a, '1', b, 2, c, 3, a, b, c] // 標記
// 忽然發現第2個字符是1,如今該怎麼辦?咱們只須要把數組b恢復初始狀態便可

b = [c, b, a] // 恢復初始狀態 
==================================================
接下來繼續遍歷,重複上面的處理步驟,直到數組b爲空或者數組a遍歷完成,返回結果

七、JavaScript代碼實現
下面是第6步說明的代碼實現,從代碼中能夠看到,無論B字符有多少排列組合,咱們始終只須要遍歷A的全部字符便可,內部實現也是用空間換時間。

// 遍歷數組 a
    for (let j = 0; j < a.length; j++) {
        // 數組 b不爲空,下一步
        if (b.length > 0) {
            // 數組a存在當前遍歷的數組b的元素
            if (b.indexOf(a[j]) > -1) {
                // 刪除b數組中匹配到的第一個對應下標的元素
                b.splice(b.indexOf(a[j]), 1)
                if (b.length === 0) {
                    // 若是數組b所有被刪除了,則證實b是a的子串
                    return true
                }
            } else {
                // 數組b不存在當前遍歷的數組b的元素,恢復b數組
                b = B.split('')
            }
        } else {
            // 數組 b爲空返回true
            return true
        }
    }

總結

與其餘前端工程師的交流中,我還了解到了其餘的解題思路,有些頗有趣,好比考慮使用Map或Set、滑塊區間比較等,不過我沒有去用代碼實現過,若是你有其餘的方法,能夠在下面留言交流。

完整源碼

評論區有人指出不能覆蓋某些場景的測試用例,因此我對上面的具體過程作了改進,下面是改進後的源碼。
增長了2個字段,isBack和isRestart,isRestart用來標記是否從新在當前位置遍歷,isBack判斷是否對數組遍歷的下標進行回退一個單位。

var A = 'abc123'
  , B = 'cba'
function interface(A, B) {
    // 將A和B轉成數組
    let a = A.split('')
    let b = B.split('')
    if (a.length === 0 || b.length === 0) {
        return false
    }
    let isBack = false, isRestart = 0
    // 遍歷數組 a
    for (let j = 0; j < a.length; j++) {
        // 數組 b不爲空,下一步
        if (b.length > 0) {
            isBack = false
            // 數組a存在當前遍歷的數組b的元素
            if (b.indexOf(a[j]) > -1) {
                // 刪除b數組中匹配到的第一個對應下標的元素
                b.splice(b.indexOf(a[j]), 1)
                if (b.length === 0) {
                    // 若是數組b所有被刪除了,則證實b是a的子串
                    return true
                }
            } else {
                if (isRestart !== 0) {
                    isBack = false
                } else {
                    isBack = true
                }
                // 數組b不存在當前遍歷的數組b的元素,恢復b數組
                b = B.split('')
                if (isBack) {
                    j -= 1
                    isRestart = 0
                }
                isRestart++
            }
        } else {
            // 數組 b爲空返回true
            return true
        }
    }
    return false
}
interface(A, B)
相關文章
相關標籤/搜索