一文了解分而治之和动态规则算法在前端中的应用

简介: 该文章详细介绍了分而治之策略和动态规划算法在前端开发中的应用,并通过具体的例子和LeetCode题目解析来说明这两种算法的特点及使用场景。

分而治之和动态规则

众多周知,分而治之算法和动态规则算法是前端面试中的“宠儿”。而在我们的日常生活中,这两个场景的应用也相对比较广泛。比如,分而治之算法常用于翻转二叉树、快速搜索等场景中,而动态规则算法,则常用于最少硬币找零问题、背包问题等场景中。

在下面的这篇文章中,将讲解分而治之和动态规则的常用场景以及对 leetcode 的一些经典例题进行解析。

一、分而治之

1、分而治之是什么?

  • 分而治之是算法设计中的一种方法。
  • 它将一个问题成多个和原问题相似的小问题,递归解决小问题再将结果合并以解决原来的问题。

2、应用场景

  • 归并排序
  • 快速搜索
  • 二分搜索
  • 翻转二叉树
  • ……

3、场景剖析:归并排序和快速排序

(1)场景一:归并排序

  • :把数组从中间一分为二。
  • :递归地递归的对两个子数组进行归并排序。
  • :合并有序子数组。

(2)场景二:快速排序

  • :选基准,按照基准把数组分成两个子数组。
  • :递归地对两个子数组进行快速排序。
  • :对两个子数组进行合并。

二、动态规则

1、动态规则是什么?

  • 动态规则是算法设计中的一种方法;
  • 它将一个问题分解为相互重叠的子问题,通过反复求解子问题,来解决原来的问题。

看到这里,很多小伙伴会想着,动态规则和分而治之不是解决同样的问题吗?其实不是的。

注意:

  • 动态规则解决相互重叠的子问题。

  • 分而治之解决的是相互独立的子问题。

这样说可能还有点抽象,稍后将在第3点的时候做详细解析。

2、应用场景

  • 最少硬币找零问题
  • 背包问题
  • 最长公共子序列
  • 矩阵链相乘
  • ……

3、场景剖析:斐波那契数列

斐波那契数列是一个很典型的数学问题。斐波那契数列指的是这样一个数列:

斐波那契数列

这个数列从第3项开始,每一项都等于前两项之和。即:

