Skip to content

字符串

1. 反转字符串 【简单】

🔗力扣题目链接https://leetcode.cn/problems/reverse-string/description/

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:

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

示例 2:

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

补充

数组元素交换位置:可以利用解构赋值实现,[arr[i], arr[j]] = [arr[j], arr[i]]是一种解构赋值语法糖,无需临时变量即可交换两个元素

js
const arr = [1, 2, 3, 4, 5]
;[arr[0], arr[1]] = [arr[1], arr[0]]
console.log(arr) // [ 2, 1, 3, 4, 5 ]
;[arr[0], arr[2]] = [arr[1], arr[0]]
console.log(arr) // [ 1, 1, 2, 4, 5 ]

效果等同于:

js
const arr = [1, 2, 3, 4, 5]
let temp = arr[0]
arr[0] = arr[2]
arr[2] = temp
console.log(arr) // [ 3, 2, 1, 4, 5 ]
md
由题“你必须原地修改输入数组,O(1) 的额外空间解决”,表明算法的空间复杂度要求为 1,也就是对原数组进行操作
js
// 通过首尾替换实现
var reverseString = function (s) {
  const len = s.length
  for (let i = 0; i < len / 2; i++) {
    ;[s[i], s[len - i - 1]] = [s[len - i - 1], s[i]]
  }
  return s
}
js
var reverseString = function (s) {
  let left = 0,
    right = s.length - 1
  while (left < right) {
    ;[s[left], s[right]] = [s[right], s[left]]
    left++
    right--
  }
}

2. 反转字符串 II

🔗力扣题目链接https://leetcode.cn/problems/reverse-string-ii/description/

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例 1:

输入:s = "abcdefg", k = 2 输出:"bacdfeg"

示例 2:

输入:s = "abcd", k = 2 输出:"bacd"

md
- 每次循环都增加 2 \* k(一个区间)
- 根据条件去交换位置(这题要用到上一题的互换方法)
- 看似 3 种情况,实则只有两种:(反转前 k 个 和 反转剩余所有)
  1. 正常情况:反转前 k 个
  2. 剩余小于 2k 但大于等于 k:反转前 k 个
  3. 剩余字符小于 k 的情况:反转剩余字符
js
var reverseStr = function (s, k) {
  // 将字符串转为数组
  s = s.split('')
  const len = s.length
  for (let i = 0; i < len; i += 2 * k) {
    // 根据条件进行交换
    reverse(s, i, Math.min(i + k, len) - 1)
  }
  // 再将数组转为字符串
  return s.join('')
}

// 双指针法
function reverse(s, left, right) {
  while (left < right) {
    const tmp = s[left]
    s[left] = s[right]
    s[right] = tmp
    left++
    right--
  }
}

/**
条件分析:
if (s - i >= k && s - i < 2 * k) {
  // 剩余: < 2k && >= k
  // 和正常情况一样,反转前k个
  reverse(s, i, i + k - 1)
} else if (s - i < k) {
  // 剩余: < k
  reverse(s, i, len - 1)
} else {
  // 正常情况
  reverse(s, i, i + k - 1)
}
 */

3. 找出字符串中第一个匹配项的下标

🔗力扣题目链接https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/

给你两个字符串 haystackneedle,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1。

示例 1:

输入:haystack = "sadbutsad", needle = "sad" 输出:0 解释:"sad" 在 "sadbutsad" 中出现的位置是 0 和 6,第一个匹配项的下标是 0,所以返回 0。

示例 2:

输入:haystack = "leetcode", needle = "leeto" 输出:-1 解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1。

md
思路 1:

- 开启两层循环,外层循环 haystack,i 为 haystack 的索引,内层循环 needle,j 为 needle 的索引。
- 如果 `haystack[i + j] != needle[j]` 就表示匹配失败,结束内层循环,外层继续

