【学习笔记】算法101--字符串篇(一)

前言:
    之前一直都说想好好学习一下算法,但是,迟迟都没有行动,这次,立个flag,2021年12月底之前,学习完《算法101》。每天学习一道题,每天进步一点点,加油!
    希望看完这些算法题以后,我可以收获到这些:

  • 更好的逻辑思维能力;
  • 对数据结构更深的理解;
  • 能够写出更加牛逼的代码;
  • 一份更好的工作。

    下面,让我们进入字符串篇幅吧。

一、翻转整数、有效的字母异位词和翻转整数

    1.1  翻转整数
    **题目:**给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
    示例:

示例 1:
输入: 123
输出: 321

示例 2:
输入: -123
输出: -321

示例 3:
输入: 120
输出: 21

注意:
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 。请根据这个假设,如果反转后整数溢出那么就返回 0。
方法一:翻转字符串方法
思路:
如果将数字看成是有符号的字符串,那么,我们就能通过使用JS提供的字符串方法,来实现非符号部分的翻转,又因为整数的翻转并不影响符号,所以我们最后补充符号,完成算法。
详解:

  1. 首先设置边界极值;
  2. 使用字符串的翻转函数进行主逻辑;
  3. 补充符号;
  4. 然后拼接最终结果。
    代码:
/**
 1. @param {number} x
 2. @return {number}
 */
const reverse = (x) => {
  // 非空判断
  if (typeof x !== 'number') {
    return;
  }
  // 极值
  const MAX = 2147483647;
  const MIN = -2147483648;

  // 识别数字剩余部分并翻转
  const rest =
    x > 0
      ? String(x)
        .split('')
        .reverse()
        .join('')
      : String(x)
        .slice(1)
        .split('')
        .reverse()
        .join('');

  // 转换为正常值,区分正负数
  const result = x > 0 ? parseInt(rest, 10) : 0 - parseInt(rest, 10);

  // 边界情况
  if (result >= MIN && result <= MAX) {
    return result;
  }
  return 0;
};

方法二:类似 欧几里得算法 求解
思路:
我们借鉴欧几里得求最大公约数的方法来解题。符号的处理逻辑同方法一,这里我们通过模10取到最低位,然后又通过乘10将最低位迭代到最高位,完成翻转。
详解:

  1. 设置边界极值;
  2. 取给定数值的绝对值,遍历循环生成每一位数字,借鉴欧几里得算法,从num的最后一位开始取值拼成新的数;
  3. 同步剔除掉被消费的部分;
  4. 如果最终结果为异常值,则直接返回0;如果,原本数据为负数,则对最终结果取反;
  5. 返回最终结果。
    代码:
/**
 * @param {number} x
 * @return {number}
 */
const reverse = (x) => {
  // 获取相应数的绝对值
  let int = Math.abs(x);
  // 极值
  const MAX = 2147483647;
  const MIN = -2147483648;
  let num = 0;

  // 遍历循环生成每一位数字
  while (int !== 0) {
    // 借鉴欧几里得算法,从 num 的最后一位开始取值拼成新的数
    num = (int % 10) + (num * 10);
    // 剔除掉被消费的部分
    int = Math.floor(int / 10);
  }
  // 异常值
  if (num >= MAX || num <= MIN) {
    return 0;
  }
  if (x < 0) {
    return num * -1;
  }
  return num;
};

    1.2  有效的字母异位词
    **题目:**给定两个字符串s和t,编写一个函数来判断t是否是s的字母异位词。
    示例1:

输入: s = "anagram", t = "nagaram"
输出: true

    示例2:

输入: s = "rat", t = "car"
输出: false

方法一:利用数组sort()方法
思路:
首先,对字符串字母进行排序,然后,比较两字符串是否相等。
详解:

  1. 首先,将字符串转为数组;
  2. 利用数组sort方法进行排序;
  3. 然后,转为字符串进行比较,如果相等返回true,反之返回false。

代码:

const isAnagram = (s, t) => {
  const sArr = s.split('');
  const tArr = t.split('');
  const sortFn = (a, b) => {
    return a.charCodeAt() - b.charCodeAt();
  };
  sArr.sort(sortFn);
  tArr.sort(sortFn);
  return sArr.join('') === tArr.join('');
};

