java面试必备--JAVA算法(一) 之 常用

2021/7/21 9:36:36

本文主要是介绍java面试必备--JAVA算法(一) 之 常用,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

         相信很多同行小伙伴会因为许多原因想跳槽,不论是干得不开心还是想跳槽涨薪,在如此内卷的行业,我们都面临着“面试造火箭,上班拧螺丝”的局面,鉴于当前形势博主呕心沥血整理的干货满满的造火箭的技巧来了,本博主花费2个月时间,整理归纳java全生态知识体系常见面试题!总字数高达百万! 干货满满,每天更新,关注我,不迷路,用强大的归纳总结,全新全细致的讲解来留住各位猿友的关注,希望能够帮助各位猿友在应付面试笔试上!当然如有归纳总结错误之处请各位指出修正!如有侵权请联系博主QQ1062141499!
 

目录

1 什么是递归?递归的优缺点是什么?

2 什么是复杂度?为什么要进行复杂度分析?

3 什么是时间复杂度?什么是空间复杂度?

4 如何进行复杂度分析?

5 O(1), O(n), O(n2), O(logn), O(nlogn) 的区别

6 常见加密算法有哪些?是否对称?

7 数组与链表的区别

8 同样的复杂度,为什么插入排序比冒泡排序更受欢迎?


1 什么是递归?递归的优缺点是什么?

递归:直接或间接调用自身算法的过程

满足使用递归的条件:

  • 子问题为同类事物,且更简单
  • 必须有个出口

优点:

  • 代码简洁
  • 符合思维习惯,容易理解

缺点:

  • 效率较低
  • 递归层次太深,耗内存且容易栈溢出一定要使用的话,最好使用缓存避免相同的计算,限制递归调用的次数

2 什么是复杂度?为什么要进行复杂度分析?

复杂度

  • 复杂度也叫渐进复杂度,包括时间复杂度和空间复杂度,用来分析算法执行效率与数据规模之间的增长关系,可以粗略地表示,越高阶复杂度的算法,执行效率越低。
  • 复杂度描述的是算法执行时间或占用内存空间随数据规模的增长关系。

为什么要进行复杂度分析?

  • 借助复杂度分析,有利于编写出性能更优的代码,降低成本。
  • 复杂度分析不依赖执行环境、成本低、效率高、易操作、指导性强,是一套理论方法。

3 什么是时间复杂度?什么是空间复杂度?

  • 时间复杂度的全称是渐进时间复杂度(asymptotic time complexity),表示算法的执行时间与数据规模之间的增长关系
  • 空间复杂度的全称是渐进空间复杂度(asymptotic space complexity),表示算法的存储空间与数据规模之间的增长关系。

4 如何进行复杂度分析?

     时间复杂度分析:

 1、大 O 复杂度表示法:T(n) = O(f(n)),公式中的 O,表示代码的执行时间 T(n) 与 f(n) 表达式成正比

  • 只关注循环执行次数最多的一段代码
  • 总复杂度等于量级最大的那段代码的复杂度
  • 嵌套代码的复杂度等于嵌套内外代码复杂度的乘积

2、最好情况时间复杂度:代码在最理想情况下执行的时间复杂度

3、最坏情况时间复杂度:代码在最坏情况下执行的时间复杂度

4、平均时间复杂度:代码在所有情况下执行的次数的加权平均值

5、均摊时间复杂度:极少数高级别复杂度且发生具有时序关系时,可以将这几个高级别的复杂度均摊到低级别复杂度上,一般均摊结果就等于低级别复杂度

空间复杂度分析:

  • 与时间复杂度分析类似,关注算法的存储空间与数据规模之间的增长关系

常见的复杂度:

  • 常见的复杂度并不多,从低到高阶:O(1)、O(logn)、O(n)、O(nlogn)、O(n2)

5 O(1), O(n), O(n2), O(logn), O(nlogn) 的区别

      在描述算法复杂度时,经常用到O(1), O(n), O(logn), O(nlogn)来表示对应复杂度程度, 不过目前大家默认也通过这几个方式表示空间复杂度 。

      那么,O(1), O(n), O(logn), O(nlogn)就可以看作既可表示算法复杂度,也可以表示空间复杂度。

       大O加上()的形式,里面其实包裹的是一个函数f(),O(f()),指明某个算法的耗时/耗空间与数据增长量之间的关系。其中的n代表输入数据的量。

如果ax=N(a>0,且a≠1),那么数x叫做以a为底N的对数,记作x=logaN,读作以a为底N的对数,其中a叫做对数的底数,N叫做真数。

6 常见加密算法有哪些?是否对称?

  • 常用的对称加密算法:DES、AES、3DES、RC2、RC4
  • 常用的非对称加密算法:RSA、DSA、ECC
  • 单向散列函数的加密算法:MD5、SHA

