查看原文
其他

一道二进制子串算法,让面试官都解不出来?

脚本之家 2021-06-30

The following article is from 程序员小灰 Author Jeskson

  脚本之家

你与百万开发者在一起

本文经授权转自公众号 程序员小灰(ID:chengxuyuanxiaohui)
如若转载请联系原公众号




算法题目:


给定一个字符串 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-57.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>
输出:
worldnullnullworld!


第五种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计数。


写在最后


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

更多精彩


在公众号后台对话框输入以下关键词

查看更多优质内容!


女朋友 | 大数据 | 运维 | 书单 | 算法

大数据 | JavaScript | Python | 黑客

AI | 人工智能 | 5G | 区块链

机器学习 | 数学 | 送书

●  鲁大师原来真的姓鲁

●  脚本之家粉丝福利,请查看!

●  人人都欠微软一个正版?

● 致敬经典:Linux/UNIX必读书单推荐给你

 B站跨年晚会究竟做对了什么?

● 终于有人把 Nginx 说清楚了,图文详解!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存