第二章 面试需要的基础知识

2.1 面试官谈基础知识
  • 基础很重要
  • 算法、复杂的
  • 编程能力
  • 数据结构
2.2 编程语言
  • 程序员写代码总是基于某一种编程语言,因此技术面试的时候直接或者间接都会涉及至少一种编程语言。在面试的过程中,面试官要么直接问语言的语法,要么让应聘者用-~种编程语言写代码解决一个问题,通过写出的代码来判断应聘者对他使用的语言的掌握程度。现在流行的编程语言很多,不同公司开发用的语言也不尽相同。做底层开发比如经常写驱动的人更习惯用C, Linux下有很多程序员用C++开发应用程序,基于Windows的C#项目已经越来越多,跨平台开发的程序员则可能更喜欢Java,随着苹果iPad、iPhone 的热销已经有很多程序员投向了Objective C的阵营,同时还有很多人喜欢用脚本语言如Perl、Python 开发短小精致的小应用软件。因此,不同公司面试的时候对编程语言的要求也有所不同。每一种编程语言都可以写出一本大部头的书籍,本书限于篇幅不可能面面俱到。本书中所有代码都用C/C++/C#实现,下 面简要介绍一些C++/C#常见的面试题。
 
面试题1:赋值运算符函数
  • 题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数。
  • 思路
    • 将返回值类型声明为该类型的引用
    • 把传入的参数类型声明为常量引用
    • 释放实例自身已有的内存
    • 判断传入的参数和当前的实例是不是同一个实例
  • 不适用java
 
面试题2:实现Singleton模式
  • 题目:设计一个类,我们只能生成该类的一个实例。
    • https://www.cnblogs.com/cielosun/p/6582333.html
    • 1. 懒汉模式
    • public class SingletonDemo {
    •     private static SingletonDemo instance;
    •     private SingletonDemo(){
    •     }
    •     public static SingletonDemo getInstance(){
    •         if(instance==null){
    •             instance=new SingletonDemo();
    •         }
    •         return instance;
    •     }
    • }
    • 如上,通过提供一个静态的对象instance,利用private权限的构造方法和getInstance()方法来给予访问者一个单例。缺点是,没有考虑到线程安全,可能存在多个访问者同时访问,并同时构造了多个对象的问题。之所以叫做懒汉模式,主要是因为此种方法可以非常明显的lazy loading。针对懒汉模式线程不安全的问题,我们自然想到了,在getInstance()方法前加锁,于是就有了第二种实现。
  • 2. 线程安全的懒汉模式
    • public class SingletonDemo {
    •     private static SingletonDemo instance;
    •     private SingletonDemo(){
    •     }
    •     public static synchronized SingletonDemo getInstance(){
    •         if(instance==null){
    •             instance=new SingletonDemo();
    •         }
    •         return instance;
    •     }
    • }
    • 然而并发其实是一种特殊情况,大多时候这个锁占用的额外资源都浪费了,这种打补丁方式写出来的结构效率很低。
  • 3. 饿汉模式
    • public class SingletonDemo {
    • private static SingletonDemo instance=new SingletonDemo();
    •     private SingletonDemo(){
    •     }
    •     public static SingletonDemo getInstance(){
    •         return instance;
    •     }
    • }
    • 直接在运行这个类的时候进行一次loading,之后直接访问。显然,这种方法没有起到lazy loading的效果,考虑到前面提到的和静态类的对比,这种方法只比静态类多了一个内存常驻而已。
  • 4. 静态类内部加载
    • public class SingletonDemo {
    •     private static class SingletonHolder{
    •         private static SingletonDemo instance=new SingletonDemo();
    •     }
    •     private SingletonDemo(){
    •         System.out.println("Singleton has loaded");
    •     }
    •     public static SingletonDemo getInstance(){
    •         return SingletonHolder.instance;
    •     }
    • }
    • 使用内部类的好处是,静态内部类不会在单例加载时就加载,而是在调用getInstance()方法时才进行加载,达到了类似懒汉模式的效果,而这种方法又是线程安全的。
 
