分治算法的应用:结合力扣几道经典例题

一、什么是分治算法

分治算法的主要思想是将原问题递归地分成若干个子问题,直到子问题满足边界条件,停止递归。将子问题逐个击破(一般是同种方法),将已经解决的子问题合并,最后,算法会层层合并得到原问题的答案

分治算法的主要步骤

  • 分:递归地将问题分解为各个的子问题(性质相同的、相互独立的子问题);
  • 治:将这些规模更小的子问题逐个击破
  • 合:将已解决的子问题逐层合并,最终得出原问题的解;

分治法适用的情况

  • 原问题的计算复杂度随着问题的规模的增加而增加。
  • 原问题能够被分解成更小的子问题。
  • 子问题的结构和性质与原问题一样,并且相互独立,子问题之间不包含公共的子子问题。
  • 原问题分解出的子问题的解可以合并为该问题的解。

算法伪代码

def divide_conquer(problem, paraml, param2,...):
    # 不断切分的终止条件
    if problem is None:
        print_result
        return
    # 准备数据
    data=prepare_data(problem)
    # 将大问题拆分为小问题
    subproblems=split_problem(problem, data)
    # 处理小问题,得到子结果
    subresult1=self.divide_conquer(subproblems[0],p1,..)
    subresult2=self.divide_conquer(subproblems[1],p1,...)
    subresult3=self.divide_conquer(subproblems[2],p1,.)
    # 对子结果进行合并 得到最终结果
    result=process_result(subresult1, subresult2, subresult3,...)

二、算法应用

1.力扣169.多数元素【传送门】

在这里插入图片描述
解法1:分治法
解题思路

  • 确定切分的终止条件
    直到所有的子问题都是长度为 1 的数组,停止切分。

  • 准备数据,将大问题切分为小问题
    递归地将原数组二分为左区间与右区间,直到最终的数组只剩下一个元素,将其返回

  • 处理子问题得到子结果,并合并

    • 长度为 1 的子数组中唯一的数显然是众数,直接返回即可。
    • 如果它们的众数相同,那么显然这一段区间的众数是它们相同的值。
    • 如果他们的众数不同,比较两个众数在整个区间内出现的次数来决定该区间的众数

代码

