发布于 2026-01-06 1 阅读
0

近期 FAANG 面试中最难的 5 道编程题

近期 FAANG 面试中最难的 5 道编程题

编程面试似乎越来越难,准备起来也绝非易事。面试中可能会遇到各种各样的问题,而且很多问题都不容易。“最难”的问题因人而异。对你来说轻而易举的问题,对别人来说可能难如登天,反之亦然。

无论你认为“最难”的问题是什么,做好编程面试的准备都至关重要。我们采访了一些初级和高级开发人员,了解他们对最难编程面试题的看法,并整理出了排名前五的难题。今天,我们将更详细地探讨这些难题,并为你提供一些准备技巧。

我们将涵盖以下内容

如何设计垃圾收集器

如果你从未听说过垃圾回收问题,那么它确实非常棘手。垃圾回收是大多数人在学校里不会学到的知识,而且相关资料极其晦涩难懂。学习垃圾回收涉及大量的理论知识,这可能会让人感到不知所措。无论你使用哪种编程语言,要想有效地解决这个问题,都必须深入了解你所使用语言的方方面面。

在解决这个问题时,不要害怕向面试官提问。记住,面试官是来帮助你的,他们希望看到你表现出色。面试官通常会提供一些线索,引导你朝着正确的方向前进。

注意:垃圾回收问题在 Java 核心和高级面试中特别常见,但对于其他编程语言来说,了解垃圾回收也很重要。

找零问题

找零问题经常出现在 Facebook 和亚马逊的面试中。题目会给出不同面额的硬币和一个总金额。你需要编写一个函数,计算出凑齐这个金额所需的最少硬币数量。如果无法用任何硬币组合凑齐给定的金额,则返回错误-1

以下是解决此问题的三种方法:

  • 蛮力
  • 自顶向下动态规划与记忆化
  • 基于表格的自底向上动态规划

让我们来看一下 C++ 中基于表格化的自底向上动态规划解决方案:

#include <iostream>
using namespace std;

int countChange(int denoms[], int denomsLength, int amount) {
  // Edge cases
  if(amount <= 0 || denomsLength <= 0)
    return 0;

  int i, j, x, y;

  // We need n+1 rows as the table 
  // is constructed in bottom up 
  // manner using the base case 0 
  // value case (n = 0) 
  int lookupTable[amount + 1][denomsLength];

  // Fill the enteries for 0 
  // value case (n = 0) 
  for (i = 0; i < denomsLength; i++)
    lookupTable[0][i] = 1;

  // Fill rest of the table entries 
  // in bottom up manner 
  for (i = 1; i < amount + 1; i++) {
    for (j = 0; j < denomsLength; j++) {
      // Count of solutions including denoms[j] 
      x = (i - denoms[j] >= 0) ? lookupTable[i - denoms[j]][j] : 0;

      // Count of solutions excluding denoms[j] 
      y = (j >= 1) ? lookupTable[i][j - 1] : 0;

      // total count 
      lookupTable[i][j] = x + y;
    }
  }
  return lookupTable[amount][denomsLength - 1];
}

// Driver Code 
int main() { 
  int denoms[4] = {25,10,5,1};
  cout << countChange(denoms, 4, 10) << endl;
}
Enter fullscreen mode Exit fullscreen mode

在内层循环的每次迭代中,我们都会获取包含参数的解denoms[j]并将其存储在变量中x。同时,我们也会获取不包含参数的解并将denoms[j]其存储在变量中y。通过这种方式,我们可以引用之前的解,从而避免重复计算。

对于该面额的每种硬币,只有两种可能:包含或排除。我们知道,如果硬币denom[j]大于amount,则x设置为 0,因为不可能将其纳入考虑范围。

时间复杂度为 *O(金额 * 词缀长度),即 for 循环迭代次数。

注意:这三种方法都包含时间复杂度,这意味着理解时间复杂度是解决找零问题的重要概念。

哲学家就餐问题(多线程)

哲学家就餐问题常用于并发算法设计中,以演示同步问题及其解决方法。该问题描述五位哲学家围坐在一张圆桌旁,他们必须交替进行思考和用餐。

每位哲学家面前都有一碗食物,他们需要双手各拿一把叉子才能进食。然而,只有五把叉子可用。你需要设计一个解决方案,使每位哲学家都能吃完食物,且不会造成死锁。

开发者在面对这个问题时,常常会忽略一个关键点:它并非真正考察一个真实世界的场景,而是为了说明在多线程程序执行和/或锁管理不善的情况下可能遇到的问题。其目的是引导你思考限制因素以及完成任务的正确顺序,从而以最高效的方式完成任务。

