第三章 高质量的代码

3.1 面试官谈代码质量
  • 不能容忍代码’正常值’进行处理,不考虑异常状况。
  • 功能错误、边界情况。
  • 命名规范、适合的数据结构。
  • 正确性和鲁棒性。
  • 由于精度原因不能直接用==判断两个小数是否相等。
    • if(Math.abs(a-b)<=0){相等}
 
3.2 代码的规范性

 

  • 首先,规范的代码书写清晰。绝大部分面试都是要求应聘者在白纸或者白板上书写。由于现代人已经习惯了敲键盘打字,手写变得越来越不习惯,因此写出来的字潦草难辨。虽然应聘者没有必要为了面试特意去练字,但在面试过程中减慢写字的速度,尽量把每个字母写清楚还是很有必要的。不用担心没有时间去写代码,通常编程面试的代码量都不会超过50行,书写不用花多少时间,关键是在写代码之前形成清晰的思路并能把思路用编程语言清楚地书写出来。
  • 其次,规范的代码布局清晰。平时程序员在集成开发环境如Visual Studio里面写代码,依靠专业工具调整代码的布局,加入合理的缩进并让括号对齐成对呈现。离开了这些工具手写代码,我们就要格外注意布局问题。当循环、判断较多,逻辑较复杂时,缩进的层次可能会比较多。如果布局不够清晰,缩进也不能体现代码的逻辑,面试官面对这样的代码将会头晕脑胀。
  • 最后,规范的代码命名合理。很多初学编程的人在写代码时总是习惯用最简单的名字来命名,变量名是i、j、k,函数名是f、g、h。由于这样的名字不能告诉读者对应的变量或者函数的意义,代码一长就会变得晦涩难懂。强烈建议应聘者在写代码的时候,用完整的英文单词组合命名变量和函数,比如函数需要传入一个二叉树的根结点作为参数,则可以把该参数
  • 面试小提示:应聘者在写代码的时候,最好用完整的英文单词组合命名变量和函数,以便面试官能一眼读懂代码的意图。
 
3.3 代码的完整性
1. 从3方面确保代码的完整性
    

 

  • 首先要考虑的是普通功能测试的测试用例。我们首先要保证写出的代码能够完成面试官要求的基本功能。比如面试题要求完成的功能是把字符串转换成整数,我们就可以考虑输入字符串”123"来测试自己写的代码。这里要把零、正数和负数都考虑进去。
  • 考虑功能测试的时候,我们要尽量突破常规思维的限制。面试的时候我们经常受到惯性思维的限制,从而看不到更多的功能需求。比如面试题12“打印1到最大的n位数”,很多人觉得这题很简单。最大的3位数是999、最大的4位数是9999,这些数字很容易就能算出来。但是最大的n位数都能用int型表示吗?超出int的范围我们可以考虑long long类型,超出long long能够表示的范围呢?面试官是不是要求考虑任意大的数字?如果面试官确认题目要求的是任意大的数字,那么这个题目就是一个大数问题,此时我们需要特殊的数据结构来表示数字,比如用字符串或者数组来表示大的数字,以确保不会溢出。
  • 其次需要考虑各种边界值的测试用例。很多时候我们的代码中都会有循环或者递归。如果我们的代码是基于循环,那么结束循环的边界条件是否正确?如果是递归,递归终止的边界值是否正确?这些都是边界测试时要考虑的用例。还是以字符串转换成整数的问题为例,我们写出的代码应该确保能够正确转换最大的正整数和最小的负整数。
  • 最后还需要考虑各种可能的错误的输入,也就是通常所说的负面测试的测试用例。我们写出的函数除了要顺利地完成要求的功能之外,当输入不符合要求的时候还能做出合理的错误处理。在设计把字符串转换成整数的函数的时候,我们就要考虑当输入的字符串不是一个数字,比如"1a2b3c",我们怎么告诉函数的调用者这个输入是非法的。
