一道二进制子串算法让面试官都解不出来?_前端数据结构与算法系列

作者 | Jeskson

算法题目:

给定一个字符串 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”呢?

这道题目,难度系数为简单题目,涉及到的知识点为字符串。

JavaScript的解法:

给定函数体:

/**

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

};

题目理解:

通过看看这两个示例,字符串 s 给的都是二进制数,要求计算具有相同数量 0 和 1 的非空(连续)子字符串的数量,这句话里面的条件有三个:

第一 不为空,非空(连续)

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

第三 0 和 1 要是连续出现的子字符串的数量

描述:

如果遇到10或者是01的情况,则说明连续的1或者是连续的0断了,那么可以拿到前面连续1或者是连续0的数量,然后再查找后面连续1或者是连续0的数量,作比较看看有多少个符合的子串。

目录:

一道二进制子串算法让面试官都解不出来?_前端数据结构与算法系列_02

第一种JavaScript:
var countBinarySubstrings = function(s) {
    // 前面一个数,和当前数
    let pre=0, count=0, count1=0, count2=0;
    // 循环字符串
    for(let i = 1; i < s.length; i  ) {
    // 遇到10或01的情况,则说明连续的1或连续的0断了
    if(s[i] !== s[pre]) {
      if(count1 === 0) {
        // 拿到前面连续1或0的数量
        count1 = i - pre;
        pre = i;
      } else {
        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;
}
第二种JavaScript:
/**
 * @param {string} s
 * @return {number}
 */
var countBinarySubstrings = function(s) {
    // 字符串的长度
    const len = s.length;
    // 次数为0,前一个为0,current当前为1。
    let n = 0, pre = 0, current = 1;
    // 循环字符串
    for(let i = 0; i<len; i  ){
     if(s[i] === s[i 1]) {
      current  ;
     }else {
      // 比如00011,就有2组
      if(pre > 0) {
       n  = Math.min(pre.current);
      }
      pre = current;
      current = 1;
     }
    }
    return n;
};
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);

一道二进制子串算法让面试官都解不出来?_前端数据结构与算法系列_03

第三种JavaScript:
/**
 * @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);
    let total = 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

解题规律

一道二进制子串算法让面试官都解不出来?_前端数据结构与算法系列_04

  • 000111必定有三个子串
  • 00011必定有两个子串
  • 0111必定有1个子串

以此类推, 每两组数据之间长度最短的值为子串的数量
把字符串按数字分组切割,如:[‘00’, ‘11’, ‘00’, ‘11’]
但是如果 是 1010100 这种,怎么解释才合理呢?

/**
 * @param {string} s
 * @return {number}
 */
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;
}
代码注解
/**
 * @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计数。

 

一道二进制子串算法让面试官都解不出来?_前端数据结构与算法系列_05