前端面试拼图-数据结构与算法(二)

news2024/11/27 0:39:36

摘要:最近,看了下慕课2周刷完n道面试题,记录下...

1. 求一个二叉搜索树的第k小值

        二叉树(Binary Tree)

        是一棵树

        每个节点最多两个子节点

        树节点的数据结构{value, left?, right?}

        二叉树的遍历

        前序遍历:root→left→right

        中序遍历:left→root→right

        后序遍历:left→right→root

        二叉搜索树BST(Binary Search Tree)

        left(包括其后代) value ≤ root value

        right (包括其后代) value ≥ root value

        可使用二分法进行快速查找

        解题思路:BST中序遍历,从小到大的排序

                          找到排序后的第k个值

/**
* 二叉搜索树
*/
interface ITreeNode {
  value: number
  left: ITreeNode | null
  right: ITreeNode | null
}

const arr: number[] = []

/**
* 二叉树前序遍历
*/
function preOrderTraverse(node: ITreeNode | null) {
  if ( node == null) return
  //console.log(node.value)
  arr.push(node.value)
  preOrderTraverse(node.left)
  preOrderTraverse(node.right)
}

/**
* 二叉树中序遍历
*/
function inOrderTraverse(node: ITreeNode | null) {
  if ( node == null) return
  inOrderTraverse(node.left)
  // console.log(node.value)
  arr.push(node.value)
  inOrderTraverse(node.right)
}

/**
* 二叉树后序遍历
*/
function postOrderTraverse(node: ITreeNode | null) {
  if ( node == null) return
  postOrderTraverse(node.left)
  postOrderTraverse(node.right)
  // console.log(node.value)
  arr.push(node.value)
}

/**
* **寻找BST中的第k小值**
*/
function getKthValue(node: ITreeNode, k: number): number | null {
  inOrderTraverse(node)
  console.log(arr)
  return arr[k-1] || null
}

const bst: ITreeNode = {
  value: 5,
  left: {
    value: 3,
    left: {
      value: 2,
      left: null,
      right: null
    },
    right: {
      value: 4,
      left: null,
      right: null
    }
  },
  right: {
      value: 7,
      left: {
        value: 6,
        left: null,
        right: null
      },
      right: {
        value: 8,
        left: null,
        right: null
      }
   }
}

//preOrderTraverse(tree)

平衡二叉树 | HZFE - 剑指前端 Offer题目描述icon-default.png?t=N7T8https://febook.hzfe.org/awesome-interview/book1/algorithm-balanced-binary-trees        扩展:为何二叉树如此重要,而不是三叉树、四叉树?

        性能、性能、还是性能!重要的事情说三遍

        数组:查找快O(1),增删慢O(n);链表:查找慢O(n),增删快O(1)

        二叉搜索树BST:查找快、增删快—"木桶效应"

        平衡二叉树

        BST如果不平衡,那就又成了链表

        所以要尽量平衡:平衡二叉搜索树BBST(其增删查,时间复杂度都是O(logn),即树的高度)

        红黑树:本质是一种自平衡二叉树

        分为红/黑两种颜色,通过颜色转换来维持输的平衡

        相对于普通平衡二叉树,它维持平衡的效率更高

        B树

        物理上是多叉树,但逻辑上是二叉树

        一般用于高效I/O, 关系型数据库常用B树 来组织数据

        扩展2:堆有什么特点?和二叉树又什么关系?

        堆栈模型

        JS执行时,值类型变量,存储在栈中;引用类型变量,存储在堆中

        堆是完全二叉树

        最大堆:父节点 ≥子节点

        最小堆:父节点≤子节点

        满二叉树(又叫完美二叉树):所有层的节点都被填满;

        完全二叉树:最底层节点靠左填充,其它层节点全被填满

7.1   二叉树 - Hello 算法动画图解、一键运行的数据结构与算法教程icon-default.png?t=N7T8https://www.hello-algo.com/chapter_tree/binary_tree/#1_1        逻辑结构 VS 物理结构

        堆:逻辑结构是一颗二叉树,但它的物理结构式一个数组

        堆的使用场景

        特别适合"堆栈模型"

        堆的数据,都是在栈中引用的,不需要从root遍历

        堆恰巧是数组形式,根据栈的地址,可用O(1)找到目标

2. JS计算斐波那契数列的第n个值

/**
* 斐波那契额数列(递归)
*/
function fibonacci(n:number): number{
  if(n <=1 ) return n
  return fibonacci(n-1) + fibonacci(n-2)
}

        递归有大量重复计算,其时间复杂度是O(2^n)

        优化:不用递归用循环,记录中间结果,时间复杂度O(n)

/**
* 斐波那契额数列(循环)
*/
function fibonacci(n:number): number{
  if(n <=1 ) return n
  let n1 = 1  //记录n-1的结果
  let n2 = 0  //记录n-2的结果
  let res = 0
  
  for(let i = 2; i <= n; i++) {
    res = n1 + n2;
    // 记录中间结果
    n2 = n1
    n1 = res
  } 
  return res
}

        动态规划:

        把一个大问题,拆解为一个小问题,逐级向下拆解

        用递归的思想去分析问题,再改用循环来实现

        算法三大思维:贪心、二分、动态规划

        扩展:青蛙跳台阶问题,一只青蛙,一次可跳1级,也可跳两级,请问青蛙跳到n级台阶,总共有多少种方式?

        第一次跳1级则有f(n-1)种方式,跳2级则有f(n-2)种方式,则结果和斐波那契额数列一样。

3. 将数组的0 移动到末尾

        如输入[1,0,3,0,11,0],输出[1,3,11,0,0,0],只移动0,其他顺序不变;必须在原数组进行操作

        传统思路

        遍历数组,遇到0则push到数组末尾

        用splice截取当前元素

        时间复杂度O(n^2)—算法不可用

        数组是连续存储,要慎用splice unshift 等API

/**
* 移动0到数组末尾(嵌套循环)
*/
function moveZero1(arr:number[]):void {
  const length = arr.length
  if(length === 0) return
  
  let zeroLength = 0
  // **O(n^2)**
  for (let i = 0; i < length - zeroLength; i++) {
    if (arr[i] === 0) {
      arr.push(0)
      arr.splice(i,1)  // 本身就有O(n)
      i-- //数组接去了一个元素,i要递减,否则连续0就会有错误
      zeroLength++ // 累加0的长度
    }
  }
}

        双指针思路(解决嵌套循环的有效)

        定义j指向第一个0,i指向j后面的第一个非0

        交换i和j的值,继续向后移动

        只遍历一次,所以时间复杂度是O(n)

/**
* 移动0到数组末尾(双指针)
*/
function moveZero2(arr:number[]):void {
  const length = arr.length
  if(length === 0) return
  
  let i
  let j = -1 // 指向第一个0
  for(i=0; i < length; i++) {
    if(arr[i] === 0) {
      if (j < 0) {   // 第一个0
        j = i
      }
    }
    if(arr[i] !== 0 && j >=0 ) {
      const n = arr[i]
      arr[i] = arr[j]
      arr[j] = n
      j++
    }
  }
}

4. 获取字符串中连续最多的字符,以及次数

        如输入'abbcccddeeee1234',计算得到连输最多的字符是'e',为4次

        传统思路

        嵌套循环,找出每个字符的连续次数,并记录

        看似时间复杂度是O(n^2)

        但实际时间复杂度是多少?—O(n),因为有'跳步'

/**
* 求连续最多的字符和次数(嵌套循环)
*/
interface IRes {
  char: string
  length: number
}
function findContinuousChars1(str:string):IRes {
  const res:IRes = {
    char: '',
    length: 0
  }
  const length = str.length
  if (length === 0) return res
  
  let tempLength = 0 //临时记录当前连续字符串的长度
  // 时间复杂度O(n)
  for(let i = 0; i < length; i++) {
    tempLength = 0 // 重置
    for(let j = i; j < length; j++) {
      if (str[i] === str[j]) {
        tempLength++
      }
      if(str[i] !== str[j] || j === length-1) {
        // 不相等,或者已经到最后一个元素。要去判断最大值
        if (tempLength > res.length) {
          res.char = str[i]
          res.length = tempLength
        }
        if (i < length - 1) {
          i = j -1   // 跳步
        }
        break
      }
    }
  }  
  return res
}

        双指针思路(适用于解决嵌套循环类问题)

        定义指针i和j;j不动,i继续移动

        如果i和j的值一直相等,则i继续移动

        直到i和j的值不相等,记录处理,让j追上i。继续第一步

