算法随笔-数据结构(字符串)

138 阅读11分钟

算法随笔-数据结构(字符串)

本文主要介绍字符串的基本概念(表示形式、字符编码)和字符串属性和方法,题解 leetCode 真题。供自己以后查漏补缺,也欢迎同道朋友交流学习。

引言

之前写了几篇关于队列链表哈希相关的文章,回过头发现最最基础的字符串数组忘记写了。

虽然很多人不把字符串当成一种算法里的数据结构、但其实字符串相关的知识点也是不少的,而且很多我们了解的也不是很清楚。

所以写一篇文章做个总结记录下字符串相关的一些知识点以及做俩道字符串相关的 leetcode 真题。

字符串基本概念

字符串是由字符组成的数据结构,可以包括字母数字符号空格等。字符串通常被赋予了特定的语法和操作,允许程序员进行创建修改连接比较等操作。

表示形式

JS 使用 单引号双引号模板字符串 在代码中表示字符串:

var str1 = 'hello'
var str2 = 'niunai'
var str3 = `${str1} ${str2}` // hello niunai

字符编码

字符编码是计算机科学中的一个重要概念,用于将字符映射到二进制数表示,以便于存储和传输。以下是几种常见的字符编码标准,包括 ASCIIUnicodeUTF-8

我们前端一般开发对应的都是 UTF-8 的字符编码,但也有必要了解其他字符编码。

ASCII(American Standard Code for Information Interchange)

ASCII 是最早的字符编码标准之一,它定义了 128 个字符(包括控制字符和可打印字符),每个字符占用 7 位二进制数。由于早期计算机系统通常使用 8 位字节(即 1 字节),因此 ASCII 字符实际上占用 8 位中的 7 位,最高位通常被设为 0

A -> 65 (十进制) -> 01000001 (二进制)
Unicode

Unicode 是一种国际化的字符编码标准,旨在为世界上所有语言的文字提供统一的编码。Unicode 标准定义了大量的字符集,包括各种语言的文字、符号等。Unicode 的目标是提供一个全球通用的字符集,使得所有语言的文字都能在一个统一的编码体系下表示。

特点:

  • 包含超过 100,000 个字符。
  • 支持多种语言的文字。
  • 使用 UTF-8UTF-16UTF-32 等编码方案来表示字符。
UTF-8 (Unicode Transformation Format - 8 bits)

UTF-8 是一种可变长度的编码方案,用于编码 Unicode 字符。它是目前互联网上最广泛使用的字符编码方式之一。UTF-8 的最大优点是向后兼容 ASCII,因为所有 ASCII 字符在 UTF-8 中只需要一个字节表示。

特点:

  • 可变长度编码:不同字符占用不同数量的字节(1 到 4 个字节)。
  • 向后兼容 ASCII:ASCII 字符在 UTF-8 中占用一个字节。
  • 支持所有 Unicode 字符。
  • 易于网络传输和存储。
UTF-16

UTF-16 是另一种用于编码 Unicode 字符的方案,它使用 16 位或更多位来表示字符。对于大部分 Unicode 字符,UTF-16 使用 2 个字节表示;对于补充平面的字符,则使用 4 个字节(代理项对)。

特点:

  • 大多数字符使用 2 个字节表示。
  • 补充平面字符使用 4 个字节表示。
  • 在某些情况下比 UTF-8 更高效(例如,常用汉字)。
UTF-32

UTF-32 是一种固定长度的编码方案,每个 Unicode 字符都使用 32 位(4 字节)表示。

特点:

  • 固定长度编码:每个字符占用 4 个字节。
  • 最简单直观的编码方式。
  • 存储空间消耗较大。

字符串属性和方法

JS 字符串对象有许多内置的方法,可以用来进行各种操作,例如查找子字符串、替换字符、拼接字符串等。

属性类

直接获取字符串的属性

length

获取字符串的长度。

let str = 'hello, niunai!';
console.log(str.length); // 输出: 14

查找类

通过字符串的方法查找某个字符在字符串中的位置或者某个位置的字符。

indexOf(searchValue[, fromIndex])

