后端面试笔记(二)

作者: 小鲨鱼FF | 来源:发表于2018-07-28 04:34 被阅读255次

    以下是本人在以往的后端面试中遇到的一些题目的小整理,答案和分析仅供参考,未必正确,如果发现有错误的地方,欢迎指出。

    1、有8个球,其中有1个重一点,其它的球都一样重,现在只有1个天平,怎么只称2次就找出那个重一点的球?
    答:
    称第一次:随机取6个球出来,两边3个球放到天平上称。
    称第二次:若称第一次时天平显示两边一样重,那称剩下的2个球就能找出那个重一点的球了;若天平显示一边重,那就从天平重的那边的3个球中随机取2个球放到天平上称,如果天平显示一边重,那重的那边的那个球就是那个重一点的球,如果天平显示两边一样重,那剩下的没称的那个球就是那个重一点的球。

    2、输出1-100之间的素数。
    答:

    import java.lang.Math;
    
    /**
     * 输出1-100之间的素数
     */
    public class PrimeNumber {
        public static void main(String[] args) {
            boolean flag = true;
            for (int i = 2; i <= 100; ++i) {
                flag = true;
                for (int j = 2; j <= Math.sqrt(i); ++j) {
                    // 若发现有被整除的,就是不是素数,马上标记为false,并且中断循环
                    if (i % j == 0) {
                        flag = false;
                        break;
                    }
                }
                if (flag) {
                    System.out.println(i);
                }
            }
        }
    }
    

    运行结果:


    1-100素数

    3、输入1个整数,输出该数二进制表示中1的个数。
    答:

    import java.util.Scanner;
    
    /**
     * 输入一个整数,输出该数二进制表示中1的个数
     */
    public class StatisticsOne {
        public static void main(String[] args) {
            Scanner scanner = new Scanner(System.in);
            System.out.println("please input an integer:");
            int n = scanner.nextInt();
            scanner.close();
            // 若是负数,就转换为正数
            if (n < 0) {
                n = n / (-1);
            }
            int m = 0;
            // 通过“除2取余,逆序排列法”算出整数对应的二进制数
            // 每求出一个位数的数字就判断是否为1
            // 求出的二进制数是倒过来的,但这里只需统计二进制数中1的个数,倒过来没影响
            do {
                // 对2求余一次就得到一个二进制位数的数字
                int rem = n % 2;
                if (rem == 1) {
                    ++m;
                }
                // 不断地除2,直到除2到商为0
                n = n / 2;
            } while (n > 0);
            System.out.println("this integer contains " + m + " integer one");
        }
    }
    

    运行结果:


    统计整数二进制中1的个数

    用计算器验证下84516645,


    验证整数二进制中1的个数 验证整数二进制中1的个数2

    刚好13个1,正确。

    4、写出题目中的运行结果。

    public class HelloB extends HelloA {
        public HelloB() {
            System.out.println("HelloB");
        }
    
        {
            System.out.println("I'm B class");
        }
    
        static {
            System.out.println("static B");
        }
    
        public static void main(String[] args) {
            System.out.println("-------main start-------");
            new HelloB();
            new HelloB();
            System.out.println("-------main end-------");
        }
    }
    
    class HelloA {
        public HelloA() {
            System.out.println("HelloA");
        }
    
        {
            System.out.println("I'm A class");
        }
    
        static {
            System.out.println("static A");
        }
    }
    

    答:


    HelloB输出

    分析:
    1)首先加载两个类的静态代码块,先加载父类HelloA的静态代码块,再加载子类HelloB的静态代码块。
    2)执行System.out.println("-------main start-------")。
    3)执行new HelloB(),会先加载HelloB的父类HelloA,所以会先加载HelloA的代码块System.out.println("I'm A class"),然后加载HelloA的构造方法System.out.println("HelloA");之后才是加载HelloB的代码块System.out.println("I'm B class"),然后加载HelloB的构造方法System.out.println("HelloB")。
    4)再次执行new HelloB(),同3)。
    5)执行System.out.println("-------main end-------")。

    5、写出题目中的运行结果。

    public class Test {
        static {
            int x = 5;
        }
    
        static int x, y;
    
        public static void main(String[] args) {
            x--;
            myMethod();
            System.out.println(x + y++ + x);
            System.out.println(y);
        }
    
        public static void myMethod() {
            y = x++ + ++x;
        }
    }
    

    答:


    Test输出

    分析:
    1)首先加载Test类的静态块int x = 5,然后加载静态变量static int x, y,这时x的值被重置为0,y的值被初始化为0。
    2)然后执行x--,x的值为-1。
    3)再执行myMethod方法,第一个加数x++是后置自增的,自增是后执行的,先确定第一个加数的值为-1,然后x的值再自增为0;第二个加数++x是前置自增的,自增是先执行的,先自增x的值为1,然后确定第二个加数的值为1;所以经过myMethod方法后,y的值是-1+1=0,x的值是1。
    4)执行System.out.println(x + y++ + x),第一个加数和第三个加数的值都为x=1;第二个加数y++是后置自增的,自增是后执行的,先确定第二个加数的值为0,然后y的值再自增为1;所以是1+0+1=2,输出2,但这时的y的值是1了。
    5)执行System.out.println(y),输出1。

    6、写出题目中的运行结果。
    第一小题:

    public class TestString {
        public static void test(String str) {
            str = "World";
        }
    
        public static void main(String[] args) {
            String string = "Hello";
            test(string);
            System.out.println(string);
        }
    }
    

    第二小题:

    public class TestStringBuffer {
        public static void test(StringBuffer str) {
            str.append(", World!");
        }
    
        public static void main(String[] args) {
            StringBuffer string = new StringBuffer("Hello");
            test(string);
            System.out.println(string);
        }
    }
    

    答:
    第一小题:


    TestString输出

    分析:没有改变原来的值,因为String是final对象,值被赋予后就不能变的了,当执行String string = "Hello"时,常量池生成字符串"Hello",并且string指向字符串"Hello";当执行test(string)方法时,通过引用传递传递对象的引用给函数参数,函数参数得到一份对象引用的备份,也就是String str = string,一开始时str还是指向字符串"Hello",但执行了str = "World"后,常量池就生成新字符串"World"并且str指向了新字符串"World";当执行System.out.println(string)时,jvm会去寻找string指向的字符串对象,string指向的字符串还是原来的"Hello",所以输出的是"Hello"。

    第二小题:


    TestStringBuilder输出

    分析:改变了原来的值,因为string指向的还是同一个对象,只是这个对象的内容被添加了新的内容。这里和上一题的区别就是,上一题因为执行了str = "World"故函数里的引用指向了另一个对象,而这里执行str.append(", World!")操作的还是同一个对象。

    7、用两个栈来实现队列的功能offer(入队)和poll(出队)。
    答:

    import java.util.Stack;
    
    /**
     * 用两个栈实现一个队列
     */
    public class StackQueue<T> {
        Stack<T> stack1 = new Stack<>();
        Stack<T> stack2 = new Stack<>();
    
        public static void main(String[] args) {
            StackQueue<Integer> stackQueue = new StackQueue<>();
            stackQueue.offer(1);
            stackQueue.offer(2);
            stackQueue.offer(3);
            System.out.println(stackQueue.poll());
            System.out.println(stackQueue.poll());
            stackQueue.offer(4);
            stackQueue.offer(5);
            System.out.println(stackQueue.poll());
            System.out.println(stackQueue.poll());
            System.out.println(stackQueue.poll());
        }
    
        /**
         * 插入元素
         */
        public boolean offer(T element) {
            stack1.push(element);
            return true;
        }
    
        /**
         * 取出元素
         */
        public T poll() {
            // 若stack2为空,把stack1的元素全部pop出,stack1每pop出一个就push一个到stack2,直到stack1为空
            if (stack2.isEmpty()) {
                while (!stack1.isEmpty()) {
                    stack2.push(stack1.pop());
                }
            }
            // 若stack2不为空,直接pop出元素
            return stack2.pop();
        }
    }
    

    运行结果:


    StackQueue输出

    分析:第一个stack存入队的元素,因为只能用栈来解决,这样顺序就反了,所以就要引入第二个stack。若要出队元素,就从第二个stack取出(pop);若发现第二个stack没有元素,就从第一个stack取元素出来放到第二个stack,每从第一个stack取出(pop)一个元素就放进(push)第二个stack,直到第一个stack的元素被取完,再从第二个stack取出(pop)元素。

    8、单例模式怎么写?
    答:
    懒汉模式:

    /**
     * 单例模式(懒汉模式),即内部对象一开始是null
     */
    public class SingletonLazy {
        private static SingletonLazy instance;
    
        private SingletonLazy() {
        }
    
        public synchronized static SingletonLazy getInstance() {
            if (instance == null) {
                instance = new SingletonLazy();
            }
            return instance;
        }
    }
    

    饿汉模式:

    /**
     * 单例模式(饿汉模式),即内部对象一开始就实例化了
     */
    public class SingletonHungary {
        private static SingletonHungary instance = new SingletonHungary();
    
        private SingletonHungary() {
        }
    
        public synchronized static SingletonHungary getInstance() {
            return instance;
        }
    }
    

    运行测试:


    单例模式测试

    分析:懒汉模式是懒加载实例,初始化时实例引用还是null,等到需要时实例引用才指向实例;饿汉模式是预加载实例,初始化时实例引用就指向了实例。

    9、请用1条sql语句通过下表查出以下结果。
    game表:

      time         result
      2018-07-27   负
      2018-07-27   胜
      2018-07-27   胜
      2018-07-28   胜
      2018-07-28   负
    

    查询结果如下:

      时间          胜   负
      2018-07-27    2    1
      2018-07-28    1    1
    

    答:
    方法一(sum函数法):

    select time as '时间', sum(case result when '胜' then 1 else 0 end) as ' 胜', sum(case result when '负' then 1 else 0 end) as '负' from game group by time;
    

    运行结果:


    查game表的胜负次数(sum函数法)

    分析:先以time为条件对game表分组,再用sum函数对每组结果进行一定的统计,sum(case result when '胜' then 1 else 0 end)即以result为条件,当result的值为'胜'时就是1,否则就是0,通过这样的方式就能统计出每组中胜的次数,同样地sum(case result when '负' then 1 else 0 end)统计出每组中负的次数。

    方法二(子查询法):

    select time as '时间', (select count(*) from game where time = t.time and result = '胜') as '胜', (select count(*) from game where time = t.time and result = '负') as '负' from game as t group by time;
    

    运行结果:


    查game表的胜负次数(子查询法)

    分析:先以time为条件对game表分组,并且给game表命名为t表,'胜'这一列通过一个子查询决定,子查询再查一次game表,result的值要等于'胜',time的值要等于t表的time,当t.time=2018-07-27时,就是select count() from game where time = '2018-07-27' and result = '胜',正好是2018-07-27的胜的次数;当t.time=2018-07-28时,就是select count() from game where time = '2018-07-28' and result = '胜',正好是2018-07-28的胜的次数;'负'这一列同样这样查询出来。

    方法三(联表法):

    select a.time as '时间', a.胜, b.负 from (select time, count(*) as '胜' from game where result = '胜' group by time) a join (select time, count(*) as '负' from game where result = '负' group by time) b on a.time = b.time;
    

    运行结果:


    查game表的胜负次数(联表法)

    分析:其实这里就是把结果拆成两块,最后再横向合并在一起。select time, count() as '胜' from game where result = '胜' group by time这句先筛选出game表中所有'胜'的结果,再以time为条件分组,统计出来就是每组中胜的次数;select time, count() as '负' from game where result = '负' group by time这句先筛选出game表中所有'负'的结果,再以time为条件分组,统计出来就是每组中负的次数;最后通过join on以time为条件把两表合并在一起,这样每组中胜的次数和每组中负的次数都有了。

    以下做法是错误的:

    select time as '时间', count(result = '胜') as '胜', count(result = '负') as '负' from game group by time;
    

    运行结果:


    查game表的胜负次数(错误做法)

    分析:因为count只能统计某一列的个数,还不能用列的值作为条件,这里的count(result = '胜')估计会被解释成count(result)。

    10、Java中List和Array是怎样相互转换的?
    答:

    import java.util.List;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    
    /**
     * List和Array的互相转换
     */
    public class ListAndArray {
        public static void main(String[] args) {
            listToArray();
            arrayToList();
            arrayToList2();
            arrayToList3();
        }
    
        /**
         * List转Array(利用List的toArray方法实现List转Array)
         */
        private static void listToArray() {
            List<Integer> list = new ArrayList<>();
            list.add(1);
            list.add(2);
            list.add(3);
            Integer[] array = list.toArray(new Integer[list.size()]);
            for (int i = 0; i < array.length; ++i) {
                System.out.print(array[i] + " ");
            }
            System.out.println();
        }
    
        /**
         * Array转List方法1(直接使用Arrays的asList方法实现Array转List)
         * 该方法存在一定的弊端,返回的list是Arrays里面的一个静态内部类
         * 该类并未实现add、remove方法,因此在使用时存在局限性
         * 该ArrayList并非java.util.ArrayList
         */
        private static void arrayToList() {
            Integer[] array = {4, 5, 6};
            List<Integer> list = Arrays.asList(array);
            System.out.println(list);
        }
    
        /**
         * Array转List方法2(利用ArrayList的构造方法来实现Array转List)
         * (最简洁的方法)
         */
        private static void arrayToList2() {
            Integer[] array = {7, 8, 9};
            List<Integer> list = new ArrayList<>(Arrays.asList(array));
            System.out.println(list);
        }
    
        /**
         * Array转List方法3(利用Collections的addAll方法实现Array转List)
         */
        private static void arrayToList3() {
            Integer[] array = {10, 11, 12};
            List<Integer> list = new ArrayList<>(array.length);
            Collections.addAll(list, array);
            System.out.println(list);
        }
    }
    

    运行结果:


    List和Array的相互转换输出

    相关文章

      网友评论

      本文标题:后端面试笔记(二)

      本文链接:https://www.haomeiwen.com/subject/umqspftx.html