java面试编程面试题_完美的编程面试问题

java面试编程面试题

by Sachin Malhotra

由Sachin Malhotra

完美的编程面试问题 (The Perfect Programming Interview Problem)

Programming Interviews are hard!

编程面试很难!

Telephone screening interviews are a bit easier than the traditional onsite whiteboard interviews. The whiteboard interviews involve a whole lot of pressure and anxiety due to the lack of a code editor to code on. The thing that these interviews do have in common is the kind of skills they test.

电话筛选采访比传统的现场白板采访要容易一些。 由于缺乏用于编写代码的代码编辑器,白板面试涉及很大的压力和焦虑。 这些面试的共同点是他们测试的技能。

Usually, a programming interview will involve one programming challenge. The candidate has to work on it for the duration of the interview. The time allotted is usually 30–35 minutes. The first 10 minutes are taken up by introductions and other things.

通常,编程采访将涉及一个编程挑战。 候选人必须在面试期间继续努力。 分配的时间通常为30-35分钟。 前10分钟由介绍和其他内容占用。

Given a programming problem, the interviewer usually wants the candidate to:

给定编程问题,面试官通常希望候选人:

  1. Give a Working Solution

    提供可行的解决方案

    Come up with a working solution for the problem. This can be a brute force solution to start with. The criterion is that the candidate should be able to code up a syntactically correct program for the algorithm in that small time frame.

    提出解决该问题的方法。 首先,这可能是蛮力的解决方案。 标准是候选人应能够在那个小的时间范围内为该算法编写语法正确的程序。

  2. Ask Clarification Questions

    提出澄清问题

    Ask questions to clarify things that were intentionally left out.

    提出问题以澄清故意遗漏的事物。

    → What’s the size of the input?

    →输入的大小是多少?

    → How many numbers can be in the array?

    →数组中可以有多少个数字?

    → What is the alphabet size for the string given?

    →给定字符串的字母大小是多少?

    → Can we use extra memory?

    →我们可以使用额外的内存吗?

    → Can we modify the given input or is that read-only?

    →我们可以修改给定的输入还是只读的?

  3. Syntactically Correct Code Once the interviewer is convinced of the solution that the candidate is describing, they are expected to write a working solution for the problem. In a whiteboard interview this solution is to be written on the whiteboard. The whiteboard obviously doesn’t have any syntax correction! That’s what makes it really hard.

    语法正确的代码一旦面试官确信应聘者正在描述的解决方案,他们将被要求为该问题编写可行的解决方案。 在白板采访中,此解决方案将写在白板上。 白板显然没有任何语法更正! 这就是让它变得非常困难的原因。

  4. Come up with better solutions

    提出更好的解决方案

    If you only present a brute force solution to the interviewer to break the awkward silence, they will more often than not ask you to come up with a better solution. Unless it’s your luck day and the interviewer is convinced with the solution you proposed ?. The kind of follow up questions they generally ask are:

    如果您仅向面试官提供暴力解决方案以打破尴尬的沉默,那么他们通常会要求您提供更好的解决方案。 除非今天是您的运气好,并且面试官对您提出的解决方案深信不疑? 他们通常会问的跟进问题是:

    → Can you come up with a better solution? O(logn) → O(n)

    →您能提出更好的解决方案吗? O(登录)→O(n)

    → Can you make your solution space efficient? O(1) space.

    →您能否提高解决方案的空间利用率? O(1)空间。

  5. Edge Cases

    边缘案例

    Even if you were able to come up with an optimal working solution for the problem, it is possible that you missed out on a few edge cases. You may have missed out on a few scenarios that don’t change the algorithm. They may affect the implementation. A candidate is expected to do extensive dry runs with the code after writing it. You are expected to try out a few test cases to find any issues they might have left in their code.

    即使您能够为该问题提供最佳的工作解决方案,也有可能您错过了一些极端情况。 您可能错过了一些不会更改算法的方案。 它们可能会影响实施。 候选人应在编写代码后进行大量试运行。 您应该尝试一些测试用例,以发现他们可能在代码中留下的任何问题。

  6. Complexity Analysis

    复杂度分析

    If there is still time remaining in your interview and the interviewer seems to be satisfied with the code you came up with, they might ask you about the time and space complexity of your solution. Hence, complexity analysis is also a critical skill set required to crack these programming interviews.

    如果面试中还剩下时间,并且面试官似乎对您提出的代码感到满意,他们可能会询问您解决方案的时间和空间复杂性。 因此,复杂性分析也是破解这些编程面试所需的关键技能。

Yes, it is indeed overwhelming.

是的,的确是压倒性的。

This post is not really about tips and tricks about preparing for and attempting a programming interview. There are a lot of good posts out there for this. In case you came here looking for some general guidance and tips, I would like to redirect you to some of my favorite posts.

这篇文章并不是关于准备和尝试编程面试的提示和技巧。 那里有很多不错的帖子。 如果您是来这里寻求一般指导和提示的,我想将您重定向到我最喜欢的一些帖子。