2. 3种错误处理的方法
  • 第一种方式是函数用返回值来告诉调用者是否出现错误。
  • 第二种方式是当发生错误时设置一个全局变量。
  • 第三种方式是异常。
 
  优点 缺点
返回值 和系统API一致 不能方便地使用计算结果
全局变量 能够方便地使用计算结果 用户可能会忘记检查全局变量
异常 可以为不同的出错原因定义不同异常类型,结构清晰明了 有些语言不支持异常,抛出异常时及性能有负面影响
面试题11:数值的整数次方
  • 题目:实现函数doublePower(double basement ,int exponent),求 base的exponent次方。不得使用类库函数,同时不需要考虑大数问题。
  • 思路:不能用==比较两个浮点数是否相等,因为有误差。考虑输入值的多种情况。
  • 代码实现
    • public class Testc {
    • public static void main(String[] args) {
    • Testc t = new Testc();
    • System.out.println(t.equal(1.0000001, 1.0000001));
    • }
    • public double Power(double base, int exponent) {
    • double res = 0;
    • if (equal(base, 0)) {
    • return 0;
    • }
    • if (exponent == 0) {
    • return 1.0;
    • }
    • if (exponent > 0) {
    • res = mutiply(base, exponent);
    • } else {
    • res = mutiply(1 / base, -exponent);
    • }
    • return res;
    • }
    • public double mutiply(double base, int e) {
    • double sum = 1;
    • for (int i = 0; i < e; i++) {
    • sum = sum * base;
    • }
    • return sum;
    • }
    • public boolean equal(double a, double b) {
    • if (Math.abs(a - b) < 0.0000001) {
    • return true;
    • }
    • return false;
    • }
    • }
  • 面试小提示:
  • 由于计算机表示小数(包括float和double型小数)都有误差,我们不能直接用等号(=)判断两个小数是否相等。如果两个小数的差的绝对值很小,比如小于0.0000001,就可以认为它们相等。
  • 测试用例:
    • 把底数和指数分别设为正数、负数和零。
  • 本题考点:
    • 考查思维的全面性。这个问题本身不难,但能顺利通过的应聘者不是很多。有很多人会忽视底数为0而指数为负数时的错误处理。
    • 对效率要求比较高的面试官还会考查应聘者快速做乘方的能力。
