【八股文】算法篇

目录

  • 1. 数据结构
    • 1. 字符串
      • 1. 笔记
    • 2. 数组
      • 1. 定义
      • 2. 特点
      • 3. 代码
      • 4. 笔记
        • 1. 移出数组中指定的元素
        • 1. 数组问题的一般工具
    • 3. 链表
      • 1. 定义
      • 2. 组成部分
      • 3. 分类
        • 1. 单链表
          • 1. 笔记
          • 2. 相关的解决方案
            • 1. 虚拟头节点(dummy)+ 双指针(pre被删除的节点、判断删除标志的节点)
            • 2. 求单链表的长度
            • 3. 不改变原链表
        • 2. 双链表
        • 3. 循环链表(环形链表)
          • 1. 如何判断一个链表有环(双指针:slow1步,fast2步)
          • 2. 如何计算环形链表的节点
      • 4. 链表的存储方式
      • 5. 代码如何定义一个单链表
      • 6. 链表相关操作
        • 1. 删除节点
          • 1. 时间复杂度:O(1)
        • 2. 添加节点
          • 1. 时间复杂度:O(1)
        • 3. 查询节点
          • 1. 时间复杂度:O(n)
    • 4. 哈希表(散列表)
      • 1. 优点
      • 2. 缺点
      • 3. 哈希函数(采用哈希取余运算)
      • 4. 哈希碰撞及解决方案
        • 1. 拉链法(Java中的HashMap就是这么实现的)
        • 2. 线性探测法
      • 5. 常见的三种哈希结构
        • 1. 数组
        • 2. set集合
          • 1. HashSet
          • 2. TreeSet 有序集合
        • 3. Map映射
          • 1. HashMap
          • 2. TreeMap
    • 5. 栈
    • 6. 队列
    • 7. 二叉树
      • 1. 分类
        • 1. 满二叉树(满节点的二叉树)
        • 2. 完全二叉树(从上到下,从左到右,是连续的就行)
        • 3. 二叉搜索树
        • 4. 平衡二叉搜索树(AVL)
      • 2. 存储方式
        • 1. 链式存储(链表实现,每个节点都有2个指针)(算法中用这个比较多)
          • 1. 代码如何使用链式存储定义一个二叉树
        • 2. 顺序存储(数组实现,因为数组连续,所以实现一定是完全二叉树)
          • 1. 公式(记不住的,看图就行)
      • 3. 二叉树的遍历方式
        • 1. 深度优先遍历
          • 1. 分类
          • 2. 如何实现(用栈的递归特性)
        • 2. 广度优先遍历
          • 1. 层次遍历(迭代法)
          • 2. 如何实现(用队列的先进先出特性)
  • 1. 我的工具
    • 二分法
      • 1. 重要参数
      • 2. 问题的特点
    • 双指针
      • 1. 组成
      • 2. 作用
    • 递归
      • 1. 重要参数
      • 1. 时间复杂度
    • 滑动窗口(本质是双指针,left窗口左边界、right窗口右边界)
      • 1. 重要参数
      • 2. 动作
      • 3. 注意事项
      • 4. 问题的特点
    • 哈希表
      • 1.组成
        • 1. 数组实现
        • 2. Set实现
          • 1. HashSet
        • 3. Map实现
      • 问题的特点
    • 回溯算法
      • 1. 模板
      • 2. 问题的特点
    • 贪心算法
      • 1. 模板
      • 2. 问题的特点
    • 动态规划
    • 1. 模板
      • 2. 问题的特点
      • 3. 直面灵魂的三个问题
    • 01背包问题
  • 2. Java中我可以用到的一些类
    • Scanner相关
      • 1. nextXXX
      • 2. hasNextXXX
    • String相关
      • s.toCharArray()
      • s.charAt(i)
      • s1.compareTo(s2)
        • 1.比较两个数字(仅限0到9)的时候:
        • 2. 比较中有涉及非数字的时候:
        • 3. ASCII码表
          • 1. 题外话:int和char之间的转换(通过加减字符'0'去实现)
    • char相关
    • int相关
    • StringBuffer
      • reverse
    • List相关用法
      • sort(弃用)
        • 1. 例子
    • Arrays.sort(nums);(推荐)
    • 使用IntStream自定义排序规则
    • Math相关
      • Math.max 最大值
      • Math.min 最小值
      • Math.abs 绝对值
      • Math.pow(double a, double b)
    • Iterator相关
      • 1. hasNext
      • 2. next
      • 3. 参考代码
    • TreeMap(根据key升序排序)
    • TreeSet(排序升序+去重)
        • 题外话:迭代器和栈的原理是不是有点类似
  • 2. 经典面试题