5 things you need to know in a programming interviewThis article is intended for those who are trying to start their programming career, or are preparing to interview for…medium.freecodecamp.orgThe 30-minute guide to rocking your next coding interviewHow I improved in coding interviews and received offers from the big tech companies.medium.freecodecamp.org

5件事情你需要在一个编程采访知道 这篇文章的目的是为那些谁试图开始自己的编程生涯,或正准备采访... medium.freecodecamp.org 的30分钟指南摇摆你的下一个编码面试 如何我改进了编程访谈,并收到了大型科技公司的报价。 medium.freecodecamp.org

I recently came across a programming problem that I had to solve within a time span of 1 hour. It was a part of a programming contest being held on Leetcode. I found this problem to be a great candidate to be asked in a programming interview. I will go through the problem in detail here and discuss my reasons for why this is a good interview problem. I will do my best to try and relate it to the points mentioned before.

最近,我遇到了一个编程问题,必须在1小时的时间内解决。 这是在Leetcode上举行的编程竞赛的一部分 我发现这个问题很适合在编程采访中提出。 我将在此处详细讨论该问题,并讨论为什么这是一个很好的面试问题的原因。 我将尽力将其与前面提到的观点联系起来。

目录 (Table of Contents)

? 让我们玩蛇和梯子 (? Let’s Play Snakes and Ladders)

This question was a part of a recent weekly programming contest held on LeetCode. It’s a 1.5 hour contest with 4 programming challenges of varying difficulty. This was marked as a medium difficulty problem. Being able to solve it during the timeframe is a big thing as you will realize after going through the article.

这个问题是最近在LeetCode上举行的每周编程比赛的一部分 。 这是一场1.5小时的比赛,其中包含4个难度各异的编程挑战。 这被标记为中等难度问题。 在这篇文章中您将意识到,能够在时间表内解决问题是一件大事。

FYI, I couldn’t ?

仅供参考,我不能吗?

Snakes and Ladders - LeetCodeLevel up your coding skills and quickly land a job. This is the best place to expand your knowledge and get prepared…leetcode.com

蛇和梯子-LeetCode 提升您的编码技能,并Swift找到工作。 这是扩展您的知识并做好准备的最佳场所 。leetcode.com

It’s a big question and I would definitely urge you to go through it before reading on.

这是一个很大的问题,我绝对希望您在继续阅读之前先进行研究。

The part of the problem that I want you to focus on is the following:

我希望您重点关注的问题部分是:

Return the least number of moves required to reach square N*N. If it is not possible, return -1.

返回达到平方N * N所需的最少移动次数。 如果不可能,则返回-1

看起来像一个典型的动态编程问题 (Looks like a typical Dynamic Programming problem)

Or is it?

还是?

If you have been practicing dynamic programming problems for a while, it should be a no-brainer that problem statements similar to the one above usually employ the dynamic programming paradigm.

如果您已经练习动态编程问题已有一段时间了,那么与上述问题类似的问题陈述通常会采用动态编程范式应该是很容易理解的。

The reason we say this is because for a problem to be solvable using dynamic programming, it should have certain characteristics.

我们之所以这样说,是因为对于使用动态编程可解决的问题,它应该具有某些特征。

  1. The problem should be breakable into smaller subproblems. The subproblems can be used to solve the main problem. Optimal solutions to subproblems should help us find the optimal solution to the main problem. This means a problem should be solvable recursively.

    该问题应分解为较小的子问题。 子问题可用于解决主要问题。 子问题的最佳解决方案应有助于我们找到主要问题的最佳解决方案。 这意味着问题应该可以递归解决。

  2. The second most important property is that of overlapping subproblems. Essentially, what dynamic programming does for us is that it helps us reuse optimal solutions for subproblems. In case we have multiple recursion paths with overlapping subproblems, we should only calculate answers for them once. Then onwards reuse them. This is the caching part of dynamic programming.

    第二个最重要的属性是子问题重叠 。 从本质上讲,动态编程为我们所做的是,它可以帮助我们针对子问题重用最佳解决方案。 如果我们有多个递归路径且子问题重叠,那么我们应该只为它们计算一次答案。 然后继续重用它们。 这是动态编程的缓存部分。

The reason why this problem fits the bill for dynamic programming is because of the following components of the problem:

该问题之所以适合动态编程,是因为该问题的以下组成部分:

  • We have a grid where each cell has a specific number. That number can help us define the state of a dynamic programming solution.

    我们有一个网格,其中每个单元格都有一个特定的数字。 该数字可以帮助我们定义动态编程解决方案的状态。
  • At every cell in the grid, we have 6 options available. These represent the 6 dice values that we can possibly get on playing snakes and ladders. Naturally, these 6 steps help us transition from one state to another state. This represents the “breakable into subproblems” part of the dynamic programming requirements.

    在网格中的每个单元格中,都有6个可用选项。 这些代表我们在玩蛇和梯子时可能获得的6个骰子值。 当然,这6个步骤可帮助我们从一种状态过渡到另一种状态。 这代表了动态编程要求中的“易碎为子问题”部分。

  • Since we can break the problem down into smaller subproblems, we already have one requirement satisfied. If you think carefully, it is possible to reach the same cell on the grid multiple times via different routes. Let us look at two possible ways of reaching the cell value 22 starting from 1 .

    由于我们可以将问题分解为较小的子问题,因此我们已经满足了一个要求。 如果您仔细考虑,则可以通过不同的路线多次到达网格上的同一单元。 让我们看一下从1开始达到单元格值22两种可能方式。

