20201016:算法题+笔试题

错题一

  • 选项中哪一行代码可以替换 //add code here 而不产生编译错误:
public abstract class MyClass {
     public int constInt = 5;
     //add code here
     public void method() {
     } 
}

在这里插入图片描述

1、抽象类不一定有抽象方法,有抽象方法的类一定是抽象类

2、抽象类中可以有构造方法,抽象类不能进行实例化,那么要构造方法有什么作用呢? 用于子类访问父类数据时的初始化;

3、抽象类不能直接实例化那么,抽象类如何实例化呢? 按照多态的方式,由具体的子类实例化;其实这也是多态的一种,抽象类多态。

4、抽象类的子类,要么也是抽象类,要么必须重写抽象类中的所有抽象方法;


【抽象类的成员特点】:
成员变量:既可以是变量,也可以是常量。 成员变量不能直接进行运算,可以写在代码块{}或者静态代码块中static{}中;
构造方法:有,用于子类访问父类数据的初始化;
成员方法:既可以是抽象的,也可以是非抽象的(default关键字修饰的方法);
抽象方法:强制要求子类做的事情;
非抽象方法:子类继承的事情,提高代码复用性;

错题二

  • 下面有关servlet和cgi的描述,说法错误的是?

在这里插入图片描述
【CGI(Common Gateway Interface),通用网关接口】

  • 通用网关接口,简称CGI,是一种根据请求信息动态产生回应内容的技术。通过CGI,Web 服务器可以将根据请求不同启动不同的外部程序,并将请求内容转发给该程序,在程序执行结束后,将执行结果作为回应返回给客户端。
  • 也就是说,对于每个请求,都要产生一个新的进程进行处理。因为每个进程都会占有很多服务器的资源和时间,这就导致服务器无法同时处理很多的并发请求。
  • 另外CGI程序都是与操作系统平台相关的,虽然在互联网爆发的初期,CGI为开发互联网应用做出了很大的贡献,但是随着技术的发展,开始逐渐衰落。

【Servlet】

  • Servlet最初是在1995年由James Gosling 提出的,因为使用该技术需要复杂的Web服务器支持,所以当时并没有得到重视,也就放弃了。后来随着Web应用复杂度的提升,并要求提供更高的并发处理能力,Servlet被重新捡起,并在Java平台上得到实现,现在提起Servlet,指的都是Java Servlet
  • Java Servlet要求必须运行在Web服务器当中,与Web服务器之间属于分工和互补关系。确切的说,在实际运行的时候Java Servlet与Web服务器会融为一体,如同一个程序一样运行在同一个Java虚拟机(JVM)当中。与CGI不同的是,Servlet对每个请求都是单独启动一个线程,而不是进程。这种处理方式大幅度地降低了系统里的进程数量,提高了系统的并发处理能力
  • 另外因为Java Servlet是运行在虚拟机之上的,也就解决了跨平台问题。如果没有Servlet的出现,也就没有互联网的今天。
  • 在Servlet出现之后,随着使用范围的扩大,人们发现了它的一个很大的一个弊端。那就是为了能够输出HTML格式内容,需要编写大量重复代码,造成不必要的重复劳动。为了解决这个问题,基于Servlet技术产生了JavaServet Pages技术,也就是JSP。
  • Servlet和JSP两者分工协作,Servlet侧重于解决运算和业务逻辑问题,JSP则侧重于解决展示问题。 Servlet与JSP一起为Web应用开发带来了巨大的贡献,后来出现的众多Java Web应用开发框架都是基于这两种技术的,更确切的说,都是基于Servlet技术的。
  • Servlet处于服务器进程中,它通过多线程方式运行其service方法,一个实例可以服务于多个请求,并且其实例一般不会销毁。
  • 而CGI对每个请求都产生新的进程,服务完成后就销毁,所以效率上低于Servlet 。

错题三

  • Test.main()函数执行后的输出是( )
class Test {
    public static void main(String[] args) {
        System.out.println(new B().getValue());
    }
    static class A {
        protected int value;
        public A (int v) {
            setValue(v);
        }
        public void setValue(int value) {
            this.value= value;
        }
        public int getValue() {
            try {
                value ++;
                return value;
            } finally {
                this.setValue(value);
                System.out.println(value);
            }
        }
    }
    static class B extends A {
        public B () {
            super(5);
            setValue(getValue()- 3);
        }
        public void setValue(int value) {
            super.setValue(2 * value);
        }
    }
}

在这里插入图片描述
本题中需要遵循一个原则:除非是super.xxx显示调用父类的方法或子类没有该方法时,其余情况都是执行子类重写过后的方法;

