本文转载自微信公众号「DotNET技术圈」,作者Michael Shpilt。转载本文请联系DotNET技术圈公众号。
我最近参加了一些最大的科技公司的一系列工作面试。在没有透露姓名的情况下,我得到了世界排名前 5 的科技公司中的 3 家的面试机会。你知道我在说谁。
这些公司的面试流程大不相同,但也有很多共同点,包括对编码问题的高度重视。这些问题可能是对某些东西进行排序,打印所有可能的组合,或者找到走出迷宫的路。我不确定解决这些问题与实际软件开发有何关系,但这些公司似乎确实这么认为。我不记得在我的日常工作中必须实现排序算法。或者反转链表。或者在图形上使用 DFS。尽管如此,您仍然可以证明能够解决这些问题的开发人员在现实生活中会更好。
但这篇文章不是关于这些问题的动机。这篇文章是关于我从求职面试中学到的关于 C# 的知识。具体来说,从编码问题。我已经使用 C# 进行专业编程 10 多年了,但正如我发现的那样,开发软件与通过面试完全不同。
顺便说一下,所有大公司(译者注:仅限于西雅图)都允许候选人用任何编程语言解决问题。因此,即使该职位是针对 JavaScript 的,您仍然可以编写 C# 代码。
多维数组
我认为我在工作中从未使用过多维数组。当然,我有时会使用一个列表列表List
多维数组与数组数组不同,例如int[][] arr(锯齿状数组)。后者是一组数组,其中每个数组的长度可以不同。而多维数组更适合常见问题,例如表示 2D 迷宫或 3D 立方体。如果您很快就要参加工作面试,请更新这些语法。这包括初始值设定项、维度长度和列/行各自的索引。
- int[,] arr = new int[3, 2]{
- {1, 2},
- {3, 4},
- {5, 6}
- };
- int firstDimensionLength = arr.GetLength(0);//3
- int secondDimensionLength = arr.GetLength(1);//2
- var p00 = arr[0, 0];//1
- var p01 = arr[0, 1];//2
- var p10 = arr[1, 0];//3
这是一个多维数组派上用场的问题示例:
给定一个由 MxN 方形单元组成的二维空间,其中每个单元要么是一个自由空间,要么是一面墙,找到自由空间的最大连通区域并返回其单元数。
使用元组,而不是类
我已经用 C# 编程一段时间了,我很少使用元组。可能是因为我不习惯ValueTuple. 我的默认选择通常是一个类。对于类有一些好处:它们使代码更加结构化并且可以说更具可读性。但是元组实际上有一个非常好的语法。也许,更重要的是,它们具有更
紧凑的
语法。只是少写了。如果你想改变一些东西,那就没有什么可改变的了。这些事情在编码面试中非常重要。您希望花费最少的时间输入代码,以便有更多的时间思考。并可能回答更多问题。
这是一个例子。假设我有一个方法可以返回 3D 数组中的一个点,当我使用一个类时,我会这样写:
- class Point3D
- {
- public int X { get; set; }
- public int Y { get; set; }
- public int Z { get; set; }
- }
- private Point3D Calc(){ ... }
而对于元组,它是:
- private (int X, int Y, int Z) Calc(){ ... }
请记住,在编码面试中,您将在一种类似于在线记事本的环境中进行写作,在那里您没有代码片段、自动完成功能以及您习惯于从 Visual Studio 使用的所有其他优点。
二元运算也是一回事
您在库和应用程序中使用<<、>>、&和|运算符的频率如何?我猜没有那么多。我也没有,但它们在编码问题中很有用。这里有一个小提醒:
- int a = 15;
- Console.WriteLine(Convert.ToString(a, toBase: 2));//1111
- a = a >> 2;//shift right twice (same as divide by 4)
- Console.WriteLine(Convert.ToString(a, toBase: 2));//11
- a = a << 3;//shift left 3 times (same as multiply by 8)
- Console.WriteLine(Convert.ToString(a, toBase: 2));//11000
- a = a & 0b_11111; // stays same
- Console.WriteLine(Convert.ToString(a, toBase: 2));//11000
- a = a & 0b_1111; // remains 1000 because leftmost digit is nullified
- Console.WriteLine(Convert.ToString(a, toBase: 2));//1000
- a = a | 0b_1; // becomes 1001
- Console.WriteLine(Convert.ToString(a, toBase: 2));//1001
- a = a | 0b_110; // becomes 1111
- Console.WriteLine(Convert.ToString(a, toBase: 2));//111
关于二进制的一件事是,以基数 2 进行迭代可能有助于解决置换问题。例如,考虑以下问题:
给定一组项目,打印出这些项目的所有可能组合,其中每个项目可能包含或不包含。顺序无关紧要。
因此对于输入 [“a”,”b”,”c”],输出将是:
- [empty array], a, b, c, ab, ac, bc, abc
现在考虑从 0 到 2^3 的二进制迭代。如果每个数字都描述了结果中是否包含数组的一个项目,那么这是打印出所有可能迭代的一种方法。上面的结果可以描述为:
- 1
- 2
- 000, 001, 010, 100, 110, 101, 011, 111
不要只依赖二元运算符。解决这个置换问题的另一种方法是使用简单的递归。
字符串和数组的有用内容
编码问题中使用的大多数方法与现实生活中使用的方法相同。对于字符串,它是您的.Substring, .Contains, .Replace, string.Equals(), .ToLower(), .ToUpper(), 等。我发现在这些问题中非常有用的一种方法是string.Join. 它将加入一个字符串集合,每对字符串之间有一个分隔符。例如:
- var joined = string.Join(",", new[] { "a", "b", "c" }); // "a,b,c"
对于数组,我还发现了一些有用的方法。其中之一是Array.Sort,它可以接受Comparison
- var words = new[] { "cat", "job", "zebra", "row" };
- Array.Sort(words, (w1, w2) =>
- {
- var lastLetter1 = w1[w1.Length - 1];
- var lastLetter2 = w2[w2.Length - 1];
- return lastLetter1 < lastLetter2 ? -1 : 1;// or: return lastLetter1.CompareTo(lastLetter2);
- });
- var joined = string.Join(',', words); // zebra,job,cat,row
我以前从未真正使用过的另一个有用的方法是Array.Copy. 除此之外,它还可以将数组的切片复制到新数组中。例如:
- var words = new[] { "cat", "job", "zebra", "row" };
- string[] dest = new string[2];
- Array.Copy(words, sourceIndex:1, dest, destinationIndex: 0, length: 2);
- Console.WriteLine(string.Join(',', dest)); // job, zebra
还有其他方法可以复制数组切片。其中之一是使用 LINQ: words.Skip(1).Take(2).ToArray()。但我不确定我是否会在与 Java 程序员的面试中展示我的 ninja C# 技能。
Python有时更简短
关于 C# 有很多好话要说,但它可能不是算法的最佳语言。我的意思是,Python 中的相同解决方案会更短,而且我敢说更干净。如果您有疑问,请访问leetcode.com[1]或任何其他类似站点,并浏览解决方案。尝试比较 C# 和 Python 中同一问题的解决方案。Python 的看起来不是更……漂亮吗?再说一次,也许是因为栅栏另一边的草总是很茂盛。
我学到的与 C# 无关的东西
你可以从求职面试中学到很多东西。既是面试官,又是被面试者。我在整个过程中学到的最重要的东西与 C# 或任何其他语言无关。由于这篇文章是关于编码挑战的,我将坚持我从这些方面学到的东西。以下是我学到的一些技巧和窍门,可以帮助我解决编码问题:
熟能生巧
面试的准备是一切的不同之处。是的,这需要很多时间,但考虑到风险,这是非常值得的。从严格的财务角度来看,在薪水高、资历高的公司工作,是您一生中最重要的财务改善之一。花几天或几周的时间为它们做准备绝对值得。
对于编码问题,我使用了leetcode.com[2],这是解决此类[3]问题的最佳网站之一。我建议从简单的问题开始,花最多的时间在中等,并尝试一些困难的问题。该站点的最佳功能之一是您可以在每个问题的讨论部分看到其他提交的内容。该网站非常受欢迎,所以总是有各种语言的提交。我建议您先尝试自己解决问题,然后再查看其他提交的内容(即使您成功了)。我当然从中学到了很多。
大多数问题都很简单
如果您浏览leetcode.com 之[4]类的网站[5],您可能会认为 Facebook 或 Apple 等顶级公司会问您非常棘手的[6]问题。根据我的经验,这不是真的。大多数问题都在中等难度范围内。不太容易,但也不难,绝对不难。许多问题都是递归 DFS 或 BFS 的经典实现。或者Bin 装箱问题[7]的变体。有时您需要遍历二叉树。在极少数情况下,您可能需要使用堆。
但是你也可能在简单和中等的问题上失败。相信我,我知道。面试压力很大。您必须非常迅速地提出解决方案。有时面试官会把你引向错误的方向(无论是有意还是无意)。其他时候,您可能会记住某些事情或分心,这会使您偏离正确(且简单)的解决方案。
我学会了始终牢记这个问题可能并不棘手,但很容易。这大多是真实的,思考可以减轻一些压力。
写之前想一想
在实际编写解决方案之前,请多想一想。它是最优化的吗?你能达到更好的时间复杂度吗?代码是否会简短易懂?问问自己是否可以改进解决方案。
通常,面试官会在实际编写之前要求您描述您的解决方案。这对你来说很棒。即使他们不问,我也建议你自己解释一下。解释完之后,写代码就容易多了。此外,您经常会在解决方案中发现一些问题。这也是与面试官建立联系的好机会。你可以向他们展示你的思维过程,证明你是一个聪明的人,很容易相处。
使用简称
在软件开发中,我们非常习惯于描述性的名称。所以我会有一个方法GetAddressandCalculateFastestRoute而不是Getand Calc。在编码问题中,恰恰相反的是要走的路。至少这对我有用。坚持为变量名称和方法使用短名称。如果你觉得你的面试官是一个固执的人,你可以在你的解决方案奏效后给他们重命名,或者只是提到你在这次面试中使用了简称,而不是常规。
我不是说要命名您的方法A和B. 但我是说,如果您的问题是遍历迷宫,并且您使用的是深度优先搜索算法,请随意命名该方法DFS而不是FindAWayInAMaze.
总结
一家大企业面试是一种迷人的经历。这与为初创公司甚至大型科技公司接受面试完全不同。公司之间的面试有很大的不同,你可以了解一些公司文化。
我在这里谈到了编码问题,但这只是面试的一部分。大多数公司也有某种“系统设计”面试,你必须架构一个大型(通常是分布式)系统。面试过程的另一部分是关于你在组织中的行为、态度和行为。
你是团队成员吗?您是否总是为客户着想?你能独立工作吗?这些可能与技术问题一样重要。
References
[1] leetcode.com: https://leetcode.com/
[2] leetcode.com: https://leetcode.com/
[3] 此类: https://leetcode.com/
[4] leetcode.com 之: https://leetcode.com/
[5] 网站: https://leetcode.com/
[6] 棘手的: https://leetcode.com/
[7] Bin 装箱问题: https://en.wikipedia.org/wiki/Bin_packing_problem