剑指Offer之面试题57: 和为s的数字

举报
宇宙之一粟 发表于 2022/03/19 23:33:45 2022/03/19
【摘要】 面试题57: 和为s的数字📖每日一句: “We hold ourselves back in ways both big and small, by lacking self-confidence, by not raising our hands, and by pulling back when we should be leaning in.” — Sheryl Sandberg...

面试题57: 和为s的数字

📖每日一句: “We hold ourselves back in ways both big and small, by lacking self-confidence, by not raising our hands, and by pulling back when we should be leaning in.” — Sheryl Sandberg

题目一:和为s的两个数字

题目描述:

输入一个递增排序的数组和一个数字 s,在数组中查找两个数,使得它们的和正好是 s。如果有多对数字的和等于 s,则输出任意一对即可。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]

示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]

限制:

1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6

解题思想

遇题不会想暴力算法,先来看看直观的解题思路:

  1. 暴力法

首先选择数组一个数字,挨个从剩下的 n-1 个数字找两数之和是不是等于 s。

def twoSum(self, 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 nums[i], nums[j]
    return []

很容易看到,用到了双层循环,所以时间复杂度: $ O(n^2)$

LeetCode提交超时:26 / 36 个通过测试用例

  1. 哈希表

上面的代码用了两成循环,其实转化一下思想,能不能只用一个for循环呢?

方法:TwoSum 那道题

作为一个 Pythoner,怎么会轻言放弃,办法是一定的。list 不好使,那我们还有 set 呢。也就是哈希表法:利用一次遍历,先找一个数,然后依次遍历查看 target-i 是否在这个 set 中,如果在,即返回。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:

        li_set = set(nums)
        for i in li_set:
            if (target - i) in li_set:
                return i, target - i
        return []

时间复杂度: $ O(n)$,只使用了一次 for 遍历数组

空间复杂度: $ O(n) $,此方法利用了一个 set

  1. 双指针

就像小时候的数学刷题一样,所有的题目条件一定是最关键的,我们能够看到,还有一个很题目中关键的信息没有用到:递增数组。

做出了上面的方法后,发现其实哈希表法并没有利用题目中给出递增排序数组的特点。利用递增的特点找到空间复杂度O(1)的解题方法–双指针对撞:

算法流程可以查看此题解

取两端

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:

        rPointer, lPointer = 0, len(nums) - 1
        while rPointer < lPointer:

            twoSum = nums[rPointer] + nums[lPointer]
            if twoSum == target:
                return nums[rPointer], nums[lPointer]
            elif twoSum > target:
                lPointer -= 1
            else:
                rPointer += 1
        return []
  1. 别忘了二分查找

“递增+有序”,难道二分查找不配有名字吗?

采用二分查找法缩小双指针遍历范围:
left 和 right 分别记录当前查找数组范围的最小元素下标和最大元素下标,mid=(R-L)//2+L 为中间下标;

nums[mid]+nums[left]>target ,则 mid 下标元素及其右边任何元素两两之和均大于 target ,只可能在 mid 左边取得,故 right=mid-1

nums[mid]+nums[right]<target ,则 mid 下标元素及其左边任何元素两两之和均小于 target,只可能在 mid 右边取得,故 left=mid+1

nums[mid]+nums[left]<=target<=nums[mid]+nums[right] 或者 left>=right 时,则结束二分查找;

若以 left>=right 结束循环,则数组中不存在和为 s 的两个元素,返回空数组。

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        n=len(nums)
        L,R=0,n-1
        res=[]
        mid=(R-L)//2+L
        while L<R:
            if nums[L]+nums[R]>target:
                if nums[L]+nums[mid]>target:
                    R=mid-1
                    mid=(R-L)//2+L
                elif nums[L]+nums[mid]==target:
                    res.extend([nums[L],nums[mid]])
                    break
                else:
                    R-=1
                    mid=(R-L)//2+L

            elif nums[L]+nums[R]==target:
                res.extend([nums[L],nums[R]])
                break

            else:
                if nums[R]+nums[mid]>target:
                    L+=1
                    mid=(R-L)//2+L
                elif nums[R]+nums[mid]==target:
                    res.extend([nums[mid],nums[R]])
                    break
                else:
                    L=mid+1
                    mid=(R-L)//2+L
        return res

文章参考:

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。