7 数组与链表的区别

  • 存取方式:数组可以顺序存取或者随机存取;链表只能顺序存取
  • 存储位置:数组逻辑上相邻的元素在物理存储位置上也相邻;链表的物理存储位置不确定,一般是分散的
  • 存储空间:链表由于带有指针域,存储密度不如数组大
  • 按序号查找:数组可以随机访问,时间复杂度为 O(1);链表不支持随机访问,平均需要 O(n); 
  • 按值查找:若数组无序,数组和链表时间复杂度均为 O(n),当数组有序时,可以采用二分查找将时间复杂度降为O(log n)
  • 插入和删除:数组平均需要移动 n/2 个元素;链表只需修改指针即可
  • 空间分配方面: 数组,在静态存储分配情形下,存储元素数量受限制。动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败;链表,存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效

8 同样的复杂度,为什么插入排序比冒泡排序更受欢迎?

前面了解了 冒泡排序 和 插入排序,时间复杂度、空间复杂度都相同:

  • 最好情况时间复杂度:O(n)
  • 最坏情况时间复杂度:O(n2)
  • 平均情况下的时间复杂度:O(n2)
  • 空间复杂度:O(1),稳定排序算法

但为什么实际开发中插入排序使用偏多呢?

原因如下:

  • 针对同一个数组,冒泡排序和插入排序,最优情况下需要交互数据的次数是一样(即原数组的逆序度一样)
  • 每次数据交换,冒泡排序的移动数据要比插入排序复杂。冒泡排序进行了 3 次赋值,插入排序进行了 1 次赋值

代码对比:

//冒泡排序
int temp = array[j + 1];
array[j+1] = array[j];
array[j] = temp;
hasSwitch = true;//有数据交换
//插入排序
if (array[j] > value) {
    array[j+1] = array[j];
} else {
    break;
}

 测试代码:

package Lee.interview.algorithm;
import java.util.Random;
/**
 * 测试冒泡排序
 * @author Lee
 * @date 2020-04-10 09:36:54
 */
public class CompareBubbleAndInsertionSort {
    
    public static void main(String[] args) {
        //生成两个一样长度的随机数组
        int length = 10000;
        int[] array_1 = generateArray(length);
        int[] array_2 = new int[length]; 
        System.arraycopy(array_1, 0, array_2, 0, length);
        print(array_1);
        print(array_2);
        
        //比较冒泡排序与插入排序的耗时
        long array_1_start = System.currentTimeMillis();
        bubbleSort(array_1);
        System.out.println("bubbleSort cost time : " + (System.currentTimeMillis() - array_1_start));
        long array_2_start = System.currentTimeMillis();
        insertionSort(array_2);
        System.out.println("insertionSort cost time : " + (System.currentTimeMillis() - array_2_start));
        
        //打印排序后的两个数组,看看结果是否正确
        print(array_1);
        print(array_2);
    }
    
    /**
     * 生成随机数组
     * @param length
     * @return
     */
    private static int[] generateArray(int length) {
        Random r = new Random();
        int[] array = new int[length];
        for (int i = 0; i <array.length; i++) {
            array[i] = r.nextInt(length);
        }
        return array;
    }
    
    /**
     * 冒泡排序
     * @param array
     */
    private static void bubbleSort(int[] array) {
        for (int i = 0; i <array.length; i++) {
            //提前退出冒泡循环的标志
            boolean hasSwitch = false;
            //因为使用 j 和 j+1 的下标进行比较,所以 j 的最大值为数组长度 - 2
            for (int j = 0; j <array.length - (i+1); j++) {
                if (array[j] > array[j + 1]) {
                    int temp = array[j + 1];
                    array[j+1] = array[j];
                    array[j] = temp;
                    hasSwitch = true;//有数据交换
                }
            }
            //没有数据交换退出循环
            if (!hasSwitch) {
                break;
            }
        }
    }
    
    /**
     * 插入排序
     */
    private static void insertionSort(int[] array) {
        for (int i = 1; i <array.length; i++) {
            int j = i - 1;
            int value = array[i];
            for (; j >= 0; j--) {
                if (array[j] > value) {
                    array[j+1] = array[j];
                } else {
                    break;
                }
            }
            array[j+1] = value;
        }
    }
    
    /**
     * 打印数组
     * @param array
     */
    private static void print(int[] array) {
        for(int i : array) {
            System.out.print(i);
        }
        System.out.println();
    }
}
打印结果:

随着数组长度的提升,冒泡排序比插入排序多出的耗时也随之增多。 



这篇关于java面试必备--JAVA算法(一) 之 常用的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程