Javascript数据结构与算法

数据结构

常见的数据结构有:

  • 栈,队列,链表
  • 集合,字典
  • ,图,堆

图的常用操作:

  • 深度优先遍历:尽可能深的搜索图的分支
    • 访问根节点
    • 对根节点的没访问过的相邻节点挨个进行深度优先遍历
  • 广度优先遍历:先访问离根节点近的节点
    • 新建一个队列,把根节点入队
    • 把队头出队并访问
    • 把队头的没访问过的相邻节点入队

LeetCode题目示例:417.太平洋大西洋水流问题,题目链接:https://leetcode-cn.com/problems/pacific-atlantic-water-flow/
在这里插入图片描述

解题思路:

  • 把矩阵想象成图
  • 从海岸线逆流而上遍历图,所到之处就是可以流到某个大洋的坐标

解题步骤:

  • 新建两个矩阵,分别记录能流到两个大洋的坐标
  • 从海岸线,多管齐下,同时深度优先遍历图,过程中填充上述矩阵
  • 遍历两个矩阵,找出能流到两个大洋的坐标

  • 堆是一种特殊的完全二叉树
  • 所有的节点都大于等于(最大堆)或小于等于(最小堆)它的子节点
JS中的堆
  • JS中通常用数组表示堆
  • 任意节点的左子节点的位置是:2 * index + 1
  • 任意节点的右子节点的位置是:2 * index + 1
  • 父节点位置是(index - 1) / 2

算法

算法主要是以下几种:

  • 基础技巧:分治、二分、贪心
  • 排序算法:快速排序、归并排序、计数排序
  • 搜索算法:回溯、递归、深度优先遍历、广度优先遍历、二叉搜索树等
  • 图论:最短路径、最小生成树
  • 动态规划:背包问题、最长子序列

学习算法推荐一个动画的网址:https://visualgo.net/zh/sorting

排序和搜索

排序:把某个乱序的数组变成升序或者降序的数组。JS中的排序:数组的sort方法

搜索:找出数组中某个元素的下标。JS中的搜索:数组的indexOf方法

排序算法
  • 冒泡排序、选择排序、插入排序、归并排序、快速排序
  1. 选择排序的思路:
  • 找到数组中的最小值,选中它并将其放置在第一位
  • 接着找到第二小的值,选中它并将其放置在第二位
  • 以此类推,执行n - 1轮
  1. 插入排序的思路:
  • 从第二个数开始往前比
  • 比它大就往后排
  • 以此类推进行到最后一个数
  1. 归并排序的思路:
  • 分:把数组劈成两半,再递归地对子数组进行"分"操作,直到分成一个个单独的数
  • 合:把两个数合并为有序数组,再对有序数组进行合并,直到全部子数组合并为一个完整数组
    • 新建一个空数组res,用于存放最终排序后的数组
    • 比较两个有序数组的头部,较小者出队并推入res中
    • 如果两个数组中还有值,就重复第二步
  1. 快速排序的思路:
  • 分区:从数组中任意选择一个元素作为"基准",所有比"基准"小的元素放在基准前面,比基准大的元素放在基准的后面
  • 递归:递归地对基准前后的子数组进行分区

递归的时间复杂度为O(logN),分区操作的时间复杂度为O(n)

搜索算法
  • 顺序搜索、二分搜索
  1. 顺序搜索的思路:
  • 遍历数组
  • 找到跟目标值相等的元素,就返回它的下标
  • 遍历结束后,如果没有搜索到目标值,就返回-1
  1. 二分搜索的思路:
  • 从数组的中间元素开始,如果中间元素正好是目标值,则搜索结束
  • 如果目标值大于或者小于中间元素,则在大于或小于中间元素的那一半数据中搜索
分治算法

**场景一:**归并排序

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

**场景二:**快速排序

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

LeetCode题目示例:226.翻转二叉树https://leetcode-cn.com/problems/invert-binary-tree/

解题思路:

  • 先翻转左右子树,再将子树换个位置
  • 符合“分、解、合”特性

