1764-通过连接另一个数组的子数组得到一个数组

Raphael Liu Lv10

给你一个长度为 n 的二维整数数组 groups ,同时给你一个整数数组 nums

你是否可以从 nums 中选出 n不相交 的子数组,使得第 i 个子数组与 groups[i] (下标从 0
开始)完全相同,且如果 i > 0 ,那么第 (i-1) 个子数组在 nums 中出现的位置在第 i 个子数组前面。(也就是说,这些子数组在
nums 中出现的顺序需要与 groups 顺序相同)

如果你可以找出这样的 n 个子数组,请你返回 true ,否则返回 false

如果不存在下标为 k 的元素 nums[k] 属于不止一个子数组,就称这些子数组是 不相交 的。子数组指的是原数组中连续元素组成的一个序列。

示例 1:

**输入:** groups = [[1,-1,-1],[3,-2,0]], nums = [1,-1,0,1,-1,-1,3,-2,0]
**输出:** true
**解释:** 你可以分别在 nums 中选出第 0 个子数组 [1,-1,0, **1,** **-1,** **-1** ,3,-2,0] 和第 1 个子数组 [1,-1,0,1,-1,-1, **3,** **-2,0** ] 。
这两个子数组是不相交的,因为它们没有任何共同的元素。

示例 2:

**输入:** groups = [[10,-2],[1,2,3,4]], nums = [1,2,3,4,10,-2]
**输出:** false
**解释:** 选择子数组 [ **1,2,3,4** ,10,-2] 和 [1,2,3,4, **10,-2** ] 是不正确的,因为它们出现的顺序与 groups 中顺序不同。
[10,-2] 必须出现在 [1,2,3,4] 之前。

示例 3:

**输入:** groups = [[1,2,3],[3,4]], nums = [7,7,1,2,3,4,7,7]
**输出:** false
**解释:** 选择子数组 [7,7, **1,2,3** ,4,7,7] 和 [7,7,1,2, **3,4** ,7,7] 是不正确的,因为它们不是不相交子数组。
它们有一个共同的元素 nums[4] (下标从 0 开始)。

提示:

  • groups.length == n
  • 1 <= n <= 103
  • 1 <= groups[i].length, sum(groups[i].length) <= 103
  • 1 <= nums.length <= 103
  • -107 <= groups[i][j], nums[k] <= 107

方法一:贪心 + 双指针

使用变量 i 指向需要匹配的数组,即 groups}[i]。遍历数组 nums,假设当前遍历到第 k 个元素:

  • 以 nums}[k] 为首元素的子数组与 groups}[i] 相同,那么 groups}[i] 可以找到对应的子数组。为了满足不相交的要求,我们将 k 加上数组 groups}[i] 的长度,并且将 i 加 1;

  • 以 nums}[k] 为首元素的子数组与 groups}[i] 不相同,那么我们直接将 k 加 1。

遍历结束时,如果 groups 的所有数组都找到对应的子数组,即 i = n 成立,返回 true;否则返回 false。

贪心的正确性

证明:假设存在 n 个不相交的子数组,使得第 i 个子数组与 groups}[i] 完全相同,并且第 i 个子数组的首元素下标为 k,那么在匹配查找的过程中,如果存在下标 k_1 \lt k 也满足第 i 个子数组的要求,显然我们将 k_1 替代 k 是不影响后续的匹配的。

