沐游虞笔记
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 课程导读-必看
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速diff
  • 最长递增子序列
    • 模板编译器
    • 模板编译提升
    • 组件name作用
    • Vue项目性能优化
    • 路由传参方式
    • vue3笔面试题汇总
    luzhichang
    2024-09-27
    目录

    最长递增子序列

    # 最长递增子序列

    基本介绍

    最长递增子序列(Longest Increasing Subsequence,简称 LIS)是计算机科学中一个经典的算法问题。这看上去是很难的一个词语,遇到这种词,最简单的方法就是拆词,这里可以拆为 3 个词:最长、递增、子序列。

    1. 子序列

      [1, 2, 3, 4, 5]
      

      子序列有多个:

      [1, 2, 3]
      [1, 3]
      [2, 4, 5]
      
    2. 递增

      [2, 1, 5, 3, 6, 4, 8, 9, 7]
      

      这个子序列里面的元素必须是递增的:

      [1, 5] // 子序列,并且是递增的
      [1, 3, 6] // 子序列,并且是递增的
      [2, 1, 5] // 子序列,但是不是递增的
      
    3. 最长

      相当于在上面的基础上,有增加了一个条件,需要是最长的、递增的子序列

      [2, 1, 5, 3, 6, 4, 8, 9, 7]
      

      最长递增子序列:

      [1, 3, 4, 8, 9]
      [1, 3, 6, 8, 9]
      [1, 5, 6, 8, 9]
      [2, 3, 4, 8, 9]
      [2, 3, 6, 8, 9]
      [2, 5, 6, 8, 9]
      

      可以看出,即便是最长递增子序列,仍然是可以有多个的。在开发中,不同的算法可能拿到不一样的结果,不过一般拿到其中一个最长递增子序列即可。

    实际意义

    • 股票趋势分析
    • 手写识别
    • 文本编辑和版本控制
    • ....

    暴力法

    暴力法的核心思想是:找到所有的递增子序列,然后从中找到长度最长的那一个。

    function getSequence(arr) {
      let maxLength = 0; // 记录最长递增子序列的长度
      let longetSeq = []; // 记录最长递增子序列
    
      /**
       *
       * @param {*} index 列表的下标
       * @param {*} subSeq 当前递增子序列
       */
      function findSubsequence(index, subSeq) {
        let currentNum = arr[index]; // 当前元素
        // 先把之前的递增子序列展开,再加上当前元素
        let newSeq = [...subSeq, currentNum]; // 新的递增子序列
    
        // 遍历下标之后的内容
        for (let i = index + 1; i < arr.length; i++) {
          // 遍历当前下标之后的元素时,发现有比当前元素大的元素
          if (arr[i] > currentNum) {
            findSubsequence(i, newSeq);
          }
        }
    
        // 每一次递归结束后,就会得到一个新的递增子序列
        // 相当于找到了所有的递增子序列
        // console.log("newSeq:", newSeq);
    
        if (newSeq.length > maxLength) {
          maxLength = newSeq.length;
          longetSeq = newSeq;
        }
      }
    
      for (let i = 0; i < arr.length; i++) {
        findSubsequence(i, []);
      }
    
      return longetSeq;
    }
    
    const list = [2, 1, 5, 3, 6, 4, 8, 9, 7];
    const result = getSequence(list);
    console.log(result); // [2, 5, 6, 8, 9]
    

    动态规划

    动态规划(Dynamic Programming)的核心思想是利用问题的最优子结构和重叠子问题特性,将复杂问题分解为更小的子问题,并且在解决这些子问题的时候会保存子问题的解,避免重复计算,从而高效地求解原问题。

    function getSequence(arr) {
      let maxLength = 0; // 记录最长递增子序列的长度
      let maxSeq = []; // 记录最长递增子序列
    
      let sequences = new Array(arr.length).fill().map(() => []);
    
      //   console.log(sequences);
    
      // 遍历数组
      for (let i = 0; i < arr.length; i++) {
        // 创建一个以当前元素为结尾的递增子序列
        let seq = [arr[i]];
        // 遍历之前的元素,找到比当前元素小的元素,从而构建递增子序列
        for (let j = 0; j < i; j++) {
          if (arr[j] < arr[i]) {
            // 把之前存储的序列和当前元素拼接起来
            seq = sequences[j].concat(arr[i]);
          }
        }
    
        // 将当前递增子序列存储起来
        sequences[i] = seq;
    
        // 更新最大的序列
        if (seq.length > maxLength) {
          maxLength = seq.length;
          maxSeq = seq;
        }
      }
      //   console.log(sequences);
      return maxSeq;
    }
    
    const list = [2, 1, 5, 3, 6, 4, 8, 9, 7];
    const result = getSequence(list);
    console.log(result); // [ 1, 3, 4, 8, 9 ]
    

    Vue3中的算法

    Vue3 中获取最长递增子序列,用到了 贪心 和 二分 查找。

    function getSequence(arr) {
      // 用于记录每个位置的前驱索引,以便最后重建序列
      const p = arr.slice();
      // 存储当前找到的最长递增子序列的索引
      const result = [0];
      // 声明循环变量和辅助变量
      let i, j, u, v, c;
      // 获取输入数组的长度
      const len = arr.length;
      // 遍历输入数组
      for (i = 0; i < len; i++) {
        const arrI = arr[i];
        // 忽略值为 0 的元素(Vue源码中的diff算法对0有特定处理)
        if (arrI !== 0) {
          // 获取当前最长序列中最后一个元素的索引
          j = result[result.length - 1];
          // 贪心算法部分:如果当前元素大于当前最长序列的最后一个元素,直接添加
          if (arr[j] < arrI) {
            // 记录当前元素的前驱索引为 j
            p[i] = j;
            // 将当前元素的索引添加到 result 中
            result.push(i);
            continue;
          }
          // 二分查找部分:在 result 中寻找第一个大于等于 arrI 的元素位置
          u = 0;
          v = result.length - 1;
          while (u < v) {
            // 取中间位置
            c = ((u + v) / 2) | 0;
            // 比较中间位置的值与当前值
            if (arr[result[c]] < arrI) {
              // 如果中间值小于当前值,搜索区间缩小到 [c + 1, v]
              u = c + 1;
            } else {
              // 否则,搜索区间缩小到 [u, c]
              v = c;
            }
          }
          // 如果找到的值大于当前值,进行替换
          if (arrI < arr[result[u]]) {
            // 如果 u 不为 0,记录前驱索引
            if (u > 0) {
              p[i] = result[u - 1];
            }
            // 更新 result 中的位置 u 为当前索引 i
            result[u] = i;
          }
        }
      }
      // 重建最长递增子序列
      u = result.length;
      v = result[u - 1];
      while (u-- > 0) {
        // 将索引替换为对应的前驱索引
        result[u] = v;
        v = p[v];
      }
      // 返回最长递增子序列的索引数组
      return result;
    }
    

    追踪流程:

    1. 初始化:

      • p = [2, 1, 5, 3, 6, 4, 8, 9, 7] 用于记录每个元素的前驱索引,初始为原数组的副本。
      • result = [0] 初始化结果数组,开始时只包含第一个元素的索引 0。
    2. 遍历数组:

      • i = 0, arrI = 2 第一个元素,索引已在 result 中,继续下一次循环。

      • i = 1, arrI = 1

        • arr[result[result.length - 1]] = arr[0] = 2
        • arrI (1) < 2,需要二分查找替换位置。
        • 二分查找 (u = 0, v = 0):
          • c = 0
          • arr[result[0]] = 2 > arrI (1)
          • v = c = 0
        • arrI (1) < arr[result[u]] (2),替换 result[0] = 1
        • 更新 result = [1]
      • i = 2, arrI = 5

        • arr[result[result.length - 1]] = arr[1] = 1
        • arrI (5) > 1,贪心算法:直接添加到 result
        • p[2] = 1
        • result.push(2)
        • 更新 result = [1, 2]
      • i = 3, arrI = 3

        • arr[result[result.length - 1]] = arr[2] = 5
        • arrI (3) < 5,需要二分查找。
        • 二分查找 (u = 0, v = 1):
          • c = 0
          • arr[result[0]] = arr[1] = 1 < arrI (3)
          • u = c + 1 = 1
          • arr[result[1]] = arr[2] = 5 > arrI (3)
          • v = c = 1
        • arrI (3) < arr[result[u]] (5),替换 result[1] = 3
        • p[3] = result[0] = 1
        • 更新 result = [1, 3]
      • i = 4, arrI = 6

        • arr[result[result.length - 1]] = arr[3] = 3
        • arrI (6) > 3,贪心算法:直接添加到 result
        • p[4] = 3
        • result.push(4)
        • 更新 result = [1, 3, 4]
      • i = 5, arrI = 4

        • arr[result[result.length - 1]] = arr[4] = 6
        • arrI (4) < 6,需要二分查找。
        • 二分查找 (u = 0, v = 2) :
          • c = 1
          • arr[result[1]] = arr[3] = 3 < arrI (4)
          • u = c + 1 = 2
          • arr[result[2]] = arr[4] = 6 > arrI (4)
          • v = c = 2
        • arrI (4) < arr[result[u]] (6),替换 result[2] = 5
        • p[5] = result[1] = 3
        • 更新 result = [1, 3, 5]
      • i = 6, arrI = 8

        • arr[result[result.length - 1]] = arr[5] = 4
        • arrI (8) > 4,贪心算法:直接添加到 result
        • p[6] = 5
        • result.push(6)
        • 更新 result = [1, 3, 5, 6]
      • i = 7, arrI = 9

        • arr[result[result.length - 1]] = arr[6] = 8
        • arrI (9) > 8,贪心算法:直接添加到 result
        • p[7] = 6
        • result.push(7)
        • 更新 result = [1, 3, 5, 6, 7]
      • i = 8, arrI = 7

        • arr[result[result.length - 1]] = arr[7] = 9
        • arrI (7) < 9,需要二分查找。
        • 二分查找 (u = 0, v = 4) :
          • c = 2
          • arr[result[2]] = arr[5] = 4 < arrI (7)
          • u = c + 1 = 3
          • c = 3
          • arr[result[3]] = arr[6] = 8 > arrI (7)
          • v = c = 3
        • arrI (7) < arr[result[u]] (8),替换 result[3] = 8
        • p[8] = result[2] = 5
        • 更新 result = [1, 3, 5, 8, 7]
    3. 重建序列:

      • u = result.length = 5
      • v = result[u - 1] = result[4] = 7
      • 迭代过程:
        • result[4] = v = 7
        • v = p[7] = 6
        • result[3] = v = 6
        • v = p[6] = 5
        • result[2] = v = 5
        • v = p[5] = 3
        • result[1] = v = 3
        • v = p[3] = 1
        • result[0] = v = 1
        • v = p[1](p[1] 初始为 1)
      • 最终 result = [1, 3, 5, 6, 7]
    4. 映射回原数组的值:

      • result.map(index => list[index]) 得到 [1, 3, 4, 8, 9]
      • 这是输入数组中的一个最长递增子序列

    -EOF-

    图解快速diff
    模板编译器

    ← 图解快速diff 模板编译器→

    Theme by Vdoing | Copyright © 2021-2024 蜀ICP备2024068710号-1
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式