實現除法操做

前文

  1. 一道算法題,涉及到了二進制的位操做,借這個機會整理一下相關的知識點,而且在這道算法題中進行了實踐
  2. 本文的解法來自於該算法題的一篇討論

1、算法題介紹

對除數和被除數實現除法運算,其中不使用乘法、除法和求餘操做,返回對應的商。如,
Input: dividend = 10, divisor = 3
Output: 3算法

解法1:暴力解法

class Solution
{
    public int divide(int dividend, int divisor)
    {
	    //定義除法,被除數是最小值,除以-1時,是最大值
	    if(dividend == Integer.MIN_VALUE && divisor == -1)
		return Integer.MAX_VALUE;
	    //^運算符,只有不一樣時,才爲true ; sign定義商是否爲負數  
	    int sign = (dividend < 0) ^ (divisor < 0) ? -1 : 1;
	    int result = 0;
	    //取除數和被除數的正數形式
	    long x = Math.abs((long)dividend);
	    long y = Math.abs((long)divisor);

	    //統計被除數共減去多少個除數,即爲商
	    while(x >= y)
	    {
		x -= y;
		result++;
	     }
	     return sign > 0 ? result : -result;
    }
}
複製代碼
  1. 暴力解法主要是經過不斷循環將y(除數)從x(被除數)中減去,直到x<y. x減去y的次數就是商,而最後剩下的小於y的部分就是餘數。暴力解法的時間複雜度很高,好比說,當x=231-1,y=1時,須要循環231-1次。
  • 時間複雜度:O(x),x是被除數。
  • 空間複雜度:O(1)。

解法2:除數左移位累加

class Solution
{
    public int divide(int dividend, int divisor)
    {
        if(dividend == Integer.MIN_VALUE && divisor == -1)
            return Integer.MAX_VALUE;
        int sign = (dividend < 0) ^ (divisor < 0) ? -1 : 1;
        int result = 0;
        long x = Math.abs((long)dividend);
        long y = Math.abs((long)divisor);
        while(x >= y)
        {
            int shift = 1;
            //將y持續向左移位,至關於y*2^(shift);
            //當y*2^(shift)< x <y*2^(shift+1)時,循環結束
            while(x >= (y << shift))
            {
                shift++;
            }
            x -= y << (shift - 1);
            //至關於x = y*2^(shift)+y*2^(shift-n)+.....+y*2^(0) + m;m是餘數
            result += 1 << (shift - 1);
        }
        return sign > 0 ? result : -result;
    }
}
複製代碼
  1. 這個方法,是計算最大的k,使得y*2k<=x,而後將y*2k從x中減去,將2k加到商裏面。好比說, 若是x= (1011)2,y=(10)2,那麼由於2*22<= 11並且2*23>11 因此k=2。因此,咱們將 (1011)2減去 (1000)2獲得 (11)2,而後,將2k=22=(100)2加到商中,而後將x更新爲(11)2繼續運算。
  2. 使用y*2k的優點在於,可使用很是高效的移位操做,並且每次循環後,最差的狀況下,x都會變爲原來的一半。若是n是x/y的商的位數,那麼總共須要n次循環。子循環中每次經過循環k次來找到y*2k的最大的k,所以:
  • 時間複雜度:O(n2),n是x/y的商的位數。
  • 空間複雜度:O(1)。

解法3:除數左移位遞減

class Solution
{
    public int divide(int dividend, int divisor)
    {
           if(dividend == Integer.MIN_VALUE && divisor == -1)
               return Integer.MAX_VALUE;
        
           int sign = (dividend < 0) ^ (divisor < 0) ? -1 : 1;
           int result = 0;
           int power = 32;
          //long是8字節,64位;int是4字節,32位
           long x = Math.abs((long)dividend);
           long y = Math.abs((long)divisor);
        
           while(x >= y)
           {
               //將y*2^32增長到最大,而後逐步減少,靠近x
               //y*2^power<x時結束
               while((y << power) > x)
               {
                   //每次只須要執行有限步就能找到power,不像前面中的方法,是執行O(n)步才能找到
                   power--;
               }
               x -= y << power;
               result += 1 << power;
               
           }
           return sign > 0 ? result : -result;
   }
}
複製代碼

1.在每次循環中找到最大的k的最好的方式就是認識到k實際上是在不斷縮小的。所以,咱們不須要在每次子循環的時候都檢驗一下21,22,23......是否小於或者等於x,咱們能夠在找到最大的k後,直接在後面的每次循環中檢驗x和2k-1,2k-2,2k-3....的關係。
2. 咱們能夠繼續以前的例子。商爲(100)2後,咱們繼續計算(11)2。由於y*2k<=x的最大k是0,因此咱們把20=(1)2加到商裏,所以商就變成了(101)2。而後咱們繼續算法,所以(1)2<y,因此算法結束。最後,咱們能夠獲得,x= (1011)2,y=(10)2相除,商爲(101)2,餘數爲(1)2bash

  • 時間複雜度:O(n),n是x/y的商的位數。
  • 空間複雜度:O(1)
相關文章
相關標籤/搜索