20201022:算法题+选择题

错题一

  • 下面程序的运行结果()
Object obj=new Object();
List aList=new ArrayList();
List bList=new LinkedList();

long t1=System.currentTimeMillis();
for(int i=0;i<50000;i++){
    aList.add(0,obj);
}
long t2=System.currentTimeMillis()-t1;

t1=System.currentTimeMillis();
for(int i=0;i<50000;i++){
    bList.add(0,obj);
}
long t3=System.currentTimeMillis()-t1; 

在这里插入图片描述
ArrayList与LinkedList性能分析比较详解

原文中最后得到的结论如下:

  • 在尾部插入数据,数据量较小时LinkedList比较快,因为ArrayList要频繁扩容,当数据量大时ArrayList比较快,因为ArrayList扩容是当前容量*1.5,大容量扩容一次就能提供很多空间,当ArrayList不需扩容时效率明显比LinkedList高,因为直接数组元素赋值不需new Node

  • 在首部插入数据,LinkedList较快,因为LinkedList遍历插入位置花费时间很小,而ArrayList需要将原数组所有元素进行一次System.arraycopy;

  • 插入位置越往中间,LinkedList效率越低,因为它遍历获取插入位置是从两头往中间搜,index越往中间遍历越久,因此ArrayList的插入效率可能会比LinkedList高;

  • 插入位置越往后,ArrayList效率越高,因为数组需要复制后移的数据少了,那么System.arraycopy就快了,因此在首部插入数据LinkedList效率比ArrayList高,尾部插入数据ArrayList效率比LinkedList高

  • LinkedList可以实现队列,栈等数据结构,这是它的优势;

错题二

  • 下面有关java threadlocal说法正确的有?

在这里插入图片描述
【ThreadLocal介绍】

1、ThreadLocal叫做线程变量,指的是属于当前线程的一个变量,该变量首先对其他线程来说是隔离的,也就是说ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量;

2、作为一个面试常问的点,理解起来就没有那么容易了;

3、它的核心作用就是:实现线程范围的局部变量;

4、在数据库连接的时候,ThreadLocal使用的比较多:

假如这时候有一个客户端频繁的连接数据库资源,服务端需要建立多次的连接和关闭,如果客户量增多,服务器压力太大;这时候可以使用ThreadLocal,它在每个线程中对连接会创建一个副本,且在线程内部任意地方可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会影响程序执行性能;

5、ThreadLocal中的set(),是将当前线程对象作为key,线程局部变量作为value存储在ThreadLocalMap中;ThreadLocalMap是ThreadLocal中的一个静态内部类,里面定义了一个Entry来保存数据;

6、也就是说,ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个变量,因此保存在map中;

7、内存泄漏

在这里插入图片描述

  • 上图详解了了ThreadLocal和Thread以及ThreadLocalMap三者的关系;

1、Thread中有一个Map,就是ThreadLocalMap;

2、ThreadLocalMap中的key是ThreadLocal,值是我们自己设定的;

3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收;

4、如果我们的ThreadLocal是null,也就是要被垃圾回收器回收了,但是ThreadLocalMap生命周期和Thread一样,它不会被回收,这就造成了内存泄漏的问题;

5、解决办法就是:使用完ThreadLocal后,执行remove操作,避免出现内存泄漏的情况;