The path followed by the player in the above GIF is as follows:

播放器在上述GIF中所遵循的路径如下:

--> Start at the node marked 1--> Dice value 3, hence move to node marked 4--> Dice value 6, hence move to node marked 10--> Since there was a snake at node 10, move to its head and hence the node valued 22.

The path followed by the player in the above GIF is as follows:

播放器在上述GIF中所遵循的路径如下:

--> Start at the node marked 1--> Dice value 3, hence move to node marked 4--> Dice value 5, hence move to node marked 9--> Dice value 5, hence move to node marked 14--> Dice value 3, hence move to node marked 7--> Dice value 5, hence move to node marked 22

As we can see from the two paths above in a sample snakes and ladders grid, there are two ways of reaching the node valued 22 . There are a lot of other ways as well. To put across the point of overlapping subproblems, the two shown here are enough.

从上面的示例蛇和梯子网格中的两条路径可以看出,有两种方法可以到达值为22的节点。 还有很多其他方式。 为了说明重叠的子问题,这里显示的两个就足够了。

Now that we know a single cell defines the state of our dynamic programming solution and there are multiple ways of reaching the same cell, this implies that our second requirement for a dynamic programming problem is also satisfied i.e. overlapping subproblems. Once having calculated the answer for a given subproblem, it shouldn’t be computed again. It should just be re-used.

现在我们知道单个单元格定义了动态编程解决方案的状态,并且有多种到达同一单元格的方式,这意味着我们对动态编程问题的第二个要求也得到了满足,即子问题重叠。 一旦计算出给定子问题的答案,就不应再次对其进行计算。 应该重新使用它。

Let us now look at a formal dynamic programming formulation for this problem.

现在让我们看一下针对此问题的正式动态编程公式。

dp[i] = Minimum number of steps from cell(i) to reach the destination cell.
dp[i] = min(dp[i + 1], dp[i + 2], ... dp[1 + 6])We have to choose the move that gives the minimum number of steps.

The above formulation looks pretty clean and we can proceed with it.

上面的公式看起来很干净,我们可以继续进行。

However, there’s a major flaw in the above formulation. ?

但是,上述公式存在一个重大缺陷。 ?

The way we have modeled our problem here is something that will continue till the end of the article. So, let us first define the model of our problem. Then continue with describing the flaw in the dynamic programming approach.

我们在此处为问题建模的方式将一直持续到本文结尾。 因此,让我们首先定义问题的模型。 然后继续描述动态编程方法中的缺陷。

图模型 (Graph Model)

We can consider each of the cells in our snakes and ladders grid as a node in a graph. The six possible moves that a player can make from a given cell represent the edges. These edges are directed edges. A move that takes us from cell i to cell j , doesn’t necessarily take us back from cell j to cell i . So, for the problem formulation we have:

我们可以将蛇形和梯形网格中的每个单元视为图中的一个节点。 玩家可以从给定单元中进行的六个可能动作代表了优势。 这些边缘是有向边缘。 将我们从单元格i转移到单元格j的举动并不一定会使我们从单元格j返回到单元格i 。 因此,对于问题的表述,我们有:

  • A Graph G(V, E)

    G(V,E)

  • Each cell in our snakes and ladders grid represents a node in the graph. Naturally, there are nodes in the graph.

    蛇形和梯形网格中的每个单元格代表图中的一个节点。 自然地,图中有N²个节点。

  • Every move from cell i to cell j represents a directed edge in the graph from node i to node j .

    从单元格i到单元格j每一步代表图中从节点i到节点j有向边。

  • Since, for every cell in the grid, we have at most 6 moves, this means the total number of edges in our graph would be 6N².

    由于对于网格中的每个像元,我们最多有6个移动,这意味着图形中的边总数为6N²

Let’s consider a small grid and its corresponding graph for more clarity.

为了更清楚,让我们考虑一个小的网格及其对应的图形。

And the corresponding graph for this grid would be.

并且该网格的相应图形将是。

我们的DP配方中的缺陷 (The Flaw in our DP formulation)

Now that we have defined our graphical model for the problem, we can look at the flaw in our dynamic programming formulation. The formulation we looked at was the following:

现在,我们已经为问题定义了图形模型,我们可以看看动态编程公式中的缺陷。 我们看的公式如下:

dp[i] = Minimum number of steps from cell(i) to reach the destination cell.
dp[i] = min(dp[i + 1], dp[i + 2], ... dp[1 + 6])We have to choose the move which gives the minimum number of steps.

If there were no snakes involved in the problem, then the above formulation would have been complete in itself. The problem induced by the snakes is that of loops in our graph. A snake can bring us back to an already visited state in our graph.

