Day8-D9-字符串-leetcode344,541,151,28,459
字符串
题目
- 反转字符串
-
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
-
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
-
思路:
-
双指针,一个指向头,一个指向尾,两两交换数值,然后同时向中间移动,遍历长度的1/2,如果偶数,直接一半,两两互换,如果奇数,正好剩中间一个不用交换,其它也是两两互换
// 双指针法反转字符串(原地交换)
// 用两个指针,分别从头和尾向中间移动。
// 每次交换头尾字符,直到两个指针相遇或交错。
var reverseString = function(s) {
let left = 0, right = s.length - 1;
while (left < right) {
// 交换头尾字符
[s[left], s[right]] = [s[right], s[left]];
left++;
right--;
}
};
// 用一个 for 循环,从头到中间(i < n/2)。
// 每次交换 s[i] 和 s[n-1-i]。
// 逻辑和双指针类似,但用的是下标对称。
var reverseString = function (s) {
const n = s.length
for (let i = 0; i< Math.floor(n/2);i++){
// const top = 0, tail = n;
let temp = s[i]
s[i] = s[n-1-i]
s[n-i-i] = temp
}
return s
}
var reverseString = function(s) {
return s.reverse()
};
- 反转字符串 II
-
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
-
如果剩余字符少于 k 个,则将剩余字符全部反转。
-
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
-
思路:
-
当需要固定规律一段一段去处理字符串的时候,可以考虑for循环的表达式一次性加多少。 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。
// 你的这个写法有下标越界和反转区间不准确的问题,不推荐这样写。
var reverseStr = function(s, k) {
const len = s.length;
let resArr = s.split("");
for(let i = 0; i < len; i += 2 * k) { // 每隔 2k 个字符的前 k 个字符进行反转
let l = i - 1, r = i + k > len ? len : i + k;
while(++l < --r) [resArr[l], resArr[r]] = [resArr[r], resArr[l]];
}
return resArr.join("");
};
// 推荐这个
var reverseStr = function(s, k) {
const len = s.length
// 先转为数组,方便原地修改
let arr = s.split('')
// 每隔 2k 个字符的前 k 个字符进行反转
for (let i = 0; i< len; i += 2 * k) {
// 反转区间
let left = i , right = Math.min(i + k - 1, len - 1)
while(left < right) {
[arr[left], arr[right]] = [arr[right], arr[left]]
left++
right--
}
}
return arr.join('')
}
var reverseStr = function(s, k) {
let res = '';
for (let i = 0; i < s.length; i += 2 * k) {
// 反转前k个字符
res += s.slice(i, i + k).split('').reverse().join('');
// 拼接后面k个字符(不反转)
res += s.slice(i + k, i + 2 * k);
}
return res;
};
- 卡码网题目,给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。
- 思路:
- 先预先给数组扩容带填充后的大小,然后在从后向前进行操作。从后向前的操作避免了每次添加元素都要将添加元素之后的所有元素都向后移动的情况。
// 直接遍历字符串,遇到数字就拼接 'number',否则拼接原字符。
// 代码简洁,逻辑清晰。
// 适合一般字符串处理,效率高,空间复杂度 O(n)。
function replaceNumber(s) {
let res = ''
for (let c of s) {
if (c >= '0' && c <= '9') {
res += 'number'
} else {
res += c
}
}
return res
}
// 先计算扩容后数组的长度,然后从后往前填充,遇到数字就一次性写入 'number' 六个字符。
// 适合需要原地扩容、大数据量、面试考察空间优化等场景。
// 这种写法避免了每次插入都要移动后面元素,效率高
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
function main() {
const num0 = '0'.charCodeAt()
const num9 = '9'.charCodeAt()
const a = 'a'.charCodeAt()
const z = 'z'.charCodeAt()
function isAZ(str) {
return str >= a && str <= z
}
function isNumber(str) {
return str >= num0 && str <= num9
}
rl.on('line', (input) => {
// 计算原字符串更改后需要的空间大小
let n = 0
for (let i = 0; i< input.length; i++ ) {
const val = input[i].charCodeAt()
if (isNumber(val)) {
n += 6
}
if (isAZ(val)) {
n++
}
}
const ans = new Array(n).fill(0)
let index = input.length - 1
for (let i = n - 1; i>= 0; i--) {
const val = input[index].charCodeAt()
if (isAZ(input[index])) {
ans[i] = input[index]
}
if (isNumber(val)) {
ans[i] = 'r'
ans[i - 1] = 'e'
ans[i - 2] = 'b'
ans[i - 3] = 'm'
ans[i - 4] = 'u'
ans[i - 5] = 'n'
i -= 5
}
index--
}
console.log(ans.join(''));
})
}
main()
- 反转字符串中的单词
-
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
-
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
-
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
-
注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
-
思路:
-
移除多余空格
-
将整个字符串反转
-
将每个单词反转
-
双指针,快指针指向我们想要获取到元素,慢指针指向我们获取快指针获取到元素所指向新的位置,
var reverseWords = function(s) {
// 字符串转数组
const strArr = Array.from(s);
// 移除多余空格
removeExtraSpaces(strArr);
// 翻转
reverse(strArr, 0, strArr.length - 1);
let start = 0;
for(let i = 0; i <= strArr.length; i++) {
if (strArr[i] === ' ' || i === strArr.length) {
// 翻转单词
reverse(strArr, start, i - 1);
start = i + 1;
}
}
return strArr.join('');
};
// 删除多余空格
function removeExtraSpaces(strArr) {
let slowIndex = 0;
let fastIndex = 0;
while(fastIndex < strArr.length) {
// 移除开始位置和重复的空格
if (strArr[fastIndex] === ' ' && (fastIndex === 0 || strArr[fastIndex - 1] === ' ')) {
fastIndex++;
} else {
strArr[slowIndex++] = strArr[fastIndex++];
}
}
// 移除末尾空格
strArr.length = strArr[slowIndex - 1] === ' ' ? slowIndex - 1 : slowIndex;
}
// 翻转从 start 到 end 的字符
function reverse(strArr, start, end) {
let left = start;
let right = end;
while(left < right) {
// 交换
[strArr[left], strArr[right]] = [strArr[right], strArr[left]];
left++;
right--;
}
}
// 151. 反转字符串中的单词
// trim() 去除首尾空格。
// split(/\s+/) 按多个空格分割成单词数组。
// reverse() 反转单词顺序。
// join(' ') 用单个空格拼接成结果字符串
var reverseWords = function(s) {
// 去除首尾和多余空格,按空格分割单词
const words = s.trim().split(/\s+/);
// 反转单词数组
words.reverse();
// 用单个空格拼接
return words.join(' ');
};
- 卡码网 55. 右旋字符串
-
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
-
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
-
输入:输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。
-
输出:输出共一行,为进行了右旋转操作后的字符串。
-
思路:
-
整体分为两个子串,先将整个字符串倒序,然后再把两个子串再分别倒序
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
const inputs = []
rl.on('line', function(data) {
inputs.push(data)
}).on('close', function() {
const res = rightRotate(inputs)
console.log(res)
})
function rightRotate(inputs) {
let [k, s] = inputs
k = parseInt(k)
const n = s.length
k = k % n // 防止k大于n
let arr = s.split('')
// 整体反转
reverse(arr, 0, n - 1)
// 反转前k个
reverse(arr, 0, k - 1)
// 反转后n-k个
reverse(arr, k, n - 1)
return arr.join('')
}
function reverse(arr, left, right) {
while (left < right) {
[arr[left], arr[right]] = [arr[right], arr[left]]
left++
right--
}
}
- 实现 strStr()
-
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
-
示例 1: 输入: haystack = "hello", needle = "ll" 输出: 2
-
示例 2: 输入: haystack = "aaaaa", needle = "bba" 输出: -1
-
思路
-
每次从 haystack 的第 i 个字符开始,逐个比较 needle 的每个字符。
-
如果不匹配就从下一个位置重新开始。
-
时间复杂度最坏 O(mn),m 为 haystack 长度,n 为 needle 长度。
// 暴力法:
var strStr = function(haystack, needle) {
if (needle === "") return 0;
for (let i = 0; i <= haystack.length - needle.length; i++) {
if (haystack.slice(i, i + needle.length) === needle) {
return i;
}
}
return -1;
};
- 重复的子字符串
- 给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
- 示例 1:
- 输入: "abab"
- 输出: True
- 解释: 可由子字符串 "ab" 重复两次构成。
// 正则匹配 只适用于只包含字母、数字、下划线的字符串(\w),如果有其它字符需要调整正则。
// ^(\w+):匹配字符串开头的一个或多个字母/数字/下划线,并用括号捕获为分组1(即可能的重复子串)。
// \1+:表示分组1的内容重复一次或多次,即整个字符串由同一个子串重复组成。
// $:匹配字符串结尾。
var repeatedSubstringPattern = function(s) {
let reg = /^(\w+)\1+$/
return reg.test(s)
};
// 移动匹配
// 如果 s 由某个子串重复多次构成,那么 s+s 去掉首尾后,s 一定会出现在中间。
// 如果 s 不是重复子串构成的,去掉首尾后不会再出现 s。
var repeatedSubstringPattern = function (s) {
let ss = s + s;
return ss.substring(1, ss.length - 1).includes(s);
};