Fibonacci[n]= {0,n=01,n=1Fibonacci[n1]+Fibonacci[n2],n>1

那么我们来梳理一下,斐波那契数列是怎么运用动态规则算法的。主要有以下两点:

  • 定义子问题:F(n)=F(n - 1) + F(n - 2);
  • 反复执行:从2循环到n,执行上述公式。

4、动态规则VS分而治之

看完上面的内容,我们来梳理下动态规则和分而治之的区别。先用一张图展示两者的区别。

动态规则和分而治之的区别

大家可以看到,左边的斐波那契数列是将所有问题分解为若干个相互重叠的问题,每个问题的解法都一样。

右边的翻转二叉树,左右子树是相互独立的,需先翻转左右子树,且在翻转过程中,它们各自翻转,互不干扰,左子树干左子树的活,右子树干右子树的活。

不像斐波那契数列那样,每一层都是相互依赖的,一层嵌套一层,相互重叠。

这就是动态规则和分而治之的区别。

三、分而治之算法常见应用

引用leetcode的几道经典题目来强化分而治之算法

1、leetcode 374:猜数字大小

(1)题意

这里附上原题链接

猜数字游戏的规则如下:

  • 每轮游戏,我都会从 1n 随机选择一个数字。 请你猜选出我选的是哪个数字。
  • 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。

你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

  • -1 :我选出的数字比你猜的数字小 pick < num
  • 1 :我选出的数字比你猜的数字大 pick > num
  • 0 :我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num

返回我选出的数字。

(2)解题思路

  • 二分搜索,同样具备“分、解、合”的特性。
  • 考虑选择分而治之。

(3)解题步骤

  • :计算中间元素,分割数组。
  • :递归地在较大或者较小的数组进行二分搜索。
  • :不需要此步,因为在子数组中搜到就返回了。

(4)代码实现

/** 
 * Forward declaration of guess API.
 * @param {number} num   your guess
 * @return                 -1 if num is lower than the guess number
 *                         1 if num is higher than the guess number
 *                       otherwise return 0
 * var guess = function(num) {}
 */

/**
 * @param {number} n
 * @return {number}
 */
let guessNumber = function(n) {
   

    const rec = (low, high) => {
   
        if(low > high){
   
            return;
        }

       // 1.计算中间元素,分割数组
        const mid = Math.floor((low + high) / 2);

        // 2.与猜测的数字进行比较
        const res = guess(mid);

        // 3.递归地在较大或者较小子数组进行二分搜索
        if(res === 0){
   
            return mid;
        }else if(res === 1){
   
            return rec(mid + 1, high);
        }else{
   
            return rec(low, mid - 1);
        }
    }
    return rec(1, n);
};
AI 代码解读

2、leetcode 226:翻转二叉树

(1)题意

这里附上原题链接

翻转一棵二叉树。

翻转二叉树

(2)解题思路

  • 先翻转左右子树,再将子树换个位置。
  • 符合“分、解、合”特性。
  • 考虑选择分而治之。

(3)解题步骤

  • :获取左右子树。
  • :递归地翻转左右子树。
  • :将翻转后的左右子树换个位置放到根节点上。

(4)代码实现

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
 var invertTree = function(root) {
   
    if(!root){
   
        return null;
    }
    return{
   
        //1.根节点值不变
        val:root.val,
        //2.递归地将左子树与右子树结点变换
        left:invertTree(root.right),
        //3.递归地将右子树与左子树结点变换
        right:invertTree(root.left)
    }
};
AI 代码解读

3、leetcode 100:相同的树

(1)题意

这里附上原题链接

给你两棵二叉树的根节点 pq ,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

相同的树

(2)解题思路

  • 两棵树:根节点的值相同,左子树相同,右子树相同。
  • 符合“分、解、合”特性。
  • 考虑选择分而治之。

(3)解题步骤

  • :获取两棵树的左子树和右子树。
  • :递归地判断两棵树的左子树是否相同,右子树是否相同。
  • :将上述结果合并,如果根节点的值也相同,两棵树就相同。

(4)代码实现

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {boolean}
 */
let isSameTree = function(p, q) {
   
    if(!p && !q){
   
        return true;
    }
    /**
     * 判断条件:
     * 1.p树和q树同时存在;
     * 2.每遍历一个节点,两棵树的节点值都存在;
     * 3.递归左子树,比较每个节点值;
     * 4.递归右子树,比较每个节点值。
     */
    if(p && q && p.val === q.val &&
        isSameTree(p.left, q.left) &&
        isSameTree(p.right, q.right)
    ){
   
        return true;
    }
    return false;
};
AI 代码解读

4、leetcode 101:对称二叉树

(1)题意

这里附上原题链接

给定一个二叉树,检查它是否是镜像对称的。

对称二叉树

(2)解题思路

  • 转化为:左右子树是否镜像。
  • 分解为:树1的左子树和树2的右子树是否镜像,树1的右子树和树2的左子树是否镜像。
  • 符合“分、解、合”特性,考虑选择分而治之。

(3)解题步骤

  • :获取两棵树的左子树和右子树。
  • :递归地判断树1的左子树和树2的右子树是否镜像,树1的右子树和树2的左子树是否镜像。
  • :如果上述成立,且根节点值也相同,两棵树就镜像。

(4)代码实现

let isSymmetric = function(root){
   
    if(!root){
   
        return true;
    }

    const isMirror = (l, r) => {
   
        if(!l && !r){
   
            return true;
        }

        /**
         * 判断条件:
         * 1.左子树和右子树同时存在;
         * 2.左子树和右子树的根节点相同;
         * 3.左子树的左节点和右子树的右节点镜像相同;
         * 4.左子树的右结点和右子树的左结点镜像相同
         */
        if(l && r && l.val === r.val &&
            isMirror(l.left, r.right) &&
            isMirror(l.right, r.left)
        ){
   
            return true;
        }
        return false;
    }
    return isMirror(root.left, root.right);
}
AI 代码解读

四、动态规则算法常见应用

引用leetcode的几道经典题目来强化动态规则算法

1、leetcode 70:爬楼梯

(1)题意

这里附上原题链接

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

**注意:**给定 n 是一个正整数。

(2)解题思路

  • 爬到第n阶可以在第n - 1阶爬1个台阶,或者在第n - 2阶爬2个台阶。
  • F(n) = F(n - 1) + F(n - 2)。
  • 使用动态规则。

(3)解题步骤

  • 定义子问题:F(n) = F(n - 1) + F(n - 2)。
  • 反复执行:从2循环到n,执行上述公式。

(4)代码实现

 /*
 * @param {number} n
 * @return {number}
 */
// 数组方法
var climbStairs = function(n) {
   
    if(n < 2){
   
        return 1;
    }
     // 记录第0阶和第1阶可以走多少步
    const dp = [1, 1];
    // 从第2阶开始遍历,直至第5阶
    for(let i = 2; i <= n; i++){
   
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
};
AI 代码解读

如果dp用一维数组来记录的话,时间复杂度和空间复杂度都为O(n),这样子的话效率还是偏低的。

那么有什么方法可以来降低它的复杂度呢?

可以采用变量的方法。从上面的代码中我们可以看出,dp的值用一个数组存着,一直在线性增长。那么这个时候我们可以考虑把这个一维数组变换成单变量的形式,不断进行替换,来降低空间复杂度

下面用代码实现一遍。

let climbStairs2 = function(n){
   
    if(n < 2){
   
        return 1;
    }
    //定义一个变量,记录 n - 2 时的台阶数
    let dp0 = 1;
    //定义一个变量,记录 n - 1 时的台阶数
    let dp1 = 1;
    for(let i = 2; i <= n; i++){
   
        const temp = dp0;
        //每遍历一次,就让dp0指向下一个数的值,即dp1
        dp0 = dp1;
        //每遍历一次,就让dp1指向dp1下一个数的值,即前两个数的和,也就是dp1和原来dp0的值
        dp1 = dp1 + temp;
    }
    return dp1;
}
AI 代码解读

从上面的代码中可以看出,没有了数组或者像矩阵一样线性增长的数组,空间复杂度就变为了O(1)。

2、leetcode 198:打家劫舍

(1)题意

这里附上原题链接

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

(2)解题思路

  • f(k) = 从前k个房屋中能偷窃到的最大数额。
  • Ak = 第k个房屋的钱数。
  • f(k) = max(f(k - 2) + Ak, f(k - 1))。
  • 考虑使用动态规则。

(3)解题步骤

  • 定义子问题:f(k) = max(f(k - 2) + Ak, f(k - 1))。
  • 反复执行:从2循环到n,执行上述公式。

(4)代码实现

/**
 * @param {number[]} nums
 * @return {number}
 */

let rob1 = function(nums) {
   
    if(nums.length === 0){
   
        return 0;
    }
    // 前0个房屋和前1个房屋能劫持到的金钱数
    const dp = [0,nums[0]];
    for(let i = 2; i <= nums.length; i++){
   
        dp[i] = Math.max(dp[i - 2] + nums[i - 1], dp[i - 1]);
    }
    return dp[nums.length];
};
AI 代码解读

与爬楼梯同样,如果dp用一维数组来记录的话,时间复杂度和空间复杂度都为O(n),这样子的话效率还是偏低的。

那这个时候就可以采用单变量的方法,来降低空间复杂度

下面用代码实现一遍。

let rob2 = function(nums) {
   
    if(nums.length === 0){
   
        return 0;
    }
    let dp0 = 0;
    let dp1 = nums[0];
    for(let i = 2; i <= nums.length; i++){
   
        const dp2 = Math.max(dp0 + nums[i - 1], dp1);
        dp0 = dp1;
        dp1 = dp2;
    }
    return dp1;
};
AI 代码解读

此时空间复杂度自然也就变为O(1)了。

3、leetcode 62:不同路径

(1)题意

这里附上原题链接

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

不用路径

(2)解题思路

  • 每一步只能向下或者向右移动一步,因此想要走到(i,j),如果向下走一步,那么从(i-1,j)走过来;如果向右走一步,那么从(i,j-1)走过来。
  • f(i, j) = f(i-1, j) + f(i, j-1)。
  • 使用动态规则。

(3)解题步骤

  • 定义子问题:f(i, j) = f(i-1, j) + f(i, j-1)。
  • 反复执行:从2循环到n,执行上述公式。

(4)代码实现

let uniquePaths = function(m, n){
   
    const f = new Array(m).fill(0).map(() => new Array(n).fill(0));
    for(let i = 0; i < m; i++){
   
        // 将第一列全部补上1
        f[i][0] = 1;
    }
    for(let j = 0; j < n; j++){
   
        // 将第一行全部补上1
        f[0][j] = 1;
    }

    for(let i = 1; i < m; i++){
   
        for(let j = 1; j < n; j++){
   
            f[i][j] = f[i - 1][j] + f[i][j - 1];
        }
    }
    return f[m - 1][n - 1];
}
AI 代码解读

五、结束语

分而治之和动态规则算法在前端中的应用还是挺多的,特别是在面试或笔试的时候会经常出现这类题目,大家可以在此之外再继续多刷刷此类 leetcode 的题,做多了慢慢就能举一反三了~

  • 关注公众号 星期一研究室 ,第一时间关注学习干货,更多有趣的专栏待你解锁~
  • 如果这篇文章对你有用,记得点个赞加个关注再走哦~
  • 我们下期见!🥂🥂🥂
目录
打赏
0
0
0
0
68
分享
相关文章
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
45 15
MapReduce在实现PageRank算法中的应用
总结来说,在实现PageRank算法时使用MapReduce能够有效地进行大规模并行计算,并且具有良好的容错性和可扩展性。
106 76
AI程序员:通义灵码 2.0应用VScode前端开发深度体验
AI程序员:通义灵码 2.0应用VScode前端开发深度体验,在软件开发领域,人工智能技术的融入正深刻改变着程序员的工作方式。通义灵码 2.0 作为一款先进的 AI 编程助手,与广受欢迎的代码编辑器 Visual Studio Code(VScode)相结合,为前端开发带来了全新的可能性。本文将详细分享通义灵码 2.0 在 VScode 前端开发环境中的深度使用体验。
154 2
从第十批算法备案通过名单中分析算法的属地占比、行业及应用情况
2025年3月12日,国家网信办公布第十批深度合成算法通过名单,共395款。主要分布在广东、北京、上海、浙江等地,占比超80%,涵盖智能对话、图像生成、文本生成等多行业。典型应用包括医疗、教育、金融等领域,如觅健医疗内容生成算法、匠邦AI智能生成合成算法等。服务角色以面向用户为主,技术趋势为多模态融合与垂直领域专业化。
JavaScript 中通过Array.sort() 实现多字段排序、排序稳定性、随机排序洗牌算法、优化排序性能,JS中排序算法的使用详解(附实际应用代码)
Array.sort() 是一个功能强大的方法,通过自定义的比较函数,可以处理各种复杂的排序逻辑。无论是简单的数字排序,还是多字段、嵌套对象、分组排序等高级应用,Array.sort() 都能胜任。同时,通过性能优化技巧(如映射排序)和结合其他数组方法(如 reduce),Array.sort() 可以用来实现高效的数据处理逻辑。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
通过Milvus内置Sparse-BM25算法进行全文检索并将混合检索应用于RAG系统
阿里云向量检索服务Milvus 2.5版本在全文检索、关键词匹配以及混合检索(Hybrid Search)方面实现了显著的增强,在多模态检索、RAG等多场景中检索结果能够兼顾召回率与精确性。本文将详细介绍如何利用 Milvus 2.5 版本实现这些功能,并阐述其在RAG 应用的 Retrieve 阶段的最佳实践。
通过Milvus内置Sparse-BM25算法进行全文检索并将混合检索应用于RAG系统
从第九批深度合成备案通过公示名单分析算法备案属地、行业及应用领域占比
2024年12月20日,中央网信办公布第九批深度合成算法名单。分析显示,教育、智能对话、医疗健康和图像生成为核心应用领域。文本生成占比最高(57.56%),涵盖智能客服、法律咨询等;图像/视频生成次之(27.32%),应用于广告设计、影视制作等。北京、广东、浙江等地技术集中度高,多模态融合成未来重点。垂直行业如医疗、教育、金融加速引入AI,提升效率与用户体验。
前端大模型应用笔记(三):Vue3+Antdv+transformers+本地模型实现浏览器端侧增强搜索
本文介绍了一个纯前端实现的增强列表搜索应用,通过使用Transformer模型,实现了更智能的搜索功能,如使用“番茄”可以搜索到“西红柿”。项目基于Vue3和Ant Design Vue,使用了Xenova的bge-base-zh-v1.5模型。文章详细介绍了从环境搭建、数据准备到具体实现的全过程,并展示了实际效果和待改进点。
414 14
前端大模型应用笔记(一):两个指令反过来说大模型就理解不了啦?或许该让第三者插足啦 -通过引入中间LLM预处理用户输入以提高多任务处理能力
本文探讨了在多任务处理场景下,自然语言指令解析的困境及解决方案。通过增加一个LLM解析层,将复杂的指令拆解为多个明确的步骤,明确操作类型与对象识别,处理任务依赖关系,并将自然语言转化为具体的工具命令,从而提高指令解析的准确性和执行效率。
145 6

热门文章

最新文章