2.3 数据结构
    数据结构一直是技术面试的重点,大多数面试题都是围绕着数组、字符串、链表、树、栈及队列这几种常见的数据结构展开的,因此每一个应聘者都要熟练掌握这几种数据结构。
    数组和字符串是两种最基本的数据结构,它们用连续内存分别存储数字和字符。链表和树是面试中出现频率最高的数据结构。由于操作链表和树需要操作大量的指针,应聘者在解决相关问题的时候-要留意代码的鲁棒性,否则容易出现程序崩溃的问题。是一个与递归紧密相关的数据结构,同样队列也与广度优先遍历算法紧密相关。深刻理解这两种数据结构能帮助我们解决很多算法问题。
 
2.3.1 数组
面试题3:二维数组中的查找
  • 题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
  • 思路:从右上角或左下角开始找,逐行删除,或者用二分法查找
  • 代码实现 右上角开始实现
    •  publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • int[][] arr= { { 1, 2, 8, 9 }, { 2, 4, 9, 12 }, { 4, 7, 10, 13 }, { 6, 8, 11, 15 } };
    • System.out.println(find(arr, 7));
    • }
    • publicstaticbooleanfind(int[][] array, inttarget) {
    • if(array== null) {
    • returnfalse;
    • }
    • introw= 0;
    • intcolumn= array[0].length- 1;
    • while(row< array.length&& column>= 0) {
    • if(array[row][column] == target) {
    • returntrue;
    • }
    • if(array[row][column] > target) {
    • column--;
    • } else{
    • row++;
    • }
    • }
    • returnfalse;
    • }
    • }
 
2.3.2 字符串
面试题4:替换空格
  • 题目:请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We are happy.",则输出“We%20are%20happy.”。
  • 思路:从后往前复制,数组长度会增加,或使用StringBuilder、StringBuffer类 , 先遍历空格个数直接扩容
  • 代码实现
    •  publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • System.out.println(replaceSpace(newStringBuffer("We Are Happy.")));
    • }
    • publicstaticString replaceSpace(StringBuffer str) {
    • if(str== null)
    • returnnull;
    • StringBuilder sb= newStringBuilder();
    • for(inti= 0; i< str.length(); i++) {
    • if(String.valueOf(str.charAt(i)).equals(" ")) {
    • sb.append("%20");
    • } else{
    • sb.append(str.charAt(i));
    • }
    • }
    • returnString.valueOf(sb);
    • }
    • }
  • 方法二
    • "We Are Happy.".replace(" ", "%20")
  • 举一反三:
  • 合并两个数组(包括字符串)时,如果从前往后复制每个数字(或字符)需要重复移动数字(或字符)多次,那么我们可以考虑从后往前复制,这样就能减少移动的次数,从而提高效率。
2.3.3 链表
    我们说链表是一种动态数据结构,是因为在创建链表时,无须知道链
表的长度。当插入一一个结点时,我们只需要为新结点分配内存,然后调整指针的指向来确保新结点被链接到链表当中。内存分配不是在创建链表时.次性完成,而是每添加一个结点分配一 次内存。由于没有闲置的内存,链表的空间效率比数组高。
面试题5:从尾到头打印链表
  • 题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。
  • 面试小提示:在面试中如果我们打算修改输入的数据,最好先问面试官是不是允许做修改。
  • 思路:借助栈实现,或使用递归的方法。
  • 代码实现
    • publicArrayList<Integer> printListFromTailToHead(ListNodelistNode) {
    • ArrayList<Integer> list= newArrayList<>();
    • if(listNode== null)
    • returnlist;
    • Stack<ListNode> stack= newStack<>();
    • while(listNode!= null) {
    • stack.push(listNode);
    • listNode= listNode.next;
    • }
    • while(!stack.isEmpty()) {
    • list.add(stack.pop().val);
    • }
    • returnlist;
    • }
  • 简单演示 栈
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • ArrayList<Integer> arr= newArrayList<Integer>();
    • arr.add(1);
    • arr.add(2);
    • arr.add(3);
    • arr.add(4);
    • arr.add(5);
    • printListFromTailToHead(arr);
    • }
    • publicstaticvoidprintListFromTailToHead(ArrayList<Integer> list) {
    • Stack<Integer> stack= newStack<Integer>();
    • for(Integer integer: list) {
    • stack.push(integer);
    • }
    • while(!stack.isEmpty()) {
    • System.out.println(stack.pop());
    • }
    • }
    • }
  • 简单演示 递归
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • int[] arr= {1,2,3,4,5};
    • printListFromTailToHead(arr,0);
    • }
    • publicstaticvoidprintListFromTailToHead(int[] arr,intnum) {
    • if(arr.length> num){
    • printListFromTailToHead(arr,num+1);
    • System.out.println(arr[num]);
    • }
    • }
    • }
  • 测试用例:
    • 功能测试(输入的链表有多个结点,输入的链表只有一个结点)。
    • 特殊输入测试(输入的链表头结点指针为NULL)。
  • 本题考点:
    • 考查对单项链表的理解和编程能力。
    • 考查对循环、递归和栈3个相互关联的概念的理解。
