题目链接
题目描述
给你一个长度为 n 的整数数组 nums ,下标从 0 开始。
如果在下标 i 处 分割 数组,其中 0 <= i <= n - 2 ,使前 i + 1 个元素的乘积和剩余元素的乘积互质,则认为该分割 有效 。
例如,如果 nums = [2, 3, 3] ,那么在下标 i = 0 处的分割有效,因为 2 和 9 互质,而在下标 i = 1 处的分割无效,因为 6 和 3 不互质。在下标 i = 2 处的分割也无效,因为 i == n - 1 。
返回可以有效分割数组的最小下标 i ,如果不存在有效分割,则返回 -1 。
当且仅当 gcd(val1, val2) == 1 成立时,val1 和 val2 这两个值才是互质的,其中 gcd(val1, val2) 表示 val1 和 val2 的最大公约数。
示例 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
解题思路
暴力的做法是求分隔后的数组乘积,再用gcd判断,虽然可以用BigInt使得数字范围不会爆,但是gcd的调用栈会爆
var findValidSplit = function(nums) {
let pre = BigInt(nums[0]), suf = 1n, j = 1
for (let i = 1; i < nums.length; i++) {
suf *= BigInt(nums[i])
}
for (;j < nums.length - 1; j++) {
if (gcd(pre, suf) !== 1n) {
pre *= BigInt(nums[j])
suf /= BigInt(nums[j])
j++
} else break
}
if (j === nums.length) return -1
return j - 1
};
function gcd(x, y) {
if (y === 0n) return x
return gcd(y, x % y)
}
我们可以先观察示例1,nums = [4,7,8,15,3,5],从下标为0开始,将数组分隔为
4 | 7,8,15,3,5
没有必要将7*8*15*3*5的结果与4求最大公约数,因为8和4的最大公约数为4,7*8*15*3*5与4显然就不会互质,所以我们至少要将数组从8这个位置开始分隔,即
4,7,8 | 15,3,5
因此,正确的暴力思路如下
暴力
/**
* @param {number[]} nums
* @return {number}
*/
var gcb = (a, b) => (b === 0 ? a : gcb(b, a % b));
var findValidSplit = function (nums) {
let ans = 0;
for (let i = 0; i <= ans; i++) {
for (let j = ans + 1; j < nums.length; j++) {
if (gcb(nums[i], nums[j]) != 1) ans = j;
}
if (ans == nums.length - 1) return -1;
}
return ans;
};
再来思考非暴力的思路
只要15,3,5的乘积与4,7,8的乘积互质即可,
那么我们应该如何快速判断是否互质呢?
再来看一个简单的例子,[6,2,3]
这个例子显然怎么分隔也无法互质,因为6 = 2 * 3,我们只需要构建一个哈希表,存储每个质数出现的最开始的位置,再维护一个数组,存储最后一个与该数非互质的下标,如果没有则为-1
[4,7,8,15,3,5]中得到的结果为
var left = {
2: 0,
3: 3,
5: 3,
7: 1
}
var right = [3, -1, 5, -1, -1, -1]
代码及注释如下
质因子分解
var findValidSplit = function(nums) {
const n = nums.length;
const left = {}; // left[p] 表示质数 p 首次出现的下标
const right = new Array(n).fill(-1); // right[i] 表示左端点为 i 的区间的右端点的最大值
// 维护哈希表left和数组right
function f(p, i) {
if (p in left) {
right[left[p]] = i; // 记录左端点 l 对应的右端点的最大值
} else {
left[p] = i; // 第一次遇到质数 p
}
}
for (let i = 0; i < n; i++) {
let x = nums[i];
let d = 2; // 从最小的质数2开始
// 求nums[i]的非1质因子
while (d * d <= x) {
// 找到了x的质因子d
if (x % d === 0) {
// 维护哈希表left和数组right
f(d, i);
x = Math.floor(x / d);
// 质因子去重
while (x % d === 0) {
x = Math.floor(x / d);
}
}
d++;
}
if (x > 1) f(x, i);
}
// max_r代表至少要从下标为max_r的位置分隔,因为已经发现max_r左边的数字非互质
let max_r = 0;
for (let l = 0; l < n; l++) {
const r = right[l];
// 代表下标为l右边都没有l左边的
if (l > max_r) {
return l - 1;
}
max_r = Math.max(max_r, r);
}
return -1;
}
参考: