最长回文子串
最长回文子串LeetCode5
题目:给你一个字符串 s,找到 s 中最长的回文子串。
输入:s = “babad” 输出:”bab” 解释:”aba” 同样是符合题意的答案。
输入:s = “cbbd” 输出:”bb”
方法一:使用暴力遍历,枚举每一个组合字符串,判断该是否是回文,时间复杂度较高O(n3),空间复杂度O(1)
// 判断字符串是否是回文const isReverse = function (str, left, right) {let start = left;let end = right;while (start < end) {if (str[start] === str[end]) {start++;end--;} else {return false;}}return true;}var longestPalindrome = function(s) {// 最长回文字符串开始索引let start = 0;// 最长回文字符串长度let len = 1;for (let i = 0; i < s.length; i++) {for (let j = i + 1; j < s.length; j++) {if (isReverse(s, i, j) && j - i + 1 > len) {start = i;len = j - i + 1;}}}return s.substr(start, len);};
方法二:使用中心扩散法,以一个元素为中心向两边扩散,判断扩散后的字符串是否为回文,如果是,继续扩散,如果不是,返回当前回文长度
每一个元素扩散时会有两种场景,奇数回文和偶数回文,奇数回文就是回文字符串是奇数,中心元素两边的字符串是对称的,偶数回文就回文字符串是偶数,两两对称,比较这两种场景下回文字符串的长度,即可获得以当前元素为中心的最长回文子串
// 获取回文子串的长度const getMaxReverseStr = function (str, left, right) {let start = left;let end = right;while (start >= 0 && end < str.length && str[start] === str[end]) {start--end++;}// end和start不相等时需要各自还原end - 1 - (start + 1) + 1return end - start - 1;}var longestPalindrome = function(s) {let start = 0;let len = 1;for (let i = 0; i < s.length; i++) {const evenReverseLen = getMaxReverseStr(s, i, i + 1);const oddReverseLen = getMaxReverseStr(s, i, i);const curLen = Math.max(evenReverseLen, oddReverseLen);if (len < curLen) {len = curLen;// 将长度转换成索引需要减一start = i - Math.floor((curLen - 1) / 2);}}return s.substr(start, len);};
方法三:动态规划
按照数学角度,i,j分别表示第i个和第j个字符,如果s[i] === s[j] 且字符串[i + 1][j-1]是回文串,那么字符串[i][j]就一定是回文串,将字符串[i + 1][j-1]是否是回文串的结果存储起来,即可知道[i][j]是否为回文,
定义result[i][j]表示字符串从i到j是否是回文子串,且每一个单独的字符都是回文串,所以result[i][i]=1
当s[i] === s[j]时,有两种场景
- 当
i + 1< j - 1,即j - i > 2,result[i][j]是否是回文依赖result[i + 1][j-1] - 当两个元素相邻且相等,或两个元素隔着一个元素且相等,即
j = i + 1 || i + 1 === j - 1时,即j - i <= 2,则result[i][j]=1
注意:外层需要从j开始循环,如果外层从i开始循环,对result[i+1][j-1]取值时,还不知道result[i+1][j-1]是否是回文串
var longestPalindrome = function (s) {const length = s.length;let start = 0;let len= 1;const result = (new Array(length)).fill().map((_, i) => (new Array(length)).fill(0).map((_, j) => i === j ? 1 : 0));for (let j = 1; j < length; j++) {for (let i = 0; i < j; i++){if (s[i] === s[j]) {if (j - i <= 2) {result[i][j] = 1;} else {result[i][j] = result[i + 1][j - 1];}if (result[i][j] && len < j - i + 1) {start = i;len = j - i + 1;}}}}return s.substr(start, len);};
空间复杂度为O(n2),观察代码发现,每次拿存储结果的时候只需要第i+1行、第j-1列的结果,对于j-2列、j-3列的结果其实不需要存储,每次使用一个数组,进行覆盖即可,优化后空间复杂度降低,但是代码可读性变差,需要耗费时间去理解这么写的意义
var longestPalindrome = function (s) {const length = s.length;let start = 0;let len= 1;const result = new Array(length).fill(0);for (let j = 1; j < length; j++) {for (let i = 0; i < j; i++){if (s[i] === s[j]) {if (j - i <= 2) {result[i] = 1;} else {result[i] = result[i + 1];}if (result[i] && len < j - i + 1) {start = i;len = j - i + 1;}} else {// 当前列不相等时进行清零result[i] = 0;}}}return s.substr(start, len);};
无重复字符的最长子串
无重复字符的最长子串LeetCode3
题目:给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
输入: s = “bbbbb” 输出: 1
输入: s = “pwwkew” 输出: 3 解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,”pwke” 是一个子序列,不是子串。
方法一:使用暴力解法,遍历每个子串,判断下一个字符在子串中是否重复
const isContain = function (s, left, right, target) {let start = left;while (start <= right) {if (s[start] === target) {return true;}start++;}return false;}var lengthOfLongestSubstring = function(s) {const length = s.length;if (length === 0) return 0;let len = 1;for (let i = 0; i < length; i++) {for (let j = i + 1; j < length; j++) {if (isContain(s, i, j - 1, s[j])) {break;}if (len < j - i + 1) {len = j - i + 1;}}}return len;};
方法二:可以使用Map结构存储已经遍历的字符串的索引,判断当前字符是否已遍历过,如果已重复,索引为index,将不重复子串开始的索引重置为index + 1,继续遍历
var lengthOfLongestSubstring = function(s) {const length = s.length;if (length === 0) return 0;let len = 1;let start = 0;let end = 0;const map = new Map();while (end < length) {const index = map.get(s[end]);if (index >= start) {start = index + 1;}len = len < end - start + 1 ? end - start + 1 : len;map.set(s[end], end);end++;}return len;};
