2584-分割数组使乘积互质

Raphael Liu Lv10

给你一个长度为 n 的整数数组 nums ,下标从 0 开始。

如果在下标 i分割 数组,其中 0 <= i <= n - 2 ,使前 i + 1 个元素的乘积和剩余元素的乘积互质,则认为该分割
有效

  • 例如,如果 nums = [2, 3, 3] ,那么在下标 i = 0 处的分割有效,因为 29 互质,而在下标 i = 1 处的分割无效,因为 63 不互质。在下标 i = 2 处的分割也无效,因为 i == n - 1

返回可以有效分割数组的最小下标 i ,如果不存在有效分割,则返回 -1

当且仅当 gcd(val1, val2) == 1 成立时,val1val2 这两个值才是互质的,其中 gcd(val1, val2)
表示 val1val2 的最大公约数。

示例 1:

**输入:** nums = [4,7,8,15,3,5]
**输出:** 2
**解释:** 上表展示了每个下标 i 处的前 i + 1 个元素的乘积、剩余元素的乘积和它们的最大公约数的值。
唯一一个有效分割位于下标 2 。

示例 2:

**输入:** nums = [4,7,15,8,3,5]
**输出:** -1
**解释:** 上表展示了每个下标 i 处的前 i + 1 个元素的乘积、剩余元素的乘积和它们的最大公约数的值。
不存在有效分割。

提示:

  • n == nums.length
  • 1 <= n <= 104
  • 1 <= nums[i] <= 106

对于一个质因子 p,设它在数组中的最左和最右的位置为 left 和 right。

那么答案是不能在区间 [\textit{left},\textit{right}) 中的。注意区间右端点可能为答案。

因此这题本质上和 55. 跳跃游戏 是类似的,找从 0 出发,最远遇到的区间右端点,即为答案。

附:视频讲解 ,包含质因子分解的讲解。

[sol1-Python3]
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
class Solution:
def findValidSplit(self, nums: List[int]) -> int:
left = {} # left[p] 表示质数 p 首次出现的下标
right = [0] * len(nums) # right[i] 表示左端点为 i 的区间的右端点的最大值

def f(p: int, i: int) -> None:
if p in left:
right[left[p]] = i # 记录左端点 l 对应的右端点的最大值
else:
left[p] = i # 第一次遇到质数 p

for i, x in enumerate(nums):
d = 2
while d * d <= x: # 分解质因数
if x % d == 0:
f(d, i)
x //= d
while x % d == 0:
x //= d
d += 1
if x > 1: f(x, i)

max_r = 0
for l, r in enumerate(right):
if l > max_r: # 最远可以遇到 max_r
return max_r # 也可以写 l-1
max_r = max(max_r, r)
return -1
[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
27
28
29
30
31
class Solution {
public int findValidSplit(int[] nums) {
int n = nums.length;
var left = new HashMap<Integer, Integer>(); // left[p] 表示质数 p 首次出现的下标
var right = new int[n]; // right[i] 表示左端点为 i 的区间的右端点的最大值

for (int i = 0; i < n; i++) {
int x = nums[i];
for (int d = 2; d * d <= x; ++d) // 分解质因数
if (x % d == 0) {
if (left.containsKey(d))
right[left.get(d)] = i; // 记录左端点对应的右端点的最大值
else
left.put(d, i); // 第一次遇到质数 d
for (x /= d; x % d == 0; x /= d) ;
}
if (x > 1)
if (left.containsKey(x))
right[left.get(x)] = i;
else
left.put(x, i);
}

for (int l = 0, maxR = 0; l < n; l++) {
if (l > maxR) // 最远可以遇到 maxR
return maxR; // 也可以写 l-1
maxR = Math.max(maxR, right[l]);
}
return -1;
}
}
[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
28
29
30
31
32
33
class Solution {
public:
int findValidSplit(vector<int> &nums) {
unordered_map<int, int> left; // left[p] 表示质数 p 首次出现的下标
int n = nums.size(), right[n]; // right[i] 表示左端点为 i 的区间的右端点的最大值
memset(right, 0, sizeof(right));
auto f = [&](int p, int i) {
auto it = left.find(p);
if (it == left.end())
left[p] = i; // 第一次遇到质数 p
else
right[it->second] = i; // 记录左端点 l 对应的右端点的最大值
};

for (int i = 0; i < n; ++i) {
int x = nums[i];
for (int d = 2; d * d <= x; ++d) { // 分解质因数
if (x % d == 0) {
f(d, i);
for (x /= d; x % d == 0; x /= d);
}
}
if (x > 1) f(x, i);
}

for (int l = 0, max_r = 0; l < n; ++l) {
if (l > max_r) // 最远可以遇到 max_r
return max_r; // 也可以写 l-1
max_r = max(max_r, right[l]);
}
return -1;
}
};
[sol1-Go]
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
func findValidSplit(nums []int) int {
left := map[int]int{} // left[p] 表示质数 p 首次出现的下标
right := make([]int, len(nums)) // right[i] 表示左端点为 i 的区间的右端点的最大值
f := func(p, i int) {
if l, ok := left[p]; ok {
right[l] = i // 记录左端点 l 对应的右端点的最大值
} else {
left[p] = i // 第一次遇到质数 p
}
}

for i, x := range nums {
for d := 2; d*d <= x; d++ { // 分解质因数
if x%d == 0 {
f(d, i)
for x /= d; x%d == 0; x /= d {
}
}
}
if x > 1 {
f(x, i)
}
}

maxR := 0
for l, r := range right {
if l > maxR { // 最远可以遇到 maxR
return maxR // 也可以写 l-1
}
maxR = max(maxR, r)
}
return -1
}

func max(a, b int) int { if a < b { return b }; return a }

复杂度分析

  • 时间复杂度:O(n\sqrt U),其中 n 为 nums 的长度,U=\max(\textit{nums})。
  • 空间复杂度:O\left(n + \dfrac{U}{\log U}\right)。U 范围内的质数个数有 O\left(\dfrac{U}{\log U}\right) 个。

相似题目

 Comments
On this page
2584-分割数组使乘积互质