如果问题没有蛇,那么上面的表述本身就是完整的。 蛇引起的问题是我们图中的 。 蛇可以使我们回到图形中已经访问过的状态。

The problem this creates in our formulation is we can’t really consider a single cell in the grid to define the state of our dynamic programming formulation.

这在我们的公式中创建的问题是,我们不能真正考虑网格中的单个单元来定义动态编程公式的状态。

Let’s see why that is the case via an example and then we will see how to fix this problem by a different formulation.

让我们通过一个例子看看为什么会这样,然后我们将看看如何通过不同的表述解决这个问题。

This is one of the possible routes of reaching the cell 36 starting from the initial cell 1.

这是从初始单元格1开始到达单元格36的可能途径之一。

Suppose that we want to find out the shortest number of steps to take from cell 22 to reach the destination cell 36 . In the above case, the player went from 1 --> 5 --> 16 (Snake Up!) --> 22 . As we can see in the above figure, the player will come down to the cell 11 due to the snake. Then take one step ahead i.e. to cell 12. Finally, take the snake up to the destination cell 36 . This is just 1 step.

假设我们想找出从单元格22到达目标单元格36的最短步骤。 在上述情况下,玩家从1 --> 5 --> 16 (Snake Up!) -> 22出发。 如上图所示,由于蛇的缘故,玩家将来到th 11格。 然后采取领先一步即t O单元12。最后,取蛇到destinat io N个单元36。 这只是第一步。

Following the snake from 22 --> 11 and from 12 --> 36 is not really a move. The actual move is from 11 --> 12 . Hence, the minimum number of steps required to move from 22 to 36 is 1.

跟随蛇从22 --> 11和from om 12 --& > 36并不是真正的举动。 实际移动是from 11 > 12。 因此,需要到m的步骤的最小数目ov E FR om 22至36是1。

Now consider the following state of the grid when the user had reached 22 .

现在考虑当用户达到22时网格的以下状态。

The above scenario is also possible during recursion. In this case, when the player reaches the cell 22, they don’t have 6 options in front of them. The reason for that is, the cell 22 is the starting point for a snake. If a cell is a starting point for a snake, then it has to be followed to its head. The player will end up landing in cell 11 which has already been visited. This is a loop in our recursion. Since we cannot make any other move from cell 22 , there is no way of finding out the minimum number of steps from 22 to 36 .

在递归过程中上述情况也是可能的。 在这种情况下,当玩家到达单元格22 ,他们前面没有6个选项。 其原因是,单元格22是蛇的起点。 如果单元格是蛇的起点,则必须跟随它的头部。 玩家将最终登陆已访问过的单元11 。 这是我们递归中的一个循环。 由于我们无法从单元格22进行任何其他移动,因此无法找出从22 to 36的最小步数。

The problem is:

问题是:

A cell alone cannot represent the state of our dynamic programming formulation. We also need to keep track of the cells visited prior to the one at hand. A combination of these two would define a unique state in our DP formulation.

一个单元不能代表我们动态编程公式的状态。 我们还需要在手边的一个单元之前跟踪访问的单元。 两者的结合将在我们的DP配方中定义一个独特的状态。

更新的DP配方 (Updated DP formulation)

The updated DP formulation, as mentioned before will have to take into account the visited cells and the current cell where the player is at.

如前所述,更新后的DP公式必须考虑访问者所在的单元以及玩家所在的当前单元。

Since, the state of a dynamic programming problem is generally used as a key to a cache that stores the results for various states (memoization), simply keeping a dictionary or a set of visited cell nodes is not feasible. These data structures are not hashable.

由于动态编程问题的状态通常用作存储各种状态( 备注 )结果的缓存的键,因此仅保留字典或一组访问的单元节点是不可行的。 这些数据结构不可散列。

An alternative approach which can be adopted is bit-masking. We can make use of a bit-mask to mark the cells of the grid that a user has already visited before visiting a particular cell.

可以采用的另一种方法是位屏蔽。 我们可以使用位掩码来标记用户在访问特定单元之前已经访问过的网格单元。

Let’s consider if this is even a feasible approach to follow. So, for a grid size of 20-by-20 , there would be 400 cells and we would need a bit-mask of 400 bits. Each of these bit values would represent if that particular cell is already visited or not during recursion. Since each of the bits can have two different values: 0 and 1 , there are 2⁴⁰⁰ possible grid states. Multiplying it with 400 since we also need the current cell, we get a whopping (2⁴⁰⁰ * 400).

让我们考虑一下这是否是可行的方法。 因此,对于20-by-20的网格大小,将有400元,而我们将需要400位的位掩码。 这些位值中的每一个将代表在递归过程中该特定单元是否已被访问。 由于每个位可以具有两个不同的值: 0 and 1 ,因此有2 可能的网格状态。 将其乘以400,因为我们还需要当前单元格,因此得到的结果非常大(2⁴⁰⁰* 400)。

Such a huge number of possible states will not work out and is not tractable at all. This is the reason that we have to move on from a dynamic programming solution to something else because of the sheer number of states in the problem formulation.

