美文网首页
〔两行哥算法系列〕两数之和问题求解与优化(一)

〔两行哥算法系列〕两数之和问题求解与优化(一)

作者: 两行哥 | 来源:发表于2018-09-12 14:20 被阅读150次

    来看看这道算法题(摘自LeetCode):
    Given an array of integers, return indices of the two numbers such that they add up to a specific target.
    You may assume that each input would have exactly one solution, and you may not use the same element twice.

    Example:

    Given nums = [2, 7, 11, 15], target = 9,
    Because nums[0] + nums[1] = 2 + 7 = 9,
    return [0, 1].

    翻译成中文:
    给定一个整数数组和一个目标值,找出数组中和为目标值的两个数。
    你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用。

    示例:

    给定 nums = [2, 7, 11, 15], target = 9,
    因为 nums[0] + nums[1] = 2 + 7 = 9,
    所以返回 [0, 1]。

    一、暴力算法

    面对这道题的时候,你会使用什么样的算法呢?是对数组进行两次暴力遍历来解决问题吗?或者有其他更有的算法?让我们先来看看简单的暴力算法。

        public int[] numbers = {2, 7, 11, 15};
        public final int target = 9;
    
        public int[] getResult() {
            int[] result = new int[2];//执行1次
            int n = numbers.length;//执行1次
            for (int i = 0; i < n; i++) {//执行n + 1次
                for (int j = i + 1; j < n; j++) {//执行n + (n - 1) + (n -2) + (n - 3) ... + 1次,即1/2 * (n + 1) * n次
                    if (numbers[i] + numbers[j] == target) {//执行(n - 1) + (n -2) + (n - 3) ... + 1次,即1/2 * n * (n - 1)次
                        result[0] = i;//执行1次
                        result[1] = j;//执行1次
                    }
                }
            }
            return result;//执行1次
        }
    
    

    对这个方法进行复杂度分析,我们进行了两轮遍历,如注释,其算法时间复杂度为O(n^2)。
    再考虑其空间复杂度,当numbers的长度无限增大时,其运行过程中临时占用的存储空间始终保持不变,因此算法空间复杂度为O(1)。

    二、哈希表算法

    为了对运行时间复杂度进行优化,我们需要一种更有效的方法来检查数组中是否存在目标元素。如果存在,我们需要找出它的索引。保持数组中的每个元素与其索引相互对应的最好方法是什么?哈希表。
    通过以空间换取速度的方式,我们可以将查找时间从O(n)降低到O(1)。哈希表正是为此目的而构建的,它支持以近似恒定的时间进行快速查找。我用“近似”来描述,是因为一旦出现碰撞冲突,查找用时可能会退化到 O(n)。
    那么基于上述思想,算法的核心操作是将数组转换为HashMap。在这个示例中,我们把target换成22,算法代码如下:

        public int[] numbers = {2, 7, 11, 15};
        public final int target = 22;
    
        public int[] getResult() {
            int[] result = new int[2];//执行1次
            Map<Integer, Integer> map = new HashMap<>();//执行1次
            int n = numbers.length;//执行1次
            for (int i = 0; i < n; i++) {//执行n + 1次
                map.put(numbers[i], i);//执行n次
            }
            for (int i = 0; i < n; i++) {//执行n + 1次
                int rest = target - numbers[i];//执行n次
                if (map.containsKey(rest) && map.get(rest) != i) {//执行n次
                    result[0] = i;//执行1次
                    result[1] = map.get(rest);//执行1次
                }
            }
            return result;//执行1次
        }
    

    此时算法的时间复杂度为O(n),我们把包含有n个元素的数组遍历了两次。由于哈希表将查找时间“近似”缩短到 O(1) ,所以时间复杂度为O(n)。还是用“近似”,那是因为需要考虑哈希表发生碰撞的情况。
    空间复杂度为O(n),因为此算法需要额外开辟空间存储一个新的哈希表,该表中存储了n个元素。

    注意,细心的读者会问为什么在if判断条件中增加了map.get(rest) != i这个条件。
    这是为什么呢?前文我们已经将target换成了22,那么如果缺少这个判断条件,就可能找出两对组合,[7,15]和[11,11],很明显,[11,11]是错误的组合,原因就是因为缺乏了map.get(rest) != i这个条件,即map.get(rest)不可以等于numbers[i]本身。
    在这里我们将数组遍历了两次,可不可以遍历一次就满足需求呢?来看一下更加优美的算法实现:

        public int[] numbers = {2, 7, 11, 15};
        public final int target = 22;
    
        public int[] getResult() {
            int[] result = new int[2];//执行1次
            int n = numbers.length;//执行1次
            Map<Integer, Integer> map = new HashMap<>();//执行1次
            for (int i = 0; i < n; i++) {//执行n + 1次
                int rest = target - numbers[i];//执行n次
                if (map.containsKey(rest)) {//执行n次
                    result[0] = map.get(rest);//执行1次
                    result[1] = i;//执行1次
                }
                map.put(numbers[i], i);//执行n次
            }
            return result;//执行1次
        }
    

    此时算法的时间复杂度为O(n),空间复杂度为O(n),与上文的哈希表算法一致,但是只遍历了一遍数组就完成了查找。

    这篇文章就讲到这里,相信大家对哈希表的威力有了一定了解,对算法的时间与空间相斥也有了一定的体会。再会。

    相关文章

      网友评论

          本文标题:〔两行哥算法系列〕两数之和问题求解与优化(一)

          本文链接:https://www.haomeiwen.com/subject/watvgftx.html