1. 程式人生 > >leetcode 29 兩數相除

leetcode 29 兩數相除

問題描述  

  給定兩個整數,被除數 dividend 和除數 divisor。將兩數相除,要求不使用乘法、除法和 mod 運算子。

  返回被除數 dividend 除以除數 divisor 得到的商。

  示例 1:

  輸入: dividend = 10, divisor = 3
  輸出: 3

  示例 2:

  輸入: dividend = 7, divisor = -3
  輸出: -2

  說明:

  • 被除數和除數均為 32 位有符號整數。
  • 除數不為 0。
  • 假設我們的環境只能儲存 32 位有符號整數,其數值範圍是 [−231,  231 − 1]。本題中,如果除法結果溢位,則返回 231 − 1。

      這個問題涉及到了計算機如何利用邏輯運算和加減法來求得除法,這個問題之前一度困擾了我很久。

  

	/**
	 * 逼近
	 * 先定符號
	 * 結果是正是負還是0?
	 * @param dividend
	 * @param divisor
	 * @return
	 */
	public static int divide(int dividend, int divisor) {
		if (dividend == 0) {
			return 0;
		}
		if (dividend == Integer.MIN_VALUE && divisor == -1) {
			return Integer.MAX_VALUE;
		}
		if (divisor == -1) {
			return -dividend;
		}
		if (divisor == 1) {
			return dividend;
		}
		int res = 0, Sum = 0;
            boolean plus = false;
            //同號
            if ((dividend > 0 && divisor > 0) || (dividend < 0 && divisor < 0)) {
              plus = true;
            }
          //將兩個數都變成負數
          dividend = dividend > 0 ? ~dividend + 1 : dividend;
          divisor = divisor > 0 ? ~divisor + 1 : divisor;
	  for (int i = 30; i > -1; i--) {
  	    //被加數未溢位  加之後的結果未溢位  加之後的結果小於被除數
		  int addNum = divisor<<i;
		  if (addNum>>i == divisor && Sum + addNum < 0 && Sum + addNum >= dividend) {
			  Sum += addNum;
			  res += plus ? 1<<i : -1<<i;
		  }
	  }return res;
	}

  

   題解(寫程式碼時候的奇怪想法。。。): 

    首先進行邊界處理之類的。

    一開始我採用了二分法猜數字,首先做一個記號記錄結果,然後把被除數(dividend)和除數(divisor)都轉換為正數 (資訊加工),這樣結果就一定是在[0,dividend]。

    初始化left = 0, right = dividend,mid = dividend << 1;

    然後通過for迴圈累加mid次驗證是否符合結果( mid * divisor <= dividend並無法取到比mid更大的mid'去滿足前面條件);

    由於平時並不是經常使用二分碰到了以下問題:

      二分的邊界問題: 

        如何寫出不雜亂的程式碼?

        因為常常使用 mid = (left + right)<<1;

        故而遺忘了mid還可以向右偏 mid = ((left + right)<<1) + 1;。

      因為要保證結果一定在邊界內,故而

        left = mid + 1; right = mid - 1;常常不能同時出現(視情況而定吧)。

      所以有時候

        採用 right = mid - 1; left = mid;這個組合時:

        mid = (left + right)<<1; (left + 1 = right) 時候回卡死!

        這個時候要mid  = ((left + right)<<1) + 1;(向右偏)

  當然這樣的思路寫出來的程式碼的結果就是我掛了。。

    for迴圈累加代替乘法實在太慢了!!!

  然鵝,這時候我想到了一個辦法。

    divisor * mid 可以寫成 divisor (m0 * 2^31 + m1 * 2^30 + m2 * 2^31 ....+m30 * 2^0)

    然後二的m次方這個東西我是可以通過左移來得到的!

    於是我興奮地用這個方法驗證mid對不對。

  發現很多邊界問題無法解決

  例如:

    之前說的右偏碰到Integer.MIN_VALUE

    Integer.MIN_VALUE無法轉換為正數

    mid取得太大,資料溢位,本來divisor * mid已經超過了Integer.MAX_VALUE。卻還是幾千。。。

  於是我處於崩潰的邊緣。。。

    這樣搞下去我要屎了!

  然鵝,解手的時候。我想:

    我可以把所有數都轉換為負數先啊

    我可以不用猜測mid是多少啊

    我直接從一步一步逼近被除數就行啦??? 好像真的是。。

    例如 :

      結果如若為101010111...(32位)

    那麼我從頭開始的非符號位開始看能不能加進去就好啦! 如若能加進去就逼近了被除數,資料本身溢位,加進去溢位,加進去大於被除數就代表不能加!

    其他的都加,反正我要的也是最逼近的數。。。