[sol1-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def canChoose(self, groups: List[List[int]], nums: List[int]) -> bool:
k = 0
for g in groups:
while k + len(g) <= len(nums):
if nums[k: k + len(g)] == g:
k += len(g)
break
k += 1
else:
return False
return True
[sol1-C++]
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
class Solution {
public:
bool check(vector<int> &g, vector<int> &nums, int k) {
if (k + g.size() > nums.size()) {
return false;
}
for (int j = 0; j < g.size(); j++) {
if (g[j] != nums[k + j]) {
return false;
}
}
return true;
}

bool canChoose(vector<vector<int>>& groups, vector<int>& nums) {
int i = 0;
for (int k = 0; k < nums.size() && i < groups.size();) {
if (check(groups[i], nums, k)) {
k += groups[i].size();
i++;
} else {
k++;
}
}
return i == groups.size();
}
};
[sol1-Java]
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
class Solution {
public boolean canChoose(int[][] groups, int[] nums) {
int i = 0;
for (int k = 0; k < nums.length && i < groups.length;) {
if (check(groups[i], nums, k)) {
k += groups[i].length;
i++;
} else {
k++;
}
}
return i == groups.length;
}

public boolean check(int[] g, int[] nums, int k) {
if (k + g.length > nums.length) {
return false;
}
for (int j = 0; j < g.length; j++) {
if (g[j] != nums[k + j]) {
return false;
}
}
return true;
}
}
[sol1-C#]
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
public class Solution {
public bool CanChoose(int[][] groups, int[] nums) {
int i = 0;
for (int k = 0; k < nums.Length && i < groups.Length;) {
if (Check(groups[i], nums, k)) {
k += groups[i].Length;
i++;
} else {
k++;
}
}
return i == groups.Length;
}

public bool Check(int[] g, int[] nums, int k) {
if (k + g.Length > nums.Length) {
return false;
}
for (int j = 0; j < g.Length; j++) {
if (g[j] != nums[k + j]) {
return false;
}
}
return true;
}
}
[sol1-C]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool check(const int *g, int gSize, const int *nums, int numsSize, int k) {
if (k + gSize > numsSize) {
return false;
}
for (int j = 0; j < gSize; j++) {
if (g[j] != nums[k + j]) {
return false;
}
}
return true;
}

bool canChoose(int** groups, int groupsSize, int* groupsColSize, int* nums, int numsSize) {
int i = 0;
for (int k = 0; k < numsSize && i < groupsSize;) {
if (check(groups[i], groupsColSize[i], nums, numsSize, k)) {
k += groupsColSize[i];
i++;
} else {
k++;
}
}
return i == groupsSize;
}
[sol1-JavaScript]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var canChoose = function(groups, nums) {
let i = 0;
for (let k = 0; k < nums.length && i < groups.length;) {
if (check(groups[i], nums, k)) {
k += groups[i].length;
i++;
} else {
k++;
}
}
return i === groups.length;
}

const check = (g, nums, k) => {
if (k + g.length > nums.length) {
return false;
}
for (let j = 0; j < g.length; j++) {
if (g[j] !== nums[k + j]) {
return false;
}
}
return true;
};
[sol1-Golang]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func canChoose(groups [][]int, nums []int) bool {
next:
for _, g := range groups {
for len(nums) >= len(g) {
if equal(nums[:len(g)], g) {
nums = nums[len(g):]
continue next
}
nums = nums[1:]
}
return false
}
return true
}

func equal(a, b []int) bool {
for i, x := range a {
if x != b[i] {
return false
}
}
return true
}

复杂度分析

  • 时间复杂度:O(m \times \max g_i),其中 m 是数组 nums 的长度,g_i 是数组 groups}[i] 的长度。最坏情况下,数组 nums 在每个位置都调用一次 check 函数,因此总时间复杂度为 O(m \times \max g_i)。

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

方法二:KMP 匹配算法

关于 KMP 算法的详细说明可以参考官方题解「实现 strStr() 」,本文不作详细说明。类似于字符串的匹配查找,数组也可以使用 KMP 算法进行匹配。我们依次枚举数组 groups}[i],并且使用变量 k 表示 nums 开始匹配查找的起点,初始时 k=0,如果匹配查找成功,那么将 k 设为查找到的下标加上 groups}[i] 的长度,否则直接返回 false,匹配到最后直接返回 true。

