对面试题(剑指offer)产生的一些思考。

时间:2022-03-25 14:16:58

零散的思绪。另外,推荐《剑指offer》。本文初期大部分思考都从剑指引发。

面试题不单单只是用来面试。其中有很多编程的经验可以学习。就如同我们当年的考试:)

1:鲁棒性的一个方面:边界条件和异常处理。

  1,鲁棒性:robust的中文音译。本身就是健全性。

  2,面试中,经常出现一些简单的代码。考验的就是一个程序员对边界条件和异常处理的考虑。

    例如:字符串转化为整数。本身题目非常简单。但,我们应该能够考虑到异常输入有:英文/未知符号,负数,空指针,超大数值。

    另外,需要明确题目。如果面试官的要求是:字符串转化为数字。需要问清楚,整数,还是浮点?。。。当然,也可能弄巧成拙,本来面试官只是想整数,但你既然问了,那就成全你吧:(

    如果是浮点,我们可能还要考虑,精度缺失的问题。我们就要考虑double的精度。

  3,对于链表,边界中最不容易考虑的应该是:链表项数。

    例如:查找倒数K项,但实际链表项数 < K。

2:对数组操作的一些胡思乱想。

  1,C++数组最麻烦的一点是下标超出。我们当然可以用vector解决。但面试中,常常用这种事情来玩你。。。

  2,我对数组的遍历,经常是for循环+数组个数。数组各数通常使用宏定义。但,这并不能解决超出问题。比如:把14打成15这种低级错误。所以,是否能够有使用一种方式解决此问题?下面是我对此想法的一个尝试。

/**********************************************************
* \file array_t.cpp
* \brief
* \note
*
* \version
* \author zheng39562@163.com
***********************************************************/
#include <iostream> using namespace std; int main( int argc, char **argv )
{
int array[] = { , , , , }; int iNumOfarray = sizeof( array ) / sizeof( array[] );
cout << iNumOfarray << endl;
for( int i=; i<iNumOfarray; i++)
{
cout << array[i] << endl;
}
}

    1)此代码中,尝试用sizeof来解决循环问题。但我无法确定,此方法是否会有其他瑕疵。

    2)当然,更好的方法是使用vector。此代码仅是一个简单的讨论性问题。如果有人对此有什么想法。希望能够联系我(评论或邮件)。

    3)另外,此方法不使用将数组指针作为形参的函数,因为通过函数传递的数组,会在函数中自动退化成为普通的指针类型。

3:对于C++常量字符串的一个疑惑。

/**********************************************************
* \file string_t.cpp
* \brief
* \note
*
* \version
* \author zheng39562@163.com
**********************************************************/
#include <iostream>
#include <stdlib.h> using namespace std; int main( int argc, char **argv )
{
char str1[] = "Hello world.";
char str2[] = "Hello world."; char *str3 = {"Hello world."};
char *str4 = {"Hello world."}; if( str1 == str2 )
cout << "1==2" << endl;
else
cout << "1!=2" << endl; if( str3 == str4 )
cout << "3==4" << endl;
else
cout << "3!=4" << endl; exit( );
}

    1)这段代码。最终执行的结果是:1!=2 3==4。剑指(指代剑指offer此书。本文不再重复)中指出,C++在常量字符串上进行了一定优化——str3/4指向同一个地址。我比较好奇的是,C++如何优化此问题。

    2)计算机如何将str3和str4识别成同一个字符串?

    3)此问题,可能涉及到编译,执行和代码内存分配等问题。个人暂时无法解答。留于此,作为记录。

4:对于C++字符串的一些补充。

  1,一个空字符串中依然包含一个'\0'。已经经过代码证实。

5:移动数据的一种方法:当从前往后移动数据,可能需要重复多次时;最好反置进行移动。因为从前往后移动时,很多数据会经历多次向后移动的经历。这点,对于字符串和数组操作尤其有效。

6:对于指针,必须判断其是否为NULL。在剑指中2.3.3链表的添加节点函数中,并没有对指向指针的指针进行判断。也许是因为经验性常识,但我个人认为此代码并不完美。因为指向指针的指针,也同样需要判断。

  1,存在一个疑问:对空指针取地址,得到的结果是否和OS具体实现有关?

7:对于剑指链表的一个另类思路的尝试。

  1,起因:剑指中,尝试使用AddtoTail一个函数完成添加操作。它可以完成添加功能,但同时也完成了链表的创建。代码如下(实现代码省略):