为了准备回答这个问题,你应该深入了解同步、并发控制和信号量

以下是解决此问题的两种可能方法:

  • 限制即将进食的哲学家
  • 重新排列叉车拾取装置

让我们来看看Java中重新排序叉车取货的解决方案:

public class DiningPhilosophers2 {

    private static Random random = new Random(System.currentTimeMillis());

    private Semaphore[] forks = new Semaphore[5];

    public DiningPhilosophers2() {
        forks[0] = new Semaphore(1);
        forks[1] = new Semaphore(1);
        forks[2] = new Semaphore(1);
        forks[3] = new Semaphore(1);
        forks[4] = new Semaphore(1);
    }

    public void lifecycleOfPhilosopher(int id) throws InterruptedException {

        while (true) {
            contemplate();
            eat(id);
        }
    }

    void contemplate() throws InterruptedException {
        Thread.sleep(random.nextInt(500));
    }

    void eat(int id) throws InterruptedException {

        // We randomly selected the philosopher with
        // id 3 as left-handed. All others must be
        // right-handed to avoid a deadlock.
        if (id == 3) {
            acquireForkLeftHanded(3);
        } else {
            acquireForkForRightHanded(id);
        }

        System.out.println("Philosopher " + id + " is eating");
        forks[id].release();
        forks[(id + 1) % 5].release();
    }

    void acquireForkForRightHanded(int id) throws InterruptedException {
        forks[id].acquire();
        forks[(id + 1) % 5].acquire();
    }

    // Left-handed philosopher picks the left fork first and then
    // the right one.
    void acquireForkLeftHanded(int id) throws InterruptedException {
        forks[(id + 1) % 5].acquire();
        forks[id].acquire();
    }
}
Enter fullscreen mode Exit fullscreen mode

在这个解决方案中,你需要让任意一位哲学家先拿起左边的叉子,而不是右边的。选择哪位哲学家作为左撇子并让他们先拿起左边的叉子都无所谓。在我们的解决方案中,我们选择了 id=3 的哲学家作为左撇子哲学家。

为什么要使用这些编程最佳实践?

学习编程时,你通常会学到一些“最佳实践”。最高效的开发者会将某些实践融入到他们的编码过程中,这有助于他们确保代码在功能和形式上都达到最佳状态

多年的编程经验让你往往知道哪些做法应该避免,哪些做法应该采纳。你可能大致明白为什么有些做法比其他做法更好,但当需要解释其中的原因时,却往往语塞。

以下是一些最佳实践示例:

  • 经常给你的代码添加注释
  • 识别并删除重复代码
  • React 中的按特征分组
  • 避免在 Ruby 中使用隐藏结构

准备这些问题的最佳方法是回顾一下哪些做法是有益的,哪些做法是可避免的,以及它们背后的原因。记住,面试时你可以和面试官讨论这些问题。

实现 LRU 缓存

最少使用(LRU)缓存的实现问题在谷歌、微软和亚马逊的一些面试中会被问到,但并不常见。这类问题需要你深入思考,并将两种或多种现有的数据结构结合起来。

仔细阅读题目并确保理解题目要求非常重要。这类问题通常会要求你完成几项任务。仔细阅读题目后,你可以请面试官确认你的思路是否正确。

在着手解决这些问题之前,请确保您理解缓存的概念。LRU 是一种常见的缓存策略,它定义了当缓存已满时,如何从缓存中移除元素以腾出空间存放新元素的策略。这意味着它会优先丢弃最近最少使用的数据项。

接下来需要做的面试准备工作

我们今天讨论的问题只是众多棘手的编程面试题中的一小部分。这些问题本来就很难,甚至可能难倒最有经验的开发者。因此,尽早开始面试准备至关重要,这样你才有机会做好充分的准备。以下是一些更难的问题:

  • 从数据流中找出中位数
  • 在旋转排序数组中搜索
  • 骑士最少步数
  • 还有更多

立即使用 Educative 的文字版面试准备课程,开始备战您的编程面试。我们的专家团队精心挑选了顶尖科技公司最常问的面试题,并将其融入一系列精心设计的场景中,供您学习。您可以在浏览器中直接使用实际的编程环境进行练习。我们的编程面试课程已帮助众多开发者成功通过了 Netflix、Facebook、微软、亚马逊和谷歌等顶尖科技公司的面试。

到最后,你就能自信地参加面试了!

学习愉快!

继续阅读有关编程面试的内容

文章来源:https://dev.to/educative/top-5-hardest-coding-questions-from-recent-faang-interviews-2n67