JS算法之剪绳子及二进制中1的个数

278 阅读4分钟

这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战

剪绳子 II

剑指Offer 14 - II. 剪绳子 II

给你一根长度为n的绳子,请把绳子剪成整数长度的m段(m、n都是整数,n > 1并且m > 1),每段绳子的长度记为k[0],k[1]...k[m-1]。请问k[0] * k[1] * ... * k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回1。

示例1:

 输入: 2
 输出: 1
 解释: 2 = 1 + 1, 1 × 1 = 1

示例2:

 输入: 10
 输出: 36
 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36

提示:2 <= n <= 1000

题解

法一 BigInt + 动态规划

与上题《剪绳子》不同的是,本题需要涉及“大数越界的求余问题”。因为Math.max不能求BigInt类型的最值,所以我们可以自己写个函数判断最值。

 /**
  * @param {number} n
  * @return {number}
  */
 var cuttingRope = function (n) {
     let dp = new Array(n+1).fill(BigInt(1));
     for (let i = 3; i <= n; i++) {
         for (let j = 1; j < i; ++j) {
             dp[i] = max(dp[i], dp[j] * BigInt((i - j)), BigInt(j * (i - j)));
         }
     }
     return dp[n] % (1000000007n);
 };
 ​
 const max = (...args) => args.reduce((prev, curr) => prev > curr ? prev : curr)

法二

  • 一般求幂计算可以用Math.pow()
  • 如果极大值的情况,那么用for循环替换,并对其进行取模运算,这样就不会造成值的溢出
 /**
  * @param {number} n
  * @return {number}
  */
 var cuttingRope = function(n){
   if(n===2) return 1;
   if(n===3) return 2;
   // a的含义:n能拆成的3的个数
   const a = Math.floor(n/3);
   const b = n % 3;
   let max = 1;
   // n是3的倍数
   if(b===0){
     // Math.pow(3,a);
     for(let i = 0;i < a;i++){
       max = 3 * max % (1e9+7);
     }
     return max;
   } 
   // n是3k+1
   if(b===1) {
     max = 4;
     for(let i = 0;i < a-1;i++){
       max = 3 * max % (1e9+7);
     }
     return max;
   }
   max = 2;
   for(let i = 0;i < a;i++){
     max = 3 * max % (1e9+7);
   }
   return max;
 }

二进制中1的个数

剑指Offer 15.二进制中1的个数

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字为‘1‘的个数(也被称为汉明重量)。

示例1:

 输入:n = 11 (控制台输入 00000000000000000000000000001011)
 输出:3
 解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。

示例2:

 输入:n = 128 (控制台输入 00000000000000000000000010000000)
 输出:1
 解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。

示例3:

 输入:n = 4294967293 (控制台输入 11111111111111111111111111111101,部分语言中 n = -3)
 输出:31
 解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。

提示:输入必须是长度为32的二进制串

题解

法一 运用api

 /**
  * @param {number} n - a positive integer
  * @return {number}
  */
 var hammingWeight = function(n) {
     return n.toString(2).split('0').join('').length;
 };

法二 循环检查二进制位

直接循环检查给定整数n的二进制的每一位是否为1。

当检查第i位时,我们可以让n与2^i进行与运算,当且仅当n的第i位为1时,运算结果不为0。

1 << 2 即 0001 -> 0010

 var hammingWeight = function(n) {
   let len = 0;
   for(let i = 0;i < 32;i++){
     if((n & (1 << i))!==0){ // 判断n的第i位是否不为0
       len++;
     }
   }
   return len;
 };

时间复杂度O(k) (k=32),空间复杂度O(1)

法三 位运算优化

观察这个运算:n&(n- 1),其预算结果恰为把n的二进制中的最低位1变为n之后的结果。

如:6&(6-1)=4,二进制6=(110),4=(100),运算结果4即为把6的二进制位中的最低位的1变为0之后的结果。

这样我们可以利用这个位运算的性质加速我们的检查过程,在实际代码中,我们不断让当前的n与n-1做与运算,直到n变为0即可。因为每次运算会使得n的最低位的1被翻转,因此运算次数就等于n的二进制中1的个数。

 var hammingWeight = function(n){
   let ret = 0;
   while(n){
     n &= n-1;
     ret++;
   }
   return ret;
 }

时间复杂度:O(logN)。循环次数等于n的二进制位中1的个数,最坏情况下n的二进制位全部为1。我们需要循环​次。

空间复杂度:O(1)


坚持每日一练!前端小萌新一枚,希望能点个哇~