2.3.4 树
    树是一种在实际编程中经常遇到的数据结构。它的逻辑很简单:除了根结点之外每个结点只有一个父结点,根结点没有父结点;除了叶结点之外所有结点都有一个或多个子结点,叶结点没有子结点。父结点和子结点之间用指针链接。由于树的操作会涉及大量的指针,因此与树有关的面试题都不太容易。当面试官想考查应聘者在有复杂指针操作的情况下写代码的能力,他往往会想到用与树有关的面试题。
    面试的时候提到的树,大部分都是二叉树。所谓二叉树是树的一种特殊结构,在二叉树中每个结点最多只能有两个子结点。在二叉树中最重要的操作莫过于遍历,即按照某一-顺序访问树中的所有结点。通常树有如下几种遍历方式:
  • 前序遍历:先访问根结点,再访问左子结点,最后访问右子结点。图2.5中的二叉树的前序遍历的顺序是10、6、4、8、14、12、16。
  • 中序遍历:先访问左子结点,再访问根结点,最后访问右子结点。图2.5中的二叉树的中序遍历的顺序是4、6、8、10、12、14、16。
  • 后序遍历:先访问左子结点,再访问右子结点,最后访问根结点。图2.5中的二叉树的后序遍历的顺序是4、8、6、12、16、14、10。
 
面试题6:重建二叉树
  • 题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
  • 思路:先找出根节点,然后利用递归方法构造二叉树
  • 代码实现
    • classTreeNode {
    • intval;
    • TreeNode left;
    • TreeNode right;
    • TreeNode(intx) {
    • val= x;
    • }
    • }
    • publicclassTestc {
    • publicTreeNode reConstructBinaryTree(int[] pre, int[] in) {
    • if(pre== null|| in== null) {
    • returnnull;
    • }
    • if(pre.length== 0 || in.length== 0) {
    • returnnull;
    • }
    • if(pre.length!= in.length) {
    • returnnull;
    • }
    • TreeNode root= newTreeNode(pre[0]);
    • for(inti= 0; i< pre.length; i++) {
    • if(pre[0] == in[i]) {
    • root.left= reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i+ 1), Arrays.copyOfRange(in, 0, i));
    • root.right= reConstructBinaryTree(Arrays.copyOfRange(pre, i+ 1, pre.length),
    • Arrays.copyOfRange(in, i+ 1, in.length));
    • }
    • }
    • returnroot;
    • }
    • }
  • 本题考点:
    • 考查应聘者对二叉树的前序遍历、中序遍历的理解程度。只有对二叉树的不同遍历算法有了深刻的理解,应聘者才有可能在遍历序列中划分出左、右子树对应的子序列。考查应聘者分析复杂问题的能力。我们把构建二叉树的大问题分解成构建左、右子树的两个小问题。我们发现小问题和大问题在本质上是一致的,因此可以用递归的方式解决。
 
2.3.5 栈和队列
    栈是一个非常常见的数据结构,它在计算机领域中被广泛应用,
比如操作系统会给每个线程创建-一个栈用来存储函数调用时各个函数的参数、返回地址及临时变量等。栈的特点是后进先出,即最后被压入(push)栈的元素会第一个被弹出(pop)。 在面试题22“栈的压入、弹出序列”中,我们再详细分析进栈和出栈序列的特点。
    通常栈是一“个不考虑排序的数据结构,我们需要O(n)时间才能找到栈中最大或者最小的元素。如果想要在O(1)时间内得到栈的最大或者最小值,我们需要对栈做特殊的设计,详见面试题21“包含min函数的栈”。         
    队列是另外一种很重要的数据结构。和栈不同的是,队列的特点是先进先出,即第一个进入队列的元素将会第一个出来。 在2.3.4节介绍的树的宽度优先遍历算法中,我们在遍历某一层树的结点时,把结点的子结点放到一个队列里,以备下一层结点的遍历。详细的代码参见面试题23“从上到下遍历二叉树”。
    栈和队列虽然是特点针锋相对的两个数据结构,但有意思的是它们却相互联系。请看面试题7“用两个栈实现队列”,同时读者也可以考患如何用两个队列实现栈。