错题三

  • URL u =new URL(“http://www.123.com”);。如果www.123.com不存在,则返回______。

在这里插入图片描述

  • 我们在执行URL u =new URL("http://www.123.com");这句话的时候确实要抛出异常,但是这个异常属于IOException,是由于字符串格式和URL不符导致的,不管网址是否存在,最后都会返回该网址的一个连接,打印出来就是该网址;

错题四

  • 关键字super的作用是?

在这里插入图片描述
1、super关键字用于对父类的访问;

2、可以使用super关键字访问被子类隐藏的变量或者覆盖的方法;

3、每个子类的构造方法第一句都会隐式调用super(),如果父类没有这种形式的构造,编译期间会报错;

4、构造不能被继承;

错题五

  • 以下哪个事件会导致线程销毁?()

在这里插入图片描述
1、调用sleep()让线程进入睡眠状态,睡眠指定的时间后再次执行;

2、调用wait()让线程进入等待状态,等待别的线程执行notify()或notifyAll()唤醒后继续执行;

3、调用start()让线程进入就绪状态,得到CPU时间就执行线程;

4、run()方法是线程的具体逻辑方法,执行完,线程就结束。

错题六

  • 下面程序段的时间复杂度是()
i = k = 0;
while( k < n ){
   i ++ ;
k += i ;
}

在这里插入图片描述

在评论区看到一个比较好理解的思路:

i= 1, 2, 3, 4,   5,   6,   7,   8
k= 1, 3, 6, 10, 15, 21, 28, 36

把k的数字两个两个一起看的话, 也就是(1,3), (6,10), (15, 21), (7,8), 求和后可以发现规律:
	(1+3=4), (6+10=16), (15+21=36), (28+36=64)
也就是2^2, 4^2, 6^2, 8^2...偶数的平方
循环在x^2>=n时终止, 可得x等于根号n, 也就是n^(1/2)
循环的次数是x/2, 时间复杂度为O((1/2)n^(1/2)), 一般而言时间复杂度认为常系数为1, 所以答案就是O(n^(1/2))

错题七

在这里插入图片描述

  • 为什么一个负责任的Java程序员想使用嵌套类?

1、为了让非常专门的类的代码与和它一起工作的类联系起来
2、为了支持产生特定事件的新的用户界面
3、为了用Java知识给打动老板,到处使用嵌套类

【使用嵌套类的原因为】:

1、如果B类嵌套在A类里面,即使A类的成员声明为private,B类也可以使用;

2、更可读性:代码在定顶级类里面嵌套小类,让代码更靠近使用的地方;

错题八

  • final、finally和finalize的区别中,下述说法正确的有?

在这里插入图片描述

  • finalize方法一个对象只能执行一次,只能在第一次进入被回收的队列,而且对象所属于的类重写了finalize方法才会被执行。第二次进入回收队列的时候,不会再执行其finalize方法,而是直接被二次标记,在下一次GC的时候被GC。

错题九

  • 以下JAVA程序的运行结果是什么( )
public static void main(String[] args) {
	Object o1 = true ? new Integer(1) : new Double(2.0);
	Object o2;
	if (true) {
    	o2 = new Integer(1);
	} else {
		o2 = new Double(2.0);
	}
	System.out.print(o1);
	System.out.print(" ");         
	System.out.print(o2);
}

在这里插入图片描述
【三元运算符的计算规则】:

1、若两个操作数不可转换,则不做转换,返回值为Object类型;
2、若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字来转换,int类型转换为long类型,long类型转换为float类型等。
3、若两个操作数中有一个是数字S,另外一个是表达式,且其类型标示为T,那么,若数字S在T的范围内,则转换为T类型;若S超出了T类型的范围,则T转换为S类型。
4、若两个操作数都是直接量数字,则返回值类型为范围较大者;

错题十

在这里插入图片描述

  • yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。但实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

错题十一

  • 给定以下JAVA代码,这段代码运行后输出的结果是()
public class Test
{  
    public static int aMethod(int i)throws Exception
    {
        try{
            return i/10;
        }
        catch (Exception ex)
        {
            throw new Exception("exception in a aMethod");
        }finally{
      System.out.printf("finally");
        }
} 
    public static void main(String[] args){
        try
        {
            aMethod(0);
        }
        catch (Exception ex)
        {
            System.out.printf("exception in main");
        }
        System.out.printf("finished");
    }
}

在这里插入图片描述

首先该方法执行正常,没有发生异常,因此依次执行两个try中对应的finally块代码;

算法题一:长键按入

  • 你的朋友正在使用键盘输入他的名字 name。偶尔,在键入字符 c 时,按键可能会被长按,而字符可能被输入 1 次或多次。

  • 你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),那么就返回 True。

示例 1:
输入:name = "alex", typed = "aaleex"
输出:true
解释:'alex' 中的 'a''e' 被长按。
示例 2:
输入:name = "saeed", typed = "ssaaedd"
输出:false
解释:'e' 一定需要被键入两次,但在 typed 的输出中不是这样。
示例 3:
输入:name = "leelee", typed = "lleeelee"
输出:true
示例 4:
输入:name = "laiden", typed = "laiden"
输出:true
解释:长按名字中的字符并不是必要的。
 
提示:
name.length <= 1000
typed.length <= 1000
name 和 typed 的字符都是小写字母。

自己的做法:

在做这道题目的时候,我用了一个看起来容易理解的做法解决了,但是时间和空间复杂度都比较高,性能不好:

public boolean isLongPressedName(String name, String typed) {
    if (typed.length() < name.length()) {
        return false;
    }

    if (typed.length() == name.length() && typed.equals(name)) {
        return true;
    } else if (typed.length() == name.length() && !typed.equals(name)) {
        return false;
    }

    StringBuilder regx = new StringBuilder();

    for (int i = 0; i < name.length(); i++) {
        regx.append(name.charAt(i)).append("+");
    }

    return typed.matches(regx.toString());
}

在这里插入图片描述

使用正则表达式的特点,将第一个字符串的所有字符拿出来,与+一起拼接成为一个regx正则规则,然后让第二个字符串去匹配他;
为了节省时间,在最开始的时候做出一些判断,如果后一个字符串长度小于前一个字符串的长度,直接返回false;
如果后一个字符串的长度等于前一个字符串的长度,返回是否相等;

官方解法:

双指针

class Solution {
public:
    bool isLongPressedName(string name, string typed) {
        int i = 0, j = 0;
        while (j < typed.length()) {
            if (i < name.length() && name[i] == typed[j]) {
                i++;
                j++;
            } else if (j > 0 && typed[j] == typed[j - 1]) {
                j++;
            } else {
                return false;
            }
        }
        return i == name.length();
    }
};

【理解】:

1、type中的字符有两个作用:

  • 作为name的一部分,此时会匹配name的一个字符;(此时指针i和j都向前挪动一位)
  • 作为长键按入的一部分,此时会和前一个字符相同;(此时指针j向前挪动一位)

2、如果不满足上面的作用,则返回false;

3、在执行完上面的判断之后,再去检查name的没个元素是否都被匹配上了;

算法题二:删除排序数组中的重复项

  • 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

  • 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

示例 1:
给定数组 nums = [1,1,2], 
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
 
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

解法:

class Solution {
    public int removeDuplicates(int[] nums) {
        int length = nums.length;
        int i = 0;

        for (int j = 1; j < length; j++) {
            if (nums[j] != nums[i]) {
                i++;
                nums[i] = nums[j];
            }
        }
        //System.out.println(Arrays.toString(nums));

        return i + 1;
    }
}

这是在学习官方解答之后的代码,这个解法的核心思路是:
1、使用双指针,i指针指向数组中最后一个有意义的元素,j指针指向当前遍历到的元素;
2、如果当前元素不等于数组中最后一个有意义的元素,i 指针挪动一位,i 指针指向的元素的值等于当前遍历到的这个元素;
3、最终返回i指针+1;

算法三:比较含退格的字符串

  • 给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果。 # 代表退格字符。

  • 注意:如果对空文本输入退格字符,文本继续为空。

示例 1:
输入:S = "ab#c", T = "ad#c"
输出:true
解释:S 和 T 都会变成 “ac”。
示例 2:
输入:S = "ab##", T = "c#d#"
输出:true
解释:S 和 T 都会变成 “”。
示例 3:
输入:S = "a##c", T = "#a#c"
输出:true
解释:S 和 T 都会变成 “c”。
示例 4:
输入:S = "a#c", T = "b"
输出:false
解释:S 会变成 “c”,但 T 仍然是 “b”。
提示:
1 <= S.length <= 200
1 <= T.length <= 200
S 和 T 只含有小写字母以及字符 '#'

自己的做法:

class Solution {
    public boolean backspaceCompare(String S, String T) {
        if (S.length() == 0 || T.length() == 0) {
            return false;
        }

        String sNew = dealString(S);
        String tNew = dealString(T);

        return sNew.equals(tNew);
    }

    private String dealString(String str) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < str.length(); i++) {
            if (str.charAt(i) == '#') {
                if (i > 0 && sb.length() > 0) {
                    sb.deleteCharAt(sb.length()-1);
                }
            } else {
                sb.append(str.charAt(i));
            }
        }

        //System.out.println(sb.toString());

        return sb.toString();
    }
}

【思路】:

1、定义方法,传入当前的字符串,返回值为去掉#之后的字符串;
2、该方法的内部是:遍历字符串,如果当前字符是#,并且StringBuffer长度不为0,则删除最后一个元素;如果当前字符不是#,则拼接进StringBuffer;
3、最终比较两个字符串是否相等;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值