双指针解法问题

LeetCode11 盛最多水的容器

LeetCode11.png

算法流程:设置双指针i,j位于数组两端代表容器壁,每次让数字较小(高端较低)的一端向中间收缩一格,遍历一遍寻找最大的面积

证明:由于又两端构成的容器面积由较矮的一端以及两端之间的间距构成,即
Area[i,j]=min(h[i],h[j])*(j-i) j>i
因此如果从两端向内侧收缩,则只存在两种可能:

  1. 如果较长一端向内收缩,水槽的短板min(h[i],h[j])只会变小或着不变
  2. 如果较短一端向内收缩,水槽的短板可能变大也可能变小

因而按照如上规则遍历,结束后就能找到使面积最大的组合的C_n^2 减小到n。

Python 代码:

class Solution:
    def maxArea( self, height: List[int] ) -> int:
        i, j, area = 0, len(height)-1, 0
        while i < j:
            if height[i] < height[j]:
                area = max(area, height[i]*(j-i))
                i += 1
            else:
                area = max(area, height[j]*(j-i))
                j -= 1
        return area

复杂度分析:

时间复杂度: O(n)

空间复杂度:O(1)

LeetCode202 快乐数

LeetCode202.png

分析问题很容易得出只要循环计算给定整数的各位数字的平方和,直到出现1就满足条件,输出True。问题的关键是当给定数字不满足时,如何跳出循环,即寻找循环终止的条件。

  1. 一种方法是,寻找规律,小于10的数,只有1和7满足条件,所以可以一直计算到平方和小于10。然后判断是否等于1或7

    class Solution:
        def isHappy(self, n: int) -> bool:
            def digitSum(n: int) -> int:
                res = 0
                tmp = 0
                
                while n > 0:
                    tmp = n % 10
                    res += tmp * tmp
                    n /= 10
                
                return res
            
            res = n
            while res >= 10:
                n = sum
                res = digitSum(n)
            return res == 1 or res == 7
    
  2. 另一种思路是判断出计算的平方和出现循环就终止计算,然后判断是否出现结果为1。这里判断出现循环用的思路就是快慢指针的思想,设置快指针,每次计算两遍平方和,满指针每次计算一次平方和。当出现快慢指针计算结果相同时,就说明计算出现了无限循环,此时就终止计算进行判断:

    class Solution:
        def isHappy(self, n: int) -> bool:
            def digitSum(n: int) -> int:
                res = 0
                tmp = 0
                
                while n > 0:
                    tmp = n % 10
                    res += tmp * tmp
                    n /= 10
                
                return res
            
            fast = slow = n
            while True:
                fast = digitSum(fast)
                fast = digitSum(fast)
                slow = digitSum(slow)
                if fast == slow:
                    break
             return slow == 1
    

    上面的程序中需要注意,while中的循环至少需要计算一次,但是python中不支持do while 循环结构,所以需要在死循环中设置break来跳出循环。

LeetCode15 三数之和

LeetCode15.png

分析问题:

题目要求在给定数组中找出和为0的三个数组合。最直接的就是采用暴力搜索,检索所有可能的三数组合,但是这种方法效率太低,时间复杂度为O(n^3) 。针对暴力检索的方式可以采用哈希表的方式像两数之和那样进行优化。但是根据三数之和的性质,如果我们采用双指针的方法,同样可以巧妙的将算法的复杂度降低。

分析三数之和为零的条件:

  1. 除去三个数都为0的情况,三个数中的最小值和最大值必然是符号相反的。
  2. 除去三个数都为0的情况,三个数中的最小值必然是负数。
  3. 如果我们固定三个数中的其中1个,那么问题就转化为找到两个数的组合,这两个数的和为第一个数的相反数。
  4. 继续分析,如果是在一个排序过的数组中寻找两个数,它们的和为目标值。这时我们就可以使用双指针的方法,根据两数和与目标数的大小关系,分别从两端逼近。

根据第1,2点,我们可以增加终止循环的条件。根据第3,4点我们可以设计出整个代码的框架。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        l = len(nums)
        nums.sort()
        res = []
        for i in range(l):  #分析3:固定一个数,使问题变为两数之和问题
            if nums[i] > 0:
                break       #分析2: 最小数大于0直接结束
            left = i+1
            right = l-1
            while left < right and nums[right] >= 0:    #分析1:最大值必然大于等于0 
                val = nums[i] + nums[left] + nums[right]
                if val == 0:    #分析4: 采用双指针从数组两边逼近结果
                    res.append([nums[i], nums[left], nums[right]])
                    left += 1
                    right -= 1
                elif val < 0:   
                    left += 1
                elif val > 0:
                    right -= 1
        return res

对结果的去重:

我们根据分析使用双指针法写出了如上的代码,但是实际测试时会发现我们忽略了结果中存在着重复的数字组合。因为题目要求结果中不能存在重复的数字组合,所以在判断过程中还需要将重复出现的组合去掉。这一点对于排序过的数组是容易的,由于数组排序过,重复的数字是连续的。因此只需要在遍历的过程中分别对三个数字去重,跳过重复数字即可。故而对如上代码做出修改如下:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        l = len(nums)
        nums.sort()
        res = []
        for i in range(l):  #分析3:固定一个数,使问题变为两数之和问题
            if nums[i] > 0:
                break       #分析2: 最小数大于0直接结束
            if i > 0 and nums[i] == nums[i-1]:
                continue    #去重1: 对选定的第一个数去重
            left = i+1
            right = l-1
            while left < right and nums[right] >= 0:    #分析1:最大值必然大于等于0 
                val = nums[i] + nums[left] + nums[right]
                if val == 0:    #分析4: 采用双指针从数组两边逼近结果
                    res.append([nums[i], nums[left], nums[right]])
                    while left < right and nums[left] == nums[left+1]:
                        left += 1           #去重2: 对第二个数去重
                    while left < right and nums[right] == nums[right-1]:
                        right -= 1          #去重3: 对第三个数去重
                    left += 1
                    right -= 1
                elif val < 0:   
                    left += 1
                elif val > 0:
                    right -= 1
        return res

复杂度分析:

时间复杂度:一次排序加两趟遍历,时间复杂度为 O(n^2)

空间复杂度:O(n)

LeetCode1013 将数组分成和相等的三个部分

LeetCode1013.png
  1. 首先分析数组能被分为和相等的三个部分的条件必然是整个数组的和能被3整除,因此通过这个条件可以直接判断是否可以直接返回false。
  2. 然后通过求出整个和的数组的三分之一,就能知道每一部分和的大小。直接通过暴力方法遍历累加到目标值即可求解,但是由于是分成三组,所以要求解两遍,如果采用双指针方法分别从两边遍历求和,只需要在一次遍历中完成。
class Solution:
    def canThreePartsEqualSum(self, A: List[int]) -> bool:
        s = sum(A)
        if s % 3:
            return False
        target = s // 3
        i, j = 0, len(A)-1
        s1, s3 = A[i], A[j]
        while i+1 < j:
            if s1 != target:
                i += 1
                s1 += A[i]
            if s3 != target:
                j -= 1
                s3 += A[j]
            if s1 == target and s3 == target:
                break
        return i+1 < j

复杂度分析:

时间复杂度:O(n)

空间复杂度:O(1)

注意

虽然使用双指针可以使代码看起来更简洁,但其实并没有使复杂度降低,所以双指针方法在这里实际的意义不大。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352

推荐阅读更多精彩内容