这道题本身过程比较多,容易做错;

错题四

在这里插入图片描述

  • Collection继承体系如下:
    在这里插入图片描述

java.lang.Iterable接口被Collection继承;

错题五

  • 关于Java中的ClassLoader下面的哪些描述是错误的:( )

在这里插入图片描述
关于类加载器,之前我写了一篇文章详解:

点击这里进入

学习了其他人的评论之后,总结:

1、从Java虚拟机的角度讲,只有两种类加载器:

启动类加载器:使用C++实现,是虚拟机的一部分;
其他所有类加载器:由Java语言实现,独立于虚拟机之外,全部继承自抽象类;

从开发人员的角度来看,类加载器还可以划分为:

启动类加载器(Bootstrap ClassLoader): 负责加载存放在<JAVA_HOME>/lib目录中或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符的类库即使放在lib目录中也不会被加载)类库加载到虚拟机中内存中。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,直接使用null代替即可。
扩展类加载器(Extension ClassLoader):这个类加载器由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
应用类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以也称它为系统类加载器(System ClassLoader)。他负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。对此,如果有必要开发者可以加入自己定义的类加载器。

2、 一般对于我们Java程序员来说,类的加载使用的是双亲委派模型,即当一个类需要加载时,会将类传给Application ClassLoader(但是Application ClassLoader并不会加载),不管它是否能加载,而是传给它的"父类" Extension ClassLoader,Extension ClassLoader同样不会加载,同样传给 Bootstrap ClassLoader(注意不是我们常说的那种父类,但是可以这样理解),这时Bootstrap ClassLoader会判断它是否能加载,能加载就直接加载了,不能加载就传给Extension ClassLoader,Extension ClassLoader同样的判断是否能加载,能加载就直接加载,不能加载就传给Application ClassLoader,然后Application ClassLoader也判断能否加载,如果还是不能加载应该就是报ClassNotFoundException了。这就是双亲委托模型的简单理解。

3、对于上面的"父类"为什么要打引号,因为它们并不是真的像Java中继承的关系,而是组合的关系,即在"子类"中存在一个成员变量指向"父类"的引用。

4、一个类只需要加载一次就够了,所以要保证线程安全。

5、之所以使用双亲委托机制是为了保证Java程序的稳定运作,比如当你使用的不是双亲委托模型的时候,然后刚好开发者又定义了一个类,一个java.lang.String这样一个类,如果不使用双亲委托模型,当类加载的时候就有可能会加载开发者定义的String类,这导致了java代码的一片混乱,可读性极差。所以可以这么说,不同的类加载器加载出来的类是不一样的,不同的类加载器加载同一个类会在方法区产生两个不同的类,彼此不可见,并且在堆中生成不同的Class实例。对于接口,其实就是一个特殊的类,和类一样,在堆中产生不同的class对象。

错题六

  • 下面哪些描述是正确的:()
public class Test {
public static class A {
private B ref;
public void setB(B b) {
ref = b;
}
}
public static Class B {
private A ref;
public void setA(A a) {
ref = a;
}
}
public static void main(String args[]) {start();.
}
public static void start() { A a = new A();
B b = new B();
a.setB(b);
b = null; //
a = null;}
}
  • 内存如下:
a -> "a(b)"
b -> "b"

a引用指向一块空间,这块空间里面包含着b对象;
b引用指向一块空间,这块空间是b对象 A选项,b = null执行后b可以被垃圾回收。
这里"b可以被垃圾回收"中的b指的是引用b指向的内存,这块内存即使不被引用b指向,还是被引用a指向着,不会被回收;
从代码中可以看到,a = null是在b = null后执行的,该行执行后,引用a和b都没有指向对象,对象会被回收。

错题七

  • 关于Java以下描述正确的有( )

在这里插入图片描述

A:native是由调用本地方法库(如操作系统底层函数),可以由C,C++实现,A正确
B:import是用于导包语句,其前面可以出现package,用来声明包的,B错误
C:接口方法的修饰符可以是:public,abstract,default,static(后两者需要有{}),C正确
D:构造方法可以用private,protected,default,private,D错误

错题八

  • 在Java语言中,下列关于字符集编码(Character set encoding)和国际化(i18n)的问题,哪些是正确的?

在这里插入图片描述
很多人都把Unicode编码挂在嘴边,其实咱们现实生活中遇到的编码基本都是Unicode的

因为Unicode兼容了大多数老版本的编码规范例如 ASCII

Unicode编码定义了这个世界上几乎所有字符(就是你眼睛看到的长那个样子的符号)的数字表示

