一道二進制子串算法,讓面試官都解不出來?

在這裏插入圖片描述
在這裏插入圖片描述
算法題目:

給定一個字符串 s ,計算具有相同數量0和1的非空(連續)子字符串的數量,並且這些子字符串中的所有0和所有1都是組合在一起的。

重複出現的 子串要計算它們出現的次數。

示例1:

輸入:“00110011”
輸出:6
解釋:有6個子串具有相同數量的連續1和0:

“0011”,“01”,“1100”,“10”,“0011”,“01”。

注意,一些重複出現的子串要計算它們出現的次數,另外,
“00110011”不是有效的子串,因爲所有的0(和1)沒有組合在一起。

示例2:

輸入:“10101”
輸出:4
解釋:有4個子串,“10”,“01”,“10”,“01”,它們具有相同數量的連續1和0。

注意:s.length 在1到50,000之間的範圍,s只包含“0”或“1”字符。

“000111”中有多少個有效的二進制子串?
“11100”中有多少個有效的二進制子串?
“00011100”呢?

這道題目,難度係數爲簡單題目,涉及到的知識點爲字符串。

給定函數體如下:

/**
 * @param {string} s
 * @return {number}
 */
var countBinarySubstrings = function(s) {

};

題目理解:

通過看看這兩個示例,字符串 s 給的都是二進制數,要求計算具有相同數量 0 和 1 的非空(連續)子字符串的數量,這句話裏面的條件有三個:

第一 不爲空,非空(連續)

第二 0 和 1 是要相同數量的

第三 0 和 1 要是連續出現的子字符串的數量

描述:

如果遇到10或者是01的情況,則說明連續的1或者是連續的0斷了,那麼可以拿到前面連續1或者是連續0的數量,然後再查找後面連續1或者是連續0的數量,作比較看看有多少個符合的子串。

多種解題思路:

我看到這個題之後,想起來好幾種解法,都寫下來可能篇幅較長,如果只是簡單羅列可能不太好找,就做了這個目錄導圖,可以對照目錄圖去看。

在這裏插入圖片描述
第一種JavaScript方法:按照前後數量判斷:

"1100"中有1和0的數量相等則有兩個符合的子串

“11000”,“0000111”中,1和0的數量不相等,則有:

min(1的數量,0的數量)個符合的子串。

如果遇到10或者是01的情況,則說明連續的1或者是連續的0都斷了,那麼就可以拿到前面連續1或者是0的數量了,然後在往後找連續的0或者是1的數量。接着看看有多少個符合的子串,之後持續向後查找。

var countBinarySubstrings = function(s) {
    // 前面一個數,和當前數,返回count計數
    let pre=0, count=0, count1=0, count2=0;
    // 循環字符串
    for(let i = 1; i < s.length; i++) {
    // 本質不等於,則計數返回往上加
    // 遇到10或01的情況,則說明連續的1或連續的0斷了
    // s[1] !== s[0]
    // 則說明它斷了,計數+1達成條件
    if(s[i] !== s[pre]) {
      if(count1 === 0) {
        // 拿到前面連續1或0的數量
        count1 = i - pre;
        pre = i;
      } else {
        // 不等於0的情況
        count2 = i - pre;
        count += count1 > count2 ? count2 : count1;
        count1 = count2;
        count2 = 0;
        pre = i;
      }
    }
    if(i === s.length - 1) {
      count2 = s.length - pre;
      count += count1 > count2 ? count2 : count1;
    }
  }

  return count;
}

看完代碼得知,返回count,循環字符串從1,開始,如果s[1]
不等於前一個數,即可能是01,或者是10的情況下,那麼前面的數量爲當前1-0爲1,前一個數量爲1的情況,pre被賦值爲i,i爲1的情況。

假設情況s爲:

var s = "001"

如果s[i]==s[pre]的數,s[i]=0 , s[pre]=0 .

count1=0 , count2=0 . pre=0 . count=0

第二次循環,s[i]=1 , s[pre]=0 . 兩者是不相等的,所以有

