leetcode算法学习-二分法

266 阅读3分钟

1. 二分法

O (logN)

374. 猜数字大小

猜数字游戏的规则如下:

  • 每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
  • 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。

你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-11 或 0):

  • -1:我选出的数字比你猜的数字小 pick < num
  • 1:我选出的数字比你猜的数字大 pick > num
  • 0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num

返回我选出的数字。

/** 
 * Forward declaration of guess API.
 * @param {number} num   your guess
 * @return 	            -1 if num is lower than the guess number
 *			             1 if num is higher than the guess number
 *                       otherwise return 0
 * var guess = function(num) {}
 */
/**
 * @param {number} n
 * @return {number}
 */
var guessNumber = function(n) {
    let start = 0
    let end = n
  while (start < end) {
       let mid =start + Math.floor( (end - start ) / 2)   //Math.floor去除多余小数入 1.5 => 1 
       let guessNum = guess(mid)
    //    console.log("start",start,"end",end,"mid",mid,"guessNum",guessNum)
       //如果返回 -1 或者 1 证明 最小值和最大值都不在范围内。
       if(guessNum == -1) { // 实际的值比较小
         end = mid -1
       } else if(guessNum == 1) {// 实际的值比较大
         start = mid + 1 //每次都要+1,因为mid 确定不在start 需要+1
       } else {
           return mid
       }
  }
  return start
};
// @lc code=end

69. Sqrt(x)

  • 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
  • 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
  • 注意: 不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

示例 1:

输入: x = 4
输出: 2

示例 2:

输入: x = 8
输出: 2
解释: 8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。

答题

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function(x) {
    //实现逻辑,从1 到 x/2 之间 一直找mid,并且判断 mid *mid 是否等于 x
    if (x < 2) {
        return x
    }
    let left = 1 
    let right = Math.floor(x / 2) 
    let mid 
    while (left <= right) {
         mid = Math.floor( left  + (right - left ) / 2 )
        if(mid * mid == x) {
            return mid
        }else {
            if(mid * mid > x) { 
                right = mid - 1 
            }else {
                left = mid + 1
            }   
        }
    }
    return right
};

50. Pow(x, n)

实现 pow(xn) ,即计算 x 的 n 次幂函数(即,xn )。

示例 1:

输入: x = 2.00000, n = 10
输出: 1024.00000

示例 2:

输入: x = 2.10000, n = 3
输出: 9.26100

示例 3:

输入: x = 2.00000, n = -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25

答题

/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
 
 //for + parseFloat 迭代
 var myPow = function(x, n) {
    if (n == 0) {
        return 1;
    }
    x = parseFloat(x);
    if (n < 0) {
        x = parseFloat(1 / x);
        n = -n;
    }
    var subResult = x;
    var result = 1;
    for (var i=n; i>0; i=parseInt(i/2)) {
        if (i&1 == 1) {
            result *= subResult;
        }
        subResult *= subResult;
    }
    return result;
};

//for + 迭代
var myPow = function (x, n) {
    // 0和1的n次幂都为其本身
    if (x === 0 || x === 1) {
      return x;
    }
    // 负1的偶数幂为1,奇数幂为-1
    if (x === -1) {
      return n % 2 ? -1 : 1;
    }
    // 特殊处理会超时的Case
    // 或者可以写成n === -0x80000000
    if (n === -2147483648) {
      return 0;
    }
    let result = 1; // 初始值为1
    const len = Math.abs(n); // 需要计算的次数,n有可能为负,因此要取绝对值
  
    for (let i = 0; i < len; i++) {
      result =
        n > 0
          ? result * x // n>0时即乘n次
          : result / x; // n>0时即/除n次
    }
  
    return result;
  }; 

 
 //分治算法
 var myPow = function(x, n) {
    if (n === 0) return 1 // n=0直接返回1
    if (n < 0) {   				//n<0时 x的n次方等于1除以x的-n次方分
        return 1 / myPow(x, -n)
    }
    if (n % 2) {    //n是奇数时 x的n次方 = x*x的n-1次方
        return x * myPow(x, n - 1)
    }
    return myPow(x * x, n / 2) //n是偶数,使用分治,一分为二,等于x*x的n/2次方 
 
};