面试题12:打印1到最大的n位数
  • 题目:输入数字n,按照顺序打印出从1到最大的n位十进制数。比如输入3,则打印出1、2、3一直到最大的3位数即999.
  • 思路:考虑大数问题,使用字符串或数组表示。
  • 代码实现
    • public class Testc {
    • public static void main(String[] args) {
    • Testc t = new Testc();
    • t.printToMaxOfNDigits(2);
    • }
    • public void printToMaxOfNDigits(int n) {
    • int[] array = new int[n];
    • if (n <= 0)
    • return;
    • printArray(array, 0);
    • }
    • private void printArray(int[] array, int n) {
    • for (int i = 0; i < 10; i++) {
    • if (n != array.length) {
    • array[n] = i;
    • printArray(array, n + 1);
    • } else {
    • boolean isFirstNo0 = false;
    • for (int j = 0; j < array.length; j++) {
    • if (array[j] != 0) {
    • System.out.print(array[j]);
    • if (!isFirstNo0)
    • isFirstNo0 = true;
    • } else {
    • if (isFirstNo0)
    • System.out.print(array[j]);
    • }
    • }
    • System.out.println();
    • return;
    • }
    • }
    • }
    • }
  • 测试用例:
    • 功能测试(输入1、2、3......
    • 特殊输入测试(输入-1、0)。
  • 本题考点:
    • 考查解决大数问题的能力。面试官出这个题目的时候,他期望应聘者能意识到这是一个大数问题,同时还期待应聘者能定义合适的数据表示方式来解决大数问题。
    • 如果应聘者采用第一种思路即在数.上加1逐个打印的思路,面试官会关注他判断是否已经到了最大的n位数时采用的方法。应聘者要注意到不同方法的时间效率相差很大。
    • 如果应聘者采用第二种思路,面试官还将考查他用递归方法解决问题的能力。
    • 面试官还将关注应聘者打印数字时会不会打印出位于数字最前面的0。这里能体现出应聘者在设计开发软件时是不是会考虑用户的使用习惯。通常我们的软件设计和开发需要符合大部分用户的人机交互习惯。
  • 本题扩展:
  • 在前面的代码中,我们都是用一一个char型字符表示十进制数字的一位。8个bit的char型字符最多能表示256个字符,而十进制数字只有0~9的10个数字。因此用char型字符串来表示十进制的数字并没有充分利用内存,有一些浪费。有没有更高效的方式来表示大数?
  • 相关题目:
  • 定义一个函数,在该函数中可以实现任意两个整数的加法。由于没有限定输入两个数的大小范围,我们也要把它当做大数问题来处理。在前面的代码的第一-一个思路中,实现了在字符串表示的数字上加1的功能,我们可以参考这个思路实现两个数字的相加功能。另外还有一“个需要注意的问题:如果输入的数字中有负数,我们应该怎么去处理?
  • 面试小提示:
  • 如果面试题是关于n位的整数并且没有限定n的取值范围,或者是输入任意大小的整数,那么这个题目很有可能是需要考虑大数问题的。字符串是一个简单、有效的表示大数的方法。
面试题13:在O(1)时间删除链表结点
  • 题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。
  • 思路:将要删除节点的下一个节点的值赋给要删除的节点,然后指向下下一个节点
  • 代码实现
    • public void deleteNode(ListNode head, ListNode deListNode) {
    • if (deListNode == null || head == null)
    • return;
    • if (head == deListNode) {
    • head = null;
    • } else {
    • // 若删除节点是末尾节点,往后移一个
    • if (deListNode.nextNode == null) {
    • ListNode pointListNode = head;
    • while (pointListNode.nextNode.nextNode != null) {
    • pointListNode = pointListNode.nextNode;
    • }
    • pointListNode.nextNode = null;
    • } else {
    • deListNode.data = deListNode.nextNode.data;
    • deListNode.nextNode = deListNode.nextNode.nextNode;
    • }
    • }
    • }
  • 测试用例:
    • 功能测试(从有多个结点的链表的中间删除一个结点, 从有多个结点的链表中删除头结点,从有多个结点的链表中删除尾结点,从只有一个结点的链表中删除唯一的结点)。
    • 特殊输入测试(指向链表头结点指针的为NULL指针,指向要删除的结点为NULL指针)。
  • 本题考点:
    • 考查应聘者对链表的编程能力。
    • 考查应聘者的创新思维能力。这道题要求应聘者打破常规的思维模式。当我们想删除一个结点时, 并不一定要删除这个结点本身。可以先把下一个结点的内容复制出来覆盖被删除结点的内容,然后把下一个结点删除。这种思路不是很容易想到的。
    • 考查应聘者思维的全面性。即使应聘者想到删除下一个结点这个办法,也未必能通过这轮面试。应聘者要全面考虑到删除的结点位于链表的尾部及输入的链表只有一个结点这些特殊情况。
面试题14:调整数组顺序使奇数位于偶数前面
  • 题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
  • 思路:每次只和前一个交换或利用辅助数组。
  • 代码实现
    • public class Testc {
    • public static void main(String[] args) {
    • int[] array = new int[] { 1, 2, 3, 4, 5, 6 };
    • Testc t = new Testc();
    • t.reOrderArray(array);
    • for (int i = 0; i < array.length; i++) {
    • System.out.println(array[i]);
    • }
    • }
    • public void reOrderArray(int[] array) {
    • if (array == null)
    • return;
    • for (int i = 1; i < array.length; i++) {
    • int j = i - 1;
    • if (array[i] % 2 != 0) {
    • while (j >= 0) {
    • if (array[j] % 2 != 0) {
    • break;
    • }
    • if (array[j] % 2 == 0) {
    • int t = array[j + 1];
    • array[j + 1] = array[j];
    • array[j] = t;
    • j--;
    • }
    • }
    • }
    • }
    • }
    • }
  • 思路前后交换
  • 代码实现
    • public class Testc {
    • public static void main(String[] args) {
    • int[] array = new int[] {1,2,3,4,5,6};
    • Testc t = new Testc();
    • t.reOrderArray(array);
    • for (int i = 0; i < array.length; i++) {
    • System.out.println(array[i]);
    • }
    • }
    • public void reOrderArray(int[] array) {
    • if (array == null)
    • return;
    • int i = 0;
    • int j = array.length - 1;
    • while(i<j){
    • System.out.println(i+" "+j);
    • for(;i<j;i++){
    • if(array[i]%2 == 0){
    • break;
    • }
    • }
    • for(;i<j;j--){
    • if(array[j]%2 == 1){
    • break;
    • }
    • }
    • System.out.println(i+j);
    • int temp = array[i];
    • array[i] = array[j];
    • array[j] = temp;
    • }
    • }
    • }
  • 测试用例:
    • 功能测试(输入数组中的奇数、偶数交替出现,输入的数组中所有偶数都出现在奇数的前面,输入的数组中所有奇数都出现在偶数的前面)。
    • 特殊输入测试(输入NULL指针、输入的数组只包含一个数字)。
  • 本题考点:
    • 考查应聘者的快速思维能力。要在短时间内按照要求把数组分隔成两部分,不是一件容易的事情,需要较快的思维能力。
    • 对于已经工作过几年的应聘者,面试官还将考查其对扩展性的理解,要求应聘者写出的代码具有可重用性。
 
3.4 代码的鲁棒性
    由于鲁棒性对软件开发非常重要,面试官在招聘的时候对应聘者写出的代码是否鲁棒也非常关注。提高代码的鲁棒性的有效途径是进行防御性编程。防御性编程是一种编程习惯,是指预见在什么地方可能会出现问题,并为这些可能出现的问题制定处理方式。比如试图打开文件时发现文件不存在,我们可以提示用户检查文件名和路径;当服务器连接不上时,我们可以试图连接备用服务器等。这样当异常情况发生时,软件的行为也尽在我们的掌握之中,而不至于出现不可预见的事情。
    在面试时,最简单也最实用的防御性编程就是在函数入口添加代码以验证用户输入是否符合要求。通常面试要求的是写一两个函数,我们需要格外关注这些函数的输入参数。如果输入的是一个指针,那指针是空指针怎么办?如果输入的是一个字符串,那么字符串的内容为空怎么办?如果能把这些问题都提前考虑到,并做相应的处理,那么面试官就会觉得我们有防御性编程的习惯,能够写出鲁棒的软件。
 
面试题15:链表中倒数第k个节点
  • 题目:输入一个链表,输出该链表中倒数第k个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是值为4的结点。
  • 思想:定义一快一慢两个指针,快指针走K步,然后慢指针开始走,快指针到尾时,慢指针就找到了倒数第K个 节点。
    • 代码实现
      • public class Testc {
      • public static void main(String[] args) {
      • int[] array = new int[] { 1, 2, 3, 4, 5, 6 };
      • Testc t = new Testc();
      • ListNode head = t.create(array);
      • System.out.println(t.FindKthToTail(head, 2).values);
      • }
      • public ListNode create(int[] arr) {
      • if(arr==null || arr.length==0)
      • return null;
      • ListNode head = new ListNode();
      • ListNode w = head;
      • for (int i = 0; i < arr.length; i++) {
      • if(i != 0){
      • w.next = new ListNode();
      • w = w.next;
      • }
      • w.values = arr[i];
      • }
      • return head;
      • }
      • public ListNode FindKthToTail(ListNode head, int k) {
      • if (head == null || k <= 0) {
      • return null;
      • }
      • ListNode fast = head;
      • ListNode slow = head;
      • while (k-- > 1) {
      • if (fast.next != null)
      • fast = fast.next;
      • else
      • return null;
      • }
      • while (fast.next != null) {
      • fast = fast.next;
      • slow = slow.next;
      • }
      • return slow;
      • }
      • }
      • class ListNode {
      • public ListNode next;
      • public int values;
      • }
  • 测试用例:
    • 功能测试(第k个结点在链表的中间,第k个结点是链表的头结点,第k个结点是链表的尾结点)。
    • 特殊输入测试(链表头结点为NULL指针,链表的结点总数少于k,k等于0)。
  • 本题考点:
    • 考查对链表的理解。
    • 考查代码的鲁棒性。鲁棒性是解决这道题的关键所在。如果应聘者写出的代码有着多处崩溃的潜在风险,那么他是很难通过这轮面试的。
  • 相关题目:
    • 求链表的中间结点。如果链表中结点总数为奇数,返回中间结点;如果结点总数是偶数,返回中间两个结点的任意一个。为了解决这个问题,我们也可以定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。当走得快的指针走到链表的末尾时,走得慢的指针正好在链表的中间。
    • 判断一个单向链表是否形成了环形结构。和前面的问题一样,定义两个指针,同时从链表的头结点出发,一个指针一 次走一步, 另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环形链表;如果走得快的指针走到了链表的末尾(m_ pNext 指向NULL)都没有追上第一个指针,那么链表就不是环形链表。
  • 举一反三:
    • 当我们用一个指针遍历链表不能解决问题的时候,可以尝试用两个指针来遍历链表。可以让其中一个指针遍历的速度快一一些(比如一次在链表上走两步),或者让它先在链表上走若干步。
 
面试题16:反转链表
  • 题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
  • 思路:定义两个指针,反向输出
  • 实现代码
    • public class Testc {
    • public static void main(String[] args) {
    • int[] array = new int[] { 1, 2, 3, 4, 5, 6 };
    • Testc t = new Testc();
    • ListNode head = t.create(array);
    • head = t.ReverseList(head);
    • while (head != null) {
    • System.out.println(head.values);
    • head = head.next;
    • }
    • }
    • public ListNode create(int[] arr) {
    • if (arr == null || arr.length == 0)
    • return null;
    • ListNode head = new ListNode();
    • ListNode w = head;
    • for (int i = 0; i < arr.length; i++) {
    • if (i != 0) {
    • w.next = new ListNode();
    • w = w.next;
    • }
    • w.values = arr[i];
    • }
    • return head;
    • }
    • public ListNode ReverseList(ListNode head) {
    • if (head == null) {
    • return null;
    • }
    • ListNode temp = null;
    • while (head != null) {
    • ListNode p = head.next;
    • head.next = temp;
    • temp = head;
    • head = p;
    • }
    • return temp;
    • }
    • }
    • class ListNode {
    • public ListNode next;
    • public int values;
    • }
  • 测试用例:
    • 功能测试(输入的链表含有多个结点,链表中只有-一个结点)。
    • 特殊输入测试(链表头结点为NULL指针)。
  • 本题考点:
    • 考查应聘者对链表、指针的编程能力。
    • 特别注重考查应聘者思维的全面性及写出来的代码的鲁棒性。
面试题17:合并两个排序的链表
  • 题目:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。例如输入图3.7中的链表1和链表2,则合并之后的升序链表如链表3所示。
  • 思路递归求解
  • 代码实现
    • public class Testc {
    • public static void main(String[] args) {
    • int[] array1 = new int[] { 1, 3, 5, 7 };
    • int[] array2 = new int[] { 2, 4, 6, 8 };
    • Testc t = new Testc();
    • ListNode head1 = t.create(array1);
    • ListNode head2 = t.create(array2);
    • ListNode head = t.Merge(head1,head2);
    • while (head != null) {
    • System.out.println(head.values);
    • head = head.next;
    • }
    • }
    • public ListNode create(int[] arr) {
    • if (arr == null || arr.length == 0)
    • return null;
    • ListNode head = new ListNode();
    • ListNode w = head;
    • for (int i = 0; i < arr.length; i++) {
    • if (i != 0) {
    • w.next = new ListNode();
    • w = w.next;
    • }
    • w.values = arr[i];
    • }
    • return head;
    • }
    • public ListNode Merge(ListNode list1, ListNode list2) {
    • if (list1 == null) {
    • return list2;
    • }
    • if (list2 == null) {
    • return list1;
    • }
    • ListNode newHead = null;
    • if (list1.values <= list2.values) {
    • newHead = list1;
    • newHead.next = Merge(list1.next, list2);
    • } else {
    • newHead = list2;
    • newHead.next = Merge(list1, list2.next);
    • }
    • return newHead;
    • }
    • }
    • class ListNode {
    • public ListNode next;
    • public int values;
    • }
  • 思想:非递归
  • 代码实现
    • public class Testc {
    • public static void main(String[] args) {
    • int[] array1 = new int[] { 1,3,5,7,9 };
    • int[] array2 = new int[] { 2,4,5 };
    • Testc t = new Testc();
    • ListNode head1 = t.create(array1);
    • ListNode head2 = t.create(array2);
    • ListNode head = t.Merge(head1,head2);
    • while (head != null) {
    • System.out.println(head.values);
    • head = head.next;
    • }
    • }
    • public ListNode create(int[] arr) {
    • if (arr == null || arr.length == 0)
    • return null;
    • ListNode head = new ListNode();
    • ListNode w = head;
    • for (int i = 0; i < arr.length; i++) {
    • if (i != 0) {
    • w.next = new ListNode();
    • w = w.next;
    • }
    • w.values = arr[i];
    • }
    • return head;
    • }
    • public ListNode Merge(ListNode list1, ListNode list2) {
    • if (list1 == null) {
    • return list2;
    • }
    • if (list2 == null) {
    • return list1;
    • }
    • ListNode newHead = null;
    • ListNode node = null;
    • if( list1.values<=list2.values){
    • newHead = list1;
    • list1 = list1.next;
    • }else{
    • newHead = list2;
    • list2 = list2.next;
    • }
    • node = newHead;
    • while(list1 != null && list2 != null){
    • if (list1.values <= list2.values) {
    • node.next = list1;
    • list1 = list1.next;
    • node = node.next;
    • } else {
    • node.next = list2;
    • list2 = list2.next;
    • node = node.next;
    • }
    • }
    • if(list1 == null){
    • node.next = list2;
    • }else{
    • node.next = list1;
    • }
    • return newHead;
    • }
    • }
    • class ListNode {
    • public ListNode next;
    • public int values;
    • }
  • 测试用例:
    • 功能测试(输入的两个链表有多个结点,结点的值互不相同或者存在值相等的多个结点)。
    • 特殊输入测试(两个链表的一一个或者两个头结点为NULL指针、两个链表中只有一个结点)。
  • 本题考点:
    • 考查应聘者分析问题的能力。解决这个问题需要大量的指针操作,应聘者如果没有透彻地分析问题形成清晰的思路,那么他很难写出正确的代码。
    • 考查应聘者能不能写出鲁棒的代码。由于有大量指针操作,应聘者如果稍有不慎就会在代码中遗留很多与鲁棒性相关的隐患。建议应聘者在写代码之前全面分析哪些情况会引入空指针,并考虑清楚怎么处理这些空指针。
 
面试题18:树的子结构
  • 题目:输入两棵二叉树A和B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
  • 思路:若根节点相等,利用递归比较他们的子树是否相等,若根节点不相等,则利用递归分别在左右子树中查找。
    

 

  • 代码实现
    • public class TestTree {
    • public static int n;
    • public static void main(String[] args) {
    • int[] array1 = new int[] { 11, 22, 33, 0, 0, 0, 44, 0, 55, 0, 0 };
    • int[] array2 = new int[] { 11, 22, 0, 0 ,55,0,0 };
    • TreeNode root1 = CreateTreeBinary(new TreeNode(0), array1);
    • n=0;
    • TreeNode root2 = CreateTreeBinary(new TreeNode(0), array2);
    • System.out.println(HasSubtree(root1,root2));
    • }
    • public static void PrintTreeBinary(TreeNode treeNode) {
    • if (treeNode != null) {
    • System.err.println(treeNode.val);
    • PrintTreeBinary(treeNode.left);
    • PrintTreeBinary(treeNode.right);
    • }
    • }
    • public static TreeNode CreateTreeBinary(TreeNode treeNode, int[] array) {
    • if (array[n] == 0) {
    • n++;
    • return null;
    • } else {
    • treeNode.val = array[n++];
    • treeNode.left = CreateTreeBinary(new TreeNode(0), array);
    • treeNode.right = CreateTreeBinary(new TreeNode(0), array);
    • return treeNode;
    • }
    • }
    • public static boolean HasSubtree(TreeNode root1, TreeNode root2) {
    • boolean result = false;
    • if (root2 != null && root1 != null) {
    • if (root1.val == root2.val) {
    • result = doesTree1HaveTree(root1, root2);
    • }
    • if (!result)
    • return HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
    • }
    • return result;
    • }
    • public static boolean doesTree1HaveTree(TreeNode node1, TreeNode node2) {
    • if (node2 == null) {
    • return true;
    • }
    • if (node1 == null) {
    • return false;
    • }
    • if (node1.val != node2.val) {
    • return false;
    • }
    • return doesTree1HaveTree(node1.left, node2.left) && doesTree1HaveTree(node1.right, node2.right);
    • }
    • }
  • 面试小提示:
  • 二叉树相关的代码有大量的指针操作,每一次使用指针的时候,我们都要问自己这个指针有没有可能是NULL,如果是NULL该怎么处理。
  • 测试用例:
    • 功能测试(树A和树B都是普通的二叉树,树B是或者不是树A的子结构)。
    • 特殊输入测试(两棵二叉树的一个或者两个根结点为NULL指针、二叉树的所有结点都没有左子树或者右子树)。
  • 本题考点:
    • 考查对二叉树遍历算法的理解及递归编程能力。
    • 考查代码的鲁棒性。本题的代码中含有大量的指针操作,稍有不慎程序就会崩溃。应聘者需要采用防御性编程的方式,每次访问指针地址之前都要考虑这个指针有没有可能是NULL。
 
3.5 本章小结
                            

 

  • 大多数面试都是要求应聘者在白纸或者白板,上写代码。应聘者在编码的时候要注意规范性,尽量清晰地书写每个字母,通过缩进和对齐括号让代码布局合理,同时合理命名代码中的变量和函数。
  • 最好在编码之前全面考虑所有可能的输入,确保写出的代码在完成了基本功能之外,还考虑了边界条件,并做好了错误处理。只有全面考虑到这3方面的代码才是完整的代码。
  • 另外,要确保自己写出的程序不会轻易崩溃。平时在写代码的时候,应聘者最好养成防御式编程的习惯,在函数入口判断输入是否有效并对各种无效输入做好相应的处理。
 
 
 
 
 
 
 
 
 
posted @ 2018-07-18 08:38  wwxxaa  阅读(338)  评论(0编辑  收藏  举报