s[i] !== s[pre]爲true,此時count1爲0,走下面的代碼塊。所以結果爲:

count1=2 , count2=0 . pre=2 . count=0

因爲最後了i === s.length - 1這種情況成立,所以輸出結果爲

count1=2 , count2=1 . pre=2 . count=1

在這裏插入圖片描述
在這裏插入圖片描述
其實可以看出代碼中一開始都會走count1===0這條路線。pre爲遞增下標數,比較當前值與前一個值的情況。每次(pre=i)

在這裏插入圖片描述
在這裏插入圖片描述
看圖帶入,解釋一下,本質,如果循環中兩者不相等
(s[i] !== s[pre])走裏面的情況,如果前者和後者比較爲相等情況,不走if(s[i] !== s[pre])裏的代碼塊,知道不相等的情況。最後一個數到了最後即尾部,滿足條件(i === s.length - 1),執行其中的代碼塊。

在這裏插入圖片描述

function daCount(s) {
    // 前面一個數,和當前數
    let pre=0, count=0, count1=0, count2=0;
    // 循環字符串
    for(let i = 1; i < s.length; i++) {
    // 遇到10或01的情況,則說明連續的1或連續的0斷了
    console.log('s[i]='+s[i]+"  ,  "+"s[pre]="+s[pre]+ " . ");
    if(s[i] !== s[pre]) {
      console.log('第一次count1=' + count1 + " , " + "count2=" + count2 + " . " + "pre=" + pre + " . " + "count=" + count);
      if(count1 === 0) {
        // 拿到前面連續1或0的數量
        count1 = i - pre;
        pre = i;
     console.log('第二次count1=' + count1 + " , " + "count2=" + count2 + " . " + "pre=" + pre + " . " + "count=" + count);
      } else {
        count2 = i - pre;
        count += count1 > count2 ? count2 : count1;

        count1 = count2;
        count2 = 0;
        pre = i;
console.log('第3次count1=' + count1 + " , " + "count2=" + count2 + " . " + "pre=" + pre + " . " + "count=" + count);
      }
    }
console.log('第n次count1=' + count1 + " , " + "count2=" + count2 + " . " + "pre=" + pre + " . " + "count=" + count);
    if(i === s.length - 1) {
      count2 = s.length - pre;
      count += count1 > count2 ? count2 : count1;
console.log('第4次count1=' + count1 + " , " + "count2=" + count2 + " . " + "pre=" + pre + " . " + "count=" + count);
    }
  }

  return count;
}

在這裏插入圖片描述

第二種JavaScript方法:藉助min() 方法:

JavaScript min() 方法

返回值:給定數值中最小的數。如果任一參數不能轉換爲數值,則返回NaN。

描述

min 是 Math 的靜態方法,應該像這樣使用:Math.min(),而不是作爲你創建的 Math 實例的方法(Math 不是構造函數)。

如果沒有參數,結果爲Infinity。如果有任一參數不能被轉換爲數值,結果爲 NaN。

什麼是JavaScript Math 對象?

定義和用法

min() 方法可返回指定的數字中帶有最低值的數字。

Math.min.apply(null, arr)

var array=[2,6,5,8,7];
Math.min.apply(null,array);

在這裏插入圖片描述

/**
 * @param {string} s
 * @return {number}
 */
var countBinarySubstrings = function(s) {
    // 字符串的長度
    const len = s.length;
    // n計數爲0,前一個爲0,current當前爲1。
    let n = 0, pre = 0, current = 1;
    // 循環字符串
    for(let i = 0; i<len; i++){
     // 如果兩者相等
     if(s[i] === s[i+1]) {
      // 當前數加1
      current++;
     }else {      
       // 比如00011,就有2組
      if(pre > 0) {
       n += Math.min(pre,current);
      }
      pre = current;
      current = 1;
     }
    }
    return n;
};

如果第一數與第二個數相等,比如00,current就要加+1的情況,當前的數量從1變爲了2。如果爲01,當前的數量給前面的數量pre,當前數量current爲1。min取得是數量,pre,current。

在這裏插入圖片描述
在這裏插入圖片描述

function daNum(s) {
    // 字符串的長度
    const len = s.length;
    // 次數爲0,前一個爲0,current當前爲1。
    let n = 0, pre = 0, current = 1;
    console.log('第一n='+n+" , "+"pre="+pre+" , "+"current="+" , "+current);
    // 循環字符串
    for(let i = 0; i<len; i++){

     if(s[i] === s[i+1]) {

      current++;
      console.log('第二n='+n+" , "+"pre="+pre+" , "+"current="+" , "+current);
      console.log("1,s[i]="+s[i]+" , "+"s[i+1]="+" , "+s[i+1]);
     }else {
      console.log("2,s[i]="+s[i]+" , "+"s[i+1]="+" , "+s[i+1]);
      // 比如00011,就有2組
      if(pre > 0) {
       n += Math.min(pre,current);
       console.log('第三n='+n+" , "+"pre="+pre+" , "+"current="+" , "+current);
      }
      pre = current;
      current = 1;
      console.log('第四n='+n+" , "+"pre="+pre+" , "+"current="+" , "+current);
     }
    }
    console.log('第五n='+n+" , "+"pre="+pre+" , "+"current="+" , "+current);
    return n;
};

第三種JavaScript方法:藉助min() 方法與push() 方法:

total爲計數,res存儲相鄰連續字符串的個數

JavaScript push() 方法

定義和用法
push() 方法可向數組的末尾添加一個或多個元素,並返回新的長度。

語法

arrayObject.push(newelement1,newelement2,....,newelementX)

newelement1 必需。要添加到數組的第一個元素。
newelement2 可選。要添加到數組的第二個元素。
newelementX 可選。可添加多個元素。

返回值

把指定的值添加到數組後的新長度。

push() 方法可把它的參數順序添加到 arrayObject 的尾部。它直接修改 arrayObject,而不是創建一個新的數組。push() 方法和 pop() 方法使用數組提供的先進後出棧的功能。

/**
 * @param {string} s
 * @return {number}
 */
var countBinarySubstrings = function(s) {
    // res 存儲相鄰連續字符串的個數
    let res = [];
    // 字符串
    let temp = s[0];
    let count = 0;
    for(let i of s) {
     // 循環字符串
     if(i !== temp) {
      res.push(count);
      temp = i;
      count = 0;
     }
     count++;
    }
    res.push(count);
    // 計數爲0
    let total = 0;
    // 計數爲0
    for(let i=0; i<res.length-1; i++){
     total += Math.min(res[i], res[i+1]);
    }
    return total;
};

如何使用 min() 來返回指定數字中帶有最低值的數字:

<script type="text/javascript">

document.write(Math.min(5,7) + "<br />")
document.write(Math.min(-3,5) + "<br />")
document.write(Math.min(-3,-5) + "<br />")
document.write(Math.min(7.25,7.30))
</script>

輸出:

5
-3
-5
7.25

第四種JavaScript方法:藉助match() 方法

在這裏插入圖片描述

000111必定有三個子串
00011必定有兩個子串
0111必定有1個子串

以此類推, 每兩組數據之間長度最短的值爲子串的數量
把字符串按數字分組切割,如:[‘00’, ‘11’, ‘00’, ‘11’]
但是如果 是 1010100 這種,怎麼解釋才合理呢?

解題思路:

把字符串按數字分組切割,如:[‘00’, ‘11’, ‘00’, ‘11’],相鄰的兩組數據組合,長度較短的數據長度即爲這組數據可能的數據次數

/**
 * @param {string} s
 * @return {num}
 */
var countBinarySubstrings = function(s) {
  let num = 0;
  const arr = s.match(/0+|1+/g); 
  // let n = 0, arr = s.match(/([1]+)|([0]+)/g)
  
  // 把字符串切割成['00', '11', '00', '11']這樣的數組

  for(let i = 0, len = arr.length; i < len - 1; i++){
    num += Math.min(arr[i].length, arr[i+1].length);     
    // 相鄰比較,長度更短的則爲這一組的出現次數
  }

  return num;
}

定義和用法

match() 方法可在字符串內檢索指定的值,或找到一個或多個正則表達式的匹配。

該方法類似 indexOf() 和 lastIndexOf(),但是它返回指定的值,而不是字符串的位置。

返回值

存放匹配結果的數組。

<script type="text/javascript">

var str="Hello world!"
document.write(str.match("world") + "<br />")
document.write(str.match("World") + "<br />")
document.write(str.match("worlld") + "<br />")
document.write(str.match("world!"))
</script>

輸出:

world
null
null
world!

第五種JavaScript方法:通用方法

/**
 * @param {string} s
 * @return {number}
 */
var countBinarySubstrings = function(s) {
    // 計算前一個字符連續出現的次數
     let pre = 0
    // 計算後一個字符連續出現的次數
     let cur = 1
    // 每當 pre >= cur 時,既滿足條件一次 count++
    // 前面有兩個0,後面它自己爲1
    // 計數count一開始爲0
     let count = 0

    // 循環字符串
     for(let i=1; i<s.length; i++) {
    // 如果前一個和後一個相等
         if(s[i] === s[i-1]) {
    // 本身當前它自己的數爲1,那麼兩者相等,這個數就+1,爲2
             cur++
    // 00
         } else {
    // 當出現不一樣的字符時,現任變前任,現任重新計數
    // 01,001,10,101,不一樣的前後,10,01
    // 請一個數字的數量爲1,後一個數字的數量爲1
           pre = cur
    // 01,10, 當前數還是1
           cur = 1
         }

    // 001, 110, 010, 101,
    // 只要  pre >= cur, 即可滿足條件一次
         if(pre >= cur) {
             count++
         }
     }
     return count
};

看了代碼解析應該是懂的了,不過在這裏還是口述一下下。

滿是條件爲01或者是10,就是兩者不同,計數加1,出現001,或者是110的情況下,爲前面2個0,後面1個1,前面的數量大於後面的數量即爲滿足一次條件,110的情況也是如此,1的數量爲2,0的數量爲1。

那麼我們來定義一個變量let pre這個變量,這個變量的意思爲計算前一個字符串出現的次數,首先這個變量的初始化值爲0。如果當前數爲 1,那麼前面就沒有數字,即爲它的數量爲0。

這裏我們需要設置當前數量爲1,即出現一個數字,那麼數量即爲1個。滿足條件爲前面的數量大於等於後面的數量,即爲pre>=cur時,我們計數滿足條件加1的情況,定義計數爲count,滿足條件時,count++

// 計算前一個字符連續出現的次數
let pre = 0
// 計算後一個字符連續出現的次數
let cur = 1
// 每當 pre >= cur 時,既滿足條件一次 count++
// 前面有兩個0,後面它自己爲1
// 計數count一開始爲0
let count = 0

注意:計算前一個字符連續出現的次數和計算後一個字符連續出現的次數不同哦!

然後我們給定一個字符串數字,“00110011”,我們需要循環這個字符串中的數字,比較前一個數字和後一個數字是否相等,如果相等,是什麼情況呢?如:00或者是11的情況下,當前數cur就要加1。

如果出現不一樣的字符時,即情況:10或者是01這些情況,那麼計算前一個字符連續出現的次數從0變爲1,它有數字,即開始有次數了。把當前cur的次數賦值給pre(計算前一個字符連續出現的次數)。看着01和10的情況,當前cur的次數賦值爲1。

滿足條件,有人問了,那麼001的情況或者是110或者是1100或者是0011或者是111000或者是000111或者是1010等情況下呢?

即這些情況滿足如下:計算前一個字符連續出現的次數大於等於計算後一個字符連續出現的次數,即爲pre>=cur的條件下滿足,計數情況count++,循環字符串後,返回我們需要的count計數。

寫在最後:

好了,本章已結束,本質上就是用法不同,解法不同而已!多多打印理解即可!

發佈了745 篇原創文章 · 獲贊 687 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章