力扣--数组类题目1.两数之和

1.两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
方法一、暴力解法
时间复杂度:O(n的平方 ),空间复杂度:O(1)
该解法即对nums进行双重遍历,将所有两个数组合情况均列举一遍,若有符合题意情况直接返回即可

from typing import List
def twoSum(nums: List[int], target: int) -> List[int]:
    for i in range(len(nums)):
        for j in range(i+1,len(nums)):
            if nums[i]+nums[j]==target:
                return [i,j]
nums=[2,7,11,15]
target = 17
a=twoSum(nums,target)
print(a)#[0, 3]

方法二、二分查找法
时间复杂度:O(nlogn),空间复杂度:O(n)
在暴力解法中,第二层遍历是对iii后面的元素进行无差别枚举,这样会导致非常多的无效枚举。
我们可以借助二分算法,来对内层循环从时间上进行优化。
因为二分算法的前提是“有序”,则我们需要先对nums进行排序。
又因为返回答案需要原nums顺序,所以我们不妨提前将nums中的值与其下标做一个绑定,统一排序

import bisect
from typing import List

def twoSum(nums: List[int], target: int) -> List[int]:
    n = len(nums)
    #print(list(zip(nums, range(n))))#[(2, 0), (11, 1), (7, 2), (15, 3)]
    nums = sorted(zip(nums, range(n))) # O(nlogn)
    print(nums)#[(2, 0), (7, 2), (11, 1), (15, 3)]
    # 以下部分时间复杂度为:O(nlogn)
    for i in range(n):
        t = target - nums[i][0]
        print("t is:",t)
        j = bisect.bisect_left(nums, (t, ), i + 1, n)#见如下备注
        print("j is:",j)
        if j < n and nums[j][0] == t:
            return [nums[i][1], nums[j][1]]#[1, 3]
    return []

nums=[2,11,7,15]
target = 26
a=twoSum(nums,target)
print(a)
# t is: 24 备注 t为24然后在nums中查找24从i为1查到3则查到24在nums中处于第4位置所以j为4
# t is: 19 备注 t为19然后在nums中查找19从i为2查到3则查到19在nums中处于第4位置所以j为4
# t is: 15 备注 t为15然后在nums中查找15从i为3查到3则查到15在nums中处于第3位置所以j为3
# [1, 3]

方法三、双指针
时间复杂度:O(nlogn),空间复杂度:O(n)
在上面二分查找的优化中,可发现“有序”可以极大的优化内层循环的时间复杂度
发散思维,摒弃内外层循环的思路,“有序”是否还可以发挥更大的作用呢?
答案是可以的,即使用双指针算法

from typing import List

def twoSum(nums: List[int], target: int) -> List[int]:
    n = len(nums)
    nums = sorted(zip(nums, range(n)))  # O(nlogn)#一定要排序要不然可能返回不了正确的结果
    print(nums)#[(2, 0), (6, 6), (7, 2), (9, 7), (11, 1), (12, 5), (15, 3), (26, 4)]
    # 以下部分时间复杂度为:O(n)
    left, right = 0, n - 1
    while left < right:
        if nums[left][0] + nums[right][0] == target:
            return [nums[left][1], nums[right][1]]
        elif nums[left][0] + nums[right][0] > target:
            right -= 1
        else:
            left += 1
    return []

nums=[2,11,7,15,26,12,6,9]
target = 20
# nums=[2,5,1,4,7,3,33,20,9]
# target= 36#如果不排序则返回不了正确的结果
a=twoSum(nums,target)
print(a)
#总结:排序后为[(2, 0), (6, 6), (7, 2), (9, 7), (11, 1), (12, 5), (15, 3), (26, 4)]
#从小到大排好后首先是2+26=28,28大于20则right-1,因为最小的数加上最大的数>target,则这里最大数28舍弃
#因为28和最小的相加都大于20,和其它数相加更大于20,所以right-1
#接下来就是2+15=17,17小于target,所以要找更大的数和15相加,所以left-1

方法四,哈希表解法
时间复杂度:O(n),空间复杂度:O(n)
1.字典是一种典型的键值对类型的数据结构,每一个元素都是由一个键值对(键key和值value)组成
2.这种数据结构可以通过某个键来访问元素,所以字典也被称为映射或散列表
3.字典的主要特性是根据键快速查找值,也可以自由添加和删除元素,这有点像List,但跟List不同的是,List是连续存储,直接定址的。 字典像链表,非连续存储,所以删除元素不需要移动后续元素,就没有在内存中移动后续元素的性能开销
4.通过键来检索值的速度是非常快的,接近于 O(1),这是因为 Dictionary 类是作为一个哈希表来实现的,Dictionary 没有顺序之分,这一点不同于list列表,有顺序之分
键必须是唯一的,而值不需要唯一
5.字典中的键和值都是object类型,所以可以是任何类型(比如:string、int、自定义类型等等)

from typing import List

def twoSum(nums: List[int], target: int) -> List[int]:
    n = len(nums)
    mp = {}
    for i in range(n):
        t = target - nums[i]
        print("i为%d时,字典为:%s"%(i,mp))#i为7时,字典为:{2: 0, 11: 1, 7: 2, 15: 3, 26: 4, 12: 5, 6: 6}
        if t in mp:
            return [mp[t], i]
        # 存放nums[i]
        mp[nums[i]] = i#依次把列表放入字典,key为列表中的值,value为列表中值的下标
    return []

nums=[2,11,7,15,26,12,6,9]
target = 20
a=twoSum(nums,target)
print(a)#[1, 7]#i为7时,字典为:{2: 0, 11: 1, 7: 2, 15: 3, 26: 4, 12: 5, 6: 6}

方法五、集合视角分类讨论
时间复杂度:O(n),空间复杂度:O(n)
从集合的角度来看
我们构造两个集合:
aaa集合为nums中所有值构成的集合。
bbb集合为nums中所有值与target的距离值构成的集合(即target−nums[i])

视角一
假设答案有解,那么aaa集合与bbb集合的交集一定有值。

假设答案无解,那么有两种情况:
aaa集合与bbb集合的交集为空集
aaa集合与bbb集合的交集只有一个值,值为target的一半,并且nums中该值仅存在一个。

视角二
此外,因为题目保证一定有解,所以我们还可以再换一个视角。
从答案的构成上来看,可以分为两种情况:
第一种为nums中两个相同元素的和为target
第二种为nums中两个不同元素的和为target,比如列表[2,11,7,15] sum=30时

from typing import List

def twoSum(nums: List[int], target: int) -> List[int]:
    # 分情况讨论
    # 情况1. nums中有两个相同的值,和为target
    a = target // 2#除以2,然后向下取整
    print(a)
    if target % 2 == 0 and nums.count(a) >= 2:#count计算列表中参数x出现的次数,这里的条件是出现了2次及以上
        return [nums.index(a), nums.index(a, nums.index(a) + 1)]
        #index获得参数x在列表中的位置,index的第二个参数表示起始位置,第3个参数默认为末位置

    # 情况2. nums中有两个不同的值,和为target
    #set(nums)把列表转为集合,并且去重,集合中不能有相同的数据
    s = set(nums) & set([target - x for x in nums])#
    #即{2,11,26,12,6,9}和{8, 9, 11, 14, 18, -6}取交集,返回一个集合
    if s != set():
        b = s.pop()#返回被删除的集合中一个元素
        return [nums.index(b), nums.index(target - b)]
    # 情况3. nums中没有满足题意的情况
    return []

nums=[2,11,26,12,6,9]
target = 20
a=twoSum(nums,target)
print(a)#[5, 1]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值