跳到主要内容

97 篇博文 含有标签「字符串」

查看所有标签

· 阅读需 3 分钟

1、题干

如果可以使用以下操作从一个字符串得到另一个字符串,则认为两个字符串 接近

  • 操作 1:交换任意两个 现有 字符。
    • 例如,abcde -> aecdb
  • 操作 2:将一个 现有 字符的每次出现转换为另一个 现有 字符,并对另一个字符执行相同的操作。
    • 例如,aacabb -> bbcbaa(所有 a 转化为 b ,而所有的 b 转换为 a

你可以根据需要对任意一个字符串多次使用这两种操作。

给你两个字符串,word1word2 。如果 word1 word2 接近 ,就返回 true ;否则,返回 false

 

示例 1:

输入:word1 = "abc", word2 = "bca"
输出:true
解释:2 次操作从 word1 获得 word2 。
执行操作 1:"abc" -> "acb"
执行操作 1:"acb" -> "bca"

示例 2:

输入:word1 = "a", word2 = "aa"
输出:false
解释:不管执行多少次操作,都无法从 word1 得到 word2 ,反之亦然。

示例 3:

输入:word1 = "cabbba", word2 = "abbccc"
输出:true
解释:3 次操作从 word1 获得 word2 。
执行操作 1:"cabbba" -> "caabbb"
执行操作 2:"caabbb" -> "baaccc"
执行操作 2:"baaccc" -> "abbccc"

示例 4:

输入:word1 = "cabbba", word2 = "aabbss"
输出:false
解释:不管执行多少次操作,都无法从 word1 得到 word2 ,反之亦然。

 

提示:

  • 1 <= word1.length, word2.length <= 105
  • word1word2 仅包含小写英文字母

2、思路

需要校验两个关键点:

  • 两个字符串包含的字母相同
  • 两个字符串分别按字母计数,计数结果排序后相同

3、代码

function closeStrings(word1: string, word2: string): boolean {
if (word1.length !== word2.length) return false;

const K = 26, AC = 'a'.charCodeAt(0);
const c1 = new Array(K).fill(0), c2 = new Array(K).fill(0);

for (let i = 0; i < word1.length; i++) {
c1[word1.charCodeAt(i) - AC] += 1;
c2[word2.charCodeAt(i) - AC] += 1;
}

return c1.every((c, i) => +!c === +!c2[i]) && c1.sort().toString() === c2.sort().toString();
};

· 阅读需 2 分钟

1、题干

给你一个字符串 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 <= s.length <= 104
  • s 仅有英文字母和空格 ' ' 组成
  • s 中至少存在一个单词

2、思路

模拟

3、代码

function lengthOfLastWord(s: string): number {
return s.split(' ').filter(Boolean).pop().length;
};

· 阅读需 2 分钟

1、题干

给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  1. 左括号必须用相同类型的右括号闭合。
  2. 左括号必须以正确的顺序闭合。
  3. 每个右括号都有一个对应的相同类型的左括号。

 

示例 1:

输入:s = "()"
输出:true

示例 2:

输入:s = "()[]{}"
输出:true

示例 3:

输入:s = "(]"
输出:false

 

提示:

  • 1 <= s.length <= 104
  • s 仅由括号 '()[]{}' 组成

2、思路

遍历字符串,使用栈存储字符,若栈顶字符与当前字符配对则出栈,否则入栈,最后栈为空则字符串有效

3、代码

function isValid(s: string): boolean {
const stack = [], map = { "(": ")", "[": "]", "{": "}" };
for (const c of s) {
map[stack.at(-1)] === c ? stack.pop() : stack.push(c);
}
return !stack.length;
};

· 阅读需 2 分钟

1、题干

给定字符串 st ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace""abcde"的一个子序列,而"aec"不是)。

进阶:

如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

致谢:

特别感谢 @pbrother 添加此问题并且创建所有测试用例。

 

示例 1:

输入:s = "abc", t = "ahbgdc"
输出:true

示例 2:

输入:s = "axc", t = "ahbgdc"
输出:false

 

提示:

  • 0 <= s.length <= 100
  • 0 <= t.length <= 10^4
  • 两个字符串都只由小写字符组成。

2、思路

双指针遍历,如果 s 中的字符能全部被消耗完,则结果为 true

3、代码

function isSubsequence(s: string, t: string): boolean {
let i = 0, j = 0;

while (i < s.length && j < t.length) {
while (j < t.length) {
if (t[j++] === s[i]) {
i++;
break;
}
}
}

return i === s.length;
};

· 阅读需 2 分钟

1、题干

给你一个仅由 01 组成的二进制字符串 s  

如果子字符串中 所有的 0 都在 1 之前 且其中 0 的数量等于 1 的数量,则认为 s 的这个子字符串是平衡子字符串。请注意,空子字符串也视作平衡子字符串。 

返回  s 中最长的平衡子字符串长度。

子字符串是字符串中的一个连续字符序列。

 

示例 1:

输入:s = "01000111"
输出:6
解释:最长的平衡子字符串是 "000111" ,长度为 6 。

示例 2:

输入:s = "00111"
输出:4
解释:最长的平衡子字符串是 "0011" ,长度为  4 。

示例 3:

输入:s = "111"
输出:0
解释:除了空子字符串之外不存在其他平衡子字符串,所以答案为 0 。

 

提示:

  • 1 <= s.length <= 50
  • '0' <= s[i] <= '1'

2、思路

单指针多循环遍历

3、代码

class Solution {
public int findTheLongestBalancedSubstring(String s) {
int ans = 0;

for (int i = 0; i < s.length();) {
int c0 = 0, c1 = 0;
for (; i < s.length() && s.charAt(i) == '0'; i++) c0++;
for (; i < s.length() && s.charAt(i) == '1'; i++) c1++;

ans = Math.max(ans, 2 * Math.min(c0, c1));
}

return ans;
}
}
function findTheLongestBalancedSubstring(s: string): number {
let ans = 0;

for (let i = 0; i < s.length;) {
let c0 = 0, c1 = 0;
for (; s[i] === '0'; i++) c0++;
for (; s[i] === '1'; i++) c1++;

ans = Math.max(ans, 2 * Math.min(c0, c1));
}

return ans;
};

· 阅读需 4 分钟

1、题干

总计有 n 个环,环的颜色可以是红、绿、蓝中的一种。这些环分别穿在 10 根编号为 09 的杆上。

给你一个长度为 2n 的字符串 rings ,表示这 n 个环在杆上的分布。rings 中每两个字符形成一个 颜色位置对 ,用于描述每个环:

  • i 对中的 第一个 字符表示第 i 个环的 颜色'R''G''B')。
  • i 对中的 第二个 字符表示第 i 个环的 位置,也就是位于哪根杆上('0''9')。

例如,"R3G2B1" 表示:共有 n == 3 个环,红色的环在编号为 3 的杆上,绿色的环在编号为 2 的杆上,蓝色的环在编号为 1 的杆上。

找出所有集齐 全部三种颜色 环的杆,并返回这种杆的数量。

 

示例 1:

输入:rings = "B0B6G0R6R0R6G9"
输出:1
解释:
- 编号 0 的杆上有 3 个环,集齐全部颜色:红、绿、蓝。
- 编号 6 的杆上有 3 个环,但只有红、蓝两种颜色。
- 编号 9 的杆上只有 1 个绿色环。
因此,集齐全部三种颜色环的杆的数目为 1 。

示例 2:

输入:rings = "B0R0G0R9R0B0G0"
输出:1
解释:
- 编号 0 的杆上有 6 个环,集齐全部颜色:红、绿、蓝。
- 编号 9 的杆上只有 1 个红色环。
因此,集齐全部三种颜色环的杆的数目为 1 。

示例 3:

输入:rings = "G4"
输出:0
解释:
只给了一个环,因此,不存在集齐全部三种颜色环的杆。

 

提示:

  • rings.length == 2 * n
  • 1 <= n <= 100
  • i偶数 ,则 rings[i] 的值可以取 'R''G''B'(下标从 0 开始计数)
  • i奇数 ,则 rings[i] 的值可以取 '0''9' 中的一个数字(下标从 0 开始计数)