思路 2:
也可以外层循环 haystack,判断 haystack[i] 是否等于 needle[0],如果等于就进入内层循环,判断 haystack[i + j] 是否等于 needle[j],如果等于就继续,直到 j == needle.length - 1。如果都匹配,返回 i
js
var strStr = function (haystack, needle) {
  const n = haystack.length
  const m = needle.length
  for (let i = 0; i + m <= n; i++) {
    let flag = true
    for (let j = 0; j < m; j++) {
      if (haystack[i + j] != needle[j]) {
        flag = false
        break
      }
    }
    if (flag) {
      return i
    }
  }
  return -1
}

4. 重复子字符串

🔗力扣题目链接https://leetcode.cn/problems/repeated-substring-pattern/description/

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

示例 1:

输入: s = "abab" 输出: true 解释: 可由子串 "ab" 重复两次构成。

示例 2:

输入: s = "aba" 输出: false

示例 3:

输入: s = "abcabcabcabc" 输出: true 解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)

md
- 定义一个 str 变量存储子串,去枚举所有子串,判断如果子串 repeat 重复次数等于字符串长度,表示字符串由这几个子串构成,则返回 true
- 循环次数:是 `s.length - 1`,而不是 `s.length`,是因为题中说是**一个能“重复”多次的“子串”**,子串的长度必须小于原字符串,否则无法满足“重复”的定义。
js
/**
 * const str = 'abc';
 * console.log(str.repeat(2)); // 输出 "abcabc"
 */
var repeatedSubstringPattern = function (s) {
  const len = s.length
  let str = ''
  for (let i = 0; i < s.length - 1; i++) {
    str += s[i]
    if (s === str.repeat(Math.floor(len / str.length))) {
      return true
    }
  }
  return false
}

5. 反转字符串中的单词

🔗力扣题目链接https://leetcode.cn/problems/reverse-words-in-a-string/description/

给定一个字符串 s,要求反转字符串中单词的顺序。单词是由非空格字符组成的字符串,s 中使用至少一个空格将单词分隔开。返回单词顺序颠倒且单词之间用单个空格连接的结果字符串。

注意:输入字符串 s 中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

示例 1

输入: s = "the sky is blue"

输出: "blue is sky the"

示例 2

输入: s = "hello world "

输出: "world hello"

解释:反转后的字符串中不能存在前导空格和尾随空格。

示例 3

输入: s = "a good example"

输出: "example good a"

解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

md
这道题可以用 js 的写法,就是将字符串转为数组,然后使用`reverse()`方法,再转为字符串。

第二种就是采用双指针,从后向前遍历字符,
1、右端的指针遇到空格跳过,直到单词的末尾,然后将左端的指针指向右端
2、在 2 的基础上,左指针继续向前,直到遇到空格或小于 0 才停下(此时左右指针之间就是单词)
3、把单词添加到结果字符串中,然后把右指针指向左指针,开始下一轮遍历
4、下一轮遍历开始后,如果还有新单词,就给上一个单词后面添加一个空格
js
/**
 * @param {string} s
 * @return {string}
 */
var reverseWords = function (s) {
  s = s
    .split(' ')
    .map((i) => {
      if (i.trim() !== '') {
        return i.trim()
      }
    })
    .filter(Boolean)
    .reverse()
  return s.join(' ')
}
js
/**
 * @param {string} s
 * @return {string}
 */
var reverseWords = function (s) {
  let r = s.length - 1,
    l = r
  let res = ''

  while (l >= 0) {
    // 去除空格,先找到单词的尾部
    while (s[r] === ' ') {
      r--
    }
    l = r

    // 给上次单词加空格,排除第一次
    if (l >= 0 && res) {
      res += ' '
    }

    // 找到单词的头部
    while (s[l] && s[l] !== ' ') {
      l--
    }

    // 此时l到r之间为单词
    for (let i = l + 1; i <= r; i++) {
      res += s[i]
    }

    // 到下一个单词,指针又为同一个起点
    r = l
  }
  return res
}