方法二:计数累加方法
思路:
声明一个对象记录字符串每个字母的个数,另外一个字符串每项与得到的对象做匹配,最后,根据计数判断是否相等。
详解:

  1. 首先,声明一个变量,遍历其中一个字符串s或t,对每个字母出现的次数进行累加;
  2. 然后,遍历另一个字符串,使每一个字母在一得到的对象中做匹配,如果匹配对象下的字母个数减1,如果匹配不到,则返回false,如果最后对象中每个字母个数都为0,则表示两字符串相等。

代码:

const isAnagram = (s, t) => {
  if (s.length !== t.length) {
    return false;
  }
  const hash = {};
  for (const k of s) {
    hash[k] = hash[k] || 0;
    hash[k] += 1;
  }
  for (const k of t) {
    if (!hash[k]) {
      return false;
    }
    hash[k] -= 1;
  }
  return true;
};

    1.3  字符串转换整数
    atoi是把字符串转换成整型数的一个函数,实现一个atoi函数,使其能将字符串转换成整数。
    示例1:

输入: "42
输出: 42

    示例2:

输入: "-42"
输出: -42

    示例3:

输入: "4193 with words"
输出: 4193

    示例4:

输入: "words and 987"
输出: 0

    示例5:

输入: "-91283472332"
输出: -2147483648

解释: 数字 “-91283472332” 超过 32 位有符号整数范围。 因此返回 INT_MIN (−2147483648) 。

方法一:正则匹配
思路:
第一步,使用正则提取满足条件的字符,/^(-|+)?\d+/g,(-|+)?表示第一位是-或+或都不是,\d+表示匹配多个数字;

const result = str.trim().match(/^(-|\+)?\d+/g);

第二步,判断目标是否超过Int整型的最大值或最小值;

 return result
    ? Math.max(Math.min(Number(result[0]), Math.pow(2,31)-1), -Math.pow(2,31))
    : 0;

代码:

/**
 * @param {string} str
 * @return {number}
 */
const myAtoi = function (str) {
  // 提取需要的字符
  const result = str.trim().match(/^(-|\+)?\d+/g);
  return result
    ? Math.max(Math.min(Number(result[0]), Math.pow(2, 31) - 1), -Math.pow(2, 31))
    : 0;
};

方法二:逐个判断
思路:
第一步,去除字符串之中的空格;

const news = str.trim();

第二步,通过执行parseInt判断是否为数字,不是数字返回0,是数组继续解析;

if(parseInt(news)){
  return retrunNum(parseInt(news));
} else {
  return 0;
}

第三步,判断目标是否超过Int整型的最大值或最小值;

const retrunNum = function (num) {
  if (num >= -Math.pow(2, 31) && num <= Math.pow(2, 31) - 1) {
    return num;
  } else {
    return num > 0 ? Math.pow(2, 31) - 1 : -Math.pow(2, 31);
  }
};

代码:

/**
 * @param {string} str
 * @return {number}
 */
const myAtoi = function (str) {
  const news = str.trim();
  if (parseInt(news)) {
    return retrunNum(parseInt(news));
  } else {
    return 0;
  }
};
const retrunNum = function (num) {
  if (num >= -Math.pow(2, 31) && num <= Math.pow(2, 31) - 1) {
    return num;
  } else {
    return num > 0 ? Math.pow(2, 31) - 1 : -Math.pow(2, 31);
  }
};

二、报数、反转字符串和字符串中的第一个唯一字符串

    2.1  报数
    **题目:**报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数。其前五项如下:

1.     1
2.     11
3.     21
4.     1211
5.     111221

    **题目:**给定一个正整数n(1≤n≥30),输出报数序列的第n项。
**注意:**整数顺序将表示为一个字符串。
示例:

输入: 1
输出: "1"
输入: 4
输出: "1211"

方法一:递归
想要获取第n项的结果,需要先获取到第n-1项的结果,然后报出第n-1项的结果做为第n项的结果。所以,可以采用递归调用法。

const countAndSay = function (n) {
  if (n === 1) {
    return '1';
  }
  const preResult = countAndSay(n - 1); // 获取第 n-1 项的结果。
  /**
    * \d 匹配一个数字
    * \1 匹配前面第一个括号内匹配到的内容
    * (\d)\1* 匹配相邻数字相同的内容
    * 使用replace方法将匹配到的内容处理为长度 + 内容的第一个字符
    * 结果为所求报数
    **/
  return preResult.replace(/(\d)\1*/g, item => `${item.length}${item[0]}`);
};