也就是说Unicode为每个字符发了一张身份证,这张身份证上有一串唯一的数字ID确定了这个字符

在这个纷乱世界上存在的唯一性。Unicode给这串数字ID起了个名字叫[码点](Code Point)

而很多人说的编码其实是想表达[Unicode转换格式](即UTF,Unicode Transformation Formats)

有没有觉得眼前一亮豁然开朗?没错 这就是我们看到的UTF-8/UTF-16/UTF-32的前缀来源

这个[Unicode转换格式]的存在是为了解决[码点]在计算机中的二进制表现形式而设计的

毕竟我们的机内表示涉及存储位宽,兼容古老编码格式,码点是数值过大的罕见字符等问题

[码点]经过映射后得到的二进制串的转换格式单位称之为[码元](Code Unit)。也就是说如果有一种UTF的码点二进制表示有n字节,其码元为8位(1个byte),那么其拥有码元n个。每种UTF的码元都不同,其宽度被作为区分写在了UTF的后缀——这就是UTF-8/UTF-16/UTF-32的由来。UTF-8的码元是8位的,UTF-16的码元是16位的。大部分的编程语言采用16位的码元作为机内表示。这就是我们在各种语言中调用获取一个字符串中character的数量时会出现这么多混乱的原因。事实上我们调用这些方法时取得的不是字符个数,而是码元个数!一旦我们的字符串中包含了位于基本平面之外的码点,那么就会需要更多的码元来表示,这个时候就会出现测试时常见的困惑——为何return的字符数比实际字符数要多?所以实际写代码时要特别注意这个问题。

采取不同的映射方式可以得到不同格式的二进制串,但是他们背后所表示的[码点]永远是一致的就好像你换身份证但是身份证号不变一样。由于平时人们误把[转换格式]也称为[编码],所以造成今天Unicode/UTF傻傻分不清楚且遣词造句运用混乱的悲桑局面。

Unicode 编码 发展到今天 扩展到了 21 位(从 U+0000 到 U+10FFFF )。这一点很重要: Unicode 不是 16 位的编码, 它是 21 位的。这 21 位提供了 1,114,112 个码点,其中,只有大概 10% 正在使用,所以还有相当大的扩充空间。

编码空间被分成 17 个平面(plane),每个平面有 65,536 个字符(正好填充2个字节,16位)。0 号平面叫做「基本多文种平面」( BMP, Basic Multilingual Plane ),涵盖了几乎所有你能遇到的字符,除了 emoji(emoji位于1号平面 - -)。其它平面叫做补充平面,大多是空的。

总结一下各种编码格式的特质:

UTF-32

最清楚明了的一个 UTF 就是 UTF-32 :它在每个码点上使用整 32 位。32 大于 21,因此每一个 UTF-32 值都可以直接表示对应的码点。尽管简单,UTF-32却几乎从来不在实际中使用,因为每个字符占用 4 字节太浪费空间了。

UTF-16 以及「***对」( Surrogate Pairs )的概念

UTF-16要常见得多,它是根据有 16 位固定长度的码元( code units )定义的。UTF-16 本身是一种长度可变的编码。基本多文种平面(BMP)中的每一个码点都直接与一个码元相映射。鉴于 BMP 几乎囊括了所有常见字符,UTF-16 一般只需要 UTF-32 一半的空间。其它平面里很少使用的码点都是用两个 16 位的码元来编码的,这两个合起来表示一个码点的码元就叫做***对( surrogate pair )。

UTF-8

UTF-8 使用一到四个字节来编码一个码点。从 0 到 127 的这些码点直接映射成 1 个字节(对于只包含这个范围字符的文本来说,这一点使得 UTF-8 和 ASCII 完全相同)。接下来的 1,920 个码点映射成 2 个字节,在 BMP 里所有剩下的码点需要 3 个字节。Unicode 的其他平面里的码点则需要 4 个字节。UTF-8 是基于 8 位的码元的,因此它并不需要关心字节顺序(不过仍有一些程序会在 UTF-8 文件里加上多余的 BOM)。

有效率的空间使用(仅就西方语言来讲),以及不需要操心字节顺序问题使得 UTF-8 成为存储和交流 Unicode 文本方面的最佳编码。它也已经是文件格式、网络协议以及 Web API 领域里事实上的标准了。

我们的JVM中保存码点是UTF16的转换格式,从char的位宽为16位也可以看得出来。由于绝大部分编码的码点位于基本平面,所以使用16位可以几乎表示所有常用字符。这就是许多语言编译器或运行时都使用UTF16的原因。英文在使用UTF16时也是2字节表示的。当我们想要使用其他平面的字符时,码元超过2个字节,就需要使用***对在语言中的特定表示方式,譬如‘\U112233’之类的。