//系统api
var myPow = function(x, n) {
    return Math.pow(x,n)
 };
//系统语法
 var myPow = function(x, n) {
    return x ** n
};

实现js里面的leftpad方法

leftpad(str,len,ch) ,是左边自动补齐剩下的长度,以ch插入,

如: leftpad("jason",10,'b') 结果为 bbbbbjason

问题分析

image.png

无论是否使用ch,ch都在一直的指数递增

通过len%2 == 1 判断,可以灵活控制total的需要 累加的ch 内容

累加关系

1-4 : b ,  bb  , b + bb  ,  bbbb ,
5-8: b + bbbb , bb + bbbb , b + bb + bbbb , bbbbbbbb

image.png

image.png

image.png

核心逻辑:

  1. 当遇到可以被2整除的多次的数,如4,8,16 ,就等价于 一直ch += ch ,直到判断1%2 == 1,total 直接加上背倍数级的b
  2. 当遇到可以被2整除的1次的数,如6,10,14,就等价于 ch += ch,只加一次,然后 total = total + ch(bb)
  3. 当遇到不可以被2整除的1次的数,如3,5,7,total直接等于 ch , total =  ch(b)

答题


//二分法 + 位运算优化 求余和除法
function leftpad(str,len,ch) {
    if(len <= str.length) {
        return str
    } 
    len = len - str.length
    let total = ''
    // console.log("len:",len)
    while (true) {
        if (len & 1) { //求余  相当于 做和1的位与运算
            total += ch
            // console.log("total += ch:",total)
        }
        if (len == 1) {//最后 只剩下 1 就直接+str 返回最终结果
            return total + str
        }
        ch += ch //这里不断翻倍的一直累加
        // console.log("ch += ch:",ch)
        len = len >> 1 // 右移相当于 除于2
        // console.log("parseInt(len/2):",len)
    }
}


//二分法
function leftpad(str,len,ch) {
    if(len <= str.length) {
        return str
    } 
    len = len - str.length
    let total = ''
    // console.log("len:",len)
    while (true) {
        if (len%2 == 1) { //判断当前len 或折半后的len 是否有余数,有的话 把当前二分的数据都保存一下
            total += ch
            // console.log("total += ch:",total)
        }
        if (len == 1) {//最后 只剩下 1 就直接+str 返回最终结果
            ////因为怎么折半后 最终都有 parseInt(3/2) = 1 ,或者2/2=1 
            return total + str
        }
        ch += ch //这里不断翻倍的一直累加
        // console.log("ch += ch:",ch)
        len = parseInt(len/2) //这里开始折半 
        // console.log("parseInt(len/2):",len)
    }
}


//测试验证逻辑
leftpad('jason',15,'b') 
ch = 'b' 
total = ''
len = 15-5 = 10 开始
//注意ch 一直都在翻倍的增加 b bb bbbb bbbbbbbb 
10 %  2  余0.  total = ''     ch = bb.    len = 10  / 2 = 5 
5  %  2  余1.  total = 'bb' , ch = bbbb.    len = 5  / 2 = 2 
2  %  2  余0.  total = 'bb' , ch = bbbbbbbb.    len = 2 / 2 = 1   
1  %  2  余1.  total = 'bbbbbbbbbb' ,   return total + str

//Array.join
function leftpad(str,len,ch) {
    if(len <= str.length) {
        return str
    } 
    len = len-str.length 
    return  Array(len+1).join(ch)+str //array join会少一位
}
//for循环
function leftpad (str,len,ch) {
    if(len <= str.length) {
        return str
    }
    if (!ch) ch = ' '; //这里修正当输入 为 ''空字符串时候 改为 ' '
    let preStr = ""
    len = len-str.length
    console.log("len",len)
    for (let i = 0;i < len ; i++) {
        preStr = preStr + ch 
    }
    return preStr + str
}
//while循环
function leftpad (str,len,ch) {
    if(len <= str.length) {
        return str
    } 
    if (!ch) ch = ' '; //这里修正当输入 为 ''空字符串时候 改为 ' '
    len = len-str.length
    while (len > 0) {
        str =  ch + str
        len--
    }
    return  str
}
// console.log(leftpad("jason",10,"a"))
//console.log(leftpad2("jason",10,'b'))