做题家:不可不会的“算法设计与分析”!【面试笔试】

简介: 最近由于要做测评,遂整理算法设计与分析这一块的内容,复习的同时,与大家分享交流~

image.png

最近由于要做测评,遂整理算法设计与分析这一块的内容,复习的同时,与大家分享交流~


喂!算法!逃不掉的!All Right?


分治法



分治法是建基于多项分支递归的一种很重要的算法范型。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。


比较典型的有:排序算法(归并排序、快速排序)、傅立叶变换(快速傅立叶变换)等;


其中最重要最常考的是快排,应该都很熟悉了,快速过一遍:


  1. 选择一个元素作为"基准"(pivot)。
  2. 于"基准"的元素,都移到"基准"的左边;所有大于"基准"的元素,都移到"基准"的右边。
  3. 准"左边和右边的两个子集,不断重复第一步和第二步,直到所有子集只剩下一个元素为止。
var quickSort = function(arr) {
  if (arr.length <= 1) { return arr; }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++){
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
};


其次,我们需了解下傅立叶变换的基本概念:即它能将满足一定条件的某个函数表示成三角函数(正弦和/或余弦函数)或者它们的积分的线性组合。



动态规划法



动态规划太重要了!动态规划于算法,可能相对于杜兰特于篮网吧~ ORZ


动态规划(简称DP)背后的基本思想非常简单。即:大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。


其中最著名的有背包问题爬梯子问题(斐波那契数列)寻找最长公共子串 这三个(每个都是重点常考!)。


  • 背包问题


背包问题的基础是【0-1背包问题】,先吃透它:

题目:有 N 件物品和一个容量为 C 的背包。第 i 件物品的重量是 w[i],价值是 v[i]。求解将哪些物品装入背包可使价值总和最大。


解::每种物品仅有一件,可以选择放或不放。用 子问题定义状态:即 f[i][w] 表示前 i 件物品恰放入一个容量为 c 的背包可以获得的最大价值。则其状态转移方程便是:


f[i][c] = max{f[i-1][c], f[i-1][c-w[i]]+v[i]} 


这个方程的释义,即分别对应两种情况:

  1. 如果背包不放第 i 件物品,那么问题就转化为 前 i-1 件物品放入容量为 v 的背包中,价值为 f[i-1][c];
  2. 如果放第i件物品,那么问题就转化为 前 i-1 件物品放入剩下的容量为 c-w[i] 的背包中,此时能获得的最大价值就是 f[i-1][c-w[i]]再加上通过放入第 i 件物品获得的价值 v[i],即f[i-1][c-w[i]]+v[i]。


这两者的最大值就是我们最终的解!


物品一个一个选,容量也一点一点增加去考虑,这一点是「动态规划」的思想。

function knapsack(weights, values, W){
    var n = weights.length -1
    var f = [[]]
    for(var j = 0; j <= W; j++){
        // 边界情况
        if(j < weights[0]){
           f[0][j] = 0
        }else{
           f[0][j] = values[0]
        }
    }
    for(var j = 0; j <= W; j++){
        for(var i = 1; i <= n; i++ ){
            if(!f[i]){ //创建新一行
                f[i] = []
            }
            if(j < weights[i]){ //等于之前的最优值
                f[i][j] = f[i-1][j]
            }else{
                f[i][j] = Math.max(f[i-1][j], f[i-1][j-weights[i]] + values[i]) 
            }
        }
    }
    return f[n][W]
}
var a = knapsack([2,2,6,5,4],[6,3,5,4,6],10)
console.log(a)


进一步,自行了解 完全背包问题


  • 爬梯子问题


爬梯子也是算法高频考点无疑了!

题目:你准备要爬楼梯。你面对的是一个 n 步可以走上去的楼梯。你每次可以走一步或者两步,那么你有几种不同的方式可以爬上去吗?


解:每次只能爬 1 步或 2 步,爬到第 n 层的方法要么是从第 n-1 层 1 步上来的,要不就是从 n-2 层 2 步上来的。采用递归!但要注意避免递归中运算重复的部分:

var temp = []
var climbStairs = function(n) {
    if(n <= 0){
        return 0
    }
    if(n <= 2){
        return n
    }
    if(temp[n]){
        return temp[n]
    }
    temp[n] = climbStairs(n-2) + climbStairs(n-1)
    return temp[n]
};


爬梯子问题又可称是 “斐波那契数列”