返回字符串中第一次出现的指定子字符串的位置。

let str = 'hello, niunai!';
console.log(str.indexOf('niunai')); // 输出: 7
lastIndexOf(searchValue[, fromIndex])

返回字符串中最后一次出现的指定子字符串的位置。

let str = 'hello, niunai! niunai!';
console.log(str.lastIndexOf('niunai')); // 输出: 15
charAt(index)

获取指定索引位置的字符。

let str = 'hello, niunai!';
console.log(str.charAt(0)); // 输出: h
charCodeAt(index)

获取指定索引位置字符的 Unicode 编码。

let str = 'hello, niunai!';
console.log(str.charCodeAt(0)); // 输出: 104
search(regex)

搜索字符串中符合正则表达式的第一个位置。

let str = 'hello, niunai!';
let regex = /niunai/;
console.log(str.search(regex)); // 输出: 7

比较判断类

检查字符串是否包含某个字符

includes(searchValue[, start])

检查字符串是否包含指定的子字符串。

let str = 'hello, niunai!';
console.log(str.includes('niunai')); // 输出: true
startsWith(searchString[, position])

检查字符串是否以指定的子字符串开头。

let str = 'hello, niunai!';
console.log(str.startsWith('hello')); // 输出: true
endsWith(searchString[, length])

检查字符串是否以指定的子字符串结尾。

let str = 'hello, niunai!';
console.log(str.endsWith('niunai!')); // 输出: true
localeCompare(that[, locales[, options]])

比较两个字符串并返回一个整数,表示字符串的顺序。这可以用于排序,例如在表格中按字母顺序排列行。

let str1 = 'abc';
let str2 = 'abd';
console.log(str1.localeCompare(str2)); // 输出: -1

拼接/转换类

对原字符串进行操作,变成新的字符串或者数组

concat(str1[, str2[, ...]])

连接两个或多个字符串。

let str1 = 'hello, ', str2 = 'niunai!';
console.log(str1.concat(str2)); // 输出: hello, niunai!
// 字符串模板拼接
console.log(`${str1} ${str2}`); // 输出: hello, niunai!
repeat(count)

重复字符串指定次数。

let str = 'abc';
console.log(str.repeat(3)); // 输出: abcabcabc
replace(searchValue, replaceValue)

替换字符串中的某些部分。

let str = 'hello, niunai!';
console.log(str.replace('niunai', 'JavaScript')); // 输出: hello, JavaScript!
replaceAll(searchValue, replaceValue)

替换字符串中的所有匹配项。

let str = 'hello, niunai! niunai!';
console.log(str.replaceAll('niunai', 'JavaScript')); // 输出: hello, JavaScript! JavaScript!
split(separator[, limit])

根据指定的分隔符分割字符串,返回数组。

let str = 'hello, niunai!';
console.log(str.split(',')); // 输出: ["hello", " niunai!"]
toLowerCase()

将字符串转换为小写。

let str = 'HELLO, NIUNAI!';
console.log(str.toLowerCase()); // 输出: hello, niunai!
toUpperCase()

将字符串转换为大写。

let str = 'hello, niunai!';
console.log(str.toUpperCase()); // 输出: HELLO, NIUNAI!
toLocaleLowerCase(locales[, options])

将字符串转换为小写,支持本地化。

let str = 'HELLO, NIUNAI!';
console.log(str.toLocaleLowerCase()); // 输出: hello, niunai!
toLocaleUpperCase(locales[, options])

将字符串转换为大写,支持本地化。

let str = 'hello, niunai!';
console.log(str.toLocaleUpperCase()); // 输出: HELLO, NIUNAI!
fromCharCode(code1[, code2[, ...]])

Unicode 编码转换成字符串。

let str = String.fromCharCode(104, 101, 108, 108, 111);
console.log(str); // 输出: hello
normalize(form)

将字符串转换为规范化形式。

let str = 'café';
console.log(str.normalize('NFD')); // 输出: café
padEnd(targetLength[, padString])

在字符串的末尾填充指定的字符串,直到达到目标长度。

let str = 'foo';
console.log(str.padEnd(10, '-')); // 输出: foo-------
padStart(targetLength[, padString])