方法二:循环法
递归法是由n到1计算相应的值并层层返回的,循环法正好相反,循环法由1计算到n。然后,将最终值返回。

const countAndSay = function (n) {
  let result = '1'; // 第一个数为'1'
  for (let i = 1; i < n; i++) { // 循环获取知道第 n 项。
    // 同方法一
    result = result.replace(/(\d)\1*/g, item => `${item.length}${item[0]}`);
  }
  return result;
};

    2.2  反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组char[]的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用O(1)的额外空间解决这一问题。
你可以假设数组中的所有字符都是ASCII码表中的可打印字符。
示例1:

输入:["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例2:

输入:["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

方法一:首尾替换法
思路:
首尾替换法,逐位遍历,进行交换。
详解

  1. 设置变量i = 0 ;
  2. 替换字符串的第i位和倒数第i位,替换方式:使用es6的解构赋值进行变量的交换;
  3. 变量 i + 1,继续替换字符串的第i位和倒数第 i 位;
  4. 直到 i 大于字符串s 的长度的中位数,完全整个字符串的反转。
/**
 1. @param {character[]} s
 2. @return {void} Do not return anything, modify s in-place instead.
 */
const reverseString = function (s) {
  for (let i = 0; i < s.length / 2; i++) {
    [s[i], s[s.length - 1 - i]] = [s[s.length - 1 - i], s[i]];
  }
  return s;
};

方法二:中间变量首尾替换法
思路:
中间变量首尾替换法,逐位遍历,进行变换。
详解

  1. 设置变量 i = 0;
  2. 替换字符串的第i位和倒数第i位,替换方式:设置一个中间变量,替换两个字符串的值;
  3. 变量 i + 1,继续替换字符串的第i位和倒数第i位;
  4. 直到i大于字符串s的长度的中位数,完成整个字符串的反转。
/**
 * @param {character[]} s
 * @return {void} Do not return anything, modify s in-place instead.
 */
const reverseString = function (s) {
  for (let i = 0; i < s.length / 2; i++) {
    const a = s[i];
    s[i] = s[s.length - i - 1];
    s[s.length - i - 1] = a;
  }
};

    2.3  字符串中的第一个唯一字符
给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
示例:

s = "leetcode"
返回 0.

s = "loveleetcode",
返回 2.

注意事项:您可以假定该字符串只包含小写字母。
方法一:库函数
思路:
某个字符从头开始开始的索引和从尾开始找的索引如果相等,就说明这个字符只出现了一次。
详解:

  1. 从头到尾遍历一遍字段串;
  2. 判断每个位置的字符的 index() 和 lastIndexOf() 的结果是否相等;

代码:

/**
 * @param {string} s
 * @return {number}
 */
const firstUniqChar = function (s) {
  for (let i = 0; i < s.length; i += 1) {
    if (s.indexOf(s[i]) === s.lastIndexOf(s[i])) {
      return i;
    }
  }
  return -1;
};

方法二:哈希
思路:
遍历两次。第一次遍历,用一个哈希对象记录所有字符的出现次数;第二次遍历,找出哈希对象中只出现一次的字符的下标。
详解:

  1. 第一次遍历,用一个哈希对象记录所有字符的出现次数;
  2. 第二次遍历,找出哈希对象中只出现一次的字符的下标。

代码:

/**
 * @param {string} s
 * @return {number}
 */
const firstUniqChar = function (s) {
  const hash = {};
  for (let i = 0; i < s.length; i += 1) {
    if (!hash[s[i]]) {
      hash[s[i]] = 1;
    } else {
      hash[s[i]] += 1;
    }
  }
  for (let i = 0; i < s.length; i += 1) {
    if (hash[s[i]] === 1) {
      return i;
    }
  }
  return -1;
};

三、验证回文字符串、实现strStr()、最长公共前缀和最长回文子串

    2.1  验证回文串
    给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
    说明:本题中,我们将空字符串定义为有效的回文串。
示例1:

输入: "A man, a plan, a canal: Panama"
输出: true

示例2:

输入: "race a car"
输出: false

方法一:
思路
首先,去除字符串中的非字母和数字,再将字符串转换为数组,再对数组首尾一一比较,即可得出结果。
详解

  1. 将传入的字符串,利用 toLowerCase() 方法统一转化为小写,再利用正则表达式 /[ ^ A-Za-z0-9]/g 在字符串中去除非字母和数字,最后将字符串转换为数组;
  2. 转换数组后,利用循环一一比较元素,先比较第一个和最后一个,再比较第二个和倒数二个,依次类推,若中间有不相等则不是回文串,反之,则是回文串。

代码

/**
 1. @param {string}
 2. @return {boolean}
 */
const isPalindrome = (s) => {
  // 将传入的字符串,统一转化为小写,同时去除非字母和数字,在转换为数组
  const arr = s.toLowerCase().replace(/[^A-Za-z0-9]/g, '').split('');
  let i = 0;
  let j = arr.length - 1;
  // 循环比较元素
  while (i < j) {
    // 从首尾开始, 一一比较元素是否相等
    if (arr[i] === arr[j]) {
      // 若相等,即第二个元素和倒数第二个元素继续比较,依次类推
      i += 1;
      j -= 1;
    } else {
      // 只要有一个相对位置上不相等,既不是回文串
      return false;
    }
  }
  // 是回文串
  return true;
};

方法二:
思路
首先,去除字符串中的非字母和数字,然后,利用数组将字符串翻转,再和原字符串进行比较,即可得到结果。
详解

  1. 将传入的字符串,利用toLowerCase()方法统一转化为小写,再利用正则表达式 /[ ^ A-Za-z0-9]/g在字符串中去除非字母和数字,得到字符串arr ;
  2. 将字符串arr转换为数组,利用数组的方法反转数组,再将数组转为字符串newArr ;
  3. 将字符串arr和字符串newArr进行比较,相等即为回文串,不相等则不为回文串。
    代码
/**
 * @param {string} s
 * @return {boolean}
 */
const isPalindrome = (s) => {
  // 方便比较,统一转化为小写,并去除非字母和数字
  const arr = s.toLowerCase().replace(/[^A-Za-z0-9]/g, '');
  // 将新字符串转换为数组,利用数组的方法获得反转的字符串
  const newArr = arr.split('').reverse().join('');
  // 将2个字符进行比较得出结果
  return arr === newArr;
};

    2.2  实现strStr()
    给定一个haystack字符串和一个needle字符串,在haystack字符串中找出needle字符串出现的第一个位置(从0开始)。如果不存在,则返回-1。
注:以下称 haystack 字符串为匹配字符串,needle 字符串为查找字符串。
示例1:

给定 haystack = 'hello world', needle = 'll'

返回2

说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。

方法一:遍历截取字符串对比
思路
截取字符串对比的思路很简单,从匹配字符串haystack中截取出与需查找字符串needle长度相等的内容后,对比截取的内容与匹配字符串相等,如果相等返回开始截取的下标。
详解
首先处理几个特殊的场景:

  1. needle的长度为0,直接返回0;
  2. needle的字符串长度大雨haystack,肯定不匹配;
  3. needle的字符串长度等于haystack,判断是否相等,相等则匹配,否则不匹配。
    剩下的就是needle字符串长度小于haystack的情况,遍历haystack。
    注:此处需要注意的是,当 haystack 剩余字符串长度小于 needle 长度时,肯定是不相等,无需再次比较。
    在遍历中判断,将要截取的字符串的首位与needle字符串的首位是否相同,如果不相同也就不需要后续截取、比较,跳过该次循环。
    代码
const strStr = function (haystack, needle) {
  const hayLen = haystack.length;
  const nedLen = needle.length;

  if (!needle) {
    return 0;
  } if (nedLen > hayLen) {
    return -1;
  } else if (nedLen === hayLen) {
    return haystack === needle ? 0 : -1;
  } else {
    for (let index = 0; index <= hayLen - nedLen; index++) {
      if (haystack[index] !== needle[0]) {
        continue;
      }
      if (haystack.substring(index, index + nedLen) === needle) {
        return index;
      }
    }
  }
  return -1;
};

方法二:双层循环对比字符
思路
循环对比字符串思路也很简单,从匹配字符串haystack的不同位置开始遍历,判断其中是否含有查找字符串needle。
如:haystack 为 hello, needle 为 ll,依次判断 he、el、ll、lo是否完全和 ll 相等,相等即返回对应字符串在 haystack 中的下标。
详解
注:首先处理特殊边际情况,这块与第一种方法相同,就不再赘述。
以下为算法步骤:

  1. 设置最外层循环,遍历次数为0 - haystack长度减去needle的长度。剩余字符串长度小于needle长度时,肯定不匹配
  2. 判断匹配字符串haystack中,该次循环使用到的字符串首尾字母是否与查找字符串needle首尾字母相同;
    ①不相等,直接跳过继续遍历;
    ②相等,执行第三步。
  3. 判断查找字符串needle的长度;
    ①长度为1,表明匹配成功,直接返回当前长字符串下标即可;
    ②长度大于1,执行第四步。
  4. 遍历对比字符串,循环判断匹配字符串haystack不同位置的字符是否与匹配字符串needle对应位置的字符相等;
    ①不相等时,跳出循环,进行下次循环;
    ②到最后一位还未跳出循环表明完全匹配,返回当前遍历次数(即查找字符串在匹配字符串中,首次出现的位置)
    代码
const strStr = function (haystack, needle) {
  const hayLen = haystack.length;
  const nedLen = needle.length;

  if (!needle) {
    return 0;
  } if (nedLen > hayLen) {
    return -1;
  } else if (nedLen === hayLen) {
    return haystack === needle ? 0 : -1;
  } else {
    for (let hasIndex = 0; hasIndex <= hayLen - nedLen; hasIndex++) {
      if (
        haystack[hasIndex] === needle[0] &&
          haystack[hasIndex + nedLen - 1] === needle[nedLen - 1]
      ) {
        if (nedLen === 1) {
          return hasIndex;
        }
        for (let nedIndex = 1; nedIndex < nedLen; nedIndex++) {
          if (haystack[hasIndex + nedIndex] !== needle[nedIndex]) {
            break;
          }
          if (nedIndex === nedLen - 1) {
            return hasIndex;
          }
        }
      }
    }
  }
  return -1;
};

    2.3  最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例

输入: ["flower","flow","flight"]
输出: "fl"

方法一:递归迭代
思路
查找n个字符串的最长公共前缀,可以拆分成两步:1.查找前n - 1个字符串的最长公共前缀;2.查找m与最后一个字符串的公共前缀。
因此,我们可以得出递归公式:

$longestCommonPrefix([S1, S2, ..., Sn]) = findCommPrefix(longestCommonPrefix([S1, S2, ..., Sn-1]), Sn)$

我们只需要实现findCommPrefix方法,然后,遍历数组即可。
详解

  1. 获取数组中第一个字符,当做最长公共前缀保存到变量commonPrefix;
  2. 从数组中取出下一个字符串,与当前的最长公共前缀commonPrefix对比,得到新的最长公共前缀存到commonPrefix;
  3. 重复第2步遍历完整个字符串,最后得到的即使数组中所有字符串的最长公共前缀。

代码

/**
 1. @param {string[]} strs
 2. @return {string}
 */
const longestCommonPrefix = function (strs) {
  function findCommonPrefix (a, b) {
    let i = 0;
    while (i < a.length && i < b.length && a.charAt(i) === b.charAt(i)) {
      i++;
    }
    return i > 0 ? a.substring(0, i) : '';
  }
  if (strs.length > 0) {
    let commonPrefix = strs[0];
    for (let i = 1; i < strs.length; i++) {
      commonPrefix = findCommonPrefix(commonPrefix, strs[i]);
    }
    return commonPrefix;
  }
  return '';
};

方法二:循环迭代
思路
最长公共前缀一定是数组中所有数组都包含的前缀子串,我们可以将任意字符串的前缀作为公共前缀,从长度0到n(n为该字符串长度),横向扫描数组中的所有字符串,看是否都有该前缀,直到找到不满足的为止。
详解

  1. 先假设最长公共子串的长度为1,存到变量 i 。以第一个字符串为基准,取它的第 i 个字符与数组中其他所有的字符串第 i 个字符进行比较,如果都相等,那么将最长公共子串的长度加1,否则停止查找,已找到最长公共前缀的长度,设置完成匹配标记flag为false;
  2. 重复第1步,直到 i 等于第一个字符串的长度,或者匹配标记flag为false;
  3. 返回第一个字符串的前 i 个字符,即为当前数组的最长公共前缀。

代码

/**
 1. @param {string[]} strs
 2. @return {string}
 */
const longestCommonPrefix = function (strs) {
  if (strs.length === 0) {
    return '';
  }
  let i = 0;
  let flag = true;
  while (flag) {
    if (strs[0].length > i) {
      const char = strs[0].charAt(i);
      for (let j = 1; j < strs.length; j++) {
        if (strs[j].length <= i || strs[j].charAt(i) !== char) {
          flag = false;
          break;
        }
      }
    } else {
      flag = false;
    }
    i++;
  }
  return strs[0].substring(0, i - 1);
};

    2.4  最长回文子串
给定一个字符串s,找到s中最长的回文字符串。可以假设s的最大长度为1000 。
示例

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

方法一:动态规划法
思路
动态规划的思想,是希望把问题划分成相关联的子问题;然后从最基本的子问题出发来推导较大的子问题,直到所有的子问题都解决。
根据字符串的长度,建立一个矩阵 dp, 通过不同情况的判断条件,通过 dp[i][j] 表示 s[i] 至 s[j] 所代表的子串是否是回文子串。
详解
3. 建立矩阵dp;
4. 循环遍历字符串,根据不同的条件进行判断是否为回文子串;
5. 不同长度的子串,根据不同的条件进行判断是否回文子串;
(1)长度为1,一定回文;
(2)长度为2或3,判断首尾是否相同:s[i] === s[j];
(3)长度>3,首尾字符相同,且去掉首尾之后的子串仍然为回文:(s[i]===s[j] && dp[i+1][j+1]);

  1. 取得长度最长的回文子串。

代码

/**
 7. @param {string} s
 8. @return {string}
 */
const longestPalindrome = function (s) {
  const dp = [];
  for (let i = 0; i < s.length; i += 1) {
    dp[i] = [];
  }
  let max = -1; let str = '';
  for (let l = 0; l < s.length; l += 1) {
    // l为所遍历的子串长度 - 1,即左下标到右下标的长度
    for (let i = 0; i + l < s.length; i += 1) {
      const j = i + l;
      // i为子串开始的左下标,j为子串开始的右下标
      if (l === 0) {
        // 当子串长度为1时,必定是回文子串
        dp[i][j] = true;
      } else if (l <= 2) {
        // 长度为2或3时,首尾字符相同则是回文子串
        if (s[i] === s[j]) {
          dp[i][j] = true;
        } else {
          dp[i][j] = false;
        }
      } else {
        // 长度大于3时,若首尾字符相同且去掉首尾之后的子串仍为回文,则为回文子串
        if ((s[i] === s[j]) && dp[i + 1][j - 1]) {
          dp[i][j] = true;
        } else {
          dp[i][j] = false;
        }
      }
      if (dp[i][j] && l > max) {
        max = l;
        str = s.substring(i, j + 1);
      }
    }
  }
  return str;
};

方法一:中心扩展
思路
回文子串一定是对称的,所以我们可以每次选择一个中心,然后从中心向两边扩展判断左右字符是否相等。
中心点的选取有两种情况:
当长度为奇数时,以单个字符为中心;
当长度为偶数时,以两个字符之间的空隙为中心。
详解

  1. 循环遍历字符串取得不同长度的子串;
  2. 通过定义好的中心扩展方法,选取奇数对称和偶数对称的中心;
  3. 通过比较选择出两种组合较大的回文子串长度,然后,对比之前的长度,判断是否更新起止位置;
  4. 全部遍历完成后,根据最后的起止位置的值,截取最长回文子串。
const longestPalindrome = function (s) {
	if (s === null || s.length < 1){
		return '';
	}
	let start = 0;let end = 0;
	//从中心向两边扩展
	const expandFromCenter = (s,left,right) {
		while (left >= 0 && right < s.length && s[left] === s[right]) {
			left -= 1;
			right += 1;
		}
		return right - left - 1;
	};
	for (let i = 0;i < s.length;i += 1) {
		//中心的两种选取(奇对称和偶对称)
		const len1 = expandFromCenter(s,i,j);
		const len2 = expandFromCenter(s,i,i+1);
		//两种组合取最大的回文子串长度
		const len = Math.max(len1,len2);
		//如果此位置为中心的回文数长度大于之前的长度,则进行处理
		if(len > end - start){
			start = i - Math.floor((len - 1)/2);
			end = i + Math.floor(len/2);
		}
	}
	return s.substring(start,end + 1);
}

参考博客: 【面试助力,算法101】https://101.zoo.team/zi-fu-chuan/zi-fu-chuan-part-3-yan-zheng-hui-wen-zi-fu-chuan-shi-xian-strstr-zui-chang-gong-gong-qian-zhui-he-zu

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值