LeetCode——【1】 两数之和

两数之和

    前几天开始在LeetCode上面开始做题,希望锻炼自己的编程思维以及能力,但是为了更好掌握知识点,我一道题目尝试了很多方法,并且参考了评论和一些答案进行修改,所以速度比较慢。新开的专栏就是为了分享自己做题经历,也分享自己的一些微不足道的经验!
    今天先分享一下一道力扣中一道简单题——两数之和,虽说简单,但是解法里使用的方法和思想却十分重要!
题目:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案,但是,数组中同一个元素不能使用两遍。
示例:

给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
法一:暴力破解法
    def twoSum(self, nums, target):
        for index1, value1 in enumerate(nums):
            for index2, value2 in enumerate(nums):
                if index1 != index2 :
                    t = value1 + value2
                    if t == target:
                       return [index1, index2]

    暴力破解法很简单,就是直接将列表遍历2遍,依次相加寻找加和为target的两个值,并且要注意,要有if index1 != index2的判断,因为题目说数组中同一个元素不能使用两遍
时间复杂度:
    暴力破解法要将列表完整遍历2遍,因此时间复杂度比较高,为 O ( n 2 ) O(n^2) O(n2),但空间复杂度还是比较低的。提交记录里显示:

29 / 29 个通过测试用例
执行用时:7844 ms
内存消耗:13.5 MB

可见用时比较长!

法二:暴力破解法(第二层遍历从第一层的后面开始)
def twoSum(self, nums, target):
     for index1 in range(len(nums)):
         for index2 in range(index1+1, len(nums)):
              t = nums[index1] + nums[index2]
                 if t == target:
                     return [index1, index2]

    同样将有两层嵌套,但和法一不同的是,法二的第二层遍历只需要从第一层后面开始搜索到nums的最后,这里前后找到的数字的索引不会一样,所以就少了if index1 != index2的判断。
时间复杂度:
    这个算法的时间复杂度同样很高,但是稍微比法一直接将列表遍历2遍来得低。该算法的时间复杂度为 O ( n m ) O(nm) O(nm),空间复杂度不变。看看LeetCode里的提交结果:

29 / 29 个通过测试用例
执行用时:3488 ms
内存消耗:13.5 MB

法二所用的时间跟法一相比降低的一半,所以思路的一些小改动,就有可能大幅提升算法的运算时间,降低时间复杂度。

法三: 利用target-num1 = num2
        for index1 in range(len(nums)):
            if (target - nums[index1]) in nums:
                #如果num2=num1,且nums中只出现了一次,说明找到是num1本身
                if (nums.count(target - nums[index1]) == 1) & (target - nums[index1] == nums[index1]):
                    continue
                else:
                    index2 = nums.index(target - nums[index1], index1+1)  # 从index1+1开始找num2
                    break
        if index2 > 0:
            return [index1, index2]
        else:
            return []

    法三先遍历一次列表,得到num1 = nums[index1],再计算出num2 = target-num1,然后判断列表中是否存在这个数,如果存在,再根据if (nums.count(target - nums[index1]) == 1) & (target - nums[index1] == nums[index1])去判断num2num1是不是同一个数(如果num2=num1,并且num1中的数在列表中只有一个,那么num2和num1就是同一个数,不符合要求)。如果不是同一个,就从index1+1后找到num2。最后判断是否有没有找到,index2>0就是用于判断是否找到num2,如果找到,返回索引列表,如果没有找到返回空列表。
时间复杂度:
    法三的时间复杂度我不太清楚,第一层为 O ( n ) O(n) O(n),但是我不太清楚Python内置的查找函数in的时间复杂度是多少,所以不太清楚,但根据提交结果来讲,时间复杂度是低于法一、法二的!

29 / 29 个通过测试用例
执行用时:772 ms
内存消耗:13.7 MB

法四: 先排序后利用二分法查找
    new_nums = [[num, i] for i, num in enumerate(nums)]
    new_nums.sort(key=lambda x: x[0])  # new_nums按照数值num的大小排序
    for i in range(len(new_nums)):
        num1 = new_nums[i][0]
        num2 = target - num1
        if num2 >= num1:
            j = binary_search(new_nums, i+1, len(nums)-1, num2)
        else:
            j = binary_search(new_nums, 0, i-1, num2)
        if j:
            break
    return sorted([new_nums[i][1], new_nums[j][1]])

二分查找法

    def binary_search(self,li, left, right, val):
    """
    li: 传入的列表
    left: 要查找的区域的最左端的索引
    right: 要查找的区域的最右端的索引
    val: 要查找的值
    """
        while left <= right:  # 候选区有值
            mid = (left + right) // 2  # 整除2
            if li[mid][0] == val:
                return mid
            elif li[mid][0] > val:  # 待查找的值在Mid左侧
                    right = mid - 1
            else:  # 待查找的值在Mid右侧
                    left = mid + 1
        else:
            return None

    法四步骤:

  1. 先将列表的值以及其对应的索引[num, i]组成一个二维列表new_nums
  2. 根据值的大小将步骤1得到的列表进行排序,因为二分查找法的前提条件是列表有序!!!
  3. 重复法三的思路,先遍历列表取出num1,再计算出num2
  4. 判断num1num2的大小,如果num2>num1,则在new_nums中从i+1即从num1后面用二分查找法查找num2,反之,在new_nums中从0i-1即从num1前面用二分查找法查找num2
  5. 最后返回结果

时间复杂度:
    时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn), for循环是 n n n, 二分查找是 l o g n logn logn,时间复杂度较低,接下来看看提交结果:

29 / 29 个通过测试用例
执行用时:60 ms
内存消耗:15 MB

法五: 利用字典查找
def twoSum(self, nums, target):
    dict_value_index = {}
    for i,num in enumerate(nums):
        if dict_value_index.get(target - num) is not None:
            return sorted([i,dict_value_index.get(target - num)])
        dict_value_index[num] = i # 这句不能放在if语句之前,解决target-num=num的情况

    法五是我从力扣的论坛里看到的解法,主要利用字典进行求解,方法巧妙,时间复杂度低。
    法五步骤:

  1. 创建dict_value_index字典,用于存储列表中的值以及索引 。
  2. 遍历列表,取出num1
  3. 利用字典的get函数在列表中查找num2,如果找到了num2,则返回索引值,否则将该值以及其对应的索引放入字典

注意: 将值值以及其对应的索引放入字典dict_value_index[num] = i这句不能放在if语句前,即不能先将从列表中取出的值先放入字典再去查找num2,因为如果num2num1的值如果相同的话,先将值放入的话则会产生错误,即两个和数为同一个值!
时间复杂度:
    时间复杂度较低,按照提交结果来看,是由于前面四个方法的, 但是我没去看(懒惰)字典的get函数的时间复杂度,所以也不太清楚法五的总的时间复杂度。看看提交结果:

29 / 29 个通过测试用例
执行用时:48 ms
内存消耗:14.1 MB
总结、比较
方法法一法二法三法四法五
时间复杂度O(n^2)O(nm)...O(nlogn)...
执行用时7844 ms3488 ms772 ms60 ms48 ms
内存消耗13.5 MB13.5 MB13.7 MB15 MB14.1 MB
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值