2、思路

模拟+位运算,遍历 rings 检查所有杆和环,位运算记录是否3环状态,返回3环状态总数。

Rust 真是又难写又难调试

3、代码

impl Solution {
pub fn count_points(rings: String) -> i32 {
let mut bits:[i32; 10] = [0; 10];
let mut ans:i32 = 0;

for i in 0..rings.len() {
if i % 2 == 1 {
continue;
}

let color:char = (&rings[i..=i]).parse().unwrap();
let j:usize = (&rings[i + 1..=i + 1]).parse().unwrap();

if bits[j] == 7 {
continue;
}

let b = if color == 'R' {
1
} else if color == 'G' {
2
} else {
4
};

bits[j] = bits[j] | b;
if bits[j] == 7 {
ans += 1;
}
}

return ans;
}
}
function countPoints(rings: string): number {
const map = { R: 1, G: 2, B: 4 };
const bits = new Array(10).fill(0);

let ans = 0;
for (let i = 0; i < rings.length; i += 2) {
const j = rings[i + 1];
if (bits[j] === 7) continue;

bits[j] |= map[rings[i]];
if (bits[j] === 7) ans++;
}

return ans;
};

· 阅读需 3 分钟

1、题干

给你一个字符串 croakOfFrogs,它表示不同青蛙发出的蛙鸣声(字符串 "croak" )的组合。由于同一时间可以有多只青蛙呱呱作响,所以 croakOfFrogs 中会混合多个 “croak”

请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。

要想发出蛙鸣 "croak",青蛙必须 依序 输出 ‘c’, ’r’, ’o’, ’a’, ’k’ 这 5 个字母。如果没有输出全部五个字母,那么它就不会发出声音。如果字符串 croakOfFrogs 不是由若干有效的 "croak" 字符混合而成,请返回 -1

 

示例 1:

输入:croakOfFrogs = "croakcroak"
输出:1
解释:一只青蛙 “呱呱” 两次

示例 2:

输入:croakOfFrogs = "crcoakroak"
输出:2
解释:最少需要两只青蛙,“呱呱” 声用黑体标注
第一只青蛙 "crcoakroak"
第二只青蛙 "crcoakroak"

示例 3:

输入:croakOfFrogs = "croakcrook"
输出:-1
解释:给出的字符串不是 "croak" 的有效组合。

 

提示:

  • 1 <= croakOfFrogs.length <= 105
  • 字符串中的字符只有 'c', 'r', 'o', 'a' 或者 'k'

2、思路

  • 遍历给定字符串,记录正在叫的青蛙数量及其最大值
  • 蛙叫声需按顺序输出,因此每个字符必须有可用的前一个字符,如果没有则返回-1
  • 最后如果还有在叫的青蛙则返回-1,否则返回青蛙数量最大值

3、代码

function minNumberOfFrogs(chars: string): number {
const L = 5;
if (chars.length % L) return -1;

let ans = 0, used = 0;
const counts = new Array(L).fill(0), dict = { c: 0, r: 1, o: 2, a: 3, k: 4 };

for (let i = 0; i < chars.length; i++) {
const j = dict[chars[i]];
counts[j]++;

if (j === 0) {
used++;
} else {
if (counts[j - 1] < 1) return -1;
if (j === L - 1) used--;
counts[j - 1]--;
}

ans = Math.max(ans, used);
}

return used > 0 ? -1 : ans;
};

4、复杂度

  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(1)O(1)

· 阅读需 3 分钟

1、题干

Alice 和 Bob 计划分别去罗马开会。

给你四个字符串 arriveAlice ,leaveAlice ,arriveBob 和 leaveBob 。Alice 会在日期 arriveAlice 到 leaveAlice 之间在城市里(日期为闭区间),而 Bob 在日期 arriveBob 到 leaveBob 之间在城市里(日期为闭区间)。每个字符串都包含 5 个字符,格式为 "MM-DD" ,对应着一个日期的月和日。

