記一次失敗的面試經歷
昨天晚上跟一家公司進行遠端視訊二面,面試官上來先扔過來一道程式設計題,要求在文件中手擼程式碼,不能用IDE編碼。
一開始沒想好,思路有問題,做到最後發現行不通,於是掛了。。。。。
第二天到公司靜下心來整理了一下思路,寫出來了。 做下記錄,算是給自己提個醒,以後遇事要沉著冷靜~~~~~
題目如下:
首先,觀察題目。 第一個圖代表的是二叉樹, 第二個圖代表的是那種某個節點的左子樹指向右子樹的情況,第三個圖代表了某個節點的子節點指向父節點或者指向祖輩節點的情況
注意我標紅的部分,這三個圖要表達的是它們各自代表一類情況,而非僅僅是圖中的這一種情形,所以你的程式邏輯要考慮周全
我們先來分析,按照二叉樹的先序遍歷規則(根->左->右), 圖一的遍歷順序應該是 ABDECF,圖二的遍歷規則應該是(假如也按照二叉樹規則來遍歷)ABDCFCF,圖三的遍歷順序是ABDBDBDBD......
通過以上分析可以知道,圖一的各個節點只會遍歷一次; 圖二的某些節點,會被遍歷兩次; 而圖三的某幾個節點,會形成一個死迴圈,一直遍歷下去。。。
那麼,這道題的答案就呼之欲出了:
1. 寫一個遞迴程式,從A節點開始,把樹遍歷一次。
2. 遍歷過程中,記錄每個節點經過的次數
3. 如果各個節點只經歷了一次,那就是tree;如果存在經歷了兩次的節點,並且沒有經歷三次的節點,就是no-tree; 如果存在經歷了三次的節點,就是circle.
4. 注意,圖三的情況特殊,因為是個死迴圈,要考慮何時退出遞迴函式、如何才能正常的退出遞迴函式,遞迴函式的退出方法,是個坑,不小心就會出錯
程式碼如下:
1.定義TreeNode型別
public class TreeNode { /// <summary> /// 節點的唯一ID,不重複 /// </summary> public int nodeId { get; set; } /// <summary> /// 節點的名稱,可能會出現重名,需要注意 /// </summary> public string nodeName { get; set; } /// <summary> ///當前節點的子節點 /// </summary> public List<TreeNode> nextNodes { get; set; } }
2. 定義TreeData型別
public enum TreeType { Tree, NoTree, Circle } //根據傳入的型別,構造樹結構的資料 public class TreeData { public TreeNode root { get; } public TreeData(TreeType treeType) { if (treeType == TreeType.Tree) { TreeNode nodeD = new TreeNode(); nodeD.nodeId = 4; nodeD.nodeName = "節點D"; nodeD.nextNodes = null; TreeNode nodeE = new TreeNode(); nodeE.nodeId = 5; nodeE.nodeName = "節點E"; nodeE.nextNodes = null; TreeNode nodeF = new TreeNode(); nodeF.nodeId = 6; nodeF.nodeName = "節點F"; nodeF.nextNodes = null; TreeNode nodeC = new TreeNode(); nodeC.nodeName = "節點C"; nodeC.nodeId = 3; nodeC.nextNodes = new List<TreeNode> { nodeF }; TreeNode nodeB = new TreeNode(); nodeB.nodeId = 2; nodeB.nodeName = "節點B"; nodeB.nextNodes = new List<TreeNode> { nodeD, nodeE }; TreeNode nodeA = new TreeNode(); nodeA.nodeId = 1; nodeA.nodeName = "節點A"; nodeA.nextNodes = new List<TreeNode> { nodeB, nodeC }; root = nodeA; } else if (treeType == TreeType.NoTree) { TreeNode nodeD = new TreeNode(); nodeD.nodeId = 4; nodeD.nodeName = "節點D"; nodeD.nextNodes = null; TreeNode nodeE = new TreeNode(); nodeE.nodeId = 5; nodeE.nodeName = "節點E"; nodeE.nextNodes = null; TreeNode nodeF = new TreeNode(); nodeF.nodeId = 6; nodeF.nodeName = "節點F"; nodeF.nextNodes = null; TreeNode nodeC = new TreeNode(); nodeC.nodeId = 3; nodeC.nodeName = "節點C"; nodeC.nextNodes = new List<TreeNode> { nodeF }; TreeNode nodeB = new TreeNode(); nodeB.nodeName = "節點B"; nodeB.nodeId = 2; nodeB.nextNodes = new List<TreeNode> { nodeD, nodeC }; TreeNode nodeA = new TreeNode(); nodeA.nodeId = 1; nodeA.nodeName = "節點A"; nodeA.nextNodes = new List<TreeNode> { nodeB, nodeC }; root = nodeA; } else { TreeNode nodeD = new TreeNode(); TreeNode nodeB = new TreeNode(); nodeD.nodeId = 4; nodeD.nodeName = "節點D"; nodeD.nextNodes = new List<TreeNode> { nodeB }; nodeB.nodeName = "節點B"; nodeB.nodeId = 2; nodeB.nextNodes = new List<TreeNode> { nodeD }; TreeNode nodeE = new TreeNode(); nodeE.nodeId = 5; nodeE.nodeName = "節點E"; nodeE.nextNodes = null; TreeNode nodeF = new TreeNode(); nodeF.nodeId = 6; nodeF.nodeName = "節點F"; nodeF.nextNodes = null; TreeNode nodeC = new TreeNode(); nodeC.nodeId = 3; nodeC.nodeName = "節點C"; nodeC.nextNodes = new List<TreeNode> { nodeF }; TreeNode nodeA = new TreeNode(); nodeA.nodeId = 1; nodeA.nodeName = "節點A"; nodeA.nextNodes = new List<TreeNode> { nodeB, nodeC }; root = nodeA; } } }
3. 在Program.cs中定義遞迴函式
public static void JudgeTreeType(TreeNode root) { //需要退出遞迴時,修改標誌位的狀態,遞迴會層層退出 if (needStop) { return; } else { if (root != null && root.nextNodes != null) { if (logDic.Keys.Contains(root.nodeId)) { //如果節點root存在,累加一 logDic[root.nodeId] = logDic[root.nodeId] + 1; } else { //如果不存在,新增到集合中 logDic.Add(root.nodeId, 1); } //判定是否存在某個節點出現過次數3的情況 var hasThree = logDic.ContainsValue(3); if (hasThree) { needStop = true; } foreach (var node in root.nextNodes) { JudgeTreeType(node); } } } }
4. 定義變數,記錄每個節點經過的次數
//定義資料集,記錄每個node被經過的次數 static Dictionary<int, int> logDic = new Dictionary<int, int>(); //注意,遞迴函式是層層呼叫的,想要在某一層讓整個遞迴程式退出迴圈 //單純用return方法是行不通的,return只是跳出了當前層的迴圈 //想要退出遞迴,需要定義一個全域性變數,通過變數控制層層退出 static bool needStop = false;
5. main函式呼叫
static void Main(string[] args) { //構造root節點資料 JudgeTreeType(new TreeData(TreeType.Tree).root); //判定樹是哪種樹 var hasThree = logDic.ContainsValue(3); var hasTwo = logDic.ContainsValue(2); if (hasThree) { Console.WriteLine("circle"); } else if (!hasThree && hasTwo) { Console.WriteLine("no-tree"); } else { Console.WriteLine("tree"); } Console.ReadLine(); }
其實整個程式很簡單,需要特別注意的是遞迴函式的退出方法。
僅僅在某一層遞迴迴圈中去return是無法真正退出整個遞迴的,它只是退出了本層遞迴。
想要退出整個遞迴,需要定義一個全域性變數,通過它來控制後續遞迴的執行!!!
另外一個難點就是不讓用IDE進行編寫,必須在文件上手寫程式碼,這對程式設計習慣和編碼能力是個考驗
原始碼附上: