[Day 9] 數字 & 位運算 & leetcode 相關練習

今天來講數字, Kotlin 的數字運算跟 Java 很類似,但多了很多有用的內建 API

字串轉數字

這裡把字串 3.14 轉成 Double,這很簡單

val numStr = "3.14"
val num0 = numStr.toDouble()
println(num0) // 3.14

但如果要把 "3.14" 字串直接轉 Int 會發生 NumberFormatException

這時候可以使用 toIntOrNull() 當無法成功轉型時會回傳 null,這裡還用了貓王運算子 ?:,當 null 的時候預設給 0,所以結果會是 0

// val num10 = numStr.toInt() // 會發生 NumberFormatException
val num1 = numStr.toIntOrNull() ?: 0
println(num1) // 0

像這樣無法轉型的狀況下,也是預設 0

val numStr2 = "abc"
val num2 = numStr2.toIntOrNull() ?: 0
val num3 = numStr2.toBigDecimalOrNull() ?: 0
println(num2) // 0
println(num3) // 0

浮點數處理

像下面的例子,如果是 Double 相減的狀況下,會發現結果不如預期,會變成 4.569999999999999

val x = 10.1
val y = 5.53
val result = x - y
println(result) // 4.569999999999999

如果希望維持小數點兩位的結果的話,可以使用 format() 來達成

println("%.2f".format(result)) // 4.57

roundToInt() 則可以四捨五入取到整數

println(result.roundToInt()) // 5

BigDecimal

那當然更好處理浮點數的方式是使用 BigDecimal,尤其是處理金額的時候

這裡可以看到結果處理得很好,在 Kotlin 裡面 BigDecimal 甚至可以直接用 - 號來做運算!不用呼叫方法了!

println("BigDecimal demo...")
println(BigDecimal.valueOf(x).minus(BigDecimal.valueOf(y))) // 4.57
println(BigDecimal.valueOf(x) - BigDecimal.valueOf(y))  // 4.57

println(BigDecimal.valueOf(x).plus(BigDecimal.valueOf(y))) // 15.63
println(BigDecimal.valueOf(x) + BigDecimal.valueOf(y)) // 15.63

連比較值也是!可以直接用 大於小於等於 符號!超方便的!

println(BigDecimal.valueOf(100.123).compareTo(BigDecimal.ZERO) > 0) // true
println(BigDecimal.valueOf(100.123) > BigDecimal.ZERO) // true

最後再來提一下,BigDecimal.valueOf()BigDecimal() 的結果是不一樣的

println(BigDecimal(x) - BigDecimal(y)) //4.56999999999999939603867460391484200954437255859375

來看原始碼,會發現 BigDecimal.valueOf() 會先把輸入值轉成字串在傳入 BigDecimal(),所以能夠保持原本的輸入值得小數點精度

https://ithelp.ithome.com.tw/upload/images/20200918/20129902ZW5We06XjG.png

但 BigDecimal() 預設是不特別保留精度

https://ithelp.ithome.com.tw/upload/images/20200918/20129902FqVNVSgsF1.png

所以如果要維持原本的輸入值的小數點精度,建議使用 BigDecimal().valueOf()

可以參考官網更多的 BigDecimal API

位元運算子 (bit operator)

最後來談談位運算,說真的以前覺得這好像沒啥用,後來才發現這東西很神啊!

舉例子來說在 Java 裡面 PriorityQueue 的原始碼,裡面就大量用到了位運算

像是

https://ithelp.ithome.com.tw/upload/images/20200918/201299025cE7vbBojP.png

https://ithelp.ithome.com.tw/upload/images/20200918/2012990202neALTO5n.png

Java 中 右移運算子是 >>,左移運算子是 <<

為什麼要用位運算呢?

下面用 Kotlin 的寫法來看,

shl(1) 代表左移一位,也就是乘 2

shr(1) 代表右移一位,也就是除2

		// bit operator
    println(42.shl(1)) // var43 = 42 << 1;  // 84
    println(42.shr(1)) // var43 = 42 >> 1;  // 21
    println(42 shr 1) // var43 = 42 >> 1;           // 21

因為位運算就是把數值都化為 2 進制,也就是 0 跟 1,所以移動一位就是 2的倍數 或 1/2,移動2位就是 2*2 的倍數 或 1/4。

位運算的方式是最有效率的,而且在面對大數字的時候,能夠避免數字的 overflow

這裡用一題 leetcode 的 binary search 來解釋

Leetcode 153. Find Minimum in Rotated Sorted Array

https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/

難度: MEDIUM

https://ithelp.ithome.com.tw/upload/images/20200918/201299026gTqJ6lU7C.png

原題是在說在原本有一個排序過的 array 在旋轉後,在這旋轉過的 arrya 中,找到其中最小的數字

解題概念: 因為這個 array 在排序後卻被旋轉了,以 [3, 4, 5, 1, 2] 這例子,在原本有順序的狀況下,左邊都小於右邊的數字,但是到 5 和 1 的時候,卻變成左邊大於右邊了,所以這題可以用 binary search 的方式,找到 nums[mid] > nums[right] (5 > 2)的狀況,最小的結果就會是 nums[mid+1] (也就是 1)

fun findMin(nums: IntArray): Int {
    var left = 0
    var right = nums.size - 1

    while (left < right) {
        var mid = (left + right).shr(1)
        if (nums[mid] > nums[right]) {
            left = mid + 1
        } else {
            right = mid
        }
    }
    return nums[left]
}

var mid = (left + right).shr(1) 這裡取中間的 index 就使用了位運算

一般來說怕數字太大 overflow 的話,不使用位運算,就得寫成 var mid = left + (right - left)/2 但這樣的寫法比較不直觀。

再舉另一個位運算的題目

Leetcode 136. Single Number

https://leetcode.com/problems/single-number/

難度: EASY

https://ithelp.ithome.com.tw/upload/images/20200918/20129902fSwqUET1uk.png

這題要在 array 中找出只出現一次的數字(其他數字會出現 2次),這時候可能會想到要用 map 跑一個 for 去存,在判斷哪個數字是一個?這樣會是 O(n) 的時間複雜度和 O(n) 的空間複雜度(用了map)

用位運算可以不用額外的空間!

這裡跑了一個 for 針對每個元素做 xor,因為只有一個數字會出現一次,其他都是出現兩次,所以做 xor,出現 2次的數字的位元會抵消,最後只留下出現一次的數字的位元組!

以 [2, 2, 5] 為例子, xor 定義是同樣的數字會變 0,不同會變 1

2 xor 2 會變成 0010 xor 0010 = 0000 (因為 2 和 2 都一樣)

0 xor 5 會變成 0000 xor 0101 = 0101 結果還是 5

fun singleNumber(nums: IntArray): Int {
    var result = 0;
    nums.forEach {
        result = result.xor(it); //do xor
    }
    return result;
}

更多的 bit API 可以參考官網 https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-int/ 搜尋 bit 關鍵字

數字的部分就講到這裡啦!謝謝大家,我們明天見!

今日練習的程式在這: 請點我