解题步骤:

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

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

动态规划

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

**LeetCode题目示例:**70.爬楼梯https://leetcode-cn.com/problems/climbing-stairs/

解题思路:

首先是分析一下,我们最后一步要干啥,要迈最后一步阶梯上楼,而这一步可能迈过一个台阶,也可能迈过两个台阶,那还愣着干啥,分情况讨论啊!**第一种情况,最后一步迈一阶,那之前呢,之前就是n-1个台阶啊,你管他几步,先打包带走!第二种情况,最后一步迈两阶,那之前呢,之前就是n-2个台阶啊,也一起打包带走!**这是不是就是总共的可能了?也就是说:

爬第n阶楼梯的方法数量,等于 2 部分之和,即:
爬上 n−1 阶楼梯的方法数量。因为再爬1阶就能到第n阶
爬上 n−2 阶楼梯的方法数量,因为再爬2阶就能到第n阶
f(n) = f(n - 1) + f(n - 2)

解题步骤:

  • 定义子问题:F(n) = F(n-1) + F(n-2)。
  • 反复执行:从2循环到n,执行上述公式
var climbStairs = function(n) {
    const dp = [1,1];
    for(let i = 2; i <= n; i+=1) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
};

**LeetCode题目示例:**198.打家劫舍https://leetcode-cn.com/problems/house-robber/

解题思路:

打家劫舍这道题目是动态规划中的简单题,但是个人觉得还是蛮有意思的,所以今天的主题就是将一些有意思的题目做以分享,也希望大家能喜欢。打家劫舍这道题在近两年作为面试题出现的公司有百度,腾讯,谷歌,微软,网易等等,这样看来互联网公司面试的时候还是比较open的哈。这道题还是先往简单的情况分析(题目降维),即如果只有一个房屋可以打劫的时候,显然最大值就是第一个房屋内的金额。当用f(n)表示从前面n个房屋中获取的最大金额,则f(1) = nums[0]。同理f(2) = max(nums[0], nums[1])。当房屋变成3个的时候情况发生了变化,就是小偷可能要思考一下我偷不偷第3个房屋的钱,如果偷了,那么第二个房屋的钱我就不能偷。也就是说他为了争取利益最大化,就要在只偷第2个房屋的钱和偷1和3房屋的钱之间进行比较大小,这样得出的答案就是在有3个房屋的情况下能获得的最大金额。现在房屋变成了4个,小偷现在又要合计了,如果我偷第4个房屋的钱,那第三个房屋的钱我就不能碰,这样我就需要将只有2个房屋情况下的最大收益与第4个房屋的收益进行求和,然后与我不偷第4个房屋情况下的收益(就是只有3个房屋可偷的情况下的收益)进行比较大小。经过这样的迭代,就会将从1到n个房屋的情况下的最大收益记录下来,然后返回出记录表里的最后一个元素就是最大值了。而这个记录表就是我们维护的dp矩阵,状态转移方程就是f(n) = max(f(n-2)+nums[n], f(n-1))。

解题步骤:

  • 定义子问题:f(n) = max(f(n-2) + nums[n] , f(n-1))
  • 反复执行:从2循环到n,执行上述公式
var rob = function(nums) {
    const n = nums.length
    if(n === 0) return 0;
    const dp = [0, nums[0]]
    for(let i = 2; i <= n; i++){
        dp[i] = Math.max(dp[i - 2] + nums[i-1], dp[i-1])
    }
    return dp[dp.length-1]
};

贪心算法

  • 期盼通过每个阶段的局部最优选择,从而达到全局的最优
  • 结果并不一定是最优

**LeetCode题目示例:**455.分发饼干https://leetcode-cn.com/problems/assign-cookies/

解题思路:

  • 局部最优:既能满足孩子,又消耗最少
  • 先将“最小的饼干”分给“胃口最小”的孩子

解题步骤:

  • 对饼干数组和胃口数组升序降序
  • 遍历饼干数组,找到能满足第一个孩子的饼干
  • 然后继续遍历饼干数组,找到满足第二、三、…、n个孩子的饼干