面试题7:用两个栈实现队列
  • 题目:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
  • 思路:一个栈压入元素,而另一个栈作为缓冲,将栈1的元素出栈后压入栈2中。也可以将栈1中的最后一个元素直接出栈,而不用压入栈2中再出栈。
  • 代码
    • publicclassTestc {
    • Stack<Integer> stack1= newStack<Integer>();
    • Stack<Integer> stack2= newStack<Integer>();
    • publicstaticvoidmain(String[] args) throwsException {
    • Testc t= newTestc();
    • t.push(1);
    • t.push(2);
    • System.out.println(t.pop());
    • t.push(3);
    • System.out.println(t.pop());
    • System.out.println(t.pop());
    • System.out.println(t.pop());
    • }
    • publicvoidpush(intnode) {
    • stack1.push(node);
    • }
    • publicintpop() throwsException {
    • if(stack1.isEmpty() && stack2.isEmpty()) {
    • thrownewRuntimeException("栈为空!");
    • }
    • if(stack2.isEmpty()) {
    • while(!stack1.isEmpty()) {
    • stack2.push(stack1.pop());
    • }
    • }
    • returnstack2.pop();
    • }
    • }
  • 测试用例:
    • 往空的队列里添加、删除元素。
    • 往非空的队列里添加、
    • 删除元素。
    • 连续删除元素直至队列为空。
  • 本题考点:
    • 考查对栈和队列的理解。
    • 考查写与模板相关的代码的能力。
    • 考查分析复杂问题的能力。本题解法的代码虽然只有只有20几行
    • 代码,但形成正确的思路却不容易。应聘者能否通过具体的例子分
    • 析问题,通过画图的手段把抽象的问题形象化,从而解决这个相对
    • 比较复杂的问题,是能否顺利通过面试的关键。
 
2.4 算法和数据操作
    和数据结构一样,考查算法的面试题也备受面试官的青睐,其中排序和查找是面试时考查算法的重点。在准备面试的时候,我们应该重点掌握二分查找、归并排序和快速排序,做到能随时正确、完整地写出它们的代码。
    有很多算法都可以用递归和循环两种不同的方式实现。通常基于递归的实现方法代码会比较简洁,但性能不如基于循环的实现方法。在面
试的时候,我们可以根据题目的特点,甚至可以和面试官讨论选择合适.的方法编程。
    位运算可以看成是一- 类特殊的算法,它是把数字表示成二进制之后对0和1的操作。由于位运算的对象为二进制数字,所以不是很直观,但掌握它也不难,因为总共只有与、或、异或、左移和右移5种位运算。
 
2.4.1 查找和排序
    查找和排序都是在程序设计中经常用到的算法。查找相对而言较为简
单,不外乎顺序查找、二分查找、哈希表查找和二叉排序树查找。在试
的时候,不管是用循环还是用递归,面试官都期待应聘者能够信手拈来写出完整正确的二分查找代码,否则可能连继续面试的兴趣都没有。
  • 面试小提示:如果面试题是要求在排序的数组( 或者部分排序的数组)中查找一个数字或者统计某个数字出现的次数,我们都可以尝试用二分查找算法。
面试题8:旋转数组的最小数字
  • 题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一 个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0
  • 思路:
  • 代码实现
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • System.out.println(minNumberInRotateArray(newint[] { 1, 0, 1, 1, 1 }));
    • }
    • publicstaticintminNumberInRotateArray(int[] array) {
    • if(array== null|| array.length== 0)
    • return0;
    • intleft= 0;
    • intright= array.length- 1;
    • intmid= 0;
    • while(array[left] >= array[right]) {
    • if(right- left<= 1) {
    • mid= right;
    • break;
    • }
    • mid= (left+ right) / 2;
    • if(array[left] == array[mid] && array[mid] == array[right]) {
    • if(array[left+ 1] != array[right- 1]) {
    • intreslut= array[left];
    • for(inti= left+ 1; i<= right; i++) {
    • if(reslut>array[i]) {
    • reslut= array[i];
    • }
    • }
    • returnreslut;
    • }
    • } else{
    • if(array[left] <= array[mid]) {
    • left= mid;
    • } else{
    • right= mid;
    • }
    • }
    • }
    • returnarray[mid];
    • }
    • }
  • 本题考点:
    • 考查对二分查找的理解。本题变换了二分查找的条件,输入的数组不是排序的,而是排序数组的一个旋转。这要求我们对二分查找的过程有深刻的理解。
    • 考查沟通学习能力。本题面试官提出了一个新的概念:数组的旋转。我们要在很短时间内学习理解这个新概念。在面试过程中如果面试官提出新的概念,我们可以主动和面试官沟通,多问几个问题把概念弄清楚。
    • 考查思维的全面性。排序数组本身是数组旋转的一一个特例。另外,我们要考虑到数组中有相同数字的特例。如果不能很好地处理这些特例,就很难写出让面试官满意的完美代码。
