目录
相关图文参考链接:代码随想录
1. 数据结构
1. 字符串
1. 笔记
常用到StringBuilder类,常用方法有 append、chatAt
操作字符串,本质是操作StringBuilder
2. 数组
1. 定义
数组是存放在连续内存空间上的相同类型数据的集合。
在删除或者增添元素的时候,就难免要移动其他元素的地址
2. 特点
- 数组下标都是从0开始的。
- 一维数组内存空间的地址是连续的,二维数组不是
- 数组的元素是不能删的,只能覆盖。
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. 数组问题的一般工具
- 二分法
- 双指针
- 滑动窗口(本质也是双指针)
3. 链表
1. 定义
链表是一种通过指针串联在一起的线性结构,
2. 组成部分
两部分组成:
一个是数据域data(值)
一个是指针域next(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链接的入口节点称为链表的头结点也就是head。
3. 分类
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

双链表 既可以向前查询也可以向后查询。
3. 循环链表(环形链表)
就是链表首尾相连。
在单链表的基础上修改,最后一个节点的指针不是null,而是指向了第一个节点head(也可能指向其他节点)
1. 如何判断一个链表有环(双指针:slow1步,fast2步)
用双指针,slow每次走1步,fast每次走2步,那么fast和slow指针的距离每次多1步,如果有环,fast肯定会突然跑到slow后面,每次多走一步,总有一次会追上slow的
2. 如何计算环形链表的节点

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. 删除节点

将被删除节点的上一个节点的next指针指向被删除节点的下一个节点就行了,JVM会自动回收这个被删除的节点
白话文:如果删除的节点是N,则把N-1的这个节点的next指针指向N+1的这个节点
1. 时间复杂度:O(1)
但是要删之前需要查询吧,查询的时间复杂度O(n)
2. 添加节点

把要添加位置的上个节点的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

栈其实就是递归的一种是实现结构,常用方法
- 压栈(push)
- 弹栈(pop)
- 查看最上一层的元素(peek)
- 查看栈是否为空(isEmpty、empty)
参考:栈的几个常用方法
6. 队列
队列是先进先出,队列也没有迭代器iterator
如RocketMq消息队列
常用方法:
- 入队(offer)
- 出队(poll)
- 查看出队元素(peek)
- 查看队列是否为空(isEmpty)
7. 二叉树
1. 分类
1. 满二叉树(满节点的二叉树)

-
如果深度为k,有2^k-1个节点的二叉树 (等比数列求和公式)
-
深度为k,对应的那层的节点数为2^(k-1)
2. 完全二叉树(从上到下,从左到右,是连续的就行)

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

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

在二叉搜索树的基础上,多了个条件:左右子树的高度差不能超过1
2. 存储方式
1. 链式存储(链表实现,每个节点都有2个指针)(算法中用这个比较多)
链式存储则是通过指针把分布在散落在各个地址的节点串联一起,内存不连续

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. 顺序存储(数组实现,因为数组连续,所以实现一定是完全二叉树)
顺序存储的元素在内存是连续分布的

1. 公式(记不住的,看图就行)
如果父节点的数组下标是 i,那么
- 它的左孩子就是 i * 2 + 1
- 它的右孩子就是 i * 2 + 2
3. 二叉树的遍历方式
我们把好好的数据变成二叉树的形式,肯定有其中的道理,奥秘所在
根据遍历的侧重点不一样,分为:
1. 深度优先遍历
先往深走,遇到叶子节点再往回走
下面的前中后,其实指的就是中间节点的遍历顺序,只要大家记住 前中后序指的就是中间节点的位置就可以了。

1. 分类
- 前序遍历(递归法,迭代法)
中左右 - 中序遍历(递归法,迭代法)
左中右 - 后序遍历(递归法,迭代法)
左右中
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循环
递归

1. 重要参数
- 递归临界点(遍历结束条件)
- 找规律,求出公式,寻找等价关系,缩小递归范围
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);
问题的特点
- 如果题目有限制数据的大小,可以用数组作为哈希表,数组的建立需要初始值
回溯算法
关键字:穷举、for循环横向遍历、递归纵向遍历、叶子节点是答案
当for循环写不出代码的时候,可以考虑回溯
回溯是递归的副产品,只要有递归就会有回溯。

1. 模板

2. 问题的特点
组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等
贪心算法
贪心的本质是选择每一阶段的局部最优,从而达到全局最优。
通过局部最优,达到全局最优,如果找不到反例,那么就可以用贪心算法!
1. 模板
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
2. 问题的特点
能找到,局部最优,没有反例
动态规划
1. 模板
-
确定dp数组以及下标的含义:
-
递推公式:
-
dp数组初始化值:
-
确定遍历顺序:
-
举例推导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码表

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


本文概述了数据结构中的重要概念,如数组操作、链表(单链表、双链表、循环链表)、哈希表(原理、碰撞解决)以及二叉树(遍历、存储)等,还介绍了常用的编程技巧如栈、队列、动态规划。深入理解这些核心内容对于编程实战至关重要。

1980

被折叠的 条评论
为什么被折叠?



