前言
今天我们来研究一下如何获取字符串中连续最多的字符以及次数
任意输入一个字符串,统计字符串中连续最多的字符以及次数
请看示例1:
示例 1 :
输入:
str = 'aabbcccddeeee11223'
输出:一个包含所得字母和长度的对象
res = {
char: '',
length: 0
}
示例 2 :
输入:
str = 'abc'
输出:
res = {
char: 'a',
length: 1
}
示例 3 :
输入:
str = 'aaa'
输出:
res = {
char: 'a',
length: 3
}
解题思路(方法一:嵌套循环)
-
嵌套循环,遍历字符串,计算当前字符出现次数,依次使用一个对象统计字符出现个数
-
当前一个字符出现次数,小于后一个字符出现次数,更新对象
-
数组遍历完毕,返回一个对象
代码实现
export function findContinuousChar1(str: string) : IRes {
const res = {
char: "",
length: 0
}
const length = str.length
let tempLength = 0
if (length === 0) return res
for (let i = 0; i < length; i++) {
// 为什么要初始化tempLength?
// 因为如果不初始化tempLength,就会累计数值越来越多
// 自然,出现最多的字符串肯定是最后一位字符
tempLength = 0
for (let j = i; j < length; j++) {
if (str[i] === str[j]) {
tempLength++
}
// 为什么此处不能使用else if?
// 因为当字符为连续字符时,str[i] === str[j]一定满足
// 如果使用else if ,那么永远不会进入else if这个循环,每次进入了if
// 因此两个都要使用if,才能使得res里面的数据更新
if (str[i] !== str[j] || j === (length - 1)) {
if (tempLength > res.length) {
res.length = tempLength
res.char = str[i]
console.info(res)
}
if (i < str.length - 1) {
i = j - 1
}
break
}
}
}
return res
}
解题思路(方法二:双指针)
-
定义指针i和j,j不动,i保持移动
-
如果i和j的值一直相等,则i保持移动
-
如果i和j的值不等,记录次数,使得j追上i,重复第一步
代码实现
export function findContinuousChar2(str: string): IRes {
const res: IRes = {
char: '',
length: 0
}
const length = str.length
if (length === 0) return res
let tempLength = 0 // 临时记录当前连续字符的长度
let i = 0
let j = 0
// O(n)
for (; i < length; i++) {
if (str[i] === str[j]) {
tempLength++
}
if (str[i] !== str[j] || i === length - 1) {
// 不相等,或者 i 到了字符串的末尾
if (tempLength > res.length) {
res.char = str[j]
res.length = tempLength
}
tempLength = 0 // reset
if (i < length - 1) {
j = i // 让 j “追上” i
// 为什么i 要减一
// 因为不减一的话,最后计算出来的次数就会少一次
i-- // 细节
}
}
}
return res
}
算法时间度分析
仍然先给出我们的结论:方法一和方法二的时间复杂度基本接近,都为O(n),我们再来逐步分析原因
方法一中,我们以字符串aaabbbccc为例,开始i不动,指向第一个变量a,只有变量j一直在遍历字符串,直到遍历到第一个b时,由于str[i] !== str[j],此时i = j - 1,发生了跳步,i直接指向最后一个a,如果字符连续的长度足够长,那么相当于,变量i始终没有参与到复杂度为O(n)的遍历中,因此,总的时间复杂度为O(n),虽然表面上是一个嵌套结构
方法二中,我们仍以字符串aaabbbccc为例,在双指针的结构中,时间复杂度就相当明显了,拢共就只有一个for循环遍历,初始,指针j定位在第一个a处,指针i一直向前移动,直到双方指向的变量不一致或者指针i指向字符串最后一位字符
当双方指向的变量不一致时,i直接与j指针相等,跳过遍历,因此在双指针中,相当于只有指针i参与了复杂度为O(n)的遍历,因此,总的时间复杂度为O(n)
我们依旧以实际性能测试的结果说话
测试环节
性能测试
// let str = ''
// for (let i = 0; i < 100 * 10000; i++) {
// str += i.toString()
// }
// console.time('findContinuousChar1')
// findContinuousChar1(str)
// console.timeEnd('findContinuousChar1') // 219ms
// console.time('findContinuousChar2')
// findContinuousChar2(str)
// console.timeEnd('findContinuousChar2') // 228ms
可见,当数据量足够大时,方法一、二的性能接近
单元测试
import { findContinuousChar1, findContinuousChar2 } from './continuous-char'
describe('连续字符和长度', () => {
it('正常情况', () => {
const str = 'aabbcccddeeee11223'
const res = findContinuousChar1(str)
expect(res).toEqual({ char: 'e', length: 4 })
})
it('空字符串', () => {
const res = findContinuousChar1('')
expect(res).toEqual({ char: '', length: 0 })
})
it('无连续字符', () => {
const str = 'abc'
const res = findContinuousChar1(str)
expect(res).toEqual({ char: 'a', length: 1 })
})
it('全部都是连续字符', () => {
const str = 'aaa'
const res = findContinuousChar1(str)
expect(res).toEqual({ char: 'a', length: 3 })
})
})
测试所需要的环境变量:
{
"name": "interview-js-code",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest --detectOpenHandles",
"dev": "cross-env NODE_ENV=development webpack serve --config build/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js",
"build:analyzer": "cross-env NODE_ENV=production_analyzer webpack --config build/webpack.prod.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.13.14",
"@babel/preset-env": "^7.13.12",
"@types/jest": "^27.0.2",
"autoprefixer": "^10.2.5",
"babel-jest": "^27.3.0",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.3",
"css-loader": "^5.2.0",
"html-webpack-plugin": "^5.3.1",
"jest": "^27.3.0",
"less": "^4.1.1",
"less-loader": "^8.0.0",
"postcss-loader": "^5.2.0",
"style-loader": "^2.0.0",
"ts-loader": "^8.1.0",
"typescript": "^4.2.3",
"url-loader": "^4.1.1",
"webpack": "^5.30.0",
"webpack-bundle-analyzer": "^4.4.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2",
"webpack-merge": "^5.7.3",
"ts-jest": "^27.0.7"
}
}