一文搞定分治算法

分治

算法入门

搜索策略

基于分治实现二分查找

构建二叉树问题

汉诺塔问题

相关例题:

leetcode226.翻转二叉树

 法一:递归

leetcode101.对称二叉树

法一:递归

法二:迭代

leetcode50. Pow(x, n)

法一:快速幂+递归

法二:快速幂+迭代

leetcode110.平衡二叉树

法一:自顶向下递归

法二:自底向上递归


算法入门

:递归地将原问题分解为两个或多个子问题,直至到达最小子问题时终止。
:从已知解的最小子问题开始,从底至顶地将子问题的解进行合并,从而构建出原问题
的解。

搜索策略

基于分治实现二分查找

给定一个长度为 𝑛 的有序数组 nums ,其中所有元素都是唯一的,请查找元素 target

 分治过程:

声明一个递归函数来求解:

/* 二分查找:问题 f(start, end) */
int recursiveSearch(int[] nums, int target, int start, int end) {
    // 如果区间无效,代表未找到目标元素,返回 -1
    if (start > end) {
        return -1;
    }
    
    // 计算中间索引 mid
    int mid = (start + end) / 2;
    
    if (nums[mid] < target) {
        // 递归搜索右半部分 f(mid + 1, end)
        return recursiveSearch(nums, target, mid + 1, end);
    } else if (nums[mid] > target) {
        // 递归搜索左半部分 f(start, mid - 1)
        return recursiveSearch(nums, target, start, mid - 1);
    } else {
        // 找到目标元素,返回它的索引
        return mid;
    }
}

/* 二分查找主函数 */
int binarySearch(int[] nums, int target) {
    int length = nums.length;
    // 从整个数组范围 f(0, length - 1) 开始求解
    return recursiveSearch(nums, target, 0, length - 1);
}

构建二叉树问题

给定一棵二叉树的前序遍历 preorder 和中序遍历 inorder ,请从中构建二叉树,返回二叉树的根节点。假设二叉树中没有值重复的节点。

分析:

前序遍历的首元素 3 是根节点的值。
查找根节点 3 在 inorder 中的索引,利用该索引可将 inorder 划分为 [ 9 | 3 1 2 7 ]
根据 inorder 的划分结果,易得左子树和右子树的节点数量分别为 1 和 3 ,从而可将 preorder 划分为 [ 3 | 9 | 2 1 7 ]
即可得到根节点、左子树、右子树在 preorder inorder 中的索引区间
将当前树的根节点在 preorder 中的索引记为 𝑖
将当前树的根节点在 inorder 中的索引记为 𝑚
将当前树在 inorder 中的索引区间记为 [𝑙, 𝑟]
得到:
代码实现:
/* 构建二叉树:分治法 */
TreeNode buildSubTree(int[] preorder, Map<Integer, Integer> inorderIndexMap, int preorderIndex, int left, int right) {
    // 子树区间无效时终止
    if (right < left) {
        return null;
    }
    
    // 创建当前根节点
    TreeNode currentRoot = new TreeNode(preorder[preorderIndex]);
    
    // 获取当前根节点在中序数组中的索引 m,从而划分左右子树
    int inorderIndex = inorderIndexMap.get(preorder[preorderIndex]);
    
    // 子问题:构建左子树
    currentRoot.left = buildSubTree(preorder, inorderIndexMap, preorderIndex + 1, left, inorderIndex - 1);
    
    // 子问题:构建右子树
    currentRoot.right = buildSubTree(preorder, inorderIndexMap, preorderIndex + 1 + inorderIndex - left, inorderIndex + 1, right);
    
    // 返回当前根节点
    return currentRoot;
}

/* 构建二叉树的主函数 */
TreeNode buildTree(int[] preorder, int[] inorder) {
    // 初始化哈希表,存储中序数组元素到索引的映射
    Map<Integer, Integer> inorderIndexMap = new HashMap<>();
    for (int i = 0; i < inorder.length; i++) {
        inorderIndexMap.put(inorder[i], i);
    }
    
    // 从预序列数组和中序映射开始构建树
    TreeNode root = buildSubTree(preorder, inorderIndexMap, 0, 0, inorder.length - 1);
    
    return root;
}

汉诺塔问题

给定三根柱子,记为 A B C 。起始状态下,柱子 A 上套着 𝑛 个圆盘,它们从上到下按照从小到大的
顺序排列。我们的任务是要把这 𝑛 个圆盘移到柱子 C 上,并保持它们的原有顺序不变(如图 12‑10 所
示)。在移动圆盘的过程中,需要遵守以下规则。
1. 圆盘只能从一根柱子顶部拿出,从另一根柱子顶部放入。
2. 每次只能移动一个圆盘。
3. 小圆盘必须时刻位于大圆盘之上。
将原问题 𝑓(𝑛) 划分为两个子问题 𝑓(𝑛−1) 和一个子问题 𝑓(1) ,并按照以下顺序解决这三个子问题。
1. 将 𝑛 − 1 个圆盘借助 C A 移至 B
2. 将剩余 1 个圆盘从 A 直接移至 C
3. 将 𝑛 − 1 个圆盘借助 A B 移至 C
对于这两个子问题 𝑓(𝑛 − 1) 可以通过相同的方式进行递归划分 ,直至达到最小子问题 𝑓(1)

 代码实现:

/* 移动一个圆盘 */
void transferDisk(List<Integer> source, List<Integer> target) {
    // 从 source 顶部拿出一个圆盘
    Integer disk = source.remove(source.size() - 1);
    // 将圆盘放入 target 顶部
    target.add(disk);
}

/* 求解汉诺塔问题 f(n) */
void solveHanoi(int n, List<Integer> source, List<Integer> auxiliary, List<Integer> target) {
    // 若 source 只剩下一个圆盘,则直接将其移到 target
    if (n == 1) {
        transferDisk(source, target);
        return;
    }
    
    // 子问题 f(n-1):将 source 顶部 n-1 个圆盘借助 target 移到 auxiliary
    solveHanoi(n - 1, source, target, auxiliary);
    // 子问题 f(1):将 source 剩余一个圆盘移到 target
    transferDisk(source, target);
    // 子问题 f(n-1):将 auxiliary 顶部 n-1 个圆盘借助 source 移到 target
    solveHanoi(n - 1, auxiliary, source, target);
}

/* 求解汉诺塔问题主函数 */
void solveHanoiMain(List<Integer> A, List<Integer> B, List<Integer> C) {
    int numDisks = A.size();
    // 将 A 顶部 numDisks 个圆盘借助 B 移到 C
    solveHanoi(numDisks, A, B, C);
}

相关例题:

leetcode226.翻转二叉树

226. 翻转二叉树icon-default.png?t=O83Ahttps://leetcode.cn/problems/invert-binary-tree/

 法一:递归

public class Method01 {
    /**
     * 反转一棵二叉树。
     *
     * @param root 二叉树的根节点
     * @return 返回反转后的二叉树的根节点
     */
    public TreeNode invertTree(TreeNode root) {
        // 如果当前节点为空,返回null
        if (root == null) {
            return null;
        }

        // 递归反转左子树,并将结果保存在left中
        TreeNode left = invertTree(root.left);

        // 递归反转右子树,并将右子树赋值给当前节点的左子树
        root.left = invertTree(root.right);

        // 将之前保存的左子树赋值给当前节点的右子树
        root.right = left;

        // 返回反转后的根节点
        return root;
    }
}

leetcode101.对称二叉树

101. 对称二叉树icon-default.png?t=O83Ahttps://leetcode.cn/problems/symmetric-tree/

法一:递归

public class Method01 {
    /**
     * 判断一棵二叉树是否是对称的。
     *
     * @param root 二叉树的根节点
     * @return 如果二叉树是对称的,返回 true;否则返回 false
     */
    public boolean isSymmetric(TreeNode root) {
        // 调用辅助方法检查根节点的左子树和右子树是否对称
        return cheakSymmetric(root.left, root.right);
    }

    /**
     * 辅助方法,检查两个子树是否对称。
     *
     * @param left  左子树的根节点
     * @param right 右子树的根节点
     * @return 如果两个子树是对称的,返回 true;否则返回 false
     */
    private boolean cheakSymmetric(TreeNode left, TreeNode right) {
        // 如果两个子树都为空,返回 true
        if (left == null && right == null) {
            return true;
        }
        // 如果一个子树为空而另一个不为空,返回 false
        if (left == null || right == null) {
            return false;
        }
        // 检查当前两个节点的值是否相等,并递归检查子树的对称性
        return left.val == right.val
                && cheakSymmetric(left.left, right.right) // 递归检查左子树的左节点与右子树的右节点
                && cheakSymmetric(left.right, right.left); // 递归检查左子树的右节点与右子树的左节点
    }
}

法二:迭代

public class Method02 {
    /**
     * 判断一棵二叉树是否是对称的。
     *
     * @param root 二叉树的根节点
     * @return 如果二叉树是对称的,返回 true;否则返回 false
     */
    public boolean isSymmetric(TreeNode root) {
        // 调用辅助方法检查树的左右子树是否对称
        return checkSymmetric(root, root);
    }