2.4.2 递归和循环
    如果我们需要重复地多次计算相同的问题,通常可以选择用递归或者循环两种不同的方法。递归是在一一个函数的内部调用这个函数自身。而循环则是通过设置计算的初始值及终止条件,在一个范围内重复运算。
    面试小提示:通常基于递归实现的代码比基于循环实现的代码要简洁很多,更加容易实现。如果面试官没有特殊要求,应聘者可以优先采用递归的方法编程。
  • 递归虽然有简洁的优点,但它同时也有显著的缺点。递归由于是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址及临时变量,而且往栈里压入数据和弹出数据都需要时间。这就不难理解.上述的例子中递归实现的效率不如循环。
  • 另外,递归中有可能很多计算都是重复的,从而对性能带来很大的负面影响。递归的本质是把一一个问题分解成两个或者多个小问题。如果多个小问题存在相互重叠的部分,那么就存在重复的计算。在面试题9“斐波那契数列”及面试题43“n个骰子的点数”中我们将详细地分析递归和循环的性能区别。
  • 除了效率之外,递归还有可能引起更严重的问题:调用栈溢出。前面分析中提到需要为每一次函数调用在内存栈中分配空间,而每个进程的栈的容量是有限的。当递归调用的层级太多时,就会超出栈的容量,从而导致调用栈溢出。在上述例子中,如果输入的参数比较小,如10,它们都能返回结果55。但如果输入的参数很大,如5000,那么递归代码在运行的时候就会出错,但运行循环的代码能得到正确的结果12502500。
 
面试题9:斐波那契数列
  • 题目一:写一个函数,输入n,求斐波那契( Fibonacci )数列的第n项斐波那契数列的定义如下:
        
  • 思路:递归实现效率低,循环实现。
  • 循环代码实现
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • System.out.println(fibonacci(9));
    • }
    • publicstaticlongfibonacci(intn) {
    • longresult= 0;
    • longpreOne= 1;
    • longpreTwo= 0;
    • if(n== 0) {
    • returnpreTwo;
    • }
    • if(n== 1) {
    • returnpreOne;
    • }
    • for(inti= 2; i<= n; i++) {
    • result= preOne+ preTwo;
    • preTwo= preOne;
    • preOne= result;
    • }
    • returnresult;
    • }
    • }
  • 递归代码实现
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • System.out.println(fibonacci(9));
    • }
    • publicstaticlongfibonacci(intn) {
    • if(n< 0) {
    • return0;
    • }
    • if(n== 1) {
    • return1;
    • }
    • returnfibonacci(n- 1) + fibonacci(n- 2);
    • }
    • }
  • 题目二:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法
    • 把问题一 0 1 改成 1 2
    • 假设,一级台阶,有f(1)种方法,二级有f(2)种,以此类推,n级有f(n)种方法。
    • 可以看出,f(1)=1;f(2)=2。
    • 那么,假设n级台阶,那么第一步就有两种情况,跳一步,跟跳两步。
    • 情况一:跳一步,那么接下去的就是f(n-1);
    • 情况二:跳两步,那么接下去的就是f(n-2)。
  • 测试用例:
    • 功能测试(如输入3、5、10 等)。
    • 边界值测试(如输入0、1、2)。
    • 性能测试(输入较大的数字,如40、50、100 等)。
  • 本题考点:
    • 考查对递归、循环的理解及编码能力。
    • 考查对时间复杂度的分析能力。
    • 如果面试官采用的是青蛙跳台阶的问题,那同时还在考查应聘者的数学建模能力。