如此众多的可能状态将无法解决,而且根本无法处理。 这就是我们必须从动态编程解决方案转到其他解决方案的原因,因为问题制定中的状态数量众多。

广度优先搜索救援! ⛑ (Breadth First Search to the Rescue! ⛑)

Let’s try and formulate the problem in a slightly different manner. We already said that the cells in our grid represent the nodes in a graph. The 6 possible moves represent the directed edges to other nodes in the graph.

让我们尝试以稍微不同的方式提出问题。 我们已经说过,网格中的单元格代表图中的节点。 这6种可能的移动代表了指向图中其他节点的有向边。

We want to find the minimum number of moves of reaching the destination starting from the initial point on the grid. This boils down to finding the shortest path in an unweighted graph.

我们想要找到从网格的初始点开始到达目的地的最小移动次数。 归结为找到未加权图中的最短路径。

You can either say this graph is unweighted, or you can say that all the edges are of equal weights. Hence the weights can be ignored. Finding the shortest path in an unweighted graph is a pretty standard problem. The most standard algorithm for solving it is the breadth first search algorithm.

您可以说该图未加权,也可以说所有边的权重均等。 因此权重可以忽略。 在未加权图中找到最短路径是一个非常标准的问题。 解决该问题的最标准算法是广度优先搜索算法

We don’t need any special state in our graph for breadth first search like we needed in the case of dynamic programming. We can definitely have multiple ways of reaching a particular node from the starting position. However, the first route that is discovered in the breadth first search algorithm is the shortest one. That is the basis for the algorithm.

像动态编程一样,我们不需要在图形中进行广度优先搜索的任何特殊状态。 我们绝对可以有多种从起始位置到达特定节点的方法。 但是,在广度优先搜索算法中发现的第一条路径是最短的 。 这是该算法的基础。

That means the first time we encounter a node/cell during our search, the number of moves performed till then would be the minimum number of moves required to get to that state/cell/node from the starting position.

这意味着我们在搜索过程中第一次遇到节点/单元时,直到那时执行的移动次数将是从起始位置到达该状态/单元/节点所需的最小移动数。

After that if we encounter the same node again, we can simply ignore it. We would already have the shortest path to that node by then. This ensures that we don’t process any node of the graph more than once in breadth first search.

之后,如果再次遇到相同的节点,则可以简单地忽略它。 到那时,我们已经拥有到该节点的最短路径。 这确保了我们在广度优先搜索中不会多次处理图形的任何节点。

The breadth first search algorithm makes use of the queue data structure. The queue contains the nodes of the graph at a particular level at any point in time. Since we will only process each node exactly once, the maximum possible size of the queue can be O(N). That’s the upper bound on the size of the queue. This approach to the problem is very tractable and is in fact the optimal way of doing it.

广度优先搜索算法利用队列数据结构。 队列在任何时间点都包含特定级别的图的节点。 由于我们仅将每个节点处理一次,因此队列的最大可能大小为O(N)。 那是队列大小的上限。 解决该问题的方法非常易于处理,实际上是解决问题的最佳方法。

Let’s look at the pseudo-code for the algorithm that we just proposed for this problem. Then we will look at the implementation for the same.

让我们看一下我们刚刚针对此问题提出的算法的伪代码。 然后,我们将看一下相同的实现。

1. Initialize a queue for the BFS algorithm. Let's call it "Q".2. Add the first cell to the Q. Note that we also need to keep track of the level of nodes in our graph. The level will tell us the minimum number of moves made to reach a specific node. The level for the initial node would be 0.3. Process until the Q becomes empty.    a. Remove the front element of the Q. Let's call it "node".    b. For each of the 6 possible moves from "node", add the ones that have not been processed before, to the Q.4. If we encounter the destination node during the processing, simply return the level value at that point.

There are a lot of interesting points that we should address before looking at the implementation. These are from my own attempt at the problem. Some of these might seem too simple to have been mentioned. I wanted to put across all these cases. They are important to they way you write the code for this algorithm we discussed.

在查看实现之前,我们需要解决很多有趣的问题。 这些是我自己尝试解决的问题。 其中一些似乎太简单了,无法被提及。 我想整理所有这些案例。 它们对于您为我们讨论的该算法编写代码的方式很重要。

单元格值到行和列的映射? (Cell Value to Row and Column Mapping?)

The first of these points that is important to think about is the mapping from the cell values to the actual row and column numbers.

这些要考虑的第一点是从单元格值到实际行号和列号的映射。

Remember, we are given a grid of some values and each cell of the grid has a numbering. The numbering system is written boustrophedonically from bottom starting from the bottom left of the board, and alternating direction each row.

请记住,我们得到了一些值的网格,并且网格的每个单元格都有一个编号。 编号系统是双向书写的 从板的左下角开始,从底部开始,并交替显示每行的方向。

The way the implementation has been done here initially is by considering the actual row and column numbers and then somehow using them to progress in the grid.

此处最初实现的方式是考虑实际的行数和列数,然后以某种方式使用它们在网格中进行。

