基础算法(一)枚举

何为枚举?

所谓枚举,就是将所有可能的答案去一一列举出来。可能很多人一开始学习算法起步都是通过学习枚举入门的,但枚举这个问题并不是一个简单的问题,相反,我认为枚举是一个比较难的问题。

如何把所有的答案一一列举出来,是第一个需要解决的问题。

那么首先就可以尝试一下下面的例题,两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

其中:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109

示例

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

那么对于上面的这个问题,使用枚举算法来解决,第一步,就是如何把所有的可能结果一一列举出来,这个题是比较简单的,很容易就能想出来思路,因为只需要两个数进行求和,也就是我们先循环枚举第一个数,然后循环枚举第二个数,这样所有的结果都能被找到。

参考代码

class Solution(object):
    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        ans=[]
        flag=True;
        for i in range(len(nums)):
            for j in range(i+1,len(nums)):
                if nums[i]+nums[j]==target:
                    ans=[i,j]
                    flag=False
                    break
            if flag==False:
                break
        return ans

这里可以把这个题再延申一下,如果不是两个数呢,是未知个数的数,也就是给出一个数组,再给出一个数,这个数组里有多少种组合可以使组合内的数相加,得到那个给出的数?

示例

输入:nums = [1,2,3,4], target = 5

输出:[[0,3],[1,2]]

解释:因为 nums[0] + nums[3] == 5,nums[1] + nums[2] == 5 ,返回 [[0,3],[1,2]] 。

那么这个题比上面的基础版本相比,每一种可能的组合个数不一样了,那么改怎么一一枚举出结果呢?

枚举优化

如果只是单纯的暴力枚举,很显然,这是个毫无含金量的工作。很多情况下,单纯的暴力往往会超时,例如如下的这个题目,计数质数

给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。

示例

输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。

那么按照我们上面写的,一一枚举出可能的结果,那么我们很显然会想到如下的代码

class Solution(object):
    def countPrimes(self, n):
        """
        :type n: int
        :rtype: int
        """
        num=0
        if n<2:
            return 0

        for i in range(2,n):
            flag=True
            for j in range(2,i):
                if i%j==0:
                    flag=False
                    break
            if flag:
                num+=1
        return num

但是这样的提交在力扣上提交会超时

所以,这边就需要对我们枚举的过程进行优化。

埃筛

那么这里,针对质数这一比较特殊的数字,我们可以使用筛法去筛选出是质数的数字。如果用一句话总结,质数的倍数一定不是质数。比如7是质数,那么7的倍数14就不是质数,因为他有因子2和7。

class Solution(object):
    def countPrimes(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n<2:
            return 0

        a=[1]*n
        
        a[0]=a[1]=0

        for i in range(2,int(n**0.5)+1):
            if a[i]==1:
                a[i*i:n:i]=[0]*((n-1-i*i)//i+1)
        return sum(a)

上面的程序中,我们定义了一个数组a为筛,1表示是质数,而0表示不是质数,最后只需要统计筛中数值为1的数就可以了。

何时暴力?

但是有的时候,仅仅需要暴力循环便可以通过题目了,那么如何判断我们写的暴力程序能否通过呢?例如下面这个题,统计平方和三元组的数目

一个 平方和三元组 (a,b,c) 指的是满足 a2 + b2 = c2 的 整数 三元组 ab 和 c 。

给你一个整数 n ,请你返回满足 1 <= a, b, c <= n 的 平方和三元组 的数目。

输入:n = 5
输出:2
解释:平方和三元组为 (3,4,5) 和 (4,3,5) 
  • 1 <= n <= 250

很显然地,我们可以得到如下的暴力程序

class Solution(object):
    def countTriples(self, n):
        """
        :type n: int
        :rtype: int
        """
        count=0
        for i in range(n-1):
            for j in range(i+1,n):
                for k in range(j+1,n+1):
                    if i*i+j*j==k*k:
                        count+=1
        return count*2

因为题目中n是小于250的,所以我们可以看作上面的程序经过了三层循环嵌套,运行了250*250*250次,也就是15625000次,按照时间复杂度换算是O(10^7),而能通过的条件是时间复杂度小于O(10^9),显然是可以通过的。

滑动窗口

文件组合

待传输文件被切分成多个部分,按照原排列顺序,每部分文件编号均为一个 正整数(至少含有两个文件)。传输要求为:连续文件编号总和为接收方指定数字 target 的所有文件。请返回所有符合该要求的文件传输组合列表。

注意,返回时需遵循以下规则:

  • 每种组合按照文件编号 升序 排列;
  • 不同组合按照第一个文件编号 升序 排列。
输入:target = 12
输出:[[3, 4, 5]]
解释:在上述示例中,存在一个连续正整数序列的和为 12,为 [3, 4, 5]。
输入:target = 18
输出:[[3,4,5,6],[5,6,7]]
解释:在上述示例中,存在两个连续正整数序列的和分别为 18,分别为 [3, 4, 5, 6] 和 [5, 6, 7]。

这个题来说,单纯的暴力枚举明显是超出时间复杂度的。这里有一个滑动窗口的相关算法,就是动态地维护一个数组,通过控制左右两端的下标,来计算当前的维护的数组之和是否与目标相同。

class Solution(object):
    def fileCombination(self, target):
        """
        :type target: int
        :rtype: List[List[int]]
        """
        ans=[]
        i,j=1,2
        while j<=target//2+1:
            cur_sum=sum(list(range(i,j+1)))
            if cur_sum<target:
                j+=1
            elif cur_sum>target:
                i+=1
            else:
                ans.append(list(range(i,j+1)))
                j+=1
        return ans

公因子数目

给你两个正整数 a 和 b ,返回 a 和 b 的  因子的数目。

如果 x 可以同时整除 a 和 b ,则认为 x 是 a 和 b 的一个 公因子 。

输入:a = 12, b = 6
输出:4
解释:12 和 6 的公因子是 1、2、3、6 。
  • 1 <= a, b <= 1000

对于这个题目来说,我们可以缩减需要枚举的数目,最简单的缩减就是去到两个数之间较小的那一个,但是,在这个基础上能否再次缩减呢?很显然,需要找到两个数之间最大的那个可能是公因子的数,很显然就是这个数的二分之一。

class Solution(object):
    def fileCombination(self, target):
        """
        :type target: int
        :rtype: List[List[int]]
        """
        ans=[]
        i,j=1,2
        while j<=target//2+1:
            cur_sum=sum(list(range(i,j+1)))
            if cur_sum<target:
                j+=1
            elif cur_sum>target:
                i+=1
            else:
                ans.append(list(range(i,j+1)))
                j+=1
        return ans

统计圆内格点数目

这个题主要就是枚举出哪些点在圆内。

然后看到了一个比较有意思的程序,把整段的python程序最后融合成了一句pyhton程序。

class Solution(object):
    def countLatticePoints(self, circles):
        """
        :type circles: List[List[int]]
        :rtype: int
        """
        return sum(any((a-i)**2+(b-j)**2 <= c**2 for a, b, c in circles)\
         for i in range(max(i+k for i, j, k in circles)+1)\ 
         for j in range(max(j+k for i, j, k in circles)+1))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值