如何获取字符串中连续最多的字符以及次数?

632 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情

锁链能够束缚我们的身体,但不应该束缚住我们那颗勇往直前的心。

示例

"abbcccddddeeeee1212111" => e,5次

解题

1. 思路一

我最先想到的是利用循环嵌套,分别记录重复字符的长度,首先把相邻的不相同的字符长度进行对比,长的留下,短的舍弃,在继续循环,再次与留下的进行对比,长的留下,短的舍弃,依次执行到循环结束

1.jpeg 在图中做了简单的介绍,下面来写一下代码

  • 嵌套循环
//首先定义返回数据类型
interface IRes {
  value: string
  num: number
}
//嵌套循环
function continuousChar1(str: string): IRes{
  const res: IRes = {
    value: "",
    num: 0
  }

  const length = str.length;
  if(length == 0) return res;//如果字符串长度为0.那么直接返回res

  let len = 0;//设置临时变量,存储相同连续字符串的长度
  for(let i = 0; i < length; i++){
    len = 0;//重置临时变量
    for(let j = i; j < length; j++){//这里注意j不是从0开始
      if(str[i] == str[j]){//如果相等,临时变量++
        len++
      }

      if(str[i] != str[j] || j == length -1){//不相等或者j到了循环最后一位
        if(len > res.num){//留下最大的
          res.num = len;
          res.value = str[i]
        }

        if(i < length - 1){//跳步
          i = j -1
        }

        break;
      }
    }
  }

  return res;
}

功能测试

const testStr1 = "abbcccddddeeeee1212111";
const res1 = continuousChar1(testStr1);
console.log(res1)//{value: 'e', num: 5}

功能测试符合预期.

简单思考一下:

  1. 这里的时间复杂度,这里用到了跳步(具体位置可以参考代码部分),那么其便不再是单纯的嵌套循环,那么它的时间复杂度是O(n)还是O(n^2)呢?
  2. 代码中定义的返回值类型IRes的num是从0开始的,那么它是不是可以从1开始,这样的同时临时变量len也从1开始,这样也能相对减少部分循环次数呢?

2. 思路二

当出现嵌套循环的时候,可以直接向双指针的方向考虑。

2.jpeg 这张图看上去和第一张很像,但仔细看你会发现,这张图是一次循环。这里利用了双指针的思想。
下面看代码:

function continuousChar2(str: string): IRes{
  const res: IRes = {
    value: "",
    num: 0
  }

  const length = str.length;
  if(length == 0) return res;//如果字符串长度为0.那么直接返回res

  let len = 0;//设置临时变量,存储相同连续字符串的长度
  
  let i;
  let j = 0;
  for(i = 0; i < length; i++){
    if(str[i] == str[j]){
      len++
    }

    if(str[i] != str[j] || i == length - 1){
      if(len > res.num){
        res.num = len;
        res.value = str[j]
      }

      if(i < length - 1){
        j = i;
        i--
      }

      len = 0;
    }
  }

  return res;
}

功能测试

const res3 = continuousChar2(testStr1);
console.log(res3)//{value: 'e', num: 5}

功能测试符合预期。 简单思考一下:

  1. 代码中定义的返回值类型IRes的num是从0开始的,那么它是不是可以从1开始,这样的同时临时变量len也从1开始,这样也能相对减少部分循环次数呢?
  2. 代码中为什么要i--呢?
  3. 是否可以再增加一个变量,记录字符串剩余的长度,如果剩余长度小于当前记录的num,则结束循环,这样做是否在某些情况下可以减少循环呢?

性能分析

//性能分析
let strTest = '';
for(let i = 0; i <  100 * 10000; i++){
  if(i < 60 * 10000){
    strTest += 'a'
  }else{
    strTest += i.toString
  }
  
}

console.time('continuousChar1')
continuousChar1(strTest)
console.timeEnd('continuousChar1')// 373.59375 ms

console.time('continuousChar2')
continuousChar2(strTest)
console.timeEnd('continuousChar2')//404.517333984375 ms

结果发现思路一和思路二的执行结果是十分相近的。

思考

在思路一和思路二中都有一些简单的思考,在最后自己也在本地进行了一些简单的测试

思路一: 1.该思路的时间复杂度是O(n),因为思路二的时间复杂度是O(n),而二者执行时间几乎无异; 2.该想法是可以实现的,执行结果也符合预期,其对思路一的性能提升理论上高于思路二;思路一和思路二按照该想法在本地已经实现,思路一按照上方的性能测试结果大约在250ms左右,性能也是有所提升的,思路二按照上方的性能测试,结果和思路二时间几乎无异; 思路二: 3.的想法在本地已实现,但是按照上方的性能测试,结果比方案二的时间要多十几ms,每次时间都会多一点(测试了大约10次)。暂定其无性能上的优化,反而因为多加了判断,有点影响性能(较真的讲),也可能是我本地代码有些问题,有时间再继续检查一下。