斐波那契数列指的是这样一个数列从第3项开始,每一项都等于前两项之和,比如:1, 2, 3, 5, 8, 13, 21......,用到递归无疑了,但是一定要记得缓存递归项,否则会内存溢出(比如climbStairs(5)分解为climbStairs(3)climbStairs(4)climbStairs(4)又分解为climbStairs(2)climbStairs(3),这样就有两个重复的climbStairs(3)了)。


  • 寻找最长公共子串


也称为 “LCS 问题”

leetcode#1143

var longestCommonSubsequence = function(text1, text2) {
    const m = text1.length, n = text2.length;
    const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0));
    for (let i = 1; i <= m; i++) {
        const c1 = text1[i - 1];
        for (let j = 1; j <= n; j++) {
            const c2 = text2[j - 1];
            if (c1 === c2) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[m][n];
};


不过这个问题也勾起了本瓜当初面腾讯 WXG 的最后一题 leetcode#3 回忆。(OS:最长子串问题真的是必考!)


贪心法



贪心算法,即使你不怎么用,一定也听过它的大名!


贪心算法(英语:greedy algorithm),又称贪婪算法,是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是最好或最优的算法。


最典型的例子有:旅行商问题(最短路径问题)(TSP)

之前有写过一篇关于最短路径问题的文章:《会一会改变世界的图算法——Dijkstra(狄克斯特拉)算法》


与之最大不同的是,旅行商问题是一个 NP 问题,即只是一个近似算法,没有完全准确的解,所以需要尽可能的“贪心”。


题目:给定一系列城市和每对城市之间的距离,求解访问每一座城市一次并回到起始城市的最短回路。

leetcode#847


回溯法



回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。 回溯算法和穷举法很像,都是树的深度优先遍历,但回溯法会进行'剪枝',比如第 5 层某 i 叶子结点时发现该节点已经无意义,会直接跳过该节点下面的遍历,提高了效率。


回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况:

  • 找到一个可能存在的正确的答案
  • 在尝试了所有可能的分步方法后宣告该问题没有答案 在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。


著名的是 八皇后问题。像这种棋盘问题也是高频考点。(面试腾讯 PCG 遇到过)

题目:在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后。为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。问,一共有多少情况可满足?


解: 由于一行只能有一个皇后,所以选择一行一行地填写皇后。在填第n行的皇后时不能与[0, n-1]行已填写的皇后在同一列、同一正对角线与反对角线上。若满足条件则继续递归,否则回溯重新选择下一列。


class Queen {
  constructor(num) {
    this.num = num;
    this.arr = [];
    this.result = [];
    this.initList();
    this.buildList(this.arr, 0);
  }
  initList() {// 设置 num * num 的矩形方阵
    let num = this.num;
    for (let i = 0; i < num; i++) {
      this.arr[i] = [];
      for (let j = 0; j < num; j++) {
        this.arr[i][j] = 0;
      }
    }
    console.log(this.arr);
  }
  buildList(list, row) {
    // 递归中止条件,找到一个解缓存起来
    if (row === list.length) {
      this.result.push(JSON.parse(JSON.stringify(list)));
      return;
    }
    for (let col = 0, len = list.length; col < len; col++) {
      if (this.isSafe(list, row, col)) {
        list[row][col] = 1; // 1 表示设置该位置有皇后
        this.buildList(list, row + 1);
        // 走到这里,说明该次递归已经结束,不管找没找到,都需要重置
        list[row][col] = 0;
      }
    }
  }
  isSafe(list, row, col) {
    const len = list.length;
    // 同一列
    for (let i = 0; i < len; i++) {
      if (list[i][col] === 1) return false;
    }
    // 斜右上方
    for (let i = row - 1, j = col + 1; i >= 0 && j < len; i--, j++) {
      if (list[i][j] === 1) return false;
    }
    // 斜左上方
    for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
      if (list[i][j] === 1) return false;
    }
    return true;
  }
}
const queen = new Queen(8);
console.log(queen.result);

参考

面试题 08.12. 八皇后


分支限界法



回溯法是深度优先策略遍历问题的解空间树。分支限界法按广度优先策略遍历问题的解空间树。


还记得我们前文有提到的 01 背包问题吗?【分支限界法】也能求解 01 背包问题

难受啊胸dei!到这里有点被劝退的赶脚(QAQ),算法确实是计算机技术的护城河!!继续啃吧!


概率算法



概率算法也叫随机化算法。 概率算法允许算法在执行过程中随机地选择下一个计算步骤。 在很多情况下,算法在执行过程中面临选择时,随机性选择比最优选择省时,因此概率算法可以在很大程度上降低算法的复杂度。