class Solution(object):
    def majorityElement2(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 【不断切分的终止条件】
        if not nums:
            return None
        if len(nums) == 1:
            return nums[0]
        # 【准备数据,并将大问题拆分为小问题】
        left = self.majorityElement(nums[:len(nums)//2])
        right = self.majorityElement(nums[len(nums)//2:])
        # 【处理子问题,得到子结果】
        # 【对子结果进行合并 得到最终结果】
        if left == right:
            return left
        if nums.count(left) > nums.count(right):
            return left
        else:
            return right    

解法2:暴力解法(排序后直接返回中间的位置)
代码

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        nums.sort()
        return nums[len(nums)//2]

解法3:投票法【参考链接】
在这里插入图片描述
在这里插入图片描述
算法复杂度:时间复杂度 O(N) : N 为数组 nums 长度。空间复杂度 O(1) : votes 变量使用常数大小的额外空间。

代码

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        # nums.sort()
        # return nums[len(nums)//2]
        votes = 0
        for num in nums:
            if votes == 0:#初始化
                x = num
            if num == x:
                votes += 1
            else:
                votes -= 1
        return x

2.最大子序和【传送门】
在这里插入图片描述
注意这边的是连续子数组,还有另外一道题是上升的子序列,注意区分,在动态规划中我们会涉及到。

解法1. 分治法
解题思路

  • 确定切分的终止条件

    直到所有的子问题都是长度为 1 的数组,停止切分。

  • 准备数据,将大问题切分为小问题

    递归地将原数组二分为左区间与右区间,直到最终的数组只剩下一个元素,将其返回

  • 处理子问题得到子结果,并合并

    • 将数组切分为左右区间

      • 对与左区间:从右到左计算左边的最大子序和
      • 对与右区间:从左到右计算右边的最大子序和
    • 由于左右区间计算累加和的方向不一致,因此,左右区间直接合并相加之后就是整个区间的和

    • 最终返回左区间的元素、右区间的元素、以及整个区间(相对子问题)和的最大值

代码

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 【确定不断切分的终止条件】
        n = len(nums)
        if n == 1:
            return nums[0]

        # 【准备数据,并将大问题拆分为小的问题】
        left = self.maxSubArray(nums[:len(nums)//2])
        right = self.maxSubArray(nums[len(nums)//2:])

        # 【处理小问题,得到子结果】
        # 从右到左计算左边的最大子序和
        max_l = nums[len(nums)//2 -1] # max_l为该左数组的最右边的元素
        tmp = 0 # tmp用来记录连续子数组的和
        
        for i in range( len(nums)//2-1 , -1 , -1 ):# 从右到左遍历数组的元素
            tmp += nums[i]
            max_l = max(tmp ,max_l)
            
        # 从左到右计算右边的最大子序和
        max_r = nums[len(nums)//2] # 右数组的左边界
        tmp = 0
        for i in range(len(nums)//2,len(nums)):
            tmp += nums[i]
            max_r = max(tmp,max_r)
            
        # 【对子结果进行合并 得到最终结果】
        # 返回三个中的最大值
        return max(left,right,max_l+ max_r)

在这里插入图片描述在这里插入图片描述
参考链接

解法2.动态规划

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        # 原地修改数组,动态规划
        maxnum = nums[0]
        for i in range(1,len(nums)):
             if nums[i-1] > 0:
                 nums[i] += nums[i-1]
             maxnum = max(maxnum,nums[i])
        return maxnum

在这里插入图片描述
在这里插入图片描述
参考链接

3.Pow(x,n)【传送门】
在这里插入图片描述
解法1.分治法
解题思路

  • 确定切分的终止条件

    n不断除以2,并更新n,直到为0,终止切分

  • 准备数据,将大问题切分为小问题

    n不断除以2,更新

  • 处理子问题得到子结果,并合并

    • x与自身相乘更新x
    • 如果n%2 ==1
      • p乘以x之后赋值给p(初始值为1),返回p
  • 最终返回p

在这里插入图片描述
参考链接

class Solution(object):
    def myPow(self, x, n):
        """
        :type x: float
        :type n: int
        :rtype: float
        """
        # 处理n为负的情况
        if n < 0 :
            x = 1/x
            n = -n
        # 【确定不断切分的终止条件】
        if n == 0 :
            return 1

        # 【准备数据,并将大问题拆分为小的问题】
        if n%2 ==1:
          # 【处理小问题,得到子结果】
          p = x * self.myPow(x,n-1)# 【对子结果进行合并 得到最终结果】
          return p
        return self.myPow(x*x,n/2)  

解法2. 快速幂解析(二进制角度)参考链接
在这里插入图片描述
在这里插入图片描述

class Solution:
    def myPow(self, x: float, n: int) -> float:
        if x == 0.0: return 0.0
        res = 1
        if  < 0: x, n = 1 / x, -n
        while n:
             if n & 1: res *= x
             x *= x
             n >>= 1
         return res

复杂度分析:
时间复杂度 O(logn) : 二分的时间复杂度为对数级别。
空间复杂度 O(1) : rres等变量占用常数大小额外空间。

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
力扣是一个在线编程平台,提供了大量的算法题目,可以帮助程序员提高算法能力。回溯算法是一种搜索算法,它通过不断地尝试所有可能的解来求解问题。在回溯算法中,我们首先定义一个解空间,然后从解空间中搜索所有可能的解,直到找到符合要求的解为止。回溯算法通常用于求解组合问题、排列问题、子集问题等。 在 Java 中实现回溯算法,通常需要定义一个递归函数来搜索解空间。在递归函数中,我们首先判断当前状态是否符合要求,如果符合要求,则将当前状态加入到解集中;否则,我们继续搜索下一个状态。在搜索下一个状态时,我们需要对当前状态进行一些修改,然后递归调用自身来搜索下一个状态。当搜索完所有可能的状态后,我们需要回溯到上一个状态,继续搜索其他可能的状态。 以下是回溯算法的一般步骤: 1. 定义解空间:确定问题的解空间,并定义一个数据结构来表示解空间中的每个状态。 2. 确定约束条件:确定哪些状态是合法的,并定义一个函数来判断当前状态是否符合要求。 3. 确定搜索策略:确定搜索解空间的顺序,并定义一个函数来生成下一个状态。 4. 搜索解空间:使用递归函数搜索解空间,如果当前状态符合要求,则将其加入到解集中;否则,继续搜索下一个状态。 5. 回溯:当搜索完所有可能的状态后,回溯到上一个状态,继续搜索其他可能的状态。 以下是一个力扣题目的回溯算法 Java 实现示例: ``` class Solution { List<List<Integer>> res = new ArrayList<>(); List<Integer> path = new ArrayList<>(); public List<List<Integer>> subsets(int[] nums) { dfs(nums, 0); return res; } private void dfs(int[] nums, int start) { res.add(new ArrayList<>(path)); for (int i = start; i < nums.length; i++) { path.add(nums[i]); dfs(nums, i + 1); path.remove(path.size() - 1); } } } ``` 该算法用于求解给定数组的所有子集。在递归函数中,我们首先将当前状态加入到解集中,然后从当前位置开始搜索下一个状态。在搜索下一个状态时,我们将当前元素加入到路径中,并递归调用自身来搜索下一个状态。当搜索完所有可能的状态后,我们需要回溯到上一个状态,继续搜索其他可能的状态。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值