    /**
     * 辅助方法,通过队列实现广度优先搜索,检查两棵子树是否对称。
     *
     * @param root1 第一棵子树的根节点
     * @param root2 第二棵子树的根节点
     * @return 如果两棵子树是对称的,返回 true;否则返回 false
     */
    private boolean checkSymmetric(TreeNode root1, TreeNode root2) {
        // 使用队列进行广度优先遍历
        Queue<TreeNode> queue = new LinkedList<>();
        // 将两棵子树的根节点加入队列
        queue.offer(root1);
        queue.offer(root2);

        // 当队列不为空时循环处理
        while (!queue.isEmpty()) {
            // 从队列中取出两个节点进行比较
            TreeNode node1 = queue.poll();
            TreeNode node2 = queue.poll();

            // 如果两个节点都为 null,继续下一轮循环
            if (node1 == null && node2 == null) {
                continue;
            }

            // 如果其中一个节点为 null,另一个不为 null,则树不对称
            if (node1 == null || node2 == null) {
                return false;
            }

            // 如果两个节点的值不相等,则树不对称
            if (node1.val != node2.val) {
                return false;
            }

            // 将左子树和右子树的节点以相反的顺序加入队列
            queue.offer(node1.left);
            queue.offer(node2.right);
            queue.offer(node1.right);
            queue.offer(node2.left);
        }

        // 如果遍历完成且没有发现不对称的情况,则树是对称的
        return true;
    }
}

leetcode50. Pow(x, n)

50. Pow(x, n)icon-default.png?t=O83Ahttps://leetcode.cn/problems/powx-n/

法一:快速幂+递归

public class Method01 {
    public double myPow(double x, int n) {
        return n >= 0 ? quickMul(x, n) : 1.0 / quickMul(x, -n);
    }

    private double quickMul(double x, int n) {
        if (n == 0) {
            return 1; // 任何数的0次幂都是1
        }
        double y = quickMul(x, n / 2); // 递归计算x的n/2次幂
        return n % 2 == 0 ? y * y : y * y * x; // 判定n是偶数还是奇数
    }
}

法二:快速幂+迭代

public class Method02 {
    public double myPow(double x, int n) {
        long N = n;
        return N >= 0 ? quickMul(x, N) : 1.0 / quickMul(x, -N);
    }

    public double quickMul(double x, long N) {
        double ans = 1.0;
        // 贡献的初始值为 x
        double x_contribute = x;
        // 在对 N 进行二进制拆分的同时计算答案
        while (N > 0) {
            if (N % 2 == 1) {
                // 如果 N 二进制表示的最低位为 1,那么需要计入贡献
                ans *= x_contribute;
            }
            // 将贡献不断地平方
            x_contribute *= x_contribute;
            // 舍弃 N 二进制表示的最低位,这样我们每次只要判断最低位即可
            N /= 2;
        }
        return ans;
    }
}

leetcode110.平衡二叉树

110. 平衡二叉树icon-default.png?t=O83Ahttps://leetcode.cn/problems/balanced-binary-tree/

法一:自顶向下递归

public class Method01 {
    // 判断一棵树是否是平衡二叉树
    public boolean isBalanced(TreeNode root) {
        // 如果根节点为空,返回true(空树是平衡的)
        if (root == null) {
            return true;
        }

        // 计算左子树和右子树的深度差
        // 如果深度差小于等于1,并且左子树和右子树均是平衡的,则返回true
        return Math.abs(getDepth(root.left) - getDepth(root.right)) <= 1
                && isBalanced(root.left)
                && isBalanced(root.right);
    }

    // 获取树的深度
    private int getDepth(TreeNode root) {
        // 如果节点为空,深度为0
        if (root == null) {
            return 0;
        }

        // 递归计算左右子树的深度,返回当前节点的深度(1 + 左右子树深度的最大值)
        return 1 + Math.max(getDepth(root.left), getDepth(root.right));
    }
}

法二:自底向上递归

public class Method02 {
    // 判断一棵树是否是平衡二叉树
    public boolean isBalanced(TreeNode root) {
        // 如果树的高度大于等于0,说明是平衡的,否则返回false
        return height(root) >= 0;
    }

    // 计算树的高度,并判断树是否平衡
    public int height(TreeNode root) {
        // 如果当前节点为空,返回0,表示空树的高度
        if (root == null) {
            return 0;
        }

        // 递归计算左子树和右子树的高度
        int leftHeight = height(root.left);
        int rightHeight = height(root.right);

        // 如果左子树或右子树不平衡,返回-1
        // 或者左右子树的高度差大于1,返回-1
        if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) {
            return -1; // 表示不平衡
        } else {
            // 返回当前节点的高度
            return Math.max(leftHeight, rightHeight) + 1; // 高度为左右子树的最大高度加1
        }
    }
}

文章记录了学习Krahets的《Hello 算法》的轨迹,代码均使用Java语言,原书支持 Python、C++、Java、C#、Go、Swift、JavaScript、TypeScript、Dart、 Rust、C 和 Zig 等语言。

教程链接:krahets/hello-algo: 《Hello 算法》:动画图解、一键运行的数据结构与算法教程。支持 Python, Java, C++, C, C#, JS, Go, Swift, Rust, Ruby, Kotlin, TS, Dart 代码。简体版和繁体版同步更新,English version ongoing (github.com)icon-default.png?t=O83Ahttps://github.com/krahets/hello-algo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值