概率算法又大致分为四类:

  1. 数值概率算法
  2. 蒙特卡罗(Monte Carlo)算法
  3. 拉斯维加斯(Las Vegas)算法
  4. 舍伍德(Sherwood)算法


先混个眼熟吧......更多自行探索。


近似算法



近似算法是计算机科学中算法研究的一个重要方向。所谓“近似”,就是指结果不一定是最优的,但是也在可以承受的范围内,而且可以比精确求解消耗更少的资源。这里的资源是计算复杂性理论中的标准,可以是时间,空间或者询问次数等。


前面提到过的旅行商问题也是近似算法。

更多可了解:P/NP问题,P/NP 问题是一个在理论信息学中计算复杂度理论领域里至今未被解决的问题,也是克雷数学研究所七个千禧年大奖难题之一。(哇!有一种触及人类数学天花板的错觉)


数据挖掘算法



数据挖掘算法是根据数据创建数据挖掘模型的一组试探法和计算。为了创建模型,算法将首先分析您提供的数据,并查找特定类型的模式和趋势。


数据挖掘算法底下又细分十大算法,更多:数据挖掘十大算法详解

本瓜先告辞,mark 一下,后面或许有空再来。


小结



以上笔试面试中常见的有:快排、最长子串问题系列、最短路径查找问题系列、棋盘问题系列、深度优先遍历系列、广度优秀遍历系列。


后面四种算法只需大致了解~

不过其中随处可见且最最核心的递归思想,真的太重要辣~

怎么说呢?


简单是最迷人的,这一点不会变。

算法确实很难,但或许难得东西,才有你让它变简单的价值吧~

我是掘金安东尼,公众号【掘金安东尼】,输出暴露输入,技术洞见生活。

大家端午安康!


相关文章
|
1月前
|
机器学习/深度学习 算法 搜索推荐
从理论到实践,Python算法复杂度分析一站式教程,助你轻松驾驭大数据挑战!
【10月更文挑战第4天】在大数据时代,算法效率至关重要。本文从理论入手,介绍时间复杂度和空间复杂度两个核心概念,并通过冒泡排序和快速排序的Python实现详细分析其复杂度。冒泡排序的时间复杂度为O(n^2),空间复杂度为O(1);快速排序平均时间复杂度为O(n log n),空间复杂度为O(log n)。文章还介绍了算法选择、分而治之及空间换时间等优化策略,帮助你在大数据挑战中游刃有余。
57 4
|
1月前
|
算法 Java 数据库
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩分享分库分表的基因算法设计,涵盖分片键选择、水平拆分策略及基因法优化查询效率等内容,助力面试者应对大厂技术面试,提高架构设计能力。
美团面试:百亿级分片,如何设计基因算法?
|
1月前
|
算法 前端开发 Java
数据结构与算法学习四:单链表面试题,新浪、腾讯【有难度】、百度面试题
这篇文章总结了单链表的常见面试题,并提供了详细的问题分析、思路分析以及Java代码实现,包括求单链表中有效节点的个数、查找单链表中的倒数第k个节点、单链表的反转以及从尾到头打印单链表等题目。
32 1
数据结构与算法学习四:单链表面试题,新浪、腾讯【有难度】、百度面试题
|
22天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
25天前
|
机器学习/深度学习 算法 Java
机器学习、基础算法、python常见面试题必知必答系列大全:(面试问题持续更新)
机器学习、基础算法、python常见面试题必知必答系列大全:(面试问题持续更新)
|
1月前
|
算法 Java 数据库
美团面试:百亿级分片,如何设计基因算法?
40岁老架构师尼恩在读者群中分享了关于分库分表的基因算法设计,旨在帮助大家应对一线互联网企业的面试题。文章详细介绍了分库分表的背景、分片键的设计目标和建议,以及基因法的具体应用和优缺点。通过系统化的梳理,帮助读者提升架构、设计和开发水平,顺利通过面试。
美团面试:百亿级分片,如何设计基因算法?
|
1月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
67 2
|
28天前
|
算法
PID算法原理分析
【10月更文挑战第12天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。
|
2月前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
54 4
|
1月前
|
算法
PID算法原理分析及优化
【10月更文挑战第6天】PID控制方法从提出至今已有百余年历史,其由于结构简单、易于实现、鲁棒性好、可靠性高等特点,在机电、冶金、机械、化工等行业中应用广泛。