力扣每日一题0402-420. 强密码检验器

212 阅读3分钟

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

如果一个密码满足下述所有条件,则认为这个密码是强密码:

  • 由至少 6 个,至多 20 个字符组成。
  • 至少包含 一个小写 字母,一个大写 字母,和 一个数字 。
  • 同一字符 不能 连续出现三次 (比如 "...aaa..." 是不允许的, 但是 "...aa...a..." 如果满足其他条件也可以算是强密码)。

给你一个字符串 password ,返回 将 password 修改到满足强密码条件需要的最少修改步数。如果 password 已经是强密码,则返回 0 。

在一步修改操作中,你可以:

  • 插入一个字符到 password ,
  • 从 password 中删除一个字符,或
  • 用另一个字符来替换 password 中的某个字符。

示例 1:

输入:password = "a"
输出:5

示例 2:

输入:password = "aA1"
输出:3

示例 3:

输入:password = "1337C0d3"
输出:0

提示:

  • 1 <= password.length <= 50
  • password 由字母、数字、点 '.' 或者感叹号 '!'

超过20一定要删除,怎么删 ? 以下过程用表格图示

  • 333a3333a33333aAaAa333333 长度25,需要删除5次,优先删除连续3+次字符

    • 只 替换 需要 333->1 + 3333->1 + 33333->1 + 333333 -> 2 次

    • 第一轮删除:

      • 长度是3倍数的连续字符(333和333333)各删除1次
      • 替换 需要 33->0 + 3333->1 + 33333->1 + 33333 -> 1 次
      • 本轮,共删除 2 次,替换少2次,两者置换比1:1
    • 第二轮删除:还需要删除 5 - 2 = 3 次

      • 长度除3余数为1连续字符(3333)各删除2次(2字符)
      • 替换 需要 33->0 + 33->0 + 33333->1 + 33333 -> 1 次
      • 本轮,共删除 2 次,替换少1次,两者置换比2:1
    • 第三轮删除:还需要删除 5 - 2 - 2 = 1 次

      • 剩余都是长度除3余数为2的连续字符(33333)各删除3次 (3字符)
      • 替换 需要 33->0 + 33->0 + 33->0 + 33 -> 0 次
      • 本轮,共删除 6次,替换 少 2次,两者置换比3:1
    • 删多了!回到第三轮删除:还需要删除1次

      • 删除6次删多了,只能删1次,把其中一个33333->3333
      • 替换 需要 33->0 + 33->0 + 3333->1 + 33333 -> 1 次
      • 本轮需要删除3:1置换替换,删不够,替换次数不变
  • 三轮删除后为33a33a3333aAaAa33333,已删除5次,再替换2次,共7次即解

/**
 * @param {string} password
 * @return {number}
 */
var strongPasswordChecker = function (password) {
  const n = password.length
  let needLowerCase = needUpperCase = needNumber = 1 // 需要小写字母,大写字母和数字
  for (let i = 0; i < n; i++) {
    const charCode = password.charCodeAt(i)
    if (charCode >= 97 && charCode <= 122) needLowerCase = 0 // 已有小写字母
    else if (charCode >= 65 && charCode <= 90) needUpperCase = 0 // 已有大写字母
    else if (charCode >= 48 && charCode <= 57) needNumber = 0 // 已有数字
  }
  const needLetter = needLowerCase + needUpperCase + needNumber // 缺少字符数
  let delOne = delTwo = replace = 0
  for (let i = 2; i < n; i++) { // 遍历找长度 >= 3 的连续字符串
    if (password[i - 2] === password[i - 1] && password[i - 1] === password[i]) {
      let len = 3
      while (i + 1 < n && password[i + 1] === password[i++]) len++ // 连续字符串长度
      replace += len / 3 | 0 // 每 3 个字符替换 1 个字符,即可中断 3 个字符的连续
      if (len % 3 === 0) delOne++ // 长度是 3 的倍数的字符串数(需删 1 字符)
      else if (len % 3 === 1) delTwo++ // 长度 / 3 余数 1 的连续字符串数(需删 2 字符)
    } // 长度是 3 倍数,删 1 字符 => 长度 / 3 余数 2 字符串
  }// 长度 / 3 余数 1, 删 2 字符 => 长度 / 3 余数 2 字符串,所以,剩下都是余数 2 字符串
  if (n < 6) return Math.max(6 - n, needLetter) // 不够 6,优先插入缺少字符
  if (n <= 20) return Math.max(replace, needLetter) // 长度合适,优先用缺少字符替换
  const needDel = n - 20 // 请参考上面的三轮删除的说明
  replace -= Math.min(needDel, delOne) // ↓ 如果还需要删除次数,不够删,按 2:1 置换
  if (needDel - delOne > 0) replace -= Math.min((needDel - delOne) / 2 | 0, delTwo)
  if (needDel - delOne - delTwo * 2 > 0) replace -= (needDel - delOne - delTwo * 2) / 3 | 0
  return needDel + Math.max(replace, needLetter)
};