2.4.3 位运算
五种运算:与、或、异或、左移、右移
面试题10:二进制中的1的个数
  • 题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。
  • 思路:
    • 1.可能引起死循环的解法 判断尾数 右移。面试官接下来可能要问的第二个问题就是:上面的函数如果输入一个负数,比如0x80000000,运行的时候会发生什么情况?把负数0x80000000右移一位的时候 并不是简单地把最高位的1移到第二位变成0x4000000而是0xC000000。这是因为移位前是个负数,仍然要保证移位后是个负数,因此移位后的最高位会设为1。如果一直做右移运算,最终这个数字就会变成0xFFFFFFFF而陷入死循环。
    • 2.为了避免死循环,我们可以不右移输入的数字i。首先把i和1做与运算,判断i的最低位是不是为1。接着把1左移一-位得到2,再和i做与运算,就能判断i的次低位是不是.....这样反复左移,每次都能判断i的其中一位是不是1。
    • 代码实现
      • publicclassTestc {
      • publicstaticvoidmain(String[] args) {
      • System.out.println(NumberOf(5));
      • }
      • publicstaticintNumberOf(intn) {
      • intcount= 0;
      • intflag= 1;
      • while(flag!= 0){
      • if((n& flag) != 0){
      • count++;
      • }
      • flag= flag<< 1;
      • }
      • returncount;
      • }
      • }
  • 思路二:把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0。那么一一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。
  • 代码实现
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • System.out.println(NumberOf(7));
    • }
    • publicstaticintNumberOf(intn) {
    • intcount= 0;
    • while(n> 0){
    • if(n!= 0)
    • n= n&(n-1);
    • count++;
    • }
    • returncount;
    • }
    • }
  • 测试用例:
    • 正数(包括边界值1、0x7FFFFFFF)。
    • 负数(包括边界值0x80000000、0xFFFFFFFF)。
  • 本题考点:
    • 考查对二进制及位运算的理解。
    • 考查分析、调试代码的能力。如果应聘者在面试过程中采用的是第
    • 一种思路,当面试官提示他输入负数将会出现问题时,面试官会期待他能在心中运行代码,自己找出运行出现死循环的原因。这要求应聘者有一定的调试功底。
  • 相关题目:
    • 用一条语句判断一“个整数是不是2的整数次方。一个整数如果是2的整数次方,那么它的二进制表示中有且只有一位是1,而其他所有位都是0。根据前面的分析,把这个整数减去1之后再和它自己做与运算,这个整数中唯一的1就会变成0。
    • 输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n。比如10的二进制表示为1010, 13 的二进制表示为1101,需要改变1010中的3位才能得到1101。我们可以分为两步解决这个问题:第一步求这两个 数的异或,第二步统计异或结果中1的位数。
  • 举一反三:
    • 把一个整数减去1之后再和原来的整数做位与运算,得到的结果相当于是把整数的二进制表示中的最右边一个1变成0.很多二进制的问题都可以用这个思路解决。
 
2.5 本章小结
    本章着重介绍应聘者在面试之前应该认真准备的基础知识。为了应对编程面试,应聘者需要从编程语言、数据结构和算法3方面做好准备。
    面试官通常采用概念题、代码分析题及编程题这3种常见题型来考查应聘者对某一编程语言 的掌握程度。本章的2.2节讨论了C++/C#语言这3种题型的常见面试题。
    数据结构题目一直是面试官考查的重点。数组和字符串是两种最基本的数据结构。链表应该是面试题中使用频率最高的一种数据结构。如果面试官想加大面试的难度,他很有可能会选用与树(尤其是二叉树)相关的第2章面试需要的基础知识
面试题。由于栈与递归调用密切相关,队列在图(包括树)的宽度优先遍历中需要用到,因此应聘者也需要掌握这两种数据结构。
    算法是面试官喜欢考查的另外一个重点。查找(特别是二分查找)和排序(特别是快速排序和归并排序)是面试中最经常考查的算法,应聘者一定要熟练掌握。另外,应聘者还要掌握分析时间复杂度的方法,理解即使是同一思路,基于循环和递归的不同实现它们的时间复杂度可能大不相同。
    位运算是针对二进制数字的运算规律。只要应聘者熟练掌握了二进制的与、或、异或运算及左移、右移操作,就能解决与位运算相关的面试题。
 
 
 
 
 
 
 
 
 
 
 
 
 
 

猜你喜欢

转载自www.cnblogs.com/Sungc/p/9319868.html