【算法】JS 暴力 or 质因子分解 力扣周赛6309. 分割数组使乘积互质

题目链接

6309. 分割数组使乘积互质

题目描述

给你一个长度为 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;
}

参考:

本质是跳跃游戏(Python/Java/C++/Go) - 分割数组使乘积互质 - 力扣(LeetCode)

js 暴力 - 分割数组使乘积互质 - 力扣(LeetCode)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值