在字符串的开头填充指定的字符串,直到达到目标长度。

let str = 'foo';
console.log(str.padStart(10, '-')); // 输出: -------foo

截取类

slice(start[, end])

提取字符串的一部分,slice() 方法可以接受负索引。

let str = 'hello, niunai!';
console.log(str.slice(7, 13)); // 输出: niunai
substring(start[, end])

提取字符串的一部分,不会改变原字符串,substring() 不接受负索引。

let str = 'hello, niunai!';
console.log(str.substring(7, 13)); // 输出: niunai
substr(start[, length])

提取字符串的一部分,从下标 start 开始截取 length 长度的字符串

let str = 'hello, niunai!';
console.log(str.substr(7, 6)); // 输出: niunai
trim()

删除字符串两端的空白字符。

let str = '   hello, niunai!   ';
console.log(str.trim()); // 输出: hello, niunai!
trimStart()

删除字符串开头的空白字符。

let str = '   hello, niunai!';
console.log(str.trimStart()); // 输出: hello, niunai!
trimEnd()

删除字符串末尾的空白字符。

let str = 'hello, niunai!   ';
console.log(str.trimEnd()); // 输出: hello, niunai!

正则操作类

通过正则表达式,进行字符串的各种操作

match(regex)

在字符串中查找与正则表达式匹配的内容。

let str = 'hello, niunai!';
let regex = /niunai/;
console.log(str.match(regex)); 
// 输出: ['niunai', index: 7, input: 'hello, niunai!', groups: undefined]
matchAll(regex)

在字符串中查找所有与正则表达式匹配的内容。

let str = 'hello, niunai! niunai!';
let regex = /niunai/g;
console.log([...str.matchAll(regex)]); 
// 输出: [['niunai', index: 7, input: 'hello, niunai! niunai!', groups: undefined], ['niunai', index: 15, input: 'hello, niunai! niunai!', groups: undefined]]

字符串编码与解码

编码

encodeURIComponent()encodeURI() 用于对字符串进行编码,特别是在处理URL时非常重要。这两个函数的目的是确保字符串中的特殊字符被正确地编码,以便它们可以安全地用作URL的一部分。

encodeURI()

encodeURI() 函数用于对整个URI(Uniform Resource Identifier)进行编码。它会编码除了保留字符以外的所有字符。保留字符是指在URI中具有特殊意义的字符,如 :/?#[]@!$&'()*+,;= 等。encodeURI() 不会对这些保留字符进行编码。

let uri = "http://example.com/?query=你好,世界!";
console.log(encodeURI(uri));
// 输出: http://example.com/?query=%E4%BD%A0%E5%A5%BD%EF%BC%8C%E4%B8%96%E7%95%8C%EF%BC%81
encodeURIComponent()

encodeURIComponent() 函数用于对URI的组成部分(如查询字符串或片段标识符)进行编码。它会编码除了 -、_、. 和 ~ 之外的所有字符。这意味着 encodeURIComponent() 会将所有的保留字符和非保留字符都进行编码。

let query = "你好,世界!";
console.log(encodeURIComponent(query));
// 输出: %E4%BD%A0%E5%A5%BD%EF%BC%8C%E4%B8%96%E7%95%8C%EF%BC%81

解码

decodeURIComponent()decodeURI() 是用于解码之前通过 encodeURIComponent()encodeURI() 编码的字符串的函数。这两个函数可以将编码后的字符串还原为其原始形式。

decodeURI()

decodeURI() 函数用于解码由 encodeURI() 编码的整个URI。它可以解码之前编码的字符,使其恢复为原始形式。

let encodedUri = "http://example.com/?query=%E4%BD%A0%E5%A5%BD%EF%BC%8C%E4%B8%96%E7%95%8C%EF%BC%81";
let decodedUri = decodeURI(encodedUri);
console.log(decodedUri);
// 输出: http://example.com/?query=你好,世界!
decodeURIComponent()

decodeURIComponent() 函数用于解码由 encodeURIComponent() 编码的 URI 组成部分。它可以解码之前编码的字符,使其恢复为原始形式。

let encodedQuery = "%E4%BD%A0%E5%A5%BD%EF%BC%8C%E4%B8%96%E7%95%8C%EF%BC%81";
let decodedQuery = decodeURIComponent(encodedQuery);
console.log(decodedQuery);
// 输出: 你好,世界!

Base64 编码

btoa()atob() 是用于 Base64 编码和解码的内置函数。

btoa()

btoa() 函数将一个包含 ASCII 字符的字符串转换为 Base64 编码的字符串。

参数:

  • str:一个包含 ASCII 字符的字符串。

返回值:

  • 一个 Base64 编码的字符串。
let originalString = 'hello, niunai!';
let base64Encoded = btoa(originalString);
console.log(base64Encoded); // 输出: aGVsbG8sIG5pdW5haSE=
atob()

atob() 函数将一个 Base64 编码的字符串转换回原始的 ASCII 字符串。

参数:

  • base64Str:一个 Base64 编码的字符串。

返回值:

  • 一个原始的 ASCII 字符串。
let base64Encoded = 'aGVsbG8sIG5pdW5haSE=';
let originalString = atob(base64Encoded);
console.log(originalString); // 输出: hello, niunai!

leetCode 真题

58. 最后一个单词的长度

给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中最后一个单词的长度。

单词是指仅由字母组成、不包含任何空格字符的最大子字符串。

示例1

输入:s = "Hello World"

输出:5

解释:最后一个单词是“World”,长度为 5。

示例2

输入:s = " fly me to the moon "

输出:4

解释:最后一个单词是“moon”,长度为 4。

示例3

输入:s = "luffy is still joyboy"

输出:6

解释:最后一个单词是长度为 6 的“joyboy”。

题解1:前端看到这题直接笑了,送分啊,我们先用JS的方法来做下吧。下面再做个遍历的题解

思路:先去前后空格,然后根据空格转数组,取最后一个的长度就行了

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLastWord = function(s) {
  // 前后去空格
  var str = s.trim();
  // 判断下字符不全是空格
  if (!str) return 0;
  // split 转数组
  var arr = str.split(' ');
  // 取最后一个值的字符串的长度
  return arr[arr.length - 1].length;
};

题解2:下面再做个循环遍历

思路:从最后一个字符循环,判断如果str有值且当前值为空格就不截取了,这种方法时间复杂度和空间复杂度比较低。

/**
 * @param {string} s
 * @return {number}
 */
var lengthOfLastWord = function(s) {
  var str = '';
  var len = s.length
  for (let i = len - 1; i >= 0; i--) {
    if (s[i] === ' ' && str) {
      // 如果当前str有值且 当前s[i]为空格就跳出了
      break;
    }
    if (s[i] !== ' ') {
      str += s[i]
    } 
  }

  return str.length
};

131. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串。返回 s 所有可能的分割方案。

回文串是指正读反读都相同的字符串。

示例1

输入:s = "aab"

输出:[["a","a","b"],["aa","b"]]

示例2

输入:s = "a"

输出:[["a"]]

题解

/**
 * @param {string} s
 * @return {string[][]}
 */
var partition = function(s) {
  const result = [];
  // 判断是否是回文字符串
  const isPalindrome = (str) => str === str.split('').reverse().join('');

  // 这是一个递归函数,用于尝试从当前位置 start 开始的所有可能的分割。
  const backtrack = (start, path) => {
    // 如果 start 到达字符串末尾,说明找到了一个有效的分割,将其添加到结果中。
    if (start === s.length) {
      result.push([...path]);
      return;
    }
    // 循环从 start 到字符串末尾,检查每个可能的子串是否为回文。
    for (let end = start; end < s.length; end++) {
      if (isPalindrome(s.substring(start, end + 1))) {
        // 如果是回文,将其添加到当前路径中,并递归调用 backtrack 继续分割剩余的字符串。
        path.push(s.substring(start, end + 1));
        backtrack(end + 1, path);
        // 回溯后,从路径中移除最后一个元素,以便尝试其他可能的分割。
        path.pop();
      }
    }
  };

  backtrack(0, [])

  return result;
};