分治法(二)

本文探讨了分治法和动态规划在解决特定算法问题中的应用,通过具体实例如Searcha2DMatrixII和DifferentWaystoAddParentheses,详细阐述了两种方法的实现过程及其优劣。分治法通过将问题分解为更小的子问题来简化解决方案,而动态规划则通过存储和复用子问题的解来避免重复计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2. 分治法解题

2.1 Search a 2D Matrix II

题目链接:https://leetcode.com/problems/search-a-2d-matrix-ii/description/
(1)遍历矩阵
对于这个问题,很自然地想到对矩阵进行遍历,但遍历的时间复杂度为O(m*n)。显然,这不是一个好的算法。

class Solution:
    def searchMatrix(self, matrix, target):
        """
        :type matrix: List[List[int]]
        :type target: int
        :rtype: bool
        """
        if len(matrix) == 0:
            return False
        row, column = 0, len(matrix[0]) -  1
        row_len = len(matrix) - 1
        while (row <= row_len) and (column >= 0):
            val = matrix[row][column]
            if target == val:
                return True
            elif target > val:
                row += 1
            else:
                column -= 1
        return False

(2)分治思想
由于行和列都是排好序的,显然在行列的遍历过程中可以使用二分搜索使得遍历的时间复杂度降低。
如何利用分治思想呢?其实就是在行或列搜索过程中使用二分查找法!

class Solution:
    def searchMatrix(self, matrix, target):
        """
        :type matrix: List[List[int]]
        :type target: int
        :rtype: bool
        """
        if len(matrix) < 1 or len(matrix[0]) < 1:
            return False
        
        for row in matrix:
            if row[0] > target:
                return False
            elif row[len(row) - 1] < target:
                continue
            else:
                l = 0
                r = len(row) - 1
                while l <= r:
                    mid = (l + r) // 2
                    if row[mid] > target:
                        r = mid - 1
                    elif row[mid] < target:
                        l = mid + 1
                    else:
                        return True
        return False

2.2 Different Ways to Add Parentheses

题目链接:https://leetcode.com/problems/different-ways-to-add-parentheses/description/
本题若直接对表达式进行组合,考虑的情况非常多,但是可以采用分治的思想将复杂的大问题化简为若干个小问题进行求解。
下面以表达式2 * 3 - 4 * 5为例进行分析。
在这里插入图片描述
其实就是化简为左右两部分,然后再对每一部分进行计算,直至子问题规模为1,即左右部分只有一个元素。

class Solution:
    def diffWaysToCompute(self, input):
        """
        :type input: str
        :rtype: List[int]
        """
        if input.isdigit():
            return [int(input)]
        
        res = []
        for i in range(len(input)):
            if input[i] in '+-*':
                res1 = self.diffWaysToCompute(input[:i])
                res2 = self.diffWaysToCompute(input[i+1 :])
                for j in res1:
                    for k in res2:
                        res.append(self.helper(j, k, input[i]))
        res.reverse()
        return res
    
    
    def helper(self, j, k, op):
        if op == '+':
            return j + k
        elif op == '-':
            return j - k
        else:
            return j * k      

在这里插入图片描述
显然,该方法有待改进。


2020年9月2日更新
现提供动态规划解法,至于DP核心思想后面再介绍,这里只介绍本题的DP步骤,再与分治法时间作一下对比。

  1. dp[i][j] 存储区间 [i,j] 内可能的结果数值,为一个列表
  2. 先初始化 dp[i][i]
  3. 建立转移方程: dp[i][j] = dp[i][k] X dp[k+1][j]( i <= k < j)(假设当前遍历的是 dp[i][k] X dp[k+1][j], 那么它们之间的运算符即为 operator[k],枚举 dp[i][k] 与 dp[k+1][j] 之间的所有结果进行运算即可。)
class Solution:
    def diffWaysToCompute(self, input: str) -> List[int]:
        nums, operator = self.split(input)
        n = len(nums)
        dp = [[list() for _ in range(n)] for _ in range(n)]
        for i in range(n-1, -1, -1):
            dp[i][i].append(nums[i])
            for j in range(i + 1, n):
                # 状态转移方程有k控制
                # 对于本题举例
                # dp[0][3] = dp[0][0] X dp[1][3]
                # dp[0][3] = dp[0][1] X dp[2][3]
                # dp[0][3] = dp[0][2] X dp[3][3]
                for k in range(i, j):
                    for a in dp[i][k]:
                        for b in dp[k+1][j]:
                            if operator[k] == '+':
                                dp[i][j].append(a + b)
                            if operator[k] == '-':
                                dp[i][j].append(a - b)
                            if operator[k] == '*':
                                dp[i][j].append(a * b)
        return sorted(dp[0][-1])

    def split(self, input: str):
        nums, operator, chars, i = [], [], list(input), 0
        op = ['+', '-', '*']
        while i < (length := len(input)):
            if chars[i] in op:
                operator.append(chars[i])
                i += 1
            else:
                val = []
                while i < length and '0' <= chars[i] <= '9':
                    val.append(chars[i])
                    i += 1
                nums.append(int(''.join(val)))
        return nums, operator

在这里插入图片描述

### 关于分治法分查找算法 #### 分治法概述 分治法是一种解决问题的方法论,其核心思想在于将一个复杂的大问题分解为多个较小规模的子问题来求解。通过分别解决这些子问题并组合它们的结果,最终可以得到原问题的答案[^1]。 #### 分查找简介 作为一种典型的分治策略应用实例,分查找适用于已排序的数据集上快速定位特定目标值的位置。此方法利用数据已经按顺序排列的特点,在每次迭代过程中都将待查区间缩小一半,从而极大地提高了搜索效率[^2]。 #### 时间复杂度分析 对于长度为 \( n \) 的有序数组执行一次成功的分查找操作所需时间可表示为 \( T(n)=O(\log_2{n}) \)[^3]。这是因为每轮循环都会使剩余元素数量减半直到找到匹配项或者确认不存在为止。 ```cpp // C++ 实现递归版本的分查找 int binarySearch(int arr[], int l, int r, int x){ if (r >= l) { int mid = l + (r - l) / 2; // 如果找到了目标,则返回索引位置 if (arr[mid] == x) return mid; // 若目标小于中间值,则继续在左半部分寻找 if (arr[mid] > x) return binarySearch(arr, l, mid - 1, x); // 否则向右半边探索 return binarySearch(arr, mid + 1, r, x); } // 当未命中时返回 -1 表示找不到该元素 return -1; } ``` #### 终止条件判断 为了确保算法能够正常终止而不陷入无限循环之中,需要特别注意边界情况下的处理逻辑。具体来说就是在更新左右指针之前先验证 `left<=right` 是否依然满足;如果不成立就意味着当前范围内不再存在可能的目标值,此时应立即停止进一步的操作[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值