「Leet Conan」1 Two Sum

週末去看了柯南的也不知道第幾個劇場版的「紺青之拳」,感嘆它已經出了 1000 多集 TV 版了,我以爲程序員都應該去看柯南,雖然破案的邏輯可能會有漏洞,可是對案件真相的追求很是值得咱們學習。程序員

因而打算改變一下公衆號的文章定位:算法

  • 經過回顧柯南每一集的劇情,讓沒有看過的人產生興趣,讓看過的人撿起情懷。數組

  • 回顧柯南的同時順便作一下 leetcode 上面的題目,真的只是順便,不必定是最優解,但確定不是暴力解,又不是參加 ACM 競賽對吧。bash

  • 題目的講解儘量符合「碼上開學」系列的標準,主要是經過解題找到興趣點,而不是死記硬背完成任務,不然就太無趣了。數據結構

讓咱們開始吧~學習

第 1 集 雲霄飛車殺人事件

WechatIMG9

高中生偵探工藤新一被稱爲「日本警察界的救世主」而響譽全國,他幫助警方解決了不少棘手案件。某日,他和兩小無猜小蘭一塊兒去坐雲霄飛車時遇到了一塊兒因情生恨的兇殺事件,其中,有兩名黑衣人引發了工藤的注意,工藤暗中跟蹤卻被發現,黑衣給他灌下毒藥後離開,當被警察發現時,工藤已變成了小孩兒的模樣。測試

第 1 題 Two Sum

我只會貼英文的題目,主要是爲了讓你們熟悉計算機的一些術語。優化

Given an array of integers, return indices of the two numbers such that they add up to a specific target.ui

You may assume that each input would have exactly one solution, and you may not use the same element twice.spa

Example:

Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
複製代碼
fun twoSum(nums: IntArray, target: Int): IntArray {
}
複製代碼

以後的代碼都以 Kotlin 爲例來寫,也是爲了幫助你們熟悉 Kotlin,後面有時間再考慮 Python 吧。

讀題:

一個數組中若是有兩個數加起來等於 target,那麼就返回這兩個數的 index 數組,這裏包含如下要點:

  • 根據題目要求,一旦找到答案就不用再找了,咱們假設只存在一組答案。

那咱們首先先讓它編譯過:

fun twoSum(nums: IntArray, target: Int): IntArray {
        // 👇 兜底拋出異常
        throw IllegalArgumentException("No two sum solution")
    }
複製代碼

接下來分析一下思路:

  • 假設我遍歷到 2,那麼我只要看 7 是否是在數組裏就好了。
  • 看 7 是否是在數組裏,又得來一次遍歷,又得從 2 開始,有點浪費。
  • 不如先搞一套數據結構來備份,這樣只要判斷「7 是否是在備份數據裏」就不用遍歷了,這個數據結構的查找效率要高。
    • 數據的備份要保存數組的索引吧(由於須要返回索引)
    • 數據的備份要保存數組的值吧
    • 天然想到用 Map 來作,那麼到底哪一個是 key,哪一個是 value 呢?我要返回什麼,什麼就是 value,由於 Map 的查找時間複雜度是 O(1) 嘛,因此,用數組的索引作 value,值作 key

咱們思考下所謂的算法優化的本質:

  • 在最原始的解法,也就是「暴力 Brute Force」基礎上進行「剪支 Branch Cut」,減小多餘的分支來達到目的。
  • 大部分狀況下都是空間換時間,有得必有失,可是空間相對來講成本低,一寸光陰一寸金,寸金難買寸光陰。

根據上面的思路,嘗試開始寫代碼:

fun twoSum(nums: IntArray, target: Int): IntArray {
        // 先備份數據
        val backupMap = mutableMapOf<Int, Int>()
        nums.forEachIndexed { index, i ->
            backupMap[i] = index
        }

        nums.forEachIndexed { index, i ->
            // 2 的對立面,就是找 7 的索引
            val complementIndex = backupMap[target - i]
            if (complementIndex != null) {
                // 找到了
                return intArrayOf(index, complementIndex)
            }
            // 沒找到就無論,最後走兜底
        }

        throw IllegalArgumentException("No two sum solution")
    }
複製代碼

再寫一個測試用例:

@Test
    fun test() {
        val result = twoSum(intArrayOf(2, 7, 11, 15), 9)
        println(result.contentToString())
    }
複製代碼

最後打印出正確的結果,返回的是 2 和 7 的索引:

[0, 1]
複製代碼

原本到這裏已經搞定收工了,可是咱們再讀一下題目最後一句話:

you may not use the same element twice

意思是說一個元素只能用一次,咱們對數組自己遍歷了兩次,等於對同一個元素用了兩次,不符合題目的要求。

leetcode 官網上這道題給出了三種解法,其實遍歷超過一次的都是不符合題目要求的,也就沒有所謂的「最優解」一說,因此咱們看看能不能再優化下知足題目的要求吧。

  • 首先咱們知道遍歷數組是省不掉的
  • 能省掉的就是備份的那個遍歷
  • 備份數據的過程當中,實際上對於數組 {2, 7, 11, 15} 來說,由於 2 和 7 已經知足條件,因此不必備份 11 和 15,說明備份這件事咱們須要一邊遍歷一邊來作
    • 這個思路的前提是題目裏說了符合條件的只有一組

我認爲,leetcode 的題目的初衷不是比誰的算法更加優秀,而是切題,由於題目稍微變下,所謂的「最優解」立刻就失效了。

根據上面思路,代碼以下:

fun twoSum(nums: IntArray, target: Int): IntArray {
        val backupMap = mutableMapOf<Int, Int>()
        nums.forEachIndexed { index, i ->
            val complementIndex = backupMap[target - i]
            if (complementIndex != null) {
                // 找到了
                // 👇 注意,這裏找到的索引是以前放進去的,因此要放在前面
                return intArrayOf(complementIndex, index)
            }
            // 沒找到才進行備份
            backupMap[i] = index
        }

        throw IllegalArgumentException("No two sum solution")
    }
複製代碼

題目就講到這裏,是否是有種名偵探柯南慢慢破案的感受呢?

真実はいつも一つ!

下集再見~


本系列會在公衆號進行連載

qrcode_for_gh_faa9b42997ad_258
相關文章
相關標籤/搜索