According to the problem, if a cell contains a snake, then the value in that cell is the destination cell where the player would land. We need to map the destination cell value to the corresponding row and column number.

根据问题,如果一个单元格包含一条蛇,那么该单元格中的值就是玩家将要降落的目标单元格。 我们需要将目标单元格值映射到相应的行号和列号。

The movement for the row is always easy. We either move in different columns in the same row or we can shift one row up. That’s it for the row.

该行的移动始终很容易。 我们可以在同一行中移动不同的列,也可以向上移动一行。 就这样了。

As far as the column is concerned, there are two possible directions of movement. The question is especially tricky. The numbering of the cells alternates from one row to another. The movement within a row (for the various moves) will also have alternating directions in alternating rows e.g. for a 6-by-6 grid, the movement will be to the right for the bottom row and it will be to the left in the second last row.

就列而言,有两个可能的移动方向。 这个问题特别棘手。 单元的编号从一行交替到另一行。 一排内的移动(对于各种移动)在交替的行中也将具有交替的方向,例如对于6-by-6网格,下排的移动将在右侧,第二排的移动将在左侧最后一行。

For a 6-by-6 matrix, the row number 5 (considering a 0-based indexing of the matrix rows and columns) would be the one containing cells from 1 .. 6 and the row number 4 would contain the cells from 7 .. 12 .

对于6-by-6矩阵,行号5 (考虑矩阵行和列的从0开始的索引)将是包含1 .. 6单元格的行,而行号4将包含7 .. 12的单元格7 .. 12

This means, for the even numbered rows, the direction is to the left. For the odd numbered rows, the direction is to the right. However, This mapping gets reversed when N is odd. Consider the scenario in a 5-by-5 matrix.

这意味着,对于偶数行,方向是向左。 对于奇数行,方向是向右。 但是,当N为奇数时,此映射将反转。 考虑5-by-5矩阵中的方案。

The last row in this matrix is 4 and that row has a direction to the right. This means, in these cases, the even numbered row has a direction to the right. The odd numbered row has a direction to the left.

该矩阵的最后一行是4 ,该行的方向向右。 这意味着,在这些情况下,偶数行的方向向右。 奇数行具有向左的方向。

Implementation wise, we can use something like the following.

在实现方面,我们可以使用类似以下的内容。

even_direction = 1 if N % 2 != 0 else -1direction = even_direction * (1 if row % 2 == 0 else -1)

In our implementation, we consider a value of 1 for a direction to the right and a value of -1 for representing a direction to the left. The reason for this is the simplicity it induces in simulating movement. We can simply do something like:

在我们的实现,我们考虑的值1的方向向右和值-1为代表向左方向。 这样做的原因是它简化了模拟运动的过程。 我们可以简单地执行以下操作:

for move in range(6):     new_cell = cell[r][c + direction]

The c + direction would either go towards right or left. This is because the value of direction will either be 1 or -1 depending upon the value of N .

c + direction将向右或向左。 这是因为direction的值取决于N的值将为1-1

Now we can look at the mapping of a cell value to the corresponding row and column indices.

现在我们来看一下单元格值到对应的行和列索引的映射。

Note that the following code is only to be executed when we find a snake in one of the cells. board[row][col] will contain a -1 to represent a normal cell. The following code is executed when a snake is found i.e. board[row][col] != -1

请注意,以下代码仅在我们在其中一个单元格中找到蛇时才执行。 board[row][col]将包含-1代表正常单元格。 发现蛇时执行以下代码,即board[row][col] != -1

(1) value = board[row][col](2) snake_dest_row = N - (value / N) - 1(3) new_direction = even_direction * (1 if snake_dest_row % 2 == 0 else -1)(4) snake_dest_column = (value % N)                        (5) if new_direction < 0:(6)    snake_dest_column = N - 1 - snake_dest_column                        (7) next_cell = (snake_dest_row, snake_dest_column)
1号线 (Line 1)

Gives us the value stored at the current cell. The current cell is represented by row and col .

给我们存储在当前单元格的值。 当前单元格由rowcol表示。

2号线 (Line 2)

Using the value stored in the cell we find the row where that cell would be located. Remember, the value represents the cell we need to move the player to since they encountered a snake.

使用存储在单元格中的值,我们找到该单元格所在的行。 请记住,该值表示自玩家遇到蛇以来我们将其移动到的单元格。

3号线 (Line 3)

Since we figured out the row where the new cell (destination cell) would be located, we can also figure out the direction of cells in that particular row. We discussed directions to the right and left depending upon the value of N, above.

由于我们找出了新单元格(目标单元格)所在的行,因此我们也可以找出该特定行中单元格的方向。 我们根据上面的N值讨论了左右方向。

4、5和6行 (Line 4, 5 and 6)

Represents the offset for finding out the column value. For a row which has a direction going to the right, the offset itself will represent the column index. If the direction is to the left, then the offset will be from the right. We find the column index using N — snake_dest_column — 1 .

表示用于找出列值的偏移量。 对于具有向右方向的行,偏移量本身将代表列索引。 如果方向是向左,则偏移将是从右侧。 我们使用N — snake_dest_column — 1找到列索引。