/**
* 求连续最多的字符和次数(双指针)
*/
interface IRes {
  char: string
  length: number
}
function findContinuousChars2(str:string):IRes {
  const res:IRes = {
    char: '',
    length: 0
  }
  const length = str.length
  if (length === 0) return res
  
  let tempLength = 0 //临时记录当前连续字符串的长度
  // 时间复杂度O(n)
  let i = 0
  let j = 0
  for(; i < length; i++) {
    if(str[i] === str[j]) {
      tempLength++
    }
    if(str[i] !== str[j] || i === length-1) {
      // 不相等,或者i到了字符串的末尾
      if(tempLength > res.length) {
        res.char = str[j]
        res.length = tempLength
      }
      tempLength = 0  //重置长度
      
      if(i < length - 1) {
        j = i //让j"追上" i
        i--
      }
    }
  }
 
  return res
}

ps:算法题尽量使用低级的代码,慎用语法糖或者高级API

5. 用JS实现快速排序,并说明时间复杂度

        固定算法和思路

  • 找到中间位置midValue
  • 遍历数组,小于midValue放在left,否则放在right
  • 继续递归,最后concat拼接,返回

        获取midValue的两种方式:

        使用splice,会修改原数组

        使用slice,不会修改原数组 — 更推荐

/**
* 快速排序(使用splice)
*/
function quickSort1(arr: number[]):number[] {
  const length = arr.length
  if(length === 0) return arr
  
  const modIndex = Math.floor(length / 2)
  const midValue = arr.splice(midIndex, 1)[0]  // splice和slice返回的都是数组
  
  const left: number[] = []
  const right: number[] = []
  
  // O(n*logn)
  for(let i = 0; i < arr.length; i++) {  // 细节,不直接使用length是由于splice已经改变数组
    const n  = arr[i]
    // 二分后递归遍历O(logn)
    if (n < midValue) {
      // 小于midValue, 则放在left
      left.push(n)
    } else{
      // 大于midValue,则放在right
      right.push(n)
    }
  }
  
  return quickSort1(left).concat([midValue], quickSort1(right))
}

/**
* 快速排序(使用slice)
*/
function quickSort2(arr: number[]):number[] {
  const length = arr.length
  if(length === 0) return arr
  
  const modIndex = Math.floor(length / 2)
  const midValue = arr.slice(midIndex, midIndex + 1)[0]  // splice和slice返回的都是数组
  
  const left: number[] = []
  const right: number[] = []
  
  for(let i = 0; i < length; i++) { 
    if (i !== midIndex) {
      const n  = arr[i]
      if (n < midValue) {
        // 小于midValue, 则放在left
        left.push(n)
      } else{
        // 大于midValue,则放在right
        right.push(n)
      }
    }    
  }
  
  return quickSort2(left).concat([midValue], quickSort2(right))
}

        快速排序,有遍历有二分时间复杂度为O(nlogn); 常规排序,嵌套循环,复杂度是O(n^2)

        此处,splice和slice没区分出来

        算法本身时间复杂度就够高O(nlogn)

        外加,splice是逐步二分后执行的,二分会快速消减数量级

        如果单独使用splice和slice,效果会很明显

6. 获取1-10000之前所有的对称数

        例如:1, 2, 11, 22, 101, 232, 1221……

        思路1 使用数组反转、比较

  • 数字转换为字符串,再反转为数组
  • 数组reverse,再join为字符串
  • 前后字符串进行比较
/**
* 查询1-max的所有对称数(数组反转)
*/
function findPalindromeNumbersa(max: number): number[] {
  const res: number[] = []
  if (max <= 0) return res
  
  for(let i = 1; i <= max; i++) {
    // 转换为字符串,转换为数组,再反转, 比较
    const s = i.toString()
    if (s === s.split('').reverse().join('')) {
      res.push(i)
    }
  }
  return res
}

        思路2 字符串头尾比较

        数字转换为字符串

        字符串头尾字符比较

        (也可以用栈,像括号匹配,但需要注意奇偶数 )