使用UTF8时,常用的Alphabet和Numeric都在前127字节,被有效率地用一个字节表示。而我们的中文由于排在1920个码点之后,所以使用3个字节表示,这方面就比UTF16转换格式耗费更多空间。

最后,不论使用哪种UTF转换格式,都是程序员自己可以选择的一种表达方式而已。我们可以通过Java方便的API进行自如转换。

错题九

  • Java类Demo中存在方法func0、func1、func2、func3和func4,请问该方法中,哪些是不合法的定义?( )
public class Demo{
  float func0()
  {
    byte i=1;
    return i;
  }
  float func1()
  {
    int i=1;
    return;
  }
  float func2()
  {
    short i=2;
    return i;
  }
  float func3()
  {
    long i=3;
    return i;
  }
  float func4()
  {
    double i=4;
    return i;
  }
}

在这里插入图片描述

long → float 无须强制转换(这个最选项容易出错),正确。


  • float占4个字节为什么比long占8个字节大呢,因为底层的实现方式不同。浮点数的32位并不是简单直接表示大小,而是按照一定标准分配的。
  • 第1位,符号位,即S
  • 接下来8位,指数域,即E。
  • 剩下23位,小数域,即M,取值范围为[1 ,2 ) 或[0 , 1)
  • 然后按照公式: V=(-1)^s * M * 2^E 也就是说浮点数在内存中的32位不是简单地转换为十进制,而是通过公式来计算而来,通过这个公式虽然,只有4个字节,但浮点数最大值要比长整型的范围要大。

算法题一:最长公共前缀

  • 给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。
示例 1:
输入:[-4,-1,0,3,10]
输出:[0,1,9,16,100]

示例 2:
输入:[-7,-3,2,3,11]
输出:[4,9,9,49,121]

【提示】:

1 <= A.length <= 10000;
-10000 <= A[i] <= 10000;
A 已按非递减顺序排序。

自己的做法:

public static String longestCommonPrefix(String[] strs) {
    //先把传递过来的string[]中的每个字符串的前缀都取出来

    if (strs.length == 0) {
        return "";
    }

    ArrayList<Character> list = new ArrayList<>();

    //第一个字符串将他的所有值存储进去结果集当中
    for (int i = 0; i < strs[0].length(); i++) {
        list.add(strs[0].charAt(i));
    }
    //System.out.println(list);
    //[f, l, o, w, e, r]

    //一次遍历后续所有字符串
    for (int i = 1; i < strs.length; i++) {

        String ele = strs[i];
        //System.out.println(ele);

        //遍历list结果集
        for (int j = 0; j < list.size(); j++) {
            if (ele.length() > j && ele.charAt(j) == list.get(j)) {
                //不做操作
                //System.out.println("进来没有:" + ele.charAt(j) + "------" + list.get(j));
            } else {
                list.add(j, ' ');
                break;
            }
        }
        //System.out.println("一轮结束" + list);
    }

    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < list.size(); i++) {
        if (list.get(i) == ' ') {
            break;
        }
        sb.append(list.get(i));
    }

    //System.out.println(sb + "这是sb");
    return sb.toString();
}

自己的想法是:先把第一个字符串的所有字符当作结果集存储起来,然后依次遍历后面的字符串,每个字符串中的每个字符都拿出来与结果集进行比较,如果此时结果集中还存在字符,并且当前字符串与结果集字符串的相同位置上的字符都一样,不做任何操作;否则将结果集中的该字符置为‘ ’空字符;最后拼接结果集中的该字符返回;

官方的做法:

这个题目官方有好几种做法,这里我选择了一种理解:

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) {
            return "";
        }
        int minLength = Integer.MAX_VALUE;
        for (String str : strs) {
            minLength = Math.min(minLength, str.length());
        }
        int low = 0, high = minLength;
        while (low < high) {
            int mid = (high - low + 1) / 2 + low;
            if (isCommonPrefix(strs, mid)) {
                low = mid;
            } else {
                high = mid - 1;
            }
        }
        return strs[0].substring(0, low);
    }

    public boolean isCommonPrefix(String[] strs, int length) {
        String str0 = strs[0].substring(0, length);
        int count = strs.length;
        for (int i = 1; i < count; i++) {
            String str = strs[i];
            for (int j = 0; j < length; j++) {
                if (str0.charAt(j) != str.charAt(j)) {
                    return false;
                }
            }
        }
        return true;
    }
}