[sol2-C++]
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
39
40
41
42
43
class Solution {
public:
int find(vector<int> &nums, int k, vector<int> &g) {
int m = g.size(), n = nums.size();
if (k + g.size() > nums.size()) {
return -1;
}
vector<int> pi(m);
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && g[i] != g[j]) {
j = pi[j - 1];
}
if (g[i] == g[j]) {
j++;
}
pi[i] = j;
}
for (int i = k, j = 0; i < n; i++) {
while (j > 0 && nums[i] != g[j]) {
j = pi[j - 1];
}
if (nums[i] == g[j]) {
j++;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}

bool canChoose(vector<vector<int>>& groups, vector<int>& nums) {
int k = 0;
for (int i = 0; i < groups.size(); i++) {
k = find(nums, k, groups[i]);
if (k == -1) {
return false;
}
k += groups[i].size();
}
return true;
}
};
[sol2-Java]
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
39
40
41
42
class Solution {
public boolean canChoose(int[][] groups, int[] nums) {
int k = 0;
for (int i = 0; i < groups.length; i++) {
k = find(nums, k, groups[i]);
if (k == -1) {
return false;
}
k += groups[i].length;
}
return true;
}

public int find(int[] nums, int k, int[] g) {
int m = g.length, n = nums.length;
if (k + g.length > nums.length) {
return -1;
}
int[] pi = new int[m];
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && g[i] != g[j]) {
j = pi[j - 1];
}
if (g[i] == g[j]) {
j++;
}
pi[i] = j;
}
for (int i = k, j = 0; i < n; i++) {
while (j > 0 && nums[i] != g[j]) {
j = pi[j - 1];
}
if (nums[i] == g[j]) {
j++;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}
}
[sol2-C#]
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
39
40
41
42
public class Solution {
public bool CanChoose(int[][] groups, int[] nums) {
int k = 0;
for (int i = 0; i < groups.Length; i++) {
k = Find(nums, k, groups[i]);
if (k == -1) {
return false;
}
k += groups[i].Length;
}
return true;
}

public int Find(int[] nums, int k, int[] g) {
int m = g.Length, n = nums.Length;
if (k + g.Length > nums.Length) {
return -1;
}
int[] pi = new int[m];
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && g[i] != g[j]) {
j = pi[j - 1];
}
if (g[i] == g[j]) {
j++;
}
pi[i] = j;
}
for (int i = k, j = 0; i < n; i++) {
while (j > 0 && nums[i] != g[j]) {
j = pi[j - 1];
}
if (nums[i] == g[j]) {
j++;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}
}
[sol2-C]
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
39
40
41
int find(const int *nums, int numsSize, int k, const int *g, int gSize) {
int m = gSize, n = numsSize;
if (k + m > n) {
return -1;
}
int pi[m];
pi[0] = 0;
for (int i = 1, j = 0; i < m; i++) {
while (j > 0 && g[i] != g[j]) {
j = pi[j - 1];
}
if (g[i] == g[j]) {
j++;
}
pi[i] = j;
}
for (int i = k, j = 0; i < n; i++) {
while (j > 0 && nums[i] != g[j]) {
j = pi[j - 1];
}
if (nums[i] == g[j]) {
j++;
}
if (j == m) {
return i - m + 1;
}
}
return -1;
}

bool canChoose(int** groups, int groupsSize, int* groupsColSize, int* nums, int numsSize) {
int k = 0;
for (int i = 0; i < groupsSize; i++) {
k = find(nums, numsSize, k, groups[i], groupsColSize[i]);
if (k == -1) {
return false;
}
k += groupsColSize[i];
}
return true;
}
[sol2-JavaScript]
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
39
40
var canChoose = function(groups, nums) {
let k = 0;
for (let i = 0; i < groups.length; i++) {
k = find(nums, k, groups[i]);
if (k == -1) {
return false;
}
k += groups[i].length;
}
return true;
}

const find = (nums, k, g) => {
let m = g.length, n = nums.length;
if (k + g.length > nums.length) {
return -1;
}
const pi = new Array(m).fill(0);
for (let i = 1, j = 0; i < m; i++) {
while (j > 0 && g[i] !== g[j]) {
j = pi[j - 1];
}
if (g[i] === g[j]) {
j++;
}
pi[i] = j;
}
for (let i = k, j = 0; i < n; i++) {
while (j > 0 && nums[i] !== g[j]) {
j = pi[j - 1];
}
if (nums[i] === g[j]) {
j++;
}
if (j === m) {
return i - m + 1;
}
}
return -1;
};

复杂度分析

  • 时间复杂度:O(m + \sum g_i),其中 m 是数组 nums 的长度,g_i 是数组 groups}[i] 的长度。最坏情况下,每一个 groups}[i] 都调用一次 find,因此总时间复杂度为 O(m + \sum g_i)。

  • 空间复杂度:O(\max g_i)。对 groups}[i] 调用一次 KMP 算法需要申请 O(g_i) 的空间。

 Comments
On this page
1764-通过连接另一个数组的子数组得到一个数组