28.实现strStr()
28. 实现 strStr() - 力扣(LeetCode) (leetcode-cn.com)
字符串匹配是面试常见的问题
暴力解法O(m*n)
注意字符串的截取方法的差别
substr(start,number)
slice(start,end)
substring(start,end)
都接受两个参数
对于substr()来说,第一个参数表示开始的位置,第二个参数表示返回的子字符串串数量。
对于slice()和substrig(),第一个表示开始的位置,第二个表示截取到的位置
当参数为负数时,三个方法的行为不同:
slice()将所有负值参数当做字符串长度加上负参数值。
substr()把第一个参数当做字符串值加上该值,第二个负参数值转为0
substring()会将所有负参数值转为0,同时该方法会将较小的参数作为起点。
// 暴力
// 时间复杂度:O(m*n)
// 空间复杂度:O(1)
var strStr = function(haystack, needle) {
// 暴力解法
let m = haystack.length
let n = needle.length
// 如果是空字符串
if(n==0) return 0
if(m < n) return -1
// 遍历求解
for(let i = 0; i < m; i++) {
// 找到和needle第一个字符相等的位置,然后截取判断
if(haystack.charCodeAt(i) === needle.charCodeAt(0) && haystack.substr(i, n) === needle) {
return i
}
}
return -1
};
// KMP
// 时间复杂度:O(m + n)
// 空间复杂度:O(n)
var strStr = function(haystack, needle) {
const m = haystack.length, n = needle.length
if (n == 0) return 0
if (m < n) return -1
const nexts = getNexts(needle)
let j = 0
for (let i = 0; i < m; i++) {
while (j > 0 && haystack[i] != needle[j]) {
j = nexts[j - 1] + 1
}
if (haystack[i] == needle[j]) j++
if (j == n) return i - n + 1
}
return -1
};
var getNexts = function(needle) {
const n = needle.length
if (n == 1) return []
const nexts = new Array(n - 1).fill(0)
nexts[0] = -1
for (let j = 1; j < n - 1; j++) {
let pre = nexts[j - 1]
while (pre != -1 && needle[pre + 1] != needle[j]) {
pre = nexts[pre]
}
if (needle[pre + 1] == needle[j]) pre++
nexts[j] = pre
}
return nexts
}
459. 重复的子字符串
459. 重复的子字符串 - 力扣(LeetCode) (leetcode-cn.com)
方案1:双指针
假设子串的长度为1,比较相邻的两个,依次比较,直到遇到不相等,说明子串长度为1不能满足条件,子串的长度+1,假设子串的长度为2,再次进行比较。
/**
* @param {string} s
* @return {boolean}
*/
var repeatedSubstringPattern = function(s) {
// 重复的子字符串的长度递增
let n = s.length
// 尝试的len
for(let len = 1; len * 2 <= n; len++) {
if( n % len === 0) {
let matched = true
// i指针从0开始
let i = 0
// j 从len开始
for(let j = len; j < n; j++,i++) {
if(s.charAt(i) !== s.charAt(j)) {
// 不匹配
matched = false
break
}
}
// 没有不匹配的情况
if(matched) return true
}
}
return false
};
方案二:旋转数组
旋转数组之后的结果和原来的相同,说明是由重复的子字符串构成。
方案三:旋转优化
由上面的可知[a,c,d]旋转之后的结果可以是[d,a,c][c,d,a][a,c,d]
由重复字符串组成的字符串,旋转重复子字符串长度后得到本身
var repeatedSubstringPattern = function(s) {
return s.repeat(2).slice(1, -1).includes(s);
};
344. 反转字符串
var reverseString = function(s) {
let left = 0; right = s.length -1;
while(left < right) {
let temp = s[left]
s[left] = s[right]
s[right] = temp;
left++;
right--;
}
};
扩展:反转字符串的某一部分
345. 反转字符串中的元音字母
/**
* @param {string} s
* @return {string}
*/
var reverseVowels = function(s) {
let left = 0;
let right = s.length - 1;
let arr = s.split('')
while(left < right) {
while (left < right && !isVowel(arr[left])) left++
while (left < right && !isVowel(arr[right])) right--
[arr[left], arr[right]] = [arr[right], arr[left]]
left++
right--
}
return arr.join('')
};
// 是否是元音字母
var isVowel = function(c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'
|| c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U'
}
1119. 删除字符串中的元音
var removeVowels = function(s) {
return s.split("").filter(c => !isVowel(c)).join("")
};
var isVowel = function(c) {
return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'
}
541. 反转字符串2
541. 反转字符串 II - 力扣(LeetCode) (leetcode-cn.com)
var reverseStr = function(s, k) {
const arr = s.split("")
for (let start = 0; start < arr.length; start += 2 * k) {
let left = start;
let right = Math.min(left + k - 1, arr.length - 1);
while (left < right) {
[arr[left], arr[right]] = [arr[right], arr[left]]
left++
right--
}
}
return arr.join("")
};
557. 反转字符串中的单词3
557. 反转字符串中的单词 III - 力扣(LeetCode) (leetcode-cn.com)
var reverseWords = function(s) {
const n = s.length
const arr = s.split("")
let left = 0
while (left < n) {
if (arr[left] != ' ') {
let right = left
while (right + 1 < n && arr[right + 1] != ' ') right++
reverseWord(arr, left, right)
left = right + 1
} else {
left++
}
}
return arr.join("")
};
var reverseWord = function(arr, left, right) {
while (left < right) {
[arr[left], arr[right]] = [arr[right], arr[left]]
left++
right--
}
}
// 函数式编程
var reverseWords2 = function(s) {
return s.split(" ").map(word => word.split("").reverse().join("")).join(" ")
}
58. 最后一个单词的长度
58. 最后一个单词的长度 - 力扣(LeetCode) (leetcode-cn.com)
解法1: 从左到右循环,找到最后一个单词。记录最后一个单词的长度
解法2: 从右到左
var lengthOfLastWord = function(s) {
let len = s.length
let end = len -1
// 找到最后一个单词的结尾
while(end >= 0 && s[end] === ' ') end--;
if(end < 0) return 0;
let start = end;
while(start >= 0 && s[start] !== ' ') start--;
return end - start
};
8. 字符串转为整数
8. 字符串转换整数 (atoi) - 力扣(LeetCode) (leetcode-cn.com)
var myAtoi = function(s) {
// 1. 去掉空格
let str = s.trim();
if (parseInt(str)) {
// 做边界处理
return handler(parseInt(str));
} else {
return 0;
}
};
const handler = function (num){
if( num >= -Math.pow(2,31) && num <= Math.pow(2,31) -1 ) return num;
else return num < 0 ? -Math.pow(2,31) : Math.pow(2,31) -1 ;
}
165. 比较版本号
165. 比较版本号 - 力扣(LeetCode) (leetcode-cn.com)
只要有一个没有处理完,就应该继续处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eytR0npM-1627957156092)(Day04字符串.assets/image-20210802205215703.png)]
/**
* @param {string} version1
* @param {string} version2
* @return {number}
*/
var compareVersion = function(version1, version2) {
let i1 = 0, i2 = 0
let n1 = version1.length, n2 = version2.length
while (i1 < n1 || i2 < n2) {
let v1 = 0, v2 = 0
while (i1 < n1 && version1[i1] != '.'){
v1 = v1 * 10 + (version1[i1] - '0')
i1++
}
while (i2 < n2 && version2[i2] != '.') {
v2 = v2 * 10 + (version2[i2] - '0')
i2++
}
if (v1 != v2) {
return v1 > v2 ? 1 : -1
}
i1++
i2++
}
return 0
};
12. 整数转罗马数字
12. 整数转罗马数字 - 力扣(LeetCode) (leetcode-cn.com)
799 = 500 + 100 + 100 + 90 + 9
找拆解后最小的
比799小的是500,减掉500后是299
比299小的是100,减掉之后是199
同上
尽量选大的,有贪心的思想
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MPmvEj7R-1627957156095)(Day04字符串.assets/image-20210802213448672.png)]
var intToRoman = function(num) {
const nums = [1000,900,500,400,100,90,50,40,10,9,5,4,1]
const romans = ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"]
let res = ""
for (let index = 0; index < 13; index++) {
while (num >= nums[index]) {
res += romans[index]
num -= nums[index]
}
}
return res
};
13. 罗马数字转整数
13. 罗马数字转整数 - 力扣(LeetCode) (leetcode-cn.com)
通常情况下,罗马数字中小的数字再大的数字的右边。
所以我们定义两个指针,prev和curr,如果出现了prev小于curr的情况。则需要curr-prev。
/**
* @param {string} s
* @return {number}
*/
var romanToInt = function(s) {
let sum = 0;
let prev = getValue(s[0])
for(let i = 1; i <s.length;i++){
const num = getValue(s[i])
if(prev < num) {
sum -= prev
} else {
sum += prev
}
prev = num
}
sum += prev
return sum
};
var getValue = function(ch) {
switch(ch) {
case 'I': return 1
case 'V': return 5
case 'X': return 10
case 'L': return 50
case 'C': return 100
case 'D': return 500
case 'M': return 1000
default: return 0;
}
}
38. 外观数列
38. 外观数列 - 力扣(LeetCode) (leetcode-cn.com)
1
11
21
1211
111221
312211
var countAndSay = function(n) {
// 第一行
let curr = "1"
for (let i = 1; i < n; i++) {
const prev = curr
curr = ""
// 上一行的第一个字符,出现的数量为1
let say = prev[0], count = 1
for (let j = 1; j < prev.length; j++) {
// 遍历上一行,出现和第一个重复的字符,数量+1
if (prev[j] == say) {
count++
} else {
// 出现不同字符的时候,把用于描述的数量和字符填进字符串
curr += count
curr += say
// 重新计算下一个出现的字符和数量
say = prev[j]
count = 1
}
}
// 加入最后一个字符及其数量
curr += count
curr += say
}
return curr
};
6. Z字形变换
6. Z 字形变换 - 力扣(LeetCode) (leetcode-cn.com)
var convert = function(s, numRows) {
if (numRows == 1) return s
const n = s.length
// 字符串数组
const ret = new Array(Math.min(n, numRows)).fill("")
// goingDown代表是否要转换方向
let currRow = 0, goingDown = false;
for (let i = 0; i < n; i++) {
ret[currRow] += s[i]
// 转换方向
if (currRow == 0 || currRow == numRows - 1) goingDown = !goingDown
if (goingDown) currRow++
else currRow--
}
// 字符串拼接
for (let i = 1; i < ret.length; i++) {
ret[0] += ret[i]
}
return ret[0]
};