JavaScript刷LeetCode模板技巧篇(二)

简单总结一些用 JavaScript 刷力扣的基本调试技巧。最近又刷了点题,总结了些数据结构和算法,希望能对各为 JSer 刷题提供帮助。

此篇文章主要想给大家一些开箱即用的 JavaScipt 版本的代码模板,涉及到较复杂的知识点,原理部分可能会省略,有需要的话后面有时间可以给部分知识点单独写一篇详细的讲解。

BigInt

众所周知,JavaScript 只能精确表达 Number.MIN_SAFE_INTEGER(-2^53+1) ~ Number.MAX_SAFE_INTEGER(2^53-1) 的值。

而在一些题目中,常常会有较大的数字计算,这时就会产生误差。举个栗子:在控制台输入下面的两个表达式会得到相同的结果:

>> 123456789*123456789      // 15241578750190520
>> 123456789*123456789+1    // 15241578750190520

而如果使用 BigInt 则可以精确求值:

>> BigInt(123456789)*BigInt(123456789)              // 15241578750190521n
>> BigInt(123456789)*BigInt(123456789)+BigInt(1)    // 15241578750190522n

可以通过在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数 BigInt()。上面的表达式也可以写成:

>> 123456789n*123456789n       // 15241578750190521n
>> 123456789n*123456789n+1n    // 15241578750190522n

BigInt 只能与 BigInt 做运算,如果和 Number 进行计算需要先通过 BigInt() 做类型转换。

BigInt 支持运算符,+*-**% 。除 >>>(无符号右移)之外的位操作也可以支持。因为 BigInt 都是有符号的, >>>(无符号右移)不能用于 BigIntBigInt 不支持单目 (+) 运算符。

BigInt 也支持 / 运算符,但是会被向上取整。

const rounded = 5n / 2n; // 2n, not 2.5n

取模运算

在数据较大时,一般没有办法直接去进行计算,通常都会给一个大质数(例如,1000000007),求对质数取模后的结果。

取模运算的常用性质:

(a + b) % p = (a % p + b % p) % p
(a - b) % p = (a % p - b % p) % p
(a * b) % p = (a % p * b % p) % p
a ^ b % p = ((a % p) ^ b) % p

可以看出,加/减/乘/乘方,都可直接在运算的时候取模,至于除法则会复杂一些,稍后再讲。

举一个例子,LeetCode 1175. 质数排列

请你帮忙给从 1n 的数设计排列方案,使得所有的「质数」都应该被放在「质数索引」(索引从 1 开始)上;你需要返回可能的方案总数。

让我们一起来回顾一下「质数」:质数一定是大于 1 的,并且不能用两个小于它的正整数的乘积来表示。

由于答案可能会很大,所以请你返回答案 模 mod 10^9 + 7 之后的结果即可。

题目很简单,先求出质数的个数 x,则答案为 x!(n-x)!(不理解的可以去看题解区找题解,这里就不详细解释了)

由于阶乘的值很大,所以在求阶乘的时候需要在运算时取模,同时这里用到了上面所说的BigInt

/** * @param {number} n
 * @return {number} */
var numPrimeArrangements = function(n) {
   
    const mod = 1000000007n;
    // 先把100以内的质数打表(不想再写判断质数的代码了
    const prime = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97];
    // 预处理阶乘
    const fac = new Array(n + 1);
    fac[0] = 1n; // 要用bigint
    for (let i = 1; i <= n; i++) {
   
        fac[i] = fac[i - 1] * BigInt(i) % mod;
    }
    // 先求n以内的质数的个数
    const x = prime.filter(i => i <= n).length;
    // x!(n-x)!
    return fac[x] * fac[n - x] % mod;
};

快速幂

快速幂,顾名思义,快速求幂运算。原理也很简单,比如我们求 x^10 我们可以求 (x^5)^2 可以减少一半的运算。

假设我们求 (x^n)

  • 如果 n 是偶数,变为求 (x^(n/2))^2
  • 如果 n 是奇数,则求 (x^⌊n/2⌋)^2 * x⌊⌋ 是向下取整)

因为快速幂涉及到的题目一般数据都很大,需要取模,所以加了取模运算。其中,代码中 n>>=1 相当于 n=n/2if(n&1)是在判断n是否为奇数。

代码如下:

// x ^ n % mod
function pow(x, n, mod) {
   
    let ans = 1;
    while (n > 0) {
   
        if (n & 1) ans = ans * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return ans;
}

乘法逆元(数论倒数)

上面说了除法的取模会复杂一些,其实就是涉及了乘法逆元

当我们求 (a/b)%p 你以为会是简单的 ((a%p)/(b%p))%p?当然不是!(反例自己想去Orz

假设有 (a*x)%p=1 则称 ax关于p互为逆元(ax 关于 p 的逆元,xa 关于 p 的逆元)。比如:2*3%5=123 关于 5 互为逆元。

我们把 a 的逆元用 inv(a) 表示。那么:

(a/b) % p
= ( (a/b) * (b*inv(b)) ) % p // 因为(b*inv(b))为1
= (a * inv(b)) % p
= (a%p * inv(b)%p) % p

现在通过逆元神奇的把除法运算变没了~~~

问题在于怎么求乘法逆元。有两种方式,费马小定理扩展欧几里德算法

不求甚解的我只记了一种解法,即费马小定理:a^(p-1) ≡ 1 (mod p)

由费马小定理我们可以推论:a^(p-2) ≡ inv(a) (mod p)

数学家的事我们程序员就不要想那么多啦,记结论就好了。即:

a关于p的逆元为a^(p-2)

好了,现在

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值