请你返回 Alice和 Bob 同时在罗马的天数。

你可以假设所有日期都在 同一个 自然年,而且 不是 闰年。每个月份的天数分别为:[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] 。

 

示例 1:

输入:arriveAlice = "08-15", leaveAlice = "08-18", arriveBob = "08-16", leaveBob = "08-19"
输出:3
解释:Alice 从 8 月 15 号到 8 月 18 号在罗马。Bob 从 8 月 16 号到 8 月 19 号在罗马,他们同时在罗马的日期为 8 月 16、17 和 18 号。所以答案为 3 。

示例 2:

输入:arriveAlice = "10-01", leaveAlice = "10-31", arriveBob = "11-01", leaveBob = "12-31"
输出:0
解释:Alice 和 Bob 没有同时在罗马的日子,所以我们返回 0 。

 

提示:

  • 所有日期的格式均为 "MM-DD" 。
  • Alice 和 Bob 的到达日期都 早于或等于 他们的离开日期。
  • 题目测试用例所给出的日期均为 非闰年 的有效日期。

2、思路

先计算交集起止时间,再把日期转化为天数并求差值,具体步骤:

  • 前缀和预处理每月天数
  • 通过字典序计算交集的起止时间
  • 计算起止时间是该年第几天并求差值

3、代码

function countDaysTogether(s1: string, e1: string, s2: string, e2: string): number {
const days = [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
const s = s1 <= s2 ? s2 : s1, e = e1 <= e2 ? e1 : e2;
if (e < s) return 0;

const d1 = (days[+s.slice(0, 2) - 2] || 0) + +s.slice(3);
const d2 = (days[+e.slice(0, 2) - 2] || 0) + +e.slice(3);

return d2 - d1 + 1;
};

4、复杂度

  • 时间复杂度:O(1)O(1)
  • 空间复杂度:O(1)O(1)

5、执行结果

image.png

· 阅读需 3 分钟

1、题干

设计一个算法:接收一个字符流,并检查这些字符的后缀是否是字符串数组 words 中的一个字符串。

例如,words = ["abc", "xyz"] 且字符流中逐个依次加入 4 个字符 'a''x''y''z' ,你所设计的算法应当可以检测到 "axyz" 的后缀 "xyz" 与 words 中的字符串 "xyz" 匹配。

按下述要求实现 StreamChecker 类:

  • StreamChecker(String[] words) :构造函数,用字符串数组 words 初始化数据结构。
  • boolean query(char letter):从字符流中接收一个新字符,如果字符流中的任一非空后缀能匹配 words 中的某一字符串,返回 true ;否则,返回 false

 

示例:

输入:
["StreamChecker", "query", "query", "query", "query", "query", "query", "query", "query", "query", "query", "query", "query"]
[[["cd", "f", "kl"]], ["a"], ["b"], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["i"], ["j"], ["k"], ["l"]]
输出:
[null, false, false, false, true, false, true, false, false, false, false, false, true]

解释:
StreamChecker streamChecker = new StreamChecker(["cd", "f", "kl"]);
streamChecker.query("a"); // 返回 False
streamChecker.query("b"); // 返回 False
streamChecker.query("c"); // 返回n False
streamChecker.query("d"); // 返回 True ,因为 'cd' 在 words 中
streamChecker.query("e"); // 返回 False
streamChecker.query("f"); // 返回 True ,因为 'f' 在 words 中
streamChecker.query("g"); // 返回 False
streamChecker.query("h"); // 返回 False
streamChecker.query("i"); // 返回 False
streamChecker.query("j"); // 返回 False
streamChecker.query("k"); // 返回 False
streamChecker.query("l"); // 返回 True ,因为 'kl' 在 words 中

 

提示:

  • 1 <= words.length <= 2000
  • 1 <= words[i].length <= 200
  • words[i] 由小写英文字母组成
  • letter 是一个小写英文字母
  • 最多调用查询 4 * 104

2、思路

  • 构造函数:字典树存储所有单词的逆序串
  • query:数组存储字符流,按字符流逆序查找字典树中是否存在该后缀

3、代码

class StreamChecker {
letters = [];
trie = { end: false };

constructor(words: string[]) {
for (const word of words) this.add(word);
}

query(letter: string): boolean {
this.letters.push(letter);
return this.find(this.letters);
}

add(word: string) {
let t = this.trie;
for (let i = word.length - 1; i > -1; i--) {
if (!t[word[i]]) t[word[i]] = { end: false };
t = t[word[i]];
}
t.end = true;
}

find(letters: string[]) {
let t = this.trie;
for (let i = letters.length - 1; i > -1; i--) {
t = t[letters[i]];
if (!t) return false;
if (t.end) return true;
}
return t.end;
}
}

4、复杂度

  • 时间复杂度:构造函数 O(mn)O(mn),query O(mk)O(mk),其中 n为单词数,m 为单词长度,k 为查询次数
  • 空间复杂度:O(mn+k)O(mn+k)

5、执行结果

image.png

· 阅读需 4 分钟

1、题干

给你一个字符串 s 以及两个整数 ab 。其中,字符串 s 的长度为偶数,且仅由数字 09 组成。

你可以在 s 上按任意顺序多次执行下面两个操作之一:

  • 累加:将  a 加到 s 中所有下标为奇数的元素上(下标从 0 开始)。数字一旦超过 9 就会变成 0,如此循环往复。例如,s = "3456"a = 5,则执行此操作后 s 变成 "3951"
  • 轮转:将 s 向右轮转 b 位。例如,s = "3456"b = 1,则执行此操作后 s 变成 "6345"

请你返回在 s 上执行上述操作任意次后可以得到的 字典序最小 的字符串。

如果两个字符串长度相同,那么字符串 a 字典序比字符串 b 小可以这样定义:在 ab 出现不同的第一个位置上,字符串 a 中的字符出现在字母表中的时间早于 b 中的对应字符。例如,"0158” 字典序比 "0190" 小,因为不同的第一个位置是在第三个字符,显然 '5' 出现在 '9' 之前。

 

示例 1:

输入:s = "5525", a = 9, b = 2
输出:"2050"
解释:执行操作如下:
初态:"5525"
轮转:"2555"
累加:"2454"
累加:"2353"
轮转:"5323"
累加:"5222"
累加:"5121"
轮转:"2151"
累加:"2050"​​​​​
无法获得字典序小于 "2050" 的字符串。

示例 2:

输入:s = "74", a = 5, b = 1
输出:"24"
解释:执行操作如下:
初态:"74"
轮转:"47"
累加:"42"
轮转:"24"​​​​​
无法获得字典序小于 "24" 的字符串。

示例 3:

输入:s = "0011", a = 4, b = 2
输出:"0011"
解释:无法获得字典序小于 "0011" 的字符串。

 

提示:

  • 2 <= s.length <= 100
  • s.length 是偶数
  • s 仅由数字 09 组成
  • 1 <= a <= 9
  • 1 <= b <= s.length - 1

2、思路

没啥思路,BFS+备忘录过了

BFS枚举,遍历队列中的字符串,对每个字符串执行轮转和累加操作,如果操作结果没有被访问过则添加到下个队列,重复上述步骤直到队列为空

3、代码

function findLexSmallestString(s: string, a: number, b: number): string {
let ans = s, queue = [s], visited = new Set().add(s);

while (queue.length) {
const next = [];

for (const q of queue) {
// 是否最小
if (q < ans) ans = q;

// 轮转
const rs = q.slice(q.length - b) + q.slice(0, q.length - b);
if (!visited.has(rs)) visited.add(rs), next.push(rs);

// 累加
const arr = [...q];
for (let i = 1; i < q.length; i += 2) {
arr[i] = `${(+q[i] + a) % 10}`;
}
const as = arr.join('');
if (!visited.has(as)) visited.add(as), next.push(as);
}

queue = next;
}

return ans;
};

4、执行结果

image.png