相关图文参考链接:代码随想录

1. 数据结构

1. 字符串

1. 笔记

常用到StringBuilder类,常用方法有 append、chatAt

操作字符串,本质是操作StringBuilder

2. 数组

1. 定义

数组是存放在连续内存空间上的相同类型数据的集合。

在删除或者增添元素的时候,就难免要移动其他元素的地址

2. 特点

  1. 数组下标都是从0开始的。
  2. 一维数组内存空间的地址是连续的,二维数组不是
  3. 数组的元素是不能删的,只能覆盖。

3. 代码

定义数组

// 一维数组
int[] arr = {1, 2, 3};
// 二维数组
int[][] arr = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {9,9,9}};

4. 笔记

1. 移出数组中指定的元素

双指针,fast记录循环次数;slow记录要保留的元素,每记录一个就slow++,fast循环结束,slow就是想要的答案

1. 数组问题的一般工具
  1. 二分法
  2. 双指针
  3. 滑动窗口(本质也是双指针)

3. 链表

1. 定义

链表是一种通过指针串联在一起的线性结构

2. 组成部分

两部分组成:
一个是数据域data(值)
一个是指针域next(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链接的入口节点称为链表的头结点也就是head。

3. 分类

1. 单链表

【八股文】算法篇_第1张图片
单链表中的节点只能指向节点的下一个节点。

1. 笔记

节点的单个next操作并不会改变链表的结构

2. 相关的解决方案
1. 虚拟头节点(dummy)+ 双指针(pre被删除的节点、判断删除标志的节点)

删除头节点的时候,只需要按照原来的方式删除节点就可以了(操作上一个节点)

ListNode dummy = new ListNode(0, head);

// 双指针
ListNode pre = dummy; // 上一个节点,pre节点始终代表的是被删除节点的上一个节点
ListNode cur = head; // 当前节点
  • 直接使用原来的链表来进行删除操作。
  • 设置一个虚拟头结点在进行删除操作(推荐这种方法)
2. 求单链表的长度
int lenA = 0;
while (curA != null) { // 求链表A的长度
    lenA++;
    curA = curA.next;
}
3. 不改变原链表

只需要把原链表复制出来到新的参数就行了,然后操作新的参数

    public static ListNode method(ListNode head) {
        // 不改变原来链表
        ListNode cur = head;
        
		// 后续的一切操作都根据cur来		
2. 双链表

一个数据data,2个指针prev、next
【八股文】算法篇_第2张图片

双链表 既可以向前查询也可以向后查询。

3. 循环链表(环形链表)

就是链表首尾相连。

在单链表的基础上修改,最后一个节点的指针不是null,而是指向了第一个节点head(也可能指向其他节点)

1. 如何判断一个链表有环(双指针:slow1步,fast2步)

用双指针,slow每次走1步fast每次走2步,那么fast和slow指针的距离每次多1步,如果有环,fast肯定会突然跑到slow后面,每次多走一步,总有一次会追上slow的

2. 如何计算环形链表的节点

【八股文】算法篇_第3张图片
slow1步,fast2步,假设fast指针只在环中转了一圈,2*(x+y) = x+y+z+y得出 x = z(这个结论很关键!)

index1从head开始走,index2从相遇点开始走,直到他们相遇,相遇点就是环形的入口

4. 链表的存储方式

内存中不连续,通过指针串联在一起

5. 代码如何定义一个单链表

public class ListNode {
    int val; // 值
    ListNode next; // 指针
    // 三个构造函数
    ListNode() {}
    ListNode(int val) { this.val = val; }
    ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

// Test
public static void main(String[] args) {

    ListNode listNode1 = new ListNode(1, null);
    ListNode listNode2 = new ListNode(2, listNode1);
    ListNode listNode3 = new ListNode(3, listNode2);
    ListNode listNode4 = new ListNode(4, listNode3); //这是head
    System.out.println(listNode4);
}

6. 链表相关操作

1. 删除节点

【八股文】算法篇_第4张图片
被删除节点的上一个节点的next指针指向被删除节点的下一个节点就行了,JVM会自动回收这个被删除的节点

白话文:如果删除的节点是N,则把N-1的这个节点的next指针指向N+1的这个节点

1. 时间复杂度:O(1)

但是要删之前需要查询吧,查询的时间复杂度O(n)

2. 添加节点

【八股文】算法篇_第5张图片
要添加位置的上个节点的next指针的值取出来,存放到自己的next指针
然后把要添加位置的上个节点的next指针指向自己,然后把自己的next指针指向

白话文:把你的小弟给我,当我的小弟,然后让我来当你的小弟

tips:对于单链表来说。要添加位置的下个节点是不需要任何修改的

1. 时间复杂度:O(1)
3. 查询节点
1. 时间复杂度:O(n)

要查最后一个节点,需要从第一个节点通过next指针一直一直查下去,直到最后一个节点

4. 哈希表(散列表)

1. 优点

一般哈希表都是用来快速判断一个元素是否出现集合里,时间复杂度O(1)

2. 缺点

牺牲了空间换取了时间,我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找

3. 哈希函数(采用哈希取余运算)

使用hashcode,把其他数据格式转化为数值

hashFunction = hashcode % tableSize

4. 哈希碰撞及解决方案

1. 拉链法(Java中的HashMap就是这么实现的)

拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间

2. 线性探测法

一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。

白话文:如果Hash碰撞了,就向下找一个空位放置,本质还是在一个数组中的(问题来了,查询怎么办?)

5. 常见的三种哈希结构

1. 数组

数组在某种意义上就是一张哈希表(key是数组下标,value是数组的值)

2. set集合
1. HashSet
2. TreeSet 有序集合
3. Map映射
1. HashMap
2. TreeMap

5. 栈

栈是先进后出,栈没有迭代器iterator
【八股文】算法篇_第6张图片
栈其实就是递归的一种是实现结构,常用方法

  1. 压栈(push)
  2. 弹栈(pop)
  3. 查看最上一层的元素(peek)
  4. 查看栈是否为空(isEmpty、empty)

参考:栈的几个常用方法

6. 队列

队列是先进先出,队列也没有迭代器iterator

如RocketMq消息队列

常用方法:

  1. 入队(offer)
  2. 出队(poll)
  3. 查看出队元素(peek)
  4. 查看队列是否为空(isEmpty)

7. 二叉树

1. 分类

1. 满二叉树(满节点的二叉树)

【八股文】算法篇_第7张图片

  • 如果深度为k,有2^k-1个节点的二叉树 (等比数列求和公式)

  • 深度为k,对应的那层的节点数为2^(k-1)

2. 完全二叉树(从上到下,从左到右,是连续的就行)

【八股文】算法篇_第8张图片

  1. 除了最底层节点可能没填满外,其余每层节点数都达到最大值
  2. 最下面一层的节点都集中在该层最左边的若干位置(为了算法而设定的吧,这样的话节点就是连续的了,中间没有空挡)
3. 二叉搜索树

【八股文】算法篇_第9张图片

二叉搜索树是有数值的树,是一个有序的树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
  • 它的左、右子树也分别为二叉排序树
4. 平衡二叉搜索树(AVL)

【八股文】算法篇_第10张图片
在二叉搜索树的基础上,多了个条件:左右子树的高度差不能超过1

2. 存储方式

1. 链式存储(链表实现,每个节点都有2个指针)(算法中用这个比较多)

链式存储则是通过指针把分布在散落在各个地址的节点串联一起,内存不连续
【八股文】算法篇_第11张图片

1. 代码如何使用链式存储定义一个二叉树
public class Tree {
    int val; // 值
    Tree left; // 左子树
    Tree right; // 右子树
    // 构造函数
    public Tree() {
    }
    public Tree(int val, Tree left, Tree right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}
2. 顺序存储(数组实现,因为数组连续,所以实现一定是完全二叉树)

顺序存储的元素在内存是连续分布的

【八股文】算法篇_第12张图片

1. 公式(记不住的,看图就行)

如果父节点的数组下标是 i,那么

  • 它的左孩子就是 i * 2 + 1
  • 它的右孩子就是 i * 2 + 2

3. 二叉树的遍历方式

我们把好好的数据变成二叉树的形式,肯定有其中的道理,奥秘所在

根据遍历的侧重点不一样,分为:

1. 深度优先遍历

先往深走,遇到叶子节点再往回走

下面的前中后,其实指的就是中间节点的遍历顺序,只要大家记住 前中后序指的就是中间节点的位置就可以了。
【八股文】算法篇_第13张图片

1. 分类
  1. 前序遍历(递归法,迭代法)
    中左右
  2. 中序遍历(递归法,迭代法)
    左中右
  3. 后序遍历(递归法,迭代法)
    左右中
2. 如何实现(用栈的递归特性)

可以借助使用非递归的方式来实现的

2. 广度优先遍历

一层一层的去遍历

1. 层次遍历(迭代法)
2. 如何实现(用队列的先进先出特性)

一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。

1. 我的工具

二分法

1. 重要参数

  • 有序数组、无重复数据
  • 边界条件(循环不变量原则

区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。

我就用左闭右闭这种方法好了√

主要思路:将数组分一半,定义左闭区间left,右闭区间right,中间下标middle,根据某种条件(视情况而定),判断答案在哪个区间,如果在左边,则把right = middle -1;如果在右边,则left = middle + 1;

二分法的难点就在于如何确定这个某种条件

2. 问题的特点

  • 当出现有序数组的时候想想是不是可以用二分法

双指针

双指针法(快慢指针法): 通过一个快指针fast慢指针slow

1. 组成

  • 快指针fast:主要用于遍历,跟着for循环走,就是for循环的i参数
  • 慢指针slow用于记录

2. 作用

在一个for循环下完成两个for循环的工作,降低时间复杂度

白话文:把2个for循环变成一个for循环

递归

【八股文】算法篇_第14张图片

1. 重要参数

  1. 递归临界点(遍历结束条件)
  2. 找规律,求出公式,寻找等价关系,缩小递归范围

1. 时间复杂度

递归算法的时间复杂度:递归的次数 * 每次递归中的操作次数

滑动窗口(本质是双指针,left窗口左边界、right窗口右边界)

不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。

1. 重要参数

三个问题确定好就行

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

例如:

  • 窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
  • 窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
  • 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。

2. 动作

  • 扩大窗口:right++
  • 缩小窗口:left–

3. 注意事项

左右窗口的起始点都是数组的第一位,这时候窗口的大小是0

4. 问题的特点

关键字:连续

哈希表

1.组成

1. 数组实现

一般用int数组,其中key是数组的下标,value是数组中元素的值

2. Set实现
1. HashSet

主要方法:

hashSet.contains(Object o);

HashSet没有get方法(当然没有了。数组可以通过下标获取,你Set能通过什么获取哇?),遍历取Set的值的时候用增强for循环,坑的

int index = 0;
for (Integer i : resSet) {
    res[index++] = i;
}
3. Map实现

当需要存放两个元素的时候用到

hashMap.containsKey(Object key);

问题的特点

  1. 如果题目有限制数据的大小,可以用数组作为哈希表,数组的建立需要初始值

回溯算法

关键字:穷举for循环横向遍历递归纵向遍历叶子节点是答案

当for循环写不出代码的时候,可以考虑回溯

回溯是递归的副产品,只要有递归就会有回溯。
【八股文】算法篇_第15张图片

1. 模板

【八股文】算法篇_第16张图片

2. 问题的特点

组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等

贪心算法

贪心的本质是选择每一阶段的局部最优,从而达到全局最优

通过局部最优,达到全局最优,如果找不到反例,那么就可以用贪心算法!

1. 模板

  1. 将问题分解为若干个子问题
  2. 找出适合的贪心策略
  3. 求解每一个子问题的最优解
  4. 将局部最优解堆叠成全局最优解

2. 问题的特点

能找到,局部最优,没有反例

动态规划

1. 模板

  1. 确定dp数组以及下标的含义:

  2. 递推公式:

  3. dp数组初始化值:

  4. 确定遍历顺序:

  5. 举例推导dp数组

2. 问题的特点

某一问题有很多重叠子问题
每一个状态一定是由上一个状态推导出来的

3. 直面灵魂的三个问题

这道题目我举例推导状态转移公式了么?
我打印dp数组的日志了么?
打印出来了dp数组和我想的一样么?

01背包问题

数组模型:dp

2. Java中我可以用到的一些类

Scanner相关

常规操作:

public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in);

	while (scanner.hasNextLine()) {
	    String s = scanner.nextLine();
	    List<String> strings = Arrays.asList(s.split(","));
	    System.out.println(strings);
	}
}

1. nextXXX

将输入信息标记为XXX类型,常用nextLine、nextInt、nextBigdecimal

scanner.nextLine()方法,读取输入,包括空格除回车以外的所有符号

2. hasNextXXX

如scanner.hasNextLine()方法,判断当前是否有输入,当键盘有输入后返回true,否则会一直等待键盘输入

参考链接:Java Scanner类的常用方法及用法(很详细)

String相关

s.toCharArray()

public char[] toCharArray(){}

将字符串转化为char数组

s.charAt(i)

public char charAt(int index){}

读取String中的第i个char字符

s1.compareTo(s2)

public int compareTo(String anotherString) {}
1.比较两个数字(仅限0到9)的时候:

若s1 < s2,返回-1,小于0
若s1 = s2,返回 0,等于0
若s1 > s2,返回 1,大于0

2. 比较中有涉及非数字的时候:

例子:
a.compareTo(A) = 32
A.compareTo(a) = -32
0.compareTo(A) = 17

3. ASCII码表

【八股文】算法篇_第17张图片

0对应48
A对应65
a对应97

ASCII表:链接

1. 题外话:int和char之间的转换(通过加减字符’0’去实现)
  • char转int(减字符’0’)
    int i = char - ‘0’ 就可以实现,如char = ‘5’,那么i = 5;char = ‘A’,那么i = 17
Scanner scanner = new Scanner(System.in);
while(scanner.hasNextLine()) {
    String s = scanner.nextLine();
    char[] chars = s.toCharArray();
    for (char aChar : chars) {
        int i = aChar - '0';
        System.out.println(i);
    }
}
  • int转char(加字符’0’)
    char char = i + ‘0’ 就可以实现

char相关

int相关

  • 取int的个位数、十位数、百位数。。。。(这种取数的方法只能从个位数开始)
while (n > 0) {
    int i = n % 10; // 取个位数
    System.out.print(i);
    n = n / 10; // 如把原来的十位数放到个位数
}

StringBuffer

reverse

public synchronized StringBuffer reverse() {}

将里面的字符串翻过来,如123变成321
例如

StringBuffer stringBuffer = new StringBuffer(str).reverse();

List相关用法

sort(弃用)

default void sort(Comparator<? super E> c) {}

默认是升序的

1. 例子
list.sort((s1, s2) ->
	// 这里返回的数据 若小于0的在前,所以是升序 
	s1.compareTo(s2)
	// 下面是降序的操作 相当于强行转了
	//s1.compareTo(s2) < 0 ? 1 : -1
);

这个结果默认是升序的

Arrays.sort(nums);(推荐)

默认升序

使用IntStream自定义排序规则

如按照绝对值大小排序

IntStream.of(nums)
	// 装箱
	.boxed()
	.sorted((o1,o2)->Math.abs(o1) - Math.abs(o2))
	// 拆箱
	.mapToInt(Integer::intValue)
	.toArray();
	

Math相关

Math.max 最大值

public static int max(int a, int b) {}

取两个int中最大的,除了int 还有float、long、double类型

Math.min 最小值

public static int max(int a, int b) {}

取两个int中最小的,除了int 还有float、long、double类型

Math.abs 绝对值

public static int abs(int a) {}

取int值的绝对值,除了int 还有float、long、double类型

Math.pow(double a, double b)

Math.pow(2, 3) = 8

Iterator相关

1. hasNext

判断集合中是否还有下一个元素

2. next

指针指向下一个元素,并返回这个元素

3. 参考代码

while (iterator.hasNext()) {
    Object o = iterator.next();
    System.out.println(o);
}

TreeMap(根据key升序排序)

TreeMap自身实现了排序,根据AscII表升序排列(只根据第一个字母,然后再第二个字母排序。。)

public static void main(String[] args) {
    TreeMap<String, String> tmp = new TreeMap<String, String>();
    // 随便put操作
    tmp.put("a", "aaa");
    tmp.put("c", "ccc");
    tmp.put("A", "AAA");
    tmp.put("B", "BBB");
    tmp.put("12", "111");
    tmp.put("11", "111");
    tmp.put("3", "333");
    tmp.put("2", "222");
    tmp.put("4", "444");
    
    // 遍历操作
    Iterator<String> iterator = tmp.keySet().iterator();
    while (iterator.hasNext()) {
        Object key = iterator.next();
        System.out.println(key +":"+ tmp.get(key));
    }
}

控制台

Connected to the target VM, address: '127.0.0.1:50642', transport: 'socket'
11:111
12:111
2:222
3:333
4:444
A:AAA
B:BBB
a:aaa
c:ccc

TreeSet(排序升序+去重)

TreeSet自身也实现了排序,根据AscII表升序排列,且会根据字段进行去重

public static void main(String[] args) {
    TreeSet<String> treeSet = new TreeSet<>();
    treeSet.add("1");
    treeSet.add("3");
    treeSet.add("5");
    treeSet.add("a");
    treeSet.add("a");
    treeSet.add("A");
    treeSet.add("b");

    Iterator<String> iterator = treeSet.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}

控制台

Connected to the target VM, address: '127.0.0.1:54116', transport: 'socket'
1
3
5
A
a
b
题外话:迭代器和栈的原理是不是有点类似

2. 经典面试题

你可能感兴趣的:(算法,八股文,算法,链表,数据结构)