You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
735 lines
14 KiB
735 lines
14 KiB
3 years ago
|
---
|
||
|
title: 数据结构和算法-未完成
|
||
|
author: TianZD
|
||
|
top: true
|
||
|
cover: true
|
||
|
toc: true
|
||
|
mathjax: false
|
||
|
summary: 基于Java的数据结构和算法,未完成,粗略学了一下,没有参考价值
|
||
|
tags:
|
||
|
- 数据结构
|
||
|
- 算法
|
||
|
categories:
|
||
|
- 数据结构和算法
|
||
|
reprintPolicy: cc_by
|
||
|
abbrlink: b1f89a38
|
||
|
date: 2022-04-29 10:42:56
|
||
|
coverImg:
|
||
|
img:
|
||
|
password:
|
||
|
---
|
||
|
|
||
|
|
||
|
|
||
|
[TOC]
|
||
|
|
||
|
> 概述:学习《漫画算法:小灰的算法之旅》
|
||
|
|
||
|
# 1、数据结构基础
|
||
|
|
||
|
## 1.1、数据结构分类
|
||
|
|
||
|
>数据结构是以某种特定的布局方式可存储数据的容器,布局方式决定了数据结构对于某些操作是高效的,对于其他操作是低效的。
|
||
|
|
||
|
数据结构 = 逻辑结构+物理结构(顺序、链式、索引、散列)
|
||
|
|
||
|
* 逻辑结构
|
||
|
* 线性结构:顺序表、栈、队列
|
||
|
* 非线性结构:树、图
|
||
|
|
||
|
* 物理结构:(存储结构),在计算机存储器中的存储形式
|
||
|
* 顺序存储结构:数组
|
||
|
* 链式存储结构:链表
|
||
|
|
||
|
逻辑分类:
|
||
|
|
||
|
1. 线性结构:结构中的元素存在一对一的相互关系(元素之间有序,一个挨一个),常见的有:线性表、栈、队列、串(一维数组);
|
||
|
2. 树形结构:元素存在一对多的相互关系;常见的有:二叉树、红黑树、B树、哈夫曼树等;
|
||
|
3. 图形结构:元素存在多对多的关系;常见:有向图、无向图、简单图;
|
||
|
|
||
|
## 1.2、数组
|
||
|
|
||
|
### 概述
|
||
|
|
||
|
* 有限个相同类型的变量组成的有序集合
|
||
|
* 物理结构:**顺序存储结构**,在内存中顺序存储
|
||
|
* 逻辑结构:**线性结构**
|
||
|
|
||
|
### 基本操作
|
||
|
|
||
|
> 读取元素
|
||
|
|
||
|
可以直接通过下标进行随机读取,时间复杂度O(1)
|
||
|
|
||
|
> 更新元素
|
||
|
|
||
|
通过下标直接进行赋值替换,时间复杂度O(1)
|
||
|
|
||
|
> 插入元素
|
||
|
|
||
|
涉及到元素的移动,时间复杂度O(n)
|
||
|
|
||
|
* 尾部插入
|
||
|
|
||
|
* 中间插入
|
||
|
|
||
|
* 超范围插入(扩容)
|
||
|
|
||
|
> 删除元素
|
||
|
|
||
|
和插入相反,时间复杂度O(n)
|
||
|
|
||
|
### 优劣势
|
||
|
|
||
|
优点:
|
||
|
|
||
|
* 数据访问:可以通过下标进行,效率高
|
||
|
|
||
|
缺点:
|
||
|
|
||
|
* 插入删除:在内存中采用顺序存储,插入和删除导致大量元素移动,效率低
|
||
|
|
||
|
总结:适用于**读操作多、写操作少**的场景
|
||
|
|
||
|
## 1.3、链表
|
||
|
|
||
|
### 概述
|
||
|
|
||
|
* 分为单向链表和双向链表
|
||
|
* 物理上非连续、非顺序,在内存中**随机存储**
|
||
|
* 由若干节点**node**组成
|
||
|
* 物理结构:**链式存储结构**
|
||
|
* 逻辑结构:**线性结构**
|
||
|
|
||
|
> 单向链表
|
||
|
|
||
|
每一个node包含两个属性:
|
||
|
|
||
|
* data:存放数据的变量
|
||
|
* node节点指针next:指向下一个节点
|
||
|
|
||
|
```java
|
||
|
private static class Node{
|
||
|
int data;
|
||
|
Node next;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
单向链表只能通过next指针找到关联的下一个节点,逐一寻找,只需要知道第一个节点,便可以根据找到所有的节点,因此用第一个节点标识链表,单向链表的第一个节点又称为**头节点**
|
||
|
|
||
|
头尾节点:
|
||
|
|
||
|
* 头节点Head:链表的第一个节点
|
||
|
* 尾节点:链表的最后一个节点,next指针指向空,null
|
||
|
|
||
|
> 双向链表
|
||
|
|
||
|
双向链表每一个node包含三个属性:
|
||
|
|
||
|
* data
|
||
|
* node节点指针next
|
||
|
* **node节点指针prev:指向前一个节点**
|
||
|
|
||
|
### 基本操作
|
||
|
|
||
|
> 查找节点
|
||
|
|
||
|
从头节点开始,向后逐一查找,时间复杂度O(n)
|
||
|
|
||
|
> 更新节点
|
||
|
|
||
|
找到后直接将node节点数据进行替换,查找时间复杂度O(n),替换复杂度O(1)
|
||
|
|
||
|
> 插入节点
|
||
|
|
||
|
如果不考虑查找元素的过程,时间复杂度O(1)
|
||
|
|
||
|
* 尾部插入
|
||
|
|
||
|
将尾节点的next指针指向新节点
|
||
|
|
||
|
* 头部插入
|
||
|
|
||
|
1.把新节点的next指向原来的头节点
|
||
|
|
||
|
2.把新节点变为头节点
|
||
|
|
||
|
* 中间插入
|
||
|
|
||
|
1.将新节点的next指向插入位置的节点
|
||
|
|
||
|
2.将插入位置前的节点的next指向新节点
|
||
|
|
||
|
> 删除元素
|
||
|
|
||
|
如果不考虑查找元素的过程,时间复杂度O(1)
|
||
|
|
||
|
* 尾部删除
|
||
|
|
||
|
把倒数第二个node的next指向空
|
||
|
|
||
|
* 头部删除
|
||
|
|
||
|
把链表的头节点设为原来的第二个节点
|
||
|
|
||
|
* 中间删除
|
||
|
|
||
|
把要删除节点的前一个节点的next指向后一个节点
|
||
|
|
||
|
### 优劣势
|
||
|
|
||
|
优势:
|
||
|
|
||
|
* 在于能够灵活地进行插入和删除操作
|
||
|
|
||
|
劣势:
|
||
|
|
||
|
* 查找需要从头部开始逐一遍历
|
||
|
|
||
|
## 1.4、栈
|
||
|
|
||
|
### 概述
|
||
|
|
||
|
* 逻辑结构:**线性存储结构**
|
||
|
* 物理结构:可以用数组实现也可以用链表
|
||
|
* 先入后出FILO
|
||
|
* 栈顶:最后进入元素的位置
|
||
|
* 栈底:最先进入元素的位置
|
||
|
|
||
|
### 基本操作
|
||
|
|
||
|
> 入栈push
|
||
|
|
||
|
把新元素放入栈中,只能从栈顶放入,作为新的栈顶
|
||
|
|
||
|
时间复杂度O(1)
|
||
|
|
||
|
> 出栈pop
|
||
|
|
||
|
把元素弹出,只能弹出栈顶元素
|
||
|
|
||
|
时间复杂度O(1)
|
||
|
|
||
|
### 应用
|
||
|
|
||
|
输出顺序和输入顺序相反,通常用于对“历史”的回溯
|
||
|
|
||
|
* 代替递归
|
||
|
* 面包屑导航(浏览页面)
|
||
|
|
||
|
## 1.5、队列
|
||
|
|
||
|
### 概述
|
||
|
|
||
|
* 逻辑结构:**线性存储结构**
|
||
|
* 物理结构:可以用数组实现也可以用链表
|
||
|
* 先入先出FIFO
|
||
|
* 对头front
|
||
|
* 对尾rear
|
||
|
|
||
|
### 基本操作
|
||
|
|
||
|
> 入队enqueue
|
||
|
|
||
|
把新元素放入队尾
|
||
|
|
||
|
时间复杂度O(1)
|
||
|
|
||
|
> 出队dequeue
|
||
|
|
||
|
把对头元素移出队列
|
||
|
|
||
|
时间复杂度O(1)
|
||
|
|
||
|
> 循环队列
|
||
|
|
||
|
数组实现的队列为了防止出队时,对头左边的空间失去作用,可以采用循环队列的方式维持队列容量的恒定
|
||
|
|
||
|
* 入队:
|
||
|
* 队列满:`(rear+1)%array.length==front`
|
||
|
* `rear=(rear+1)%array.length;`
|
||
|
* 出队:
|
||
|
* 队列空:`rear==front`
|
||
|
* `front=(front+1)%array.length;`
|
||
|
|
||
|
### 应用
|
||
|
|
||
|
输出顺序和输入顺序相同,用于对历史的回放
|
||
|
|
||
|
* 多线程中争夺锁
|
||
|
* 网络爬虫抓取
|
||
|
|
||
|
## 1.6、散列表(哈希表)
|
||
|
|
||
|
### 概述
|
||
|
|
||
|
* 散列表提供了**键(key)** 和**值(value)**的映射关系,可以根据key快速找到对应的value,时间复杂度接近O(1)
|
||
|
* 本质上是**数组**
|
||
|
* **通过哈希函数将key转换为数组中的下标**
|
||
|
|
||
|
### 哈希函数
|
||
|
|
||
|
> hashcode
|
||
|
|
||
|
在java等面向对象的语言中,每一个对象都有自己的**hashcode**,hashcode是区分不同对象的重要标识,不论对象的类型是什么,hashcode都是一个**整型变量**
|
||
|
|
||
|
> 转化
|
||
|
|
||
|
通常采用hashcode对数组长度的**取模运算**
|
||
|
|
||
|
`index = HashCode(key) % Array.length`
|
||
|
|
||
|
java中的哈希函数采用的是**位运算**的方式,将数组长度取为2的次方,提高性能。
|
||
|
|
||
|
### 基本操作
|
||
|
|
||
|
> 写操作
|
||
|
|
||
|
1. 通过哈希函数,将key转化为数组下标
|
||
|
2. 放入元素
|
||
|
1. 如果对应的下标没有元素,直接放入
|
||
|
2. 如果已有元素,产生**哈希冲突**
|
||
|
|
||
|
哈希冲突解决:
|
||
|
|
||
|
* **开放寻址法**:已有元素时,`index++`,放入下一个,直至对应的下标没有元素占用,ThreadLocal采用
|
||
|
* **链表法**:**HashMap采用**,其数组中的每一个元素对应链表的一个节点,最先放入的为头节点,后续放入的通过将上一个节点的next指向该元素,插入到链表中
|
||
|
|
||
|
> 读操作
|
||
|
|
||
|
1. 通过哈希函数,将key转化为数组下标
|
||
|
2. 找到对应下标对应的元素
|
||
|
1. 如果只有一个,则找到
|
||
|
2. 如果对应的链表,遍历链表,**直到key值对应**
|
||
|
|
||
|
> 扩容
|
||
|
|
||
|
当多次插入后,key映射位置发生冲突的概率提高,大量元素拥挤在相同的位置,形成链表很长,导致查询和写入效率低下
|
||
|
|
||
|
条件:
|
||
|
|
||
|
`HashMap.Size >= Capacity * LoadFactor`
|
||
|
|
||
|
**步骤**:
|
||
|
|
||
|
* **扩容**:新建一个空数组,长度为两倍
|
||
|
* **重新Hash**:数组长度变化,需要重新根据哈希函数计算哈希值
|
||
|
|
||
|
## 1.7、树和二叉树
|
||
|
|
||
|
### 概述
|
||
|
|
||
|
> 树
|
||
|
|
||
|
* 逻辑结构:非线性结构
|
||
|
|
||
|
> 二叉树
|
||
|
|
||
|
|
||
|
|
||
|
### 二叉树应用
|
||
|
|
||
|
> 查找
|
||
|
|
||
|
|
||
|
|
||
|
> 维持相对顺序
|
||
|
|
||
|
### 二叉树遍历
|
||
|
|
||
|
从节点位置:
|
||
|
|
||
|
* **前序遍历**
|
||
|
* **中序遍历**
|
||
|
* **后序遍历**
|
||
|
* **层序遍历**
|
||
|
|
||
|
从宏观角度:
|
||
|
|
||
|
* **深度优先遍历:前序、中序、后序**
|
||
|
* **广度优先遍历:层序**
|
||
|
|
||
|
> 深度优先遍历
|
||
|
|
||
|
|
||
|
|
||
|
> 广度优先遍历
|
||
|
|
||
|
|
||
|
|
||
|
## 1.8、二叉堆
|
||
|
|
||
|
### 概述
|
||
|
|
||
|
* 最大堆
|
||
|
* 最小堆
|
||
|
|
||
|
### 自我调整
|
||
|
|
||
|
|
||
|
|
||
|
> 插入
|
||
|
|
||
|
|
||
|
|
||
|
> 删除
|
||
|
|
||
|
|
||
|
|
||
|
> 构建
|
||
|
|
||
|
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
## 1.9、优先队列
|
||
|
|
||
|
### 概述
|
||
|
|
||
|
|
||
|
|
||
|
### 实现
|
||
|
|
||
|
|
||
|
|
||
|
# 2、排序算法
|
||
|
|
||
|
## 2.1、分类
|
||
|
|
||
|
|
||
|
|
||
|
## 2.2、冒泡排序
|
||
|
|
||
|
### 基本冒泡排序
|
||
|
|
||
|
交换排序,两两比较,两层循环,第一层为遍历的轮数:`array.length-1`
|
||
|
|
||
|
时间复杂度O(n^2)
|
||
|
|
||
|
### 优化冒泡排序
|
||
|
|
||
|
> 第一轮
|
||
|
|
||
|
防止数列已经有序后仍然进行下一轮排序
|
||
|
|
||
|
如{5,8,6,3,9,2,1,7}
|
||
|
|
||
|
> 第二轮
|
||
|
|
||
|
现有逻辑:从`for(int j=0;j<array.length-1;i++)`可以看出有序区的长度为以进行排序的轮数`i`
|
||
|
|
||
|
当后面大部分已经有序后,仍然会进行比较,对此进行优化
|
||
|
|
||
|
如{3,4,2,1,5,6,7,8}
|
||
|
|
||
|
### 鸡尾酒排序
|
||
|
|
||
|
双向交换,奇数轮从左向右,偶数轮从右向左
|
||
|
|
||
|
解决大部分已经有序的情况,如{2,3,4,5,6,7,8,1}
|
||
|
|
||
|
## 2.3、快速排序
|
||
|
|
||
|
### 概述
|
||
|
|
||
|
核心:**分治思想**
|
||
|
|
||
|
选定基准元素,大的放右边,小的放左边,一分为2
|
||
|
|
||
|
时间复杂度O(nlogn)
|
||
|
|
||
|
### 基准元素选择
|
||
|
|
||
|
* 默认数列的第一个元素
|
||
|
* 可以随机选择一个元素作为基准,然后和首元素交换位置
|
||
|
|
||
|
### 元素交换
|
||
|
|
||
|
> 双边循环法
|
||
|
|
||
|
* 基准元素pivot
|
||
|
* 左指针left
|
||
|
* 右指针right
|
||
|
|
||
|
> 单边循环法
|
||
|
|
||
|
* 基准元素pivot
|
||
|
* 元素区域边界mark
|
||
|
|
||
|
### 递归、非递归实现
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
## 2.4、堆排序
|
||
|
|
||
|
|
||
|
|
||
|
## 2.5、计数排序和桶排序
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
## 2.6、排序算法复杂度分析
|
||
|
|
||
|
| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 是否稳定 |
|
||
|
| ---------- | -------------- | -------------- | ---------- | -------- |
|
||
|
| 冒泡排序 | O(n^2) | O(n^2) | O(1) | 稳定 |
|
||
|
| 鸡尾酒排序 | O(n^2) | O(n^2) | O(1) | 稳定 |
|
||
|
| 快速排序 | O(nlogn) | O(n^2) | O(logn) | 不稳定 |
|
||
|
| 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
|
||
|
| 计数排序 | O(n+m) | O(n+m) | O(m) | 稳定 |
|
||
|
| 桶排序 | O(n) | O(nlogn) | O(n) | 稳定 |
|
||
|
|
||
|
# 3、面试算法
|
||
|
|
||
|
## 3.1、判断链表是否有环
|
||
|
|
||
|
### 基础版
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
如何判断一个链表是否有环
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
链表若有环,两个指针分别不同速度,速度快的会追上速度慢的
|
||
|
|
||
|
* 两个指针指向头节点:`Node p1 = head; Node p2 = head;`
|
||
|
* p1每次移动一个位置:`p1=p1.next;`
|
||
|
* p2每次移动两个位置`p2=p2.next.next;`
|
||
|
* p1和p2相遇则有环:`p1==p2`
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
### 扩展
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
若有环,求环的长度和入环节点
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
**环的长度**:p1和p2的速度相差1,因此二次相遇之间走过的步数就是环的长度
|
||
|
|
||
|
**入环节点**:通过数学计算可以得到,入环点到头节点的距离==首次相遇点到入环点之间的距离,因此首次相遇后,把一个指针重新指向头节点,两个指针同时一次走一步,再次相遇的点就是入环节点
|
||
|
|
||
|
## 3.2、最小栈
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
实现一个栈,有出栈、入栈,另外要有取最小值方法,同时三个方法的时间复杂度都为O(1)
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
## 3.3、求最大公约数
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
求两个整数的最大公约数
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
**辗转相除法(欧几里得算法)**:两个正整数a和b(a>b)的最大公约数等于a除以b的余数c和b之间的最大公约数
|
||
|
|
||
|
**更相减损法**:两个正整数a和b(a>b)的最大公约数等于a-b的差值c和b之间的最大公约数
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
> 优化
|
||
|
|
||
|
位运算
|
||
|
|
||
|
|
||
|
|
||
|
## 3.4、判断一个数是否是2的整数次幂
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
判断一个数是否是2的整数次幂
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
* 2的整数次幂装换位二进制时,首位为1,其余位为0
|
||
|
* 2的整数次幂减去1,转换为二进制时,全部为1,首位为0
|
||
|
* 与运算&
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
## 3.5、无序数组排序后的最大相邻差
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
求出数组排序后的任意两个相邻元素的最大差值
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
采用桶排序的思想,
|
||
|
|
||
|
* 分桶
|
||
|
* 入桶,记录每一个桶的最大值和最小值
|
||
|
* 统计相邻两个桶的最大值和最小值的差
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
## 3.6、用栈实现队列
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
用栈模拟队列,实现:入队、出队
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
* 两个栈A和B
|
||
|
* 入队在A中压栈
|
||
|
* 出队
|
||
|
* B是否为空,空的话首先将A中的元素弹出到B中
|
||
|
* B弹栈
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
## 3.7、寻找全排列的下一个数
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
给一个正整数,找出全排列的下一个数
|
||
|
|
||
|
用这个数包含的全部数字重新排列,找出一个大于且仅大于原数的新整数
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
固定的几个数字进行排列,逆序排列时数字最大,顺序排列时数字最小,同时为了保持重新排列的数仅比原数大,要尽可能保持高位不变,仅仅改变低位的数字
|
||
|
|
||
|
* 从后找,找到当前整数的逆序区域(逆序区域已经是最大的了,要找到不是逆序区域的数字的位置),如12354,只变换5和4不能满足比原数大,要从3开始变
|
||
|
* 更换找到的位置的数字和最末尾数字,变为12435
|
||
|
* 把逆序区域变为顺序,即交换5和4,保持最小
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
## 3.9、删除k个数字后的最小值
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
给一个整数,删除k个数字,要求剩下的数字尽可能小
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
删除掉一个数字后,整数的位数减少1
|
||
|
|
||
|
需要从高位起,若该位置数字比后一位大,则需要去掉
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
## 3.9、实现大整数相加
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
实现两个很大的整数相加
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
根据手动相加思路,低位相加,大于10后,高位加1
|
||
|
|
||
|
* 数字逆序存到两个数组中
|
||
|
* 遍历,若两个数组对应的位置值相加大于10,则保留个位,且高位加1
|
||
|
* 下一位相加,同时要加上低位进的1
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
## 3.10、金矿问题
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
10名工人,5堆金矿,价值{500,400,350,300,200},需要人力{5,5,3,4,3}
|
||
|
|
||
|
求解如何最大收益
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
动态规划
|
||
|
|
||
|
|
||
|
|
||
|
> 代码
|
||
|
|
||
|
|
||
|
|
||
|
## 3.11、寻找缺失的整数
|
||
|
|
||
|
### 基本问题
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
一个无序数组,有99个不重复的正整数,范围1-100,求出100中缺少的那个
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
先计算累加和,然后减去有的99个数,得到的即为缺少的
|
||
|
|
||
|
### 扩展1
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
数量若干,同时99个整数出现了偶数次,1个出现了奇数次,求出这一个数
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
异或运算
|
||
|
|
||
|
### 扩展2
|
||
|
|
||
|
> 问题
|
||
|
|
||
|
2个数字出现了奇数次,求这两个数
|
||
|
|
||
|
> 思路
|
||
|
|
||
|
分治法
|
||
|
|
||
|
# 4、算法的应用
|
||
|
|
||
|
## 4.1、Bitmap
|
||
|
|
||
|
|
||
|
|
||
|
## 4.2、LRU
|
||
|
|
||
|
|
||
|
|
||
|
## 4.3、A星寻路算法
|
||
|
|
||
|
|
||
|
|
||
|
## 4.4、红包算法
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|