/**
* 查询1-max的所有对称数(字符串前后比较)
*/
function findPalindromeNumbersa(max: number): number[] {
  const res: number[] = []
  if (max <= 0) return res
  
  for(let i = 1; i <= max; i++) {
    // 转换为字符串,转换为数组,再反转, 比较
    const s = i.toString()
    const length = s.length
    
    // 字符串头尾比较
    let flag = true
    let startIndex = 0 //字符串开始
    let endIndex = length -1 //字符串结束
    while(startIndex < endIndex) {
      if (s[startIndex] != s[endIndex]) {
        flag = false
        break
      } else {
        // 继续比较
        startIndex++
        endIndex--
      }
    }
    if(flag) res.push(i)
  }
  return res
}

7. 如何实现高效的英文单词前缀匹配

未完待续……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1557085.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java类与对象:从概念到实践的全景解析!

​ 个人主页&#xff1a;秋风起&#xff0c;再归来~ 文章专栏&#xff1a;javaSE的修炼之路 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 1、类的定义格式 在java中定义类时需要用到…

记录一个写自定义Flume拦截器遇到的错误

先说结论&#xff1a; 【结论1】配置文件中包名要写正确 vim flume1.conf ... a1.sources.r1.interceptors.i1.type com.atguigu.flume.interceptor.MyInterceptor2$MyBuilder ... 标红的是包名&#xff0c;表黄的是类名&#xff0c;标蓝的是自己加的内部类名。这三个都…

大话设计模式之工厂模式

工厂模式&#xff08;Factory Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种创建对象的最佳方式&#xff0c;而无需指定将要创建的对象的确切类。通过使用工厂模式&#xff0c;我们可以将对象的创建和使用分离&#xff0c;从而使代码更具灵活性和可维护性。…

Python之Opencv教程(1):读取图片、图片灰度处理

1、Opencv简介 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个用于计算机视觉和图像处理的开源库&#xff0c;提供了丰富的图像处理、计算机视觉和机器学习功能。它支持多种编程语言&#xff0c;包括C、Python、Java等&#xff0c;广泛应用于图像处…

Unity 学习日记 13.地形系统

下载源码 UnityPackage 1.地形对象Terrain 目录 1.地形对象Terrain 2.设置地形纹理 3.拔高地形地貌 4. 绘制树和草 5.为地形加入水 6.加入角色并跑步 7.加入水声 右键创建3D地形&#xff1a; 依次对应下面的按钮 || 2.设置地形纹理 下载资源包 下载资源包后&#x…

【微服务】软件架构的演变之路

目录 单体式架构的时代单体式架构(Monolithic)优点缺点适用场景单体式架构面临诸多问题1.宽带提速&#xff0c;网民增多2.Web2.0时代的特点问题描述优化方向 集群优点缺点适用场景搭建集群后面临诸多问题用户请求问题用户的登录信息数据查询 改进后的架构 垂直架构优点缺点 分布…

OSPF基本原理和概念

文章目录 背景知识OSPF协议概述&#xff1a;OSPF区域的表示OSPF 骨干区域 –区域0OSPF 非骨干区域 -非0区域OSPF的五种区域类型OSPF工作原理OSPF 的报文类型OSPF邻居表中的七个状态 总结 背景知识 一台路由设备如何获取其他网段的路由&#xff0c;并加入到路由表中 直连路由 …

【Java】LinkedList模拟实现

目录 整体框架IMyLinkedList接口IndexNotLegalException异常类MyLinkedList类成员变量(节点信息)addFirst(头插)addLast(尾插)在指定位置插入数据判断是否存在移除第一个相等的节点移除所有相等的节点链表的长度打印链表释放回收链表 整体框架 IMyLinkedList接口 这个接口用来…

IDE/VS2015和VS2017帮助文档MSDN安装和使用

文章目录 概述VS2015MSDN离线安装离线MSDN的下载离线MSDN安装 MSDN使用方法从VS内F1启动直接启动帮助程序跳转到了Qt的帮助网页 VS2017在线安装MSDN有些函数在本地MSDN没有帮助&#xff1f;切换中英文在线帮助文档 概述 本文主要介绍了VS集成开发环境中&#xff0c;帮助文档MS…

常关型p-GaN栅AlGaN/GaN HEMT作为片上电容器的建模与分析

