算法策略 - 分治

分治(Divide And Conquer)

分治,也就是分而治之。它的一般步骤是:

(1) 将原问题分解成若干个规模较小的子问题(子问题和原问题的结构一样,只是规模不一样)
(2) 子问题又不断分解成规模更小的子问题,直到不能再分解(直到可以轻易计算出子问题的解)
(3) 利用子问题的解推导出原问题的解

因此,分治策略非常适合用递归,  需要注意的是:子问题之间是相互独立的

分治的应用: 快速排序、归并排序、Karatsuba算法(大数乘法)

主定理(Master Theorem)

习1 最大连续子序列和

问题:

给定一个长度为 n 的整数序列, 求它的最大连续子序列和
比如 –2、1、–3、4、–1、2、1、–5、4 的最大连续子序列和是 4 + (–1) + 2 + 1 = 6
这道题也属于最大切片问题(最大区段, Greatest Slice)

概念区分: 子串、子数组、子区间必须是连续的, 子序列是可以不连续的

法1 穷举

穷举出所有可能的连续子序列,并计算出它们的和,最后取它们中的最大值

static int maxSubarray1(int[] nums) {
    if (nums == null || nums.length == 0) return 0;
    int max = Integer.MIN_VALUE;
    for (int begin = 0; begin < nums.length; begin++) {
        for (int end = begin; end < nums.length; end++) {
            // sum是[begin, end]的和
            int sum = 0;
            for (int i = begin; i <= end; i++) {
                sum += nums[i];
            }
            max = Math.max(max, sum);
        }
    }
    return max;
}

空间复杂度:O(1), 时间复杂度:O(n^3)

法1 – 穷举 优化

重复利用前面计算过的结果

static int maxSubarray2(int[] nums) {
    if (nums == null || nums.length == 0) return 0;
    int max = Integer.MIN_VALUE;
    for (int begin = 0; begin < nums.length; begin++) {
        int sum = 0;
        for (int end = begin; end < nums.length; end++) {
            // sum是[begin, end]的和
            sum += nums[end];
            max = Math.max(max, sum);
        }
    }
    return max;
}

空间复杂度: O(1), 时间复杂度:O(n^2)

法2 分治

思路:

将序列均匀地分割成2个子序列
         [begin,end) = [begin,mid)+[mid,end),  mid=(begin+end) >> 1

假设[begin,end)的最大连续子序列和是S[i,j),  那么它有3种可能

  • [i, j) 存在于 [begin,mid) 中, 同时 S[i,j) 也是[begin,mid)的最大连续子序列和
  • [i, j) 存在于 [mid,end) 中, 同时 S[i,j) 也是[mid,end)的最大连续子序列和
  • [i, j) 一部分存在于[begin,mid) 中, 另一部分存在于 [mid,end) 中

[i,j) = [i,mid) + [mid,j)
S[i,mid) = max{S[k,mid)}, begin <= k < mid
S[mid,j) = max{S[mid,k)}, mid < k <= end

代码实现:

public class MaxSubArray {
    public static void main(String[] args) {
        int[] nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
        System.out.println(maxSubArray(nums));
    }

    static int maxSubArray(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        return maxSubArray(nums, 0, nums.length);
    }

    static int maxSubArray(int[] nums, int begin, int end) {
        if (end - begin <= 1) return nums[begin];
        int mid = (begin + end) >> 1;
        /**
         * 计算左侧最大连续子序列[begin,mid)
         * 怎么计算左侧最大连续子序列 ?
         *    从end开始累加, 每增加一个元素, 计算子序列大小是否增加
         * 为什么要从end开始计算 ?
         *    需要考虑最大子序列同时存在左侧 和 右侧, 并且所有的子序列都是通过mid从中间切分计算的
         */
        int leftSum = 0; //左侧子序列累计大小
        int leftMax = Integer.MIN_VALUE;
        for (int i = mid - 1; i >= begin; i--) {
            leftSum += nums[i]; //从mid位置开始向左累加
            leftMax = Math.max(leftSum, leftMax);
        }
        //计算右侧最大连续子序列[mid, end)
        int rightSum = 0; //右侧子序列累计大小
        int rightMax = Integer.MIN_VALUE;
        for (int i = mid; i < end; i++) {
            rightSum += nums[i]; //从mid位置开始向右累加
            rightMax = Math.max(rightSum, rightMax);
        }
        int partMax = Math.max(maxSubArray(nums, begin, mid), maxSubArray(nums, mid, end));
        return Math.max(leftMax + rightMax, partMax);
    }
}

空间复杂度: O(logn), 时间复杂度: O(nlogn)
跟归并排序、快速排序一样, T(n) = 2T(n/2)+O(n)

习2 大数乘法

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值