Long Luo's Life Notes

每一天都是奇迹

By Long Luo

Leetcode 686. 重复叠加字符串匹配 题目如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
686. 重复叠加字符串匹配

给定两个字符串a和b,寻找重复叠加字符串a的最小次数,使得字符串b成为叠加后的字符串a的子串,如果不存在则返回-1。

注意:字符串"abc"重复叠加0次是"",重复叠加1次是"abc",重复叠加2次是"abcabc"。

示例 1:
输入:a = "abcd", b = "cdabcdab"
输出:3
解释:a 重复叠加三遍后为"ab**cdabcdab**cd", 此时b是其子串。

示例 2:
输入:a = "a", b = "aa"
输出:2

示例 3:
输入:a = "a", b = "a"
输出:1

示例 4:
输入:a = "abc", b = "wxyz"
输出:-1

提示:
1 <= a.length <= 10^4
1 <= b.length <= 10^4
a 和 b 由小写英文字母组成

分析

可以分为下面\(3\)种情况:

  1. \(\textit{A}\) ==\(\textit{B}\),复制 \(1\) 次;

  2. \(lenA > lenB\),直觉是只需要复制 \(2\) 次;

  3. \(lenA < lenB\),首先至少将 \(\textit{A}\) 复制几次后长度 \(>=lenB\),然后至多再复制 \(\textit{A}\) 一次。

下面简单证明下,也就是复制次数的下限上限

  1. 至少将字符串 \(\textit{A}\) 复制 \(N\) 次之后主串 \(\textit{S}\),那么 \(lenS >= lenB\) ,才有可能匹配,至此我们得到了复制次数的下限\(\frac{lenB}{lenA}+1\)

  2. 如果从主串 \(\textit{S}\) 中找到子串 \(\textit{B}\),因此可以明确子串的起始位置,不会超过 \(lenA\);因为如果起始匹配位置 \(>= lenA\),那么说明在之前已经被匹配过了。

综上分析可知:

\[ \frac{lenB}{lenA} + 1 \le repeatedTimes \le \frac{lenB}{lenA} + 2 \]

方法一:暴力

思路与算法:

首先想到的是暴力法。最开始没有去分析复制次数上下限时,使用了一个朴素的想法:

  1. \(\textit{B}\) 中含有 \(\textit{A}\) 中没有的字母,那么直接返回 \(-1\)
  2. 最少复制次数:\(\textit{B}\) 中字母出现次数 \(\textit{A}\) 中字母次数的最大值。

使用 \(\texttt{HashMap}\) 存储字母出现次数。

代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public int repeatedStringMatch(String a, String b) {
if (a.equals(b) || a.contains(b)) {
return 1;
}

int min = 1;
Map<Character, Integer> mapA = new HashMap<>();
Map<Character, Integer> mapB = new HashMap<>();
for (char ch : a.toCharArray()) {
mapA.put(ch, mapA.getOrDefault(ch, 0) + 1);
}
for (char ch : b.toCharArray()) {
mapB.put(ch, mapB.getOrDefault(ch, 0) + 1);
}

for (char ch : mapB.keySet()) {
if (!mapA.containsKey(ch)) {
return -1;
} else {
int times = mapB.get(ch) / mapA.get(ch);
min = Math.max(min, times);
}
}

StringBuilder res = new StringBuilder(a);
for (int i = 2; i <= min + 1; i++) {
res.append(a);
if (res.toString().contains(b)) {
return i;
}
}

return -1;
}

因为字符串均由小写英文字母组成,所以可以直接用数组进行计数,速度提高了很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public int repeatedStringMatch(String a, String b) {
if (a.equals(b) || a.contains(b)) {
return 1;
}

int[] cntA = new int[26];
int[] cntB = new int[26];

for (char ch : a.toCharArray()) {
cntA[ch - 'a']++;
}
for (char ch : b.toCharArray()) {
cntB[ch - 'a']++;
}

int min = 1;

for (int i = 0; i < 26; i++) {
if (cntB[i] > 0 && cntA[i] == 0) {
return -1;
}

if (cntA[i] > 0) {
int times = cntB[i] / cntA[i];
min = Math.max(min, times);
}
}

StringBuilder res = new StringBuilder(a);
for (int i = 2; i <= min + 1; i++) {
res.append(a);
if (res.toString().contains(b)) {
return i;
}
}

return -1;
}

值得一提的是,如果不用比较 \(\textit{A}\), \(\textit{B}\) 字母的话,直接进行比较的话,那么耗时会成倍增加,所以预先排除一些test case有很大帮助。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int repeatedStringMatch(String a, String b) {
if (a.equals(b) || a.contains(b)) {
return 1;
}

int minRepeatTimes = Math.max(1, b.length() / a.length() + 1);

StringBuilder res = new StringBuilder(a);
for (int i = 2; i <= minRepeatTimes + 1; i++) {
res.append(a);
if (res.toString().contains(b)) {
return i;
}
}

return -1;
}

复杂度分析:

  • 时间复杂度:\(O(n + m + nm)\),其中 \(n\)\(\textit{A}\) 的长度,\(m\)\(\textit{B}\) 的长度。
  • 空间复杂度:\(O(26)\)
阅读全文 »

By Long Luo

Leetcode 997. 找到小镇的法官 题解。

方法一:图论