来源&#xff1a;Modeling and Analysis of Normally-OFF p-GaN Gate AlGaN/GaN HEMT as an ON-Chip Capacitor&#xff08;TED 20年&#xff09; 摘要 提出了一种精确基于物理的解析模型&#xff0c;用于描述p-GaN栅AlGaN/GaN高电子迁移率晶体管&#xff08;HEMT&#xff09…

初步了解C++

目录 一&#xff1a;什么是C&#xff1f; 二.C发展史 三:C关键字 四&#xff1a;命名空间 4.1命名空间的介绍 4.2命名空间的使用 4.3命名空间的使用 4.3.1使用作用域限定符 4.3.2 使用using将命名空间的某个成员引入 4.3.3使用using把整个命名空间展开 4.4命名空…

Golang生成UUID

安装依赖 go get -u github.com/google/uuid文档 谷歌UUID文档 示例 函数签名func NewV7() ( UUID ,错误) func (receiver *basicUtils) GenerateUUID() uuid.UUID {return uuid.Must(uuid.NewV7()) } uid : GenerateUUID()

鸿蒙ARKTS--简易的购物网站

目录 一、media 二、string.json文件 三、pages 3.1 登录页面:gouwuPage.ets 3.2 PageResource.ets 3.3 商品页面:shangpinPage.ets 3.4 我的页面:wodePage.ets 3.5 注册页面:zhucePage.ets 3. 购物网站主页面:gwPage.ets 一、media 图片位置:entry > src …

cron服务

Cron文件&#xff1a;Cron服务使用一个特定的配置文件来存储任务和其执行计划。在Unix系统上&#xff0c;这个文件通常是 /etc/crontab&#xff0c; 或者是位于/etc/cron.d/目录下的其他文件。 这些文件包含了任务的定义&#xff0c;包括执行时间和要执行的命令。 类似于 编…

刷爆LeetCode:两数之和 【1/1000 第一题】

&#x1f464;作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任大厂数据部门负责人。 会一些的技术&#xff1a;数据分析、算法、SQL、大数据相关、python 作者专栏每日更新&#xff1a;LeetCode解锁1000题: 打怪升级之旅https://blog.csdn.net/cciehl/category…

Scala介绍与环境搭建

Scala环境搭建与介绍 一、Scala环境搭建 1、环境准备与下载 2、验证Scala 3、IDEA新建项目&#xff0c;配置Scala&#xff0c;运行Hello world 二、Scala介绍 1、Scala 简介 2、Scala 概述 一、Scala环境搭建 1、环境准备与下载 JDK1.8 Java Downloads | Oracle 下载需求版本…

java项目通用Dockerfile

创建Dockerfile文件&#xff0c;放到项目根目录下和pom.xml同级别 仅需修改为自己项目端口号即可&#xff0c;其他的无需改动 FROM openjdk:11.0.11-jre-slimCOPY target/*.jar .EXPOSE 8080ENTRYPOINT java -jar *.jar构建语句(注意末尾的点 . ) docker build -t container…

element-ui inputNumber 组件源码分享

今日简单分享 inputNumber 组件的实现原理&#xff0c;主要从以下四个方面来分享&#xff1a; 1、inputNumber 组件的页面结构 2、inputNumber 组件的属性 3、inputNumber 组件的事件 4、inputNumber 组件的方法 一、inputNumber 组件的页面结构。 二、inputNumber 组件的…

ElasticSearch开发指北和场景题分析

前言 本篇是ES系列的第二篇&#xff0c;继上次的理论篇ElasticSearch理论体系构建后&#xff0c;带来了实战篇。实战篇来自于我对常见操作以及场景的分析总结&#xff0c;详细到每个步骤和理由&#xff0c;下一篇将是性能优化篇。 常用操作 以下操作均使用ES的API进行展示&a…

数据结构进阶篇 之 【二叉树顺序存储(堆)】的整体实现讲解(赋完整实现代码)

做人要谦虚&#xff0c;多听听别人的意见&#xff0c;然后记录下来&#xff0c;看看谁对你有意见 一、二叉树的顺序&#xff08;堆&#xff09;结构及实现 1.二叉树的顺序结构 2.堆的概念及结构 3.堆的实现 3.1 向下调整算法 AdJustDown 3.2 向上调整算法 AdJustUP 3.3 …