🔰数据结构
优先队列(堆)
直接给你排好
// 新建小根堆
PriorityQueue<Type> pq = new PriorityQueue<Type>();
🎲StringBuilder/StringBuffer(可变字符串)
Builder 速度快
例💡
public class Solution {
public String replaceSpace(String s) {
if (s == null) {
return "";
}
StringBuilder stringBuilder = new StringBuilder();
for (char c : s.toCharArray()){
if (c == ' ') {
stringBuilder.append("%20");
}
else {
stringBuilder.append(c);
}
}
return stringBuilder.toString();
}
}
🎲Stack(栈)
例💡
class Solution {
public int[] reversePrint(ListNode head) {
Stack stack = new Stack<>();
ListNode nowNode = head;
while (nowNode != null) {
stack.push(nowNode.val);
nowNode = nowNode.next;
}
// 尤为注意,不要节省代码,要在遍历前将size 拿出来 否则stack.pop() stack.size()会变化
int size = stack.size();
int[] res = new int[size];
for (int i = 0; i < size; i++) {
res[i] = (int) stack.pop();
}
return res;
}
}
🎲HashSet
例💡
public class Solution {
public int findRepeatNumber(int[] nums) {
// 看到不重复,想到hashSet
HashSet unDiffrent = new HashSet();
for (int num: nums) {
if (unDiffrent.contains(num)) {
return num;
}
unDiffrent.add(num);
}
// 没有重复和nums为null
return -1;
}
}
二叉树
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
🎲线性查找
特点🙉
矩阵,自左向右,自上而下递增,可以根据左下或右上,动态判断方向,减少时间复杂度
关键
只要存在边界内-找条加减对角线
例💡
public class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
// 边界条件 重要!!! 输入null,[] [[],[],[],[]] 这几种情况
if (matrix == null||matrix.length == 0||matrix[0].length == 0) {
return false;
}
int x = matrix[0].length-1,y=0;
// 到左到达边界时停止
while (x>=0&&y<matrix.length){
System.out.println(x+","+y);
int now_number = matrix[y][x];
if (now_number == target) {
return true;
}
else if (target<now_number){
// 左移
x--;
}
// 下移动
else {
y++;
}
}
return false;
}
}
🎲二分查找
💪关键
找到中值-校验-移动左右索引
例💡
class Solution {
/**
*
* @param nums 被查找数组
* @param targht 查找值
* @return
*/
public int binarySearch(int[]nums,int targht) {
// 边界条件
if (nums == null) {
return -1;
}
// 对其排序
// Arrays.sort(nums);
// 左索引,右索引
int left = 0,right=nums.length-1;
while (left<=right) {
// 获取中位位置 = 当前位置右移距离/2
int mid = left + (right-left + 1)/2;
if (nums[mid] == targht) {
return mid;
}
// 比中位值大
else if (nums[mid] < targht) {
// 左索引放到中位右边
left = mid+1;
}
else {
// 右索引放中位左边
right = mid-1;
}
}
return -1;
}
}
}
public class Solution {
public int findRadius(int[] houses, int[] heaters) {
// 初始化最小半径
int ans = 0;
// 对供暖器排序
Arrays.sort(heaters);
// 遍历所有房屋
for (int house : houses) {
// 传入所有供暖期位置和当前房子位置,获取最靠近该房子的左供暖器位置
int i = binarySearch(heaters, house);
// 右供暖器位置
int j = i + 1;
// 左供暖器位置不为-1(-1为左边没有任何供暖器) 求得左距离
int leftDistance = i < 0 ? Integer.MAX_VALUE : house - heaters[i];
// 右供暖器位置不为最右那个供暖器(右边没有任何供暖器) 求得右距离
int rightDistance = j >= heaters.length ? Integer.MAX_VALUE : heaters[j] - house;
// 去左距离与右距离最小为半径
int curDistance = Math.min(leftDistance, rightDistance);
// 比对更新最大半径
ans = Math.max(ans, curDistance);
}
return ans;
}
/**
* 【核心】
* @param nums 所有供暖器
* @param target 当前房屋
* @return 左索引
*/
public int binarySearch(int[] nums, int target) {
// 左索引,右索引
int left = 0, right = nums.length - 1;
// 当前房屋在所有供暖器最左边 返回-1
if (nums[left] > target) {
return -1;
}
// 直到左索引到供暖器最右边,结束循环
// 目的:将左右缩小到最靠近当前房屋的位置
while (left < right) {
// 当前供暖器位置与最右位置的中位
int mid = (right - left + 1) / 2 + left;
// 如果中位在当前房屋右边
if (nums[mid] > target) {
// 右位置向左移
right = mid - 1;
} else {
// 左位置向右移动
left = mid;
}
}
return left;
}
}
🎲回溯
概念
回溯算法是对树形或者图形结构执行一次深度优先遍历,实际上类似枚举的搜索尝试过程,在遍历的过程中寻找问题的解。
深度优先遍历有个特点:当发现已不满足求解条件时,就返回,尝试别的路径。此时对象类型变量就需要重置成为和之前一样,称为「状态重置」
关键
区分变于不变量-史前做好初始化-组合一探一退格-全局index位置先行
例💡
public class Solution {
// 映射
Map<Character, String> number2char = new HashMap() {{
put('2',"abc");
put('3',"def");
put('4',"ghi");
put('5',"jkl");
put('6',"mno");
put('7',"pqrs");
put('8',"tuv");
put('9',"wxyz");
}};
// 存放结果
ArrayList<String> res = new ArrayList<String>();
// 当前组合
StringBuffer combination = new StringBuffer();
// 输入进来的号码
String digits;
// 回溯算法
public void backtrack(int index) {
// index:当前字母位置
// 递归结束条件:组合长度等于输入数字长度
if(combination.length() == digits.length()) {
res.add(combination.toString());
return;
}else {
// 通过map查出当前号码所有字母
String nowChars = number2char.get(digits.toCharArray()[index]);
// 组合里加字母
for (char nowChar: nowChars.toCharArray()) {
combination.append(nowChar);
// 递归
backtrack(index+1);
// 退格(为了下一种情况)
combination.deleteCharAt(combination.length()-1);
}
}
}
// 执行层
public List<String> letterCombinations(String digits) {
// 边界条件
if (digits == null || digits.isEmpty()) return res;
this.digits = digits;
backtrack(0);
return res;
}
}
🎲dfs
关键
失败情况写在前(到达边界+不满足)- 衔接成功少不了-上下左右递归或满足
例💡
public class Solution {
/**
* 深度优先搜索
* @param board 矩阵
* @param words 字符数组
* @param i 矩阵行位
* @param j 矩阵列位
* @param k 字符位置
* @return
*/
public boolean dfs(char[][] board,char[]words,int i,int j,int k){
// 递归结束条件
// 失败 超出边界/当前点不等于当前字符
if(i < 0 || j < 0 || i >= board.length || j >= board[0].length || board[i][j] != words[k]) return false;
// 成功 对应完所有 字符
if (k == words.length-1) return true;
// 特殊字符标记当前位置,避免重复
board[i][j] = '\0';
// 递归 下右上左
boolean result = dfs(board, words, i+1, j, k+1)||dfs(board, words, i, j+1, k+1)||dfs(board, words, i-1, j, k+1)||dfs(board, words, i, j-1, k+1);
// 还原
board[i][j] = words[k];
return result;
}
public boolean exist(char[][] board, String word) {
// 将word转字符数组
char[] words = word.toCharArray();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
// 将每一个点作为开始进行搜素 当前节点不行则看看下一个节点,而不是结束
if (dfs(board,words,i,j,0)) return true;
}
}
return false;
}
}
bfs
特点
- 从根结点开始,沿着树的宽度遍历树的结点。如果所有结点均被访问,则算法中止
- 主要解决 路径,矩阵等问题,跟上下左右有关系的问题
- 列表队列+while循环
关键
初始化个多队列(节点,值)-while判空做循环-终止语句写在前-成功失败来嵌套-上下左右探一探-能够走通加队列
例
public class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
// 边界阻挡
if (root == null) {
return false;
}
// 创建当前节点队列
Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
// 创建存储当前路径和队列
Queue<Integer> valueQueue = new LinkedList<Integer>();
// offer 约等于 add 不用担心大小限制
nodeQueue.offer(root);
valueQueue.offer(root.val);
while (!nodeQueue.isEmpty()) {
// 成功
TreeNode nowNode = nodeQueue.poll();
int temp = valueQueue.poll();
// 到达根节点
if (nowNode.left == null&&nowNode.right == null){
if (temp == targetSum) {
return true;
}
// 节省内存,避免后面的运算
continue;
}
//右子树
if (nowNode.right == null){
nodeQueue.offer(nowNode.right);
valueQueue.offer(nowNode.right.val+temp);
}
//左子树
if(nowNode.left == null) {
nodeQueue.offer(nowNode.left);
valueQueue.offer(nowNode.left.val+temp);
}
}
return false;
}
}
🎲并查集
特点🙉
用来解决:连通分量的问题
并查集跟树有些类似,只不过她跟树是相反的。在树这个数据结构里面,每个节点会记录它的子节点。在并查集里,每个节点会记录它的父节点。
如果节点相互连通,即在同一棵树上,祖先节点相同
第一个节点添加到并查集,父节点为空
概念
- 并(Union),代表合并(认贼作父)
public void merge(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY){
father.put(rootX,rootY);
}
}
- 查(Find),代表查找(寻祖)
public int find(int x) {
int root = x;
while(father.get(root) != null){
root = father.get(root);
}
return root;
}
在此基础上,可做优化(压缩),发现是底层节点,移至二级节点,方便下次找祖宗,我称为"返祖"
public int find(int x) {
int root = x;
while(father.get(root) != null){
root = father.get(root);
}
// 返祖
while(x != root){
// 获取查找的节点的麻麻
int original_father = father.get(x);
// 将查找的节点和找到的祖宗添加到并查集(返租成功)
father.put(x,root);
// x 指向它的麻麻,让其麻麻进入下一轮返祖试炼
x = original_father;
}
return root;
}
- 集(Set),代表这是一个以字典为基础的数据结构,它的基本功能是合并集合中的元素,查找集合中的元素
class UnionFind {
// 此map用来记录该节点的父节点
private Map<Integer,Integer> father;
}
综合结构
class UnionFind {
// 集
private Map<Integer,Integer> father;
public UnionFind() {
father = new HashMap<Integer,Integer>();
}
// 添加
public void add(int x) {
if (!father.containsKey(x)) {
father.put(x, null);
}
}
// 并
public void merge(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY){
father.put(rootX,rootY);
}
}
// 查
public int find(int x) {
int root = x;
while(father.get(root) != null){
root = father.get(root);
}
while(x != root){
int original_father = father.get(x);
father.put(x,root);
x = original_father;
}
return root;
}
// 判断两个节点是否处于同一个连通分量(看祖宗是否相同)
public boolean isConnected(int x, int y) {
return find(x) == find(y);
}
}
例💡
package shengFenShuLiang;
import java.util.HashMap;
import java.util.Map;
class UnionFind{
// 集
public Map<Integer, Integer> father;
// 省份数量
public int shengFenNumbr;
public UnionFind() {
father = new HashMap<Integer, Integer>();
shengFenNumbr = 0;
}
// 添加
public void add(int node) {
if(!father.containsKey(node)) {
father.put(node,null);
}
// 省份数量+1
++shengFenNumbr;
}
// 并
public void marge(int node1,int node2) {
// 找其祖宗
int node1Root = find(node1);
int node2Root = find(node2);
// 两大族群联合(族长大型认爹仪式)
if (node1Root != node2Root){
father.put(node1Root, node2Root);
--shengFenNumbr;
}
}
// 查
public int find(int node) {
// 初始化自己为祖节点
int root = node;
// 结束条件: 父节点为null
while(father.get(root) != null) {
root = father.get(root);
}
// 返祖
if(node!=root) {
// 找到自己麻麻(不忘本)
int maMa = father.get(node);
// 返祖
father.put(node, root);
// 麻麻进入下一轮返祖计划
node = maMa;
}
return root;
}
// 判断是否连通
public boolean isConnected(int node1,int node2) {
return find(node1) == find(node2);
}
// 获取省份数量
public int getShengFenShuNumber() {
return shengFenNumbr;
}
}
public class Solution {
public int findCircleNum(int[][] isConnected) {
// 实例化一个并查集
UnionFind unionFind = new UnionFind();
// 遍历所有关系
for(int i = 0;i < isConnected.length; i++) {
// 将i添加到并查集
unionFind.add(i);
// 并关系
for(int j = 0; j < isConnected[i].length; j++) {
if (isConnected[i][j] == 1){
// 合并
unionFind.marge(i, j);
}
}
}
return unionFind.getShengFenShuNumber();
}
}
双指针
关键
- 处理的数据结构是数组
- 解决低空间复杂度问题 双指针空间复杂度O(1)
类型
同向(快慢、交换)
左阴右阳,一阴一阳协同完成任务,缺一不可,阴主后方守,阳主探索攻
例-交换
public class Solution {
public int removeDuplicates(int[] nums) {
// 边界条件 边界条件 输入如果是数组,考虑null,空数组
if (nums==null)return 0;
// 创建同向双指针
int p = 0,q = 1;
while (q < nums.length) {
if (nums[q] != nums[p]) {
// 交换
nums[p+1] = nums[q];
p++;
}
q++;
}
return p+1;
}
}
例-快慢
例-快慢(同并归)
反向
例(同贪心)
public class Solution {
public int numRescueBoats(int[] people, int limit) {
if(people==null||limit<1)return 0;
// 排序
Arrays.sort(people);
// 左右指针
int left = 0,right=people.length-1;1
// 船数
int boatNum = 0;
// 相撞时结束 = 为什么,考虑到奇数个人
while (left <= right) {
// 体重重的一个人能坐上船
if (people[right]<=limit){
++boatNum;
}
if (people[left]+people[right]<=limit) {
// 先自加再赋值
++left;
}
--right;
}
return boatNum;
}
}
贪心
关键
- 大多是处理数组问题最优组合
- 先对数组排序
- 追求局部最优解
- 使用贪心算法解决的问题必须具备「无后效性」,即某个状态以前的过程不会影响以后的状态,只与当前状态有关,这也是它与动态规划的区别
关键
先看有无后效性-有序排列先安排-小换大,大换小
例
public class Solution {
public int numRescueBoats(int[] people, int limit) {
if(people==null||limit<1)return 0;
// 排序
Arrays.sort(people);
// 左右指针
int left = 0,right=people.length-1;1
// 船数
int boatNum = 0;
// 相撞时结束 = 为什么,考虑到奇数个人
while (left <= right) {
// 体重重的一个人能坐上船
if (people[right]<=limit){
++boatNum;
}
if (people[left]+people[right]<=limit) {
// 先自加再赋值
++left;
}
--right;
}
return boatNum;
}
}
此题有两种局部最优解决思路:
- 优先满足当前饱腹感最小的,小对小,对不上,换大一点的饼干
- 优先满足当前饱腹感最大的,大对大,对不上,换小一点的孩子
这里选取第二种,代码量相对较少
public class Solution {
/**
* for循环+计数器模式
* @param g 孩子数组
* @param s 饼干数组
* @return
*/
public int findContentChildren(int[] g, int[] s) {//s:饼干
// 排序
Arrays.sort(g);
Arrays.sort(s);
// 满足孩子数
int count = 0;
// 饼干起点
int start = s.length - 1;//饼干数量
for(int index = g.length - 1; index >= 0; index--){
// start>=0 限制饼干分完
if(start>=0&&g[index] <= s[start]){
count++;
start--;
}
}
return count;
}
}
动态规划
- 自顶向下即记忆化递归,自底向上就是递推
- 求解方案数,最小数,最大数这种不需要具体方案的问题
- 当前最佳数量受上一级数量影响
关键
总结与前相关性(方程,当前最优何得来)- 滑动窗口少不了-在内在外只为更新最优秀的状态
class Solution {
public int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
}
public class Solution {
public int coinChange(int[] coins, int amount) {
int max = amount + 1;
int[] dp = new int[amount + 1];
Arrays.fill(dp, max);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
递归
- 函数自调
- 一个简单的基本案例,能够不使用递归来产生答案的终止方案。
- 一组规则,可将所有其他情况拆分到基本案例。
例
public class Solution {
/**
*
* @param root 当前根节点
* @param targetSum 当前路径剩余要求的和
* @return
*/
public boolean hasPathSum(TreeNode root, int targetSum) {
// 边界
if (root == null) {
return false;
}
// 非递归的结束条件 到根停止
if (root.left == null&&root.right == null) {
return targetSum == root.val;
}
// 左子树,右子树任意满足
return hasPathSum(root.left, targetSum-root.val) || hasPathSum(root.right,targetSum - root.val);
}
}
归并排序
精髓
快慢指针-循环得到慢指针指向中点位置(注意奇偶)- 左右迭代-构建新链表-比较大小换到下-判空确实优选择
例
public class Solution {
public ListNode sortList(ListNode head) {
// 边界条件
// 1.作为输入时校验
// 2.递归的结束条件head.next == null就是没有下一个节点即二分到只剩下一个节点结束
if (head == null || head.next == null)
return head;
// 创建快慢两个指针,分别指向头节点的下一个节点和头节点
ListNode fast = head.next, slow = head;
// 直到快指针指向右侧边界外且快指针的下一个节点为空停止
// fast.next != null 防止总数为偶数时,快指针指向最后一个节点,
// 任何一个条件不满足,循环都到结束
while (fast != null && fast.next != null) {
// 慢指针指向慢指针的下一个节点
slow = slow.next;
// 快指针指向快指针的下下一个节点
fast = fast.next.next;
// 等于两个指针向→做1步长和2步长的滑动窗口
// slow一下走一步,fast一下走两步
// 走得快的走完,走得慢的正好到中点
}
// 从中心阻断 此时 head为左段头节点,tmp为右段头节点
ListNode tmp = slow.next;
slow.next = null;
// 递归
ListNode left = sortList(head);
ListNode right = sortList(tmp);
// 创建值为0的节点
ListNode h = new ListNode(0);
// res指针指向这个节点
ListNode res = h;
// 左右任何一个为空,循环结束
while (left != null && right != null) {
// 都不为空
// 在h节点后按大小排列拼接left/right
if (left.val < right.val) {
h.next = left;
// left指针指向自己的下一个元素?
left = left.next;
} else {
h.next = right;
right = right.next;
}
// h 指向h的下一个节点,即right或
h = h.next;
}
// 判断左节点是否为空(两种原因造成:1.left = left.next;2.只有左节点)
// 不为空指向左反之则右,防止h->left->left 前面做了左节点后滑 不论如何上方的哪种情况都会正确
// 变成如下局面 h->left / h->right / h->left1->left2 / h->left1->right
// 这句代码是精髓,K神的厉害之处,节省了很多代码和判断语句
h.next = left != null ? left : right;
// 返回 h的下一个节点 如果当前为顶层,则把0节点后后排序好的值返回用户
// 如果是底层 则将 left1->right 返回到上级作为left/right 合并排序
return res.next;
}
}
最短路径
例
Floyd
public class Main {
static int[][] graph = new int[2050][2050];
static final int INF = 0x3f3f3f3f;
private static void floyd() {
for (int k = 1; k <= 2021; k++) {
for (int i = 1; i <= 2021; i++) {
for (int j = 1; j <= 2021; j++) {
if (i != j && graph[i][j] > graph[i][k] + graph[k][j]) {
graph[i][j] = graph[i][k] + graph[k][j];
}
}
}
}
}
private static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
public static void main(String[] args) {
for (int i = 1; i <= 2021; i++) {
for (int j = 1; j <= 2021; j++) {
graph[i][j] = INF;
}
}
for (int i = 1; i <= 2021; i++) {
int st = Math.max(i - 21, 1);
for (int j = st; j <= i; j++) {
int div = gcd(j, i);
int lcm = i * j / div;
graph[i][j] = lcm;
graph[j][i] = lcm;
}
}
floyd();
System.out.println(graph[1][2021]);
}
}
Dijkstra
广度优先搜素
与广度优先搜素的区别是使用的队列为堆(优先队列)
public class Main {
static class Edge {
// 指向谁,长度
int to, length;
Edge(int _to, int _length) {
to = _to;
length = _length;
}
}
static List<Edge>[] graph;
static final int INF = 0x3f3f3f3f;
/**
*
* @param st 起点
* @param ed 终点
* @return
*/
private static int dijkstra(int st, int ed) {
// 新建小根堆
PriorityQueue<Edge> pq = new PriorityQueue<>((a, b) ->
Integer.compare(a.length, b.length));
// 距离
int[] dist = new int[2050];
// 距离都填充正无穷大
Arrays.fill(dist, INF);
// 开始距离初始化为0
dist[st] = 0;
// to 为点的编号,length 为当前路径长度
pq.offer(new Edge(st, dist[st]));
while (!pq.isEmpty()) {
// 弹出对队首(即最小)元素 并获取其连接节点
int from = pq.poll().to;
// 遍历 它能指向的节点信息
for (Edge next : graph[from]) {
// 获取指向节点编号,距离
int to = next.to, len = next.length;
// 第一次:因为 初始化填充为无穷大,所以肯定指向其第一个遍历元素,这里会找到一个最短路径放到dist数组里,但堆里会放比其距离大的几个元素去探索
// 不过由于堆的特性,距离最小的考前,但都会计算
if (dist[to] > dist[from] + len) {
dist[to] = dist[from] + len;
pq.offer(new Edge(to, dist[to]));
}
}
}
return dist[ed];
}
private static int gcd(int a, int b) {
// a=21 b=1
// 如果b=0则返回a 反之递归
return b == 0 ? a : gcd(b, a % b);
}
public static void main(String[] args) {
graph = new List[2050];
// 构建邻接表
for (int i = 1; i <= 2021; i++) {
// i=1 st = 1;i = 20,st=1;i>21 st=i
int st = Math.max(i - 21, 1);
// 如果 i <= 21不进入循环 反之循环对应次
for (int j = st; j <= i; j++) {
// 求最小公倍数 21,1
int div = gcd(j, i);
int lcm = i * j / div;
// 为 i与j.创建存储关系的数组
if (graph[i] == null) {
graph[i] = new ArrayList<>();
}
if (graph[j] == null) {
graph[j] = new ArrayList<>();
}
// 将 j 节点 与 最小公倍数 传入Edge
graph[i].add(new Edge(j, lcm));
graph[j].add(new Edge(i, lcm));
}
}
System.out.println(dijkstra(1, 2021)); // 10266837
}
}
SPFA
边界条件
- 输入是数组 ,考虑 nums==null ,空数组 nums.length == 0
- 输入是矩阵,考虑 null,nums.length == 0,nums[i].length == 0
数学
- 最大公因数与最小公倍数
public class Test {
/**
* 欧几里得 最大公约数
* @param a
* @param b
* @return
*/
public static int gcd(int a, int b){
return b == 0?a : gcd(b,a%b);
}
/**
* 最小公倍数
* @param a
* @param b
* @return
*/
public static int lcm(int a, int b){
return a*b/gcd(a,b);
}
public static void main(String[] args) {
int a = 15,b=20;
System.out.println("最大公约数:"+gcd(a,b));
System.out.println("最小公倍数:"+lcm(a,b));
}
}
- 阶乘
public class Test {
/**
* 阶乘
* @param num
* @return
*/
public static int recursion(int num){
int sum=1;
if(num==1){
return 1;//根据条件,跳出循环
}else{sum=num * recursion(num-1);//运用递归计算
return sum;
}
}
public static void main(String[] args) {
int a = 2;
System.out.println(recursion(4));
}
}
- 对数
float b = Math.log(a)
- 无穷大
//整数
int INF = 0x3f3f3f3f;
//小数
double max=Double.POSITIVE_INFINITY;//无穷大
double maxx=Double.NEGATIVE_INFINITY;//无穷小