有问题的单元格值? (Problematic Cell Values ?)

As mentioned before, the cells either contain -1 or they contain a snake. The way a snake is represented is by a cell value that represents the destination cell that a player will reach by following that snake.

如前所述,单元格包含-1或包含蛇。 蛇的表示方式是通过一个单元格值表示,该值代表玩家跟随该蛇到达的目标单元格。

So, while handling the snake case, we need to be able to get the row and column number where the corresponding cell will be located.

因此,在处理蛇形案件时,我们需要能够获取相应单元格将位于的行号和列号。

We saw in the previous section the way we can do that. There’s a small mistake in the code, however.

我们在上一节中看到了我们可以做到的方式。 但是,代码中有一个小错误。

Consider the scenario where we have a 4-by-4 grid. A particular cell contains a snake that takes the player to (or whose destination cell is) 8 . Let’s see the corresponding row and column values that we get by using the code above.

考虑一下我们有一个4-by-4网格的情况。 特定的单元格包含一条将玩家带到(或目标单元格是) 8的蛇。 让我们看看通过上面的代码获得的相应的行和列值。

value = 8even_direction = -1snake_dest_row = 4 - (8 / 4) - 1 = 1new_direction = -1 * (-1) = 1snake_dest_column = 8 % 4 = 0
next_cell = (1, 0)

Clearly, this is not the correct cell. The correct cell representing the value 8 is (2, 0) and not (1, 0) . The way we fix this is by not considering the value as it is but by subtracting 1 from it. Then this problem doesn’t arise.

显然,这不是正确的单元格。 代表值8的正确像元是(2, 0) and not (1, 0) 。 我们解决此问题的方法是不考虑原样的值,而是从中减去1。 这样就不会出现这个问题。

value = board[row][col] - 1

每个蛇走一招? (Making one move per Snake ?)

One of the last important components of the implementation is adhering to the rule mentioned in the problem statement which states that “you only take a snake or ladder at most once per move”. If the destination to a snake or ladder is the start of another snake or ladder, you do not continue moving.

该实现的最后一个重要组成部分是遵守问题说明中提到的规则,该规则指出“您每条动作最多只能走一条蛇或梯子”。 如果蛇或梯子的目的地是另一条蛇或梯子的起点, 则不要继续移动

The way BFS works is, we process the node currently popped from the queue by looking at its adjacency list. Then consider all the nodes that have not been processed yet and then add them to the queue.

BFS的工作方式是,我们通过查看其邻接表来处理当前从队列中弹出的节点。 然后考虑尚未处理的所有节点,然后将它们添加到队列中。

So, for a given cell, there would be 6 adjacent nodes. Except for some cases where it’s not possible to have 6 moves in all. We have to consider all them and add the ones not processed yet to the queue.

因此,对于给定的单元,将有6个相邻节点。 除了某些情况下不可能总共进行6次移动。 我们必须考虑所有它们,并将尚未处理的那些添加到队列中。

No special handling is required for the moves that land up in cells that don’t contain a snake. So, all we do in this case is:

落在不包含蛇的牢房中的移动不需要特殊处理。 因此,在这种情况下,我们要做的是:

if board[row][col + direction] == -1:    process it normally

In case the move we make lands us with a snake, instead of considering / processing the node where we are after the move, we consider the snake’s destination cell and add that to the queue instead if it was not processed before. Let’s consider an example for this.

如果我们进行的移动是一条蛇,而不是考虑/处理移动后的节点,我们将考虑蛇的目标单元,并将其添加到队列中(如果之前未处理过)。 让我们考虑一个例子。

As you can see in the figure above, we don’t consider the node 4 when processing the 6 moves corresponding to the cell 1 . Instead we consider the eventual destination for 4 i.e. 11 .

如上图所示,在处理与单元格1对应的6个移动时,我们不考虑节点4。 相反,我们考虑最终目的地为411

If suppose there were a snake from 11 to some other node, then we will not process that. We already processed one snake move from 4 to 11 . There cannot be a continuation of snake moves here. That is an important thing to note.

如果假设有一条从11到其他节点的蛇,那么我们将不进行处理。 我们已经处理了从4 to 11一条蛇移动。 这里不能有蛇的延续。 这是要注意的重要事项。

Let’s look at the implementation that brings all these ideas together.

让我们看一下将所有这些想法融合在一起的实现。

我们可以摆脱“方向”问题吗? ? (Can we get rid of the “direction” thingy? ?)

It turns out there’s a simpler way of doing what is accomplished using the even_direction variable above.

事实证明,有一种使用上面的even_direction变量完成操作的简单方法。

Essentially we want to know, given a row number, what is the direction of cells in it. Is it towards the right or towards the left?

本质上,我们想知道给定一个行号,其中的单元格方向是什么。 是向右还是向左?

The reason we need this information is because that helps us decide the final column number when we encounter a snake.

我们需要此信息的原因是,这有助于我们在遇到蛇时确定最终的列号。

Instead of relying on the even_direction variable, we can use a simple check like the following:

无需依赖even_direction变量,我们可以使用如下所示的简单检查:

if N % 2 != row % 2:   direction = rightelse   direction = left

This covers all of the cases for us. Remember, the direction for a row depends upon the value of N.

这为我们涵盖了所有情况。 请记住,行的方向取决于N的值。

  • N is even and row is even, then N % 2 == row % 2 and hence the direction will be towards left.

    N是偶数, row是偶数,则N % 2 == row % 2 ,因此方向将朝左

  • N is even and row is odd, then N % 2 != row % 2 and hence the direction will be towards right.

    N是偶数且row是奇数,则N % 2 != row % 2 ,因此方向将是向右

  • N is odd and row is odd, then N % 2 == row % 2 and hence the direction will be towards left.

    N是奇数且row是奇数,则N % 2 == row % 2 ,因此方向将朝着离开

  • N is odd and row is even, then N % 2 != row % 2 and hence the direction will be towards right.

    N是奇数且row是偶数,则N % 2 != row % 2 ,因此方向将是向右

Thanks Divya Godayal for suggesting this simplistic check. This definitely cuts down on the code length and covers all the above cases. Cheers!

感谢Divya Godayal建议这项简单的检查。 绝对可以减少代码长度,并涵盖所有上述情况。 干杯!

反过来映射 (Mapping the other way around)

In the above implementation, we worked with row and column numbers. Then mapped a cell value to a row and column number whenever required (in case of snake that is). We can also solve this problem by working the other way around.

在上述实现中,我们使用了行号和列号。 然后在需要时将单元格值映射到行号和列号(如果是蛇形)。 我们也可以通过其他方法解决此问题。

It’s much simpler to work with cell numbers instead. Starting from 1 and for every cell we have to consider the next 6 cells.

相反,使用单元格编号要简单得多。 从1开始,对于每个单元格,我们必须考虑接下来的6单元格。

All we would have to do is then map those cell values to the corresponding row and column numbers (again, to check the presence or absence of any snakes). We already saw how to do that in the above implementation as well.

然后,我们要做的就是将这些单元格值映射到相应的rowcolumn号(再次检查是否存在任何蛇)。 我们已经在上面的实现中看到了如何做到这一点。

This shortens the code. The solution I explained above is the one I came up with when thinking about the problem. I wanted to explain it as it is to make things clearer. This might not be the best way to go about writing the code for the algorithm describer. It helps describe all the caveats.

这样可以缩短代码。 我上面解释的解决方案是我在思考问题时想到的解决方案。 我想解释一下,因为这样可以使事情更清楚。 这可能不是为算法描述程序编写代码的最佳方法。 它有助于描述所有警告。

为什么这又是最好的编程面试问题? (Why again this is the best programming interview problem?)

  1. You might be lured towards a dynamic programming based solution (like I was ?). In that case you need to reason and get out of that loophole. DP doesn’t work here.

    您可能会被引诱到基于动态编程的解决方案上(就像我以前那样?)。 在这种情况下,您需要推理并摆脱漏洞。 DP在这里不起作用。
  2. Coming up with a BFS based approach here is super critical. A good grasp of your graph based algorithms is necessary. The important thing is mapping the problem to finding shortest paths in unweighted graph.

    在这里提出基于BFS的方法非常关键。 必须很好地掌握基于图的算法。 重要的是将问题映射到未加权图中找到最短路径。

  3. Writing the code for mapping cell values to corresponding row and column numbers can be super confusing especially under time constraints. It’s not a big piece of code but you need to be able to think it through properly.

    编写用于将单元格值映射到相应行号和列号的代码可能会造成极大的混乱,尤其是在时间限制下。 这不是一段很大的代码,但是您需要能够仔细考虑一下。
  4. Handling the edge case where we get incorrect mapping if we consider cell values as it is. We had to subtract 1 from the cell value before mapping it to the corresponding row and column. Failure to do this will give incorrect results.

    如果我们原样考虑单元格值,则在处理边缘情况时会得到错误的映射。 在将其映射到相应的行和列之前,我们必须从单元格值中减去1。 否则,将导致错误的结果。
  5. Writing correct, working code during time constraints is also a tough-ish task. You really need to keep your cool.

    在时间限制内编写正确的工作代码也是一项艰巨的任务。 您真的需要保持冷静。

All in all, this problem tests a lot of different aspects of programming and thinking ability of a programmer. I feel that is one of the most important aspects to test during such interviews.

总而言之,这个问题测试了编程的许多不同方面以及程序员的思考能力。 我认为这是在此类采访中要测试的最重要方面之一。

Not that these interviews are the best way to hire the candidates. Since they exist, this problem brings out a lot of different qualities that interviewers look for in candidates.

这些面试并不是招聘候选人的最佳方法。 由于存在,这个问题带来了面试官在候选人中寻找的许多不同素质。

If you found the article to be useful, do share it as much as possible ?

如果您发现该文章有用,请尽量分享一下?

翻译自: https://www.freecodecamp.org/news/the-perfect-programming-interview-problem-8431cdeab2a7/

java面试编程面试题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值