var findContentChildren = function(g, s) {
    const sortFunc = function(a, b){
        return a-b;
    };
    g.sort(sortFunc);		// 将数组作升序排序
    s.sort(sortFunc);
    let i = 0;
    s.forEach(n => {
        if(n >= g[i]){
            i+=1;
        }
    });
    return i
};

**LeetCode题目示例:**122.买卖股票的最佳时机Ⅱhttps://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/

解题思路:

  • 前提:上帝视角,知道未来的价格
  • 局部最优:见好就收,见差不动,不做任何长远打算

解题步骤:

  • 新建一个变量,用来统计总利润
  • 遍历价格数组,如果当前价格比昨天高,就在昨天买,今天卖,否则就不交易
  • 遍历结束后,返回所有利润之和
var maxProfit = function(prices) {
    let profit = 0;
    for(let i=0; i<=prices.length; i++) {
        if(prices[i+1] > prices[i]){
            profit += prices[i+1] - prices[i]
        }
    }
    return profit
};

回溯算法

回溯算法是一种渐进式寻找并构建问题解决方式的策略

回溯算法会先从一个可能的动作开始解决问题,如果不行,就回溯并选择另一个动作,直到将问题解决

**LeetCode题目示例:**46.全排列https://leetcode-cn.com/problems/permutations/

解题思路:

  • 要求:1.所有排列情况 2.没有重复元素
  • 有出路,有死路

解题步骤:

  • 用递归模拟出所有情况
  • 遇到包含重复元素的情况,就回溯
  • 收集所有到达递归终点的情况,并返回

**LeetCode题目示例:**78.子集https://leetcode-cn.com/problems/subsets/

解题思路:

  • 要求:1.所有子集 2.没有重复元素
  • 有出路,有死路

解题步骤:

  • 用递归模拟出所有情况
  • 保证接的数字都是后面的数字

深度优先遍历与广度优先遍历

深度优先遍历:尽可能深的搜索树的分支
在这里插入图片描述

口诀:

  • 访问根节点
  • 对根节点的children挨个进行深度优先遍历

LeetCode题目示例:104.二叉树的最大深度,题目链接:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/
在这里插入图片描述

解题思路:

  • 求最大深度,考虑使用深度优先遍历
  • 在深度优先遍历的过程中,记录每个节点所在的层级level,找出最大的层级即可
//Depth-first Searches(DFS)
var maxDepth = function(root){
    let res = 0;	//initialize result
    const dfs = (node, level) => {
        if(!root) return;
        res = Math.max(res, level);		//res should be maximum between res and level
        if(node.left) dfs(node.left, level + 1);	//if node has children, dfs it and increase level by one
        if(node.right) dfs(node.right, level + 1);	//if node has children, dfs it and increase level by one
    };
    dfs(root, 1);	//input root and initialize level to 1
    return res;
}

广度优先遍历:先访问离根节点最近的节点

在这里插入图片描述

口诀:

  • 新建一个队列,把根节点入队
  • 把队头出队并访问
  • 把队头的children挨个入队
  • 重复第二、三步,直到队列为空

LeetCode题目示例:111.二叉树的最小深度,题目链接:https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/

在这里插入图片描述

解题思路:

  • 求最小深度,考虑使用广度优先遍历
  • 在广度优先遍历过程中,遇到叶子节点,停止遍历,返回节点层级
//Breadth-first Searches

二叉树

在这里插入图片描述

如图所示为一棵现实中的二叉树

  • 先序遍历
    在这里插入图片描述

    顺序:

    • 访问根节点
    • 对根节点的左子树进行先序遍历
    • 对根节点的右子树进行先序遍历
  • 中序遍历

    顺序:

    • 对根节点的左子树进行中序遍历
    • 访问根节点
    • 对根节点的右子树进行中序遍历
  • 后序遍历

    顺序:

    • 对根节点的左子树进行后序遍历
    • 对根节点的右子树进行后序遍历
    • 访问根节点
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值