【二分查找】:

1、最开始,如果该字符数组不存在或者长度为0,直接返回null;
2、遍历所有字符串找到字符串的长度最小值;
3、定义最高、最低、中间位,执行while循环;
4、将所有字符串和中间位索引交给isCommonPrefix(),该方法用于判断mid索引之前是不是公共部分,如果不是将最高位置换为mid的前一位,如果是让最低位变为当前的mid;
5、这样整个循环下来,很快找到公共前缀;
6、在字符串量很大的时候,这个算法的时间复杂度相较于我自己的做法很有优势,因为他不是遍历判断,而是一部分一部分的判断;

算法题二:有序数组的平方

  • 给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。
示例 1:
输入:[-4,-1,0,3,10]
输出:[0,1,9,16,100]

示例 2:
输入:[-7,-3,2,3,11]
输出:[4,9,9,49,121]
 
提示:

1 <= A.length <= 10000
-10000 <= A[i] <= 10000
A 已按非递减顺序排序。

自己的做法:

public int[] sortedSquares(int[] A) {
    /*
     * 1、最基本的做法就是:
     * 首先遍历原来的数组,将所有的数据都计算平方覆盖原来的值
     * 2、排序就OK了
     * */

    int[] result = new int[A.length];

    for (int i = 0; i < A.length; i++) {
        result[i] = (int) Math.pow(A[i], 2);
    }
    
    Arrays.sort(result);

    //System.out.println(Arrays.toString(result));
    
    return result;
}

就代码量来看,这个题目本身不是很困难;

官方的做法:

【双指针】:

class Solution {
    public int[] sortedSquares(int[] A) {
        int n = A.length;
        int[] ans = new int[n];
        for (int i = 0, j = n - 1, pos = n - 1; i <= j;) {
            if (A[i] * A[i] > A[j] * A[j]) {
                ans[pos] = A[i] * A[i];
                ++i;
            } else {
                ans[pos] = A[j] * A[j];
                --j;
            }
            --pos;
        }
        return ans;
    }
}

这个题目官方使用了双指针的做法,思路为:
首先明确:给定的数组已经是拍好顺序的;
1、首先给定三个指针,i和pos,i 由前向后指向的是数组中的元素,j由后向前指向数组中的元素,pos由前向后指向的是结果集中的元素;
2、如果i指向的元素的平方大于j指向的元素,就将i指向的元素的平方放在结果集的pos位置,i指针向前挪动一位;
3、否则的话结果集的最后一位放的是j指向的元素,此时j向前挪动一位;
4、最终返回结果集;

1.算法是程序的灵魂,优秀的程序在对海量数据处理时,依然保持高速计算,就需要高效的数据结构和算法支撑。2.网上数据结构和算法的课程不少,但存在两个问:1)授课方式单一,大多是照着代码念一遍,数据结构和算法本身就比较难理解,对基础好的学员来说,还好一点,对基础不好的学生来说,基本上就是听天书了2)说是讲数据结构和算法,但大多是挂羊头卖狗肉,算法讲的很少。 本课程针对上述问,有针对性的进行了升级 3)授课方式采用图解+算法游戏的方式,让课程生动有趣好理解 4)系统全面的讲解了数据结构和算法, 除常用数据结构和算法外,还包括程序员常用10大算法:二分查找算法(非递归)、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法、马踏棋盘算法。可以解决面试遇到的最短路径、最小生成树、最小连通图、动态规划等问及衍生出的面试,让你秒杀其他面试小伙伴3.如果你不想永远都是代码工人,就需要花时间来研究下数据结构和算法。教程内容:本教程是使用Java来讲解数据结构和算法,考虑到数据结构和算法较难,授课采用图解加算法游戏的方式。内容包括: 稀疏数组、单向队列、环形队列、单向链表、双向链表、环形链表、约瑟夫问、栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式、递归与回溯、迷宫问、八皇后问算法的时间复杂度、冒泡排序、选择排序、插入排序、快速排序、归并排序、希尔排序、基数排序(桶排序)、堆排序、排序速度分析、二分查找、插值查找、斐波那契查找、散列、哈希表、二叉树、二叉树与数组转换、二叉排序树(BST)、AVL树、线索二叉树、赫夫曼树、赫夫曼编码、多路查找树(B树B+树和B*树)、图、图的DFS算法和BFS、程序员常用10大算法、二分查找算法(非递归)、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法马踏棋盘算法。学习目标:通过学习,学员能掌握主流数据结构和算法的实现机制,开阔编程思路,提高优化程序的能力。
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页