void AddToTail( ListNode** pHead, int value )
{
// contents....
if ( *pHead == NULL )
// contents...
}

    1)使用指向指针的指针的原因是:当往一个空链表中插入头节点时,如果使用链表头作为指针,会导致我们丢失链表头。(具体解释,可以查看剑指offer——或者email本人)

    2)而我的问题是,如果pHead本身就是NULL,这个函数会怎么样?剑指中,对于空指针异常警惕,所以对于此,我存有疑惑。是否是C++语言体系中,已经能够保证NULL指针可以指向NULL?

    3)如果如上猜测,为何在removeNode函数中,又对phead本身进行了判断?

    4)模拟性质的对此函数进行调用,发现似乎需要在外面先创建一个ListNode**的指针。用new?或者还是可以直接NULL。感觉此函数非常模糊不清。如果使用new,感觉多此一举。

  2,对此函数,有两个想法。

 first mode
ListNode* AddToTail( ListNode* pHead, int value ) // 如果传入指针为空,则直接返回一个头指针。使用返回值来获取头指针。
second mode
ListNode* CreateList( int value );
void AddToTail( ListNode* pHead, int value ); // 相信都看的懂 :)
// 个人倾向此方法。虽然使用一个函数解决看起来更简洁。但我更喜欢将行为拆解。

    1)个人无法认同剑指的链表函数,即使NULL真的指向NULL。即使使用该函数不会造成错误——但从逻辑上,很不美好:(

  3,另外,挺喜欢剑指面试4中的环形链表。

8:碰见过的面试中,大部分公司都喜欢考递归。但,需要注意,现实中使用递归需要考虑栈溢出的问题。除非比较确定不会溢出,否则现实中还是少用递归吧。(当然,面试时还是使用递归比较好)

9:面试题6的细节记录:

  1,使用前序和中序数组来构建二叉树。大致思想不再分析,但需要注意异常输入。例如:前序中本身存在错误数据。此时,使用你的函数,是否能够抛出一个错误?

  2,虽然再异常和边界中已经提及其重要性,但个人编程习惯很难一时改变。在面试时,需要可以关注此点。

  3,二叉树是否可以通过前序+后序还原二叉树?(个人已经尝试,无法解决。)

10:注意算法(特别是排序)的额外空间消耗,平均/最差时间复杂度。

  1,基本的数据结构和常用排序算法是必须具备的知识。

11:面试题8代码中,没有考虑数组出错的情况。

  1,对于排序数组的查找和搜索,需要注意重复数字的情况。

12:使用位运算时,需要注意负整数的情况。(通常面试不会变态到求浮点数。)负整数使用补码形式保存数据。

13:很多链表问题都可以使用两个指针解决。。。如果不行,大概可以用3个?(玩笑。。。)

14:面试题28是一个相对复杂的递归方式——因为它的递归出口比较奇怪。

  1,此题思路不难,但递归代码卡了10分钟。因为在大脑中模拟递归走向时出现问题。

  2,使用和理解递归:建议将递归函数代码内的递归函数看成已经解决的结果,而不要尝试展开它。对于部分递归,模拟展开没有问题,但另一部分递归,并不适合展开。

  3,递归有两种模式:

    1)拥有比较简单的出口,并且结果可以带入上层递归函数中。把递归本身当作结果时,很容易联想到结果。对于此类递归,大脑模拟展开问题不大。

    2)如面试28,递归代码中的递归函数本身就代表多种可能性,并且递归出口并非0,1,2,3等等简单解决式。当我尝试大脑模拟打印时,发现可能性多种多样,无法例举。可能反向导致思路受阻。

  4,递归的两个重点:

    1)递归规则。

    2)寻找递归出口。需要注意的是,递归出口有时并非只有一个(虽然一个出口也可以完成递归,但可能会有隐性减分)。

  5,面试28的扩展题。(有时间将重开一章,说明遇见的问题和出现错误)

15:吐槽面试题37.好好的归并排序变种成完全想不到的奇葩题。。。

16:逻辑运算符: '!' 的返回值为TRUE OR FALSE。

17:粗略看完剑指OFFER后的感想。

对于剑指:

阅读完后,第一想法,是将其中的所有面试题进行整理。得到其算法和数据结构的本质。因为面试题可以有各类变种,但需求的算法和数据结构都是不会改变的。阅读剑指,与其说是阅读,理解和记忆面试题,不如说,其总结了常用的面试算法和结构。

18:以下无经过权威证实:

用三天的时间看玩了剑指offer。不得不说,本书写的非常好(听说被翻译成英文出版了。不知真假)。

但剑指中,实际上还有很多很多缺陷的地方。倒不是书的问题。而是知识点实在太多。剑指偏向于算法以及代码细节的处理(如异常等)。但对具体语言就有很大欠缺,需要其他面试题补充。

面试不单是拿OFFER。一个合格的公司,它拿出的面试题本身应该有一定的倾向性。例如,我希望得到服务器相关的岗位。但对于此行业,我只知道非常粗浅的概念知识。解答面试的同时,同样需要关注面试题的中包含的知识倾向。