思路与算法:

  1. 如果存在法官,那么所有人都会信任法官,在结合条件\(1\),可以得出信任法官的人数为\(n - 1\)
  2. 如果不存在法官,那么也可能存在某些人被所有人信任,这个人的信任人数也为\(n - 1\),但是他也会信任别人
  3. 可以以此作为区分other和judge的条件,假设每个人都有信任值,那么定义一个数组长度为\(n\),用来存放\(n\)个人的信任值:
  • 如果一个人信任了别人,那么这个人的信任值\(-1\)
  • 如果一个人被别人信任,那么这个人的信任值\(+1\)

所以:

  1. 当一个人被所有人信任,并且他没有信任其它人时,这个人的信任值就是\(n - 1\),那么此人就是法官;
  2. 当一个人被所有人信任,但是他也信任了其他人时,这个人的信任值\(< n - 1\)。 其它情况下,每个人的信任值都会小于\(n - 1\)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public int findJudge(int N, int[][] trust) {
if (N <= 0) {
return -1;
} else if (N == 1) {
return 1;
}

// [0]: In [1]: Out
int[][] count = new int[N][2];
for (int[] item : trust) {
count[item[0] - 1][1]++;
count[item[1] - 1][0]++;
}

for (int i = 0; i < N; i++) {
if (count[i][0] == N - 1 && count[i][1] == 0) {
return i + 1;
}
}

return -1;
}
阅读全文 »

By Long Luo

Leetcode 419. 甲板上的战舰

方法一:一次遍历 + 不修改原始数组

一次扫描,\(O(m \times n)\) 额外空间,使用一个与矩阵同等大小的辅助数组 \(visited\) 记录访问过的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static int countBattleships(char[][] board) {
int ans = 0;
int m = board.length;
int n = board[0].length;
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (!visited[i][j] && board[i][j] == 'X') {
visited[i][j] = true;
ans++;

for (int k = i + 1; k < m && board[k][j] == 'X'; k++) {
visited[k][j] = true;
}

for (int k = j + 1; k < n && board[i][k] == 'X'; k++) {
visited[i][k] = true;
}
}
}
}

return ans;
}

复杂度分析:

  • 时间复杂度:\(O(m \times n \times \max(m, n))\)
  • 空间复杂度:\(O(m \times n)\)

方法二:一次遍历 + 修改原始数组

一次扫描,允许修改原始数组,遍历之后将原来的X修改为.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static int countBattleships(char[][] board) {
int ans = 0;
int m = board.length;
int n = board[0].length;
boolean[][] visited = new boolean[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (!visited[i][j] && board[i][j] == 'X') {
visited[i][j] = true;
ans++;

for (int k = i + 1; k < m && board[k][j] == 'X'; k++) {
visited[k][j] = true;
}

for (int k = j + 1; k < n && board[i][k] == 'X'; k++) {
visited[i][k] = true;
}
}
}
}

return ans;
}

复杂度分析:

  • 时间复杂度:\(O(m \times n \times \max(m, n))\)
  • 空间复杂度:\(O(1)\)
阅读全文 »

By Long Luo

Leetcode 1816. 截断句子 题解。

方法一:Regex

很简单,首先想到的是正则表达式,根据空格分割单词。

1
2
3
4
5
6
7
8
9
10
public String truncateSentence(String s, int k) {
String[] words = s.split("\\s+");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < k; i++) {
sb.append(words[i]).append(" ");
}

sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}

复杂度分析

  • 时间复杂度:\(O(N)\) ,其中 \(N\)\(\textit{s}\) 的长度。
  • 空间复杂度:\(O(N)\)

方法二:模拟

简单的模拟,统计空格与句子结尾的数目来统计单词数,当已经有 \(k\) 个单词时,返回答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public String truncateSentence(String s, int k) {
StringBuilder sb = new StringBuilder();
int len = s.length();
for (int i = 0; i < len; i++) {
sb.append(s.charAt(i));
if (s.charAt(i) == ' ') {
k--;
}
if (k == 0) {
sb.deleteCharAt(sb.length() - 1);
break;
}
}

return sb.toString();
}

复杂度分析

  • 时间复杂度:\(O(N)\) ,其中 \(N\)\(\textit{s}\) 的长度。
  • 空间复杂度:\(O(N)\)

方法三:优化

我们可以只记录最后截断的下标 \(\textit{end}\) ,返回句子 \(\textit{s}\)\(\textit{end}\) 处截断的句子。

阅读全文 »

By Long Luo

This article is the solution 2 Approaches: Brute Force and Binary Exponentiation of Problem 372. Super Pow .

Intuition

This problem is to find a integer raised to the power a very large number whose length may be \(200\) or more.

Brute Froce

We multiply \(a\) to itself \(b\) times. That is, \(a^b = \underbrace{a \times a \dots \times a}_b\) .

We can write such code easily.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static int superPow(int a, int[] b) {
if (a == 1) {
return 1;
}

int ans = a;
int len = b.length;
for (int i = len - 1; i >= 0; i--) {
int base = (int) Math.pow(10, len - 1 - i);
int num = b[i] * base;
for (int j = 1; j < num; j++) {
ans = ((ans % 1337) * (a % 1337)) % 1337;
}
}

return ans;
}

Analysis

  • Time Complexity: \(O(10^mb_i)\) , \(m\) is the length of array b.
  • Space Complexity: \(O(1)\)

Obiviously, it will exceed time limit, so we have to find a more efficiently algorithm.

阅读全文 »
0%