「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
相关文章
相关标签/搜索