├── .gitignore ├── Java ├── alg │ ├── README.md │ ├── lc │ │ ├── 1.两数之和.md │ │ ├── 100.相同的树.md │ │ ├── 101.对称二叉树.md │ │ ├── 1013.将数组分成和相等的三个部分.md │ │ ├── 102.二叉树的层序遍历.md │ │ ├── 104.二叉树的最大深度.md │ │ ├── 107.二叉树的层序遍历2.md │ │ ├── 1071.字符串的最大公因子.md │ │ ├── 108.将有序数组转换为二叉搜索树.md │ │ ├── 11.盛最多水的容器.md │ │ ├── 110.平衡二叉树.md │ │ ├── 1103.分糖果2.md │ │ ├── 111.二叉树的最小深度.md │ │ ├── 112.路径总和.md │ │ ├── 113.路径总和2.md │ │ ├── 118.杨辉三角.md │ │ ├── 120.三角形最小路径和.md │ │ ├── 121.买卖股票的最佳时机.md │ │ ├── 122.买卖股票的最佳时机2.md │ │ ├── 125.验证回文串.md │ │ ├── 13.罗马数字转整数.md │ │ ├── 136.只出现一次的数字.md │ │ ├── 14.最长公共前缀.md │ │ ├── 141.环形链表.md │ │ ├── 15.三数之和.md │ │ ├── 155.最小栈.md │ │ ├── 160.相交链表.md │ │ ├── 167.两数之和2-输入有序数组.md │ │ ├── 17.电话号码的字母组合.md │ │ ├── 172.阶乘后的零.md │ │ ├── 19.删除链表的倒数第N个结点.md │ │ ├── 191.位1的个数.md │ │ ├── 2.两数相加.md │ │ ├── 20.有效的括号.md │ │ ├── 204.计数质数.md │ │ ├── 205.同构字符串.md │ │ ├── 206.反转链表.md │ │ ├── 21.合并两个有序链表.md │ │ ├── 217.存在重复元素.md │ │ ├── 219.存在重复元素2.md │ │ ├── 22.括号生成.md │ │ ├── 225.用队列实现栈.md │ │ ├── 226.翻转二叉树.md │ │ ├── 231.2的幂.md │ │ ├── 232.用栈实现队列.md │ │ ├── 234.回文链表.md │ │ ├── 237.删除链表中的节点.md │ │ ├── 24.两两交换链表中的节点.md │ │ ├── 242.有效的字母异位词.md │ │ ├── 26.删除排序数组中的重复项.md │ │ ├── 268.丢失的数字.md │ │ ├── 27.移除元素.md │ │ ├── 278.第一个错误的版本.md │ │ ├── 28.实现strStr.md │ │ ├── 283.移动零.md │ │ ├── 290.单词规律.md │ │ ├── 3.无重复字符的最长子串.md │ │ ├── 31.下一个排列.md │ │ ├── 326.3的幂.md │ │ ├── 33.搜索旋转排序数组.md │ │ ├── 34.在排序数组中查找元素的第一个和最后一个位置.md │ │ ├── 344.反转字符串.md │ │ ├── 35.搜索插入位置.md │ │ ├── 350.两个数组的交集2.md │ │ ├── 36.有效的数独.md │ │ ├── 367.有效的完全平方数.md │ │ ├── 371.两整数之和.md │ │ ├── 387.字符串中的第一个唯一字符.md │ │ ├── 39.组合总和.md │ │ ├── 392.判断子序列.md │ │ ├── 40.组合总和2.md │ │ ├── 409.最长回文串.md │ │ ├── 414.第三大的数.md │ │ ├── 415.字符串相加.md │ │ ├── 43.字符串相乘.md │ │ ├── 434.字符串中的单词数.md │ │ ├── 455.分发饼干.md │ │ ├── 46.全排列.md │ │ ├── 461.汉明距离.md │ │ ├── 47.全排列2.md │ │ ├── 485.最大连续1的个数.md │ │ ├── 5.最长回文子串.md │ │ ├── 509.斐波那契数.md │ │ ├── 53.最大子序和.md │ │ ├── 543.二叉树的直径.md │ │ ├── 55.跳跃游戏.md │ │ ├── 56.合并区间.md │ │ ├── 566.重塑矩阵.md │ │ ├── 572.另一个树的子树.md │ │ ├── 58.最后一个单词的长度.md │ │ ├── 594.最长和谐子序列.md │ │ ├── 6.Z字形变换.md │ │ ├── 605.种花问题.md │ │ ├── 617.合并二叉树.md │ │ ├── 62.不同路径.md │ │ ├── 628.三个数的最大乘积.md │ │ ├── 63.不同路径2.md │ │ ├── 637.二叉树的层平均值.md │ │ ├── 64.最小路径和.md │ │ ├── 645.错误的集合.md │ │ ├── 66.加一.md │ │ ├── 665.非递减数列.md │ │ ├── 67.二进制求和.md │ │ ├── 671.二叉树中第二小的节点.md │ │ ├── 674.最长连续递增序列.md │ │ ├── 680.验证回文字符串2.md │ │ ├── 69.x的平方根.md │ │ ├── 693.交替位二进制数.md │ │ ├── 696.计数二进制子串.md │ │ ├── 7.整数反转.md │ │ ├── 70.爬楼梯.md │ │ ├── 704.二分查找.md │ │ ├── 724.寻找数组的中心下标.md │ │ ├── 74.搜索二维矩阵.md │ │ ├── 744.寻找比目标字母大的最小字母.md │ │ ├── 747.至少是其他数字两倍的最大数.md │ │ ├── 75.颜色分类.md │ │ ├── 77.组合.md │ │ ├── 78.子集.md │ │ ├── 79.单词搜索.md │ │ ├── 82.删除排序链表中的重复元素2.md │ │ ├── 83.删除排序链表中的重复元素.md │ │ ├── 836.矩形重叠.md │ │ ├── 86.分隔链表.md │ │ ├── 876.链表的中间结点.md │ │ ├── 88.合并两个有序数组.md │ │ ├── 9.回文数.md │ │ ├── 90.子集2.md │ │ ├── 91.解码方法.md │ │ ├── 914.卡牌分组.md │ │ ├── 92.反转链表2.md │ │ ├── 93.复原IP地址.md │ │ ├── 94.二叉树的中序遍历.md │ │ ├── 96.不同的二叉搜索树.md │ │ └── 98.验证二叉搜索树.md │ ├── 个人刷熟题.md │ ├── 剑指offer.md │ ├── 多线程编程题.md │ └── 按热度总结lc.md ├── bishi │ ├── README.md │ ├── Shopee.md │ ├── ali.md │ ├── beike.md │ ├── dajiang.md │ ├── laohu.md │ ├── meituan.md │ ├── niuke.md │ ├── pdd.md │ ├── shunfeng.md │ ├── sql.md │ ├── tx.md │ └── wangyi.md ├── bus │ ├── README.md │ ├── Redis的key过期事件.md │ ├── Redis绑定Token.md │ ├── RocketMQ最终一致性.md │ ├── 上线遇到的bug.md │ ├── 业务逻辑SQL语句.md │ ├── 支付服务.md │ ├── 环境搭建文档.md │ ├── 班车服务.md │ ├── 用户服务.md │ └── 订单服务.md ├── classify │ ├── README.md │ ├── basis │ │ ├── ==、hashcode和equals.md │ │ ├── IO.md │ │ ├── Object.md │ │ ├── String.md │ │ ├── final.md │ │ ├── static.md │ │ ├── 值传递.md │ │ ├── 反射机制.md │ │ ├── 基本类型.md │ │ ├── 对象特性.md │ │ ├── 序列化.md │ │ ├── 异常体系.md │ │ ├── 泛型.md │ │ └── 深浅拷贝.md │ ├── col │ │ └── 谈谈集合.md │ ├── dis │ │ ├── CAP和BASE.md │ │ ├── Dubbo.md │ │ ├── RocketMQ.md │ │ ├── Sentinel.md │ │ ├── zookeeper.md │ │ ├── 分布式一致性算法.md │ │ ├── 分布式事务.md │ │ ├── 分布式概念.md │ │ ├── 布隆过滤器.md │ │ └── 限流算法.md │ ├── jvm │ │ ├── JVM内存区域.md │ │ ├── 垃圾回收.md │ │ ├── 对象的创建过程.md │ │ ├── 类加载器.md │ │ ├── 类加载过程.md │ │ ├── 类文件结构.md │ │ └── 逃逸分析.md │ ├── mysql │ │ ├── ACID.md │ │ ├── Innodb与MyISAM.md │ │ ├── MySQL数据库结构优化.md │ │ ├── MySQL日志文件.md │ │ ├── MySQL是如何执行一条SQL的.md │ │ ├── MySQL的锁.md │ │ └── 索引.md │ ├── net │ │ ├── DNS.md │ │ ├── HTTP和HTTPS.md │ │ ├── TCP和UDP.md │ │ └── 网络模型.md │ ├── redis │ │ ├── Redis分布式锁.md │ │ ├── Redis持久化.md │ │ ├── Redis数据结构.md │ │ ├── Redis模型.md │ │ ├── 内存淘汰机制.md │ │ └── 缓存穿透和缓存雪崩.md │ ├── spring │ │ ├── Bean.md │ │ ├── Ioc和AOP.md │ │ ├── SpringBoot.md │ │ ├── SpringMVC.md │ │ └── Spring事务.md │ ├── sys │ │ ├── 操作系统内存管理方式.md │ │ ├── 死锁.md │ │ ├── 系统进程调度.md │ │ ├── 虚拟内存.md │ │ ├── 进程与线程.md │ │ ├── 进程间通信.md │ │ └── 页面置换算法.md │ └── thread │ │ ├── AQS.md │ │ ├── BlockingQueue的一些问题.md │ │ ├── CAS.md │ │ ├── CountDownLatch.md │ │ ├── JMM内存模型.md │ │ ├── Java锁的介绍.md │ │ ├── ReentrantLock.md │ │ ├── ReentrantReadWriteLock.md │ │ ├── ThreadLocal.md │ │ ├── synchronized.md │ │ ├── volatile.md │ │ ├── 死锁.md │ │ ├── 生产者和消费者.md │ │ ├── 线程池.md │ │ ├── 线程的创建方式.md │ │ └── 进程和线程.md ├── crazy │ ├── Dubbo.md │ ├── JVM.md │ ├── Java基础.md │ ├── Java多线程.md │ ├── Java集合.md │ ├── MySQL.md │ ├── Mybatis.md │ ├── README.md │ ├── Redis.md │ ├── RocketMQ.md │ ├── Spring.md │ └── 计算机网络.md ├── jdk │ ├── ArrayList源码.md │ ├── Class源码.md │ ├── HashSet-HashMap源码.md │ ├── Integer源码.md │ ├── InvocationHandler源码.md │ ├── LinkedHashSet源码.md │ ├── LinkedList源码.md │ ├── Object源码.md │ ├── PriorityQueue源码.md │ ├── Proxy源码.md │ ├── README.md │ ├── SoftReference源码.md │ ├── Stack-Queue源码.md │ ├── StringBuilder源码.md │ ├── String源码.md │ ├── ThreadLocal源码.md │ ├── Thread源码.md │ ├── TreeSet-TreeMap源码.md │ └── WeakReference源码.md ├── mianjing │ ├── README.md │ ├── myshein.md │ ├── my京东.md │ ├── my作业帮.md │ ├── my字节.md │ ├── my招银.md │ ├── my猿辅导.md │ ├── my用友.md │ ├── my百度.md │ ├── my网易.md │ ├── my腾讯.md │ ├── my贝壳.md │ ├── 京东所有问题汇总.md │ ├── 字节所有问题汇总.md │ ├── 招银所有问题汇总.md │ ├── 拼多多所有问题汇总.md │ ├── 猿辅导所有问题汇总.md │ ├── 百度所有问题汇总.md │ ├── 网易所有问题汇总.md │ ├── 美团所有问题汇总.md │ ├── 腾讯所有问题汇总.md │ ├── 远景所有问题汇总.md │ └── 阿里所有问题汇总.md ├── mind │ └── README.md ├── mode │ ├── README.md │ ├── 代理模式.md │ ├── 单例模式.md │ ├── 工厂模式.md │ ├── 模板方法模式.md │ ├── 装饰器模式.md │ └── 观察者模式.md └── spring-books │ ├── MyBatis框架面试常见问题.md │ ├── README.md │ ├── SpringBoot启动流程分析.md │ ├── Spring和SpringAOP源码总结.md │ ├── docs │ ├── 1.xml注解bean方式.md │ ├── 10.bean的生命周期.md │ ├── 11.@Value的使用.md │ ├── 12.@PropertySource的使用.md │ ├── 13.@Autowired的使用.md │ ├── 14.@Qualifier的使用.md │ ├── 15.@Profile的使用.md │ ├── 16.AOP小例子.md │ ├── 17.Spring事务.md │ ├── 18.BeanFactoryPostProcessor.md │ ├── 19.BeanDefinitionRegistryPostProcessor.md │ ├── 2.xml注册包扫描方式.md │ ├── 20.ApplicationListener.md │ ├── 3.注解注册bean方式.md │ ├── 4.注解注册包扫描方式.md │ ├── 5.@Filter的使用.md │ ├── 6.@Scope的使用.md │ ├── 7.@Lazy的使用.md │ ├── 8.@Conditional的使用.md │ └── 9.@Import的使用.md │ ├── spring-aop2 │ └── src │ │ ├── Advice.java │ │ ├── AfterAdvice.java │ │ ├── BeforeAdvice.java │ │ ├── HelloService.java │ │ ├── HelloServiceImpl.java │ │ ├── MethodInvocation.java │ │ ├── SimpleAOP.java │ │ └── SimpleAOPTest.java │ └── spring-ioc │ └── src │ ├── Car.java │ ├── SimpleIOC.java │ ├── SimpleIOCTest.java │ ├── Wheel.java │ └── ioc.xml ├── README.md ├── books.md └── wx.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # idea 26 | .idea 27 | out 28 | 29 | # iml 30 | *.iml 31 | 32 | .DS_Store 33 | .aider* 34 | .env 35 | -------------------------------------------------------------------------------- /Java/alg/lc/100.相同的树.md: -------------------------------------------------------------------------------- 1 | # 100. 相同的树 2 | 3 | [url](https://leetcode-cn.com/problems/same-tree/) 4 | 5 | ## 题目 6 | 7 | 给定两个二叉树,编写一个函数来检验它们是否相同。 8 | 9 | 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 10 | 11 | 12 | ``` 13 | 输入: 1 1 14 | / \ / \ 15 | 2 3 2 3 16 | [1,2,3], [1,2,3] 17 | 输出: true 18 | 输入: 1 1 19 | / \ 20 | 2 2 21 | [1,2], [1,null,2] 22 | 输出: false 23 | 输入: 1 1 24 | / \ / \ 25 | 2 1 1 2 26 | [1,2,1], [1,1,2] 27 | 输出: false 28 | ``` 29 | 30 | ## 方法 31 | 32 | 可递归,可遍历 33 | 34 | 相同的树,三个结束条件 35 | 36 | - 如果`p`和`q`都不为空,则为true,毕竟相同的树,节点数量和方向都得一样吧? 37 | - 如果`p`和`q`其中一个为空,则为false,说明节点数量或者方向不对呀? 38 | - 如果`p`和`q`的值不相等的话,直接false 39 | 40 | ## code 41 | 42 | ### js 43 | 44 | ```js 45 | let isSameTree = (p, q) => { 46 | if (p === null && q === null) return true; 47 | if (p === null || q === null) return false; 48 | if (p.val !== q.val) return false; 49 | return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); 50 | } 51 | ``` 52 | 53 | ### go 54 | 55 | ```go 56 | func isSameTree(p, q *TreeNode) bool { 57 | if p == nil && q == nil { 58 | return true 59 | } 60 | if p == nil || q == nil { 61 | return false 62 | } 63 | if p.Val != q.Val { 64 | return false 65 | } 66 | return isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right) 67 | } 68 | ``` 69 | 70 | ### java 71 | 72 | ```java 73 | class Solution { 74 | public boolean isSameTree(TreeNode p, TreeNode q) { 75 | if(p == null && q == null) return true; 76 | if(p == null || q == null) return false; 77 | if(p.val != q.val) return false; 78 | return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); 79 | } 80 | } 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /Java/alg/lc/104.二叉树的最大深度.md: -------------------------------------------------------------------------------- 1 | # 104. 二叉树的最大深度 2 | 3 | [url](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 4 | 5 | ## 题目 6 | 7 | 给定一个二叉树,找出其最大深度。 8 | 9 | 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 10 | 11 | 说明: 叶子节点是指没有子节点的节点。 12 | 13 | 14 | ``` 15 | 给定二叉树 [3,9,20,null,null,15,7], 16 | 3 17 | / \ 18 | 9 20 19 | / \ 20 | 15 7 21 | ``` 22 | 23 | ## 方法 24 | 25 | 可递归,可遍历 26 | 27 | - 注意结束条件,如果为null则返回0 28 | - 递归不断比较左右谁的深度大 29 | 30 | ## code 31 | 32 | ### js 33 | 34 | ```js 35 | let maxDepth = root => { 36 | return root === null ? 0 : 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); 37 | } 38 | ``` 39 | 40 | ### go 41 | 42 | ```go 43 | func maxDepth(root *TreeNode) int { 44 | if root == nil { 45 | return 0 46 | } 47 | return 1 + Max(maxDepth(root.Left), maxDepth(root.Right)) 48 | } 49 | func Max(a, b int) int { 50 | if a > b { 51 | return a 52 | } else { 53 | return b 54 | } 55 | } 56 | ``` 57 | 58 | ### java 59 | 60 | ```java 61 | class Solution { 62 | public int maxDepth(TreeNode root) { 63 | if (root == null) return 0; 64 | int depth = 0; 65 | Queue> queue = new LinkedList<>(); 66 | queue.add(new Pair(root, 1)); 67 | while (!queue.isEmpty()){ 68 | Pair cur = queue.poll(); 69 | root = cur.getKey(); 70 | int curDepth = cur.getValue(); 71 | if (root != null) { 72 | depth = Math.max(depth, curDepth); 73 | queue.add(new Pair(root.left, curDepth + 1)); 74 | queue.add(new Pair(root.right, curDepth + 1)); 75 | } 76 | } 77 | return depth; 78 | } 79 | } 80 | ``` 81 | 82 | -------------------------------------------------------------------------------- /Java/alg/lc/1071.字符串的最大公因子.md: -------------------------------------------------------------------------------- 1 | # 1071. 字符串的最大公因子 2 | 3 | 4 | 5 | [url](https://leetcode-cn.com/problems/greatest-common-divisor-of-strings/) 6 | 7 | 8 | ## 题目 9 | 对于字符串 `S` 和 `T`,只有在 `S = T + ... + T`(T 自身连接 1 次或多次)时,我们才认定 “T 能除尽 S”。 10 | 11 | 返回最长字符串 `X`,要求满足 `X` 能除尽 `str1` 且 `X` 能除尽 `str2`。 12 | 13 | 14 | 15 | ``` 16 | 输入:str1 = "ABCABC", str2 = "ABC" 17 | 输出:"ABC" 18 | 输入:str1 = "ABABAB", str2 = "ABAB" 19 | 输出:"AB" 20 | 输入:str1 = "LEET", str2 = "CODE" 21 | 输出:"" 22 | ``` 23 | 24 | 25 | ## 方法 26 | 27 | 28 | ## code 29 | 30 | ### js 31 | 32 | ```js 33 | let gcdOfStrings = (str1, str2) => { 34 | if ((str1 + str2) !== (str2 + str1)) 35 | return ""; 36 | return str2.substring(0, gcd(str1.length, str2.length)); 37 | }; 38 | 39 | let gcd = (a, b) => { 40 | return b === 0 ? a : gcd(b, a % b); 41 | }; 42 | console.log(gcdOfStrings("ABCABC", "ABC")); 43 | console.log(gcdOfStrings("ABABAB", "ABAB")); 44 | console.log(gcdOfStrings("LEET", "CODE")); 45 | ``` 46 | 47 | ### go 48 | 49 | ```go 50 | func gcdOfStrings(str1 string, str2 string) string { 51 | var gcd func(a int, b int) int 52 | gcd = func (a int, b int) int { 53 | if b == 0 { 54 | return a 55 | } else { 56 | return gcd(b, a % b) 57 | } 58 | } 59 | if str1+str2 != str2+str1 { 60 | return "" 61 | } 62 | return str2[0:gcd(len(str1), len(str2))] 63 | } 64 | ``` 65 | 66 | ### java 67 | 68 | ```java 69 | class Solution { 70 | public String gcdOfStrings(String str1, String str2) { 71 | if (!(str1 + str2).equals(str2 + str1)) { 72 | return ""; 73 | } 74 | return str2.substring(0, gcd(str1.length(), str2.length())); 75 | } 76 | 77 | private int gcd(int a, int b) { 78 | return b == 0 ? a : gcd(b, a % b); 79 | } 80 | } 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /Java/alg/lc/108.将有序数组转换为二叉搜索树.md: -------------------------------------------------------------------------------- 1 | # 108. 将有序数组转换为二叉搜索树 2 | 3 | [url](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/) 4 | 5 | ## 题目 6 | 7 | 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 8 | 9 | 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 10 | 11 | ``` 12 | 给定有序数组: [-10,-3,0,5,9], 13 | 一个可能的答案是:[0,-3,9,-10,null,5],它可以表示下面这个高度平衡二叉搜索树: 14 | 15 | 返回其自底向上的层序遍历为: 16 | 0 17 | / \ 18 | -3 9 19 | / / 20 | -10 5 21 | ``` 22 | 23 | ## 方法 24 | 25 | 二叉树中序遍历的逆过程哦 26 | - 前序遍历:根结点 ---> 左子树 ---> 右子树 27 | - 中序遍历:左子树---> 根结点 ---> 右子树 28 | - 后序遍历:左子树 ---> 右子树 ---> 根结点 29 | 30 | ## code 31 | 32 | ### js 33 | 34 | ```js 35 | let sortedArrayToBST = nums => { 36 | return nums === null ? null : buildTree(nums, 0, nums.length - 1); 37 | } 38 | 39 | let buildTree = (nums, l, r) => { 40 | if (l > r) return null; 41 | let m = Math.floor(l + (r - l) / 2); 42 | let root = TreeNode(nums[m]); 43 | root.left = buildTree(nums, l, m - 1); 44 | root.right = buildTree(nums, m + 1, r); 45 | return root; 46 | } 47 | ``` 48 | 49 | ### go 50 | 51 | ```go 52 | func sortedArrayToBST(nums []int) *TreeNode { 53 | var buildTree func (nums[] int, l, r int) *TreeNode 54 | buildTree = func (nums[] int, l, r int) *TreeNode { 55 | if l > r { 56 | return nil 57 | } 58 | m := l + (r - l) / 2 59 | root := &TreeNode{Val: nums[m]} 60 | root.Left = buildTree(nums, l, m - 1) 61 | root.Right = buildTree(nums, m + 1, l) 62 | return root 63 | } 64 | if nums == nil || len(nums) == 0 { 65 | return nil 66 | } 67 | return buildTree(nums, 0, len(nums) - 1) 68 | } 69 | ``` 70 | 71 | ### java 72 | 73 | ```java 74 | class Solution { 75 | public TreeNode sortedArrayToBST(int[] nums) { 76 | return nums == null ? null : buildTree(nums, 0, nums.length - 1); 77 | } 78 | private TreeNode buildTree(int[] nums, int l, int r) { 79 | if(l > r) return null; 80 | int m = l + (r - l) / 2; 81 | TreeNode root = new TreeNode(nums[m]); 82 | root.left = buildTree(nums, l, m - 1); 83 | root.right = buildTree(nums, m + 1, r); 84 | return root; 85 | } 86 | } 87 | ``` 88 | 89 | -------------------------------------------------------------------------------- /Java/alg/lc/11.盛最多水的容器.md: -------------------------------------------------------------------------------- 1 | # 11. 盛最多水的容器 2 | 3 | [url](https://leetcode-cn.com/problems/container-with-most-water/) 4 | 5 | ## 题目 6 | 7 | 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 8 | 9 | 说明:你不能倾斜容器。 10 | 11 | ![](https://aliyun-lc-upload.oss-cn-hangzhou.aliyuncs.com/aliyun-lc-upload/uploads/2018/07/25/question_11.jpg) 12 | 13 | ``` 14 | 输入:[1,8,6,2,5,4,8,3,7] 15 | 输出:49 16 | 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 17 | 输入:height = [1,1] 18 | 输出:1 19 | 输入:height = [4,3,2,1,4] 20 | 输出:16 21 | 输入:height = [1,2,1] 22 | 输出:2 23 | ``` 24 | 25 | ## 方法 26 | 27 | 28 | 29 | ## code 30 | 31 | ### js 32 | 33 | ```js{cmd="node"} 34 | let maxArea = height => { 35 | // 算面积 36 | let max = 0; 37 | let l = 0, r = height.length - 1; 38 | while (l < r) { 39 | let min = height[l] < height[r] ? height[l++] : height[r--]; 40 | max = Math.max(max, (r - l + 1) * min); 41 | } 42 | return max; 43 | }; 44 | ``` 45 | 46 | ### go 47 | 48 | ```go 49 | func maxArea(height []int) int { 50 | max := 0 51 | l, r := 0, len(height) - 1 52 | Max := func(a int, b int) int { 53 | if a < b { 54 | return b 55 | } else { 56 | return a 57 | } 58 | } 59 | for l < r { 60 | min := 0 61 | if height[l] < height[r] { 62 | min = height[l] 63 | l++ 64 | } else { 65 | min = height[r] 66 | r-- 67 | } 68 | max = Max(max, (r - l + 1) * min) 69 | } 70 | return max 71 | } 72 | ``` 73 | 74 | ### java 75 | 76 | ```java 77 | class Solution { 78 | public int maxArea(int[] height) { 79 | int max = 0; 80 | int l = 0, r = height.length - 1; 81 | while (l < r){ 82 | int min = height[l] < height[r] ? height[l++] : height[r--]; 83 | max = Math.max(max, (r - l + 1) * min); 84 | } 85 | return max; 86 | } 87 | } 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /Java/alg/lc/110.平衡二叉树.md: -------------------------------------------------------------------------------- 1 | # 110. 平衡二叉树 2 | 3 | [url](https://leetcode-cn.com/problems/balanced-binary-tree/) 4 | 5 | ## 题目 6 | 7 | 给定一个二叉树,判断它是否是高度平衡的二叉树。 8 | 9 | 本题中,一棵高度平衡二叉树定义为: 10 | > 一个二叉树每个节点的左右两个子树的高度差的绝对值不超过`1` 11 | 12 | 13 | 14 | ``` 15 | 输入:root = [3,9,20,null,null,15,7] 16 | 输出:true 17 | 输入:root = [1,2,2,3,3,null,null,4,4] 18 | 输出:false 19 | 输入:root = [] 20 | 输出:true 21 | ``` 22 | 23 | ## 方法 24 | 25 | 可递归,当然也可迭代 26 | 27 | - 其实就是抓着平衡二叉树的定义作为判断:个节点的左右两个子树的高度差的绝对值不超过`1` 28 | 29 | ## code 30 | 31 | ### js 32 | 33 | ```js 34 | let res = true; 35 | let isBalanced = root => { 36 | depth(root); 37 | return res; 38 | } 39 | let depth = root => { 40 | if (root === null) return 0; 41 | let l = depth(root.left); 42 | let r = depth(root.right); 43 | if (Math.abs(l - r) > 1) res = false; 44 | return 1 + Math.max(l, r); 45 | } 46 | ``` 47 | 48 | ### go 49 | 50 | ```go 51 | func isBalanced(root *TreeNode) bool { 52 | var res = true 53 | var depth func (root *TreeNode) int 54 | Max := func(a int, b int) int { 55 | if a < b { 56 | return b 57 | } else { 58 | return a 59 | } 60 | } 61 | depth = func (root *TreeNode) int { 62 | if root == nil { 63 | return 0 64 | } 65 | l := depth(root.Left) 66 | r := depth(root.Right) 67 | if Abs(l - r) > 1{ 68 | res = false 69 | } 70 | return 1 + Max(l, r) 71 | } 72 | depth(root) 73 | return res 74 | } 75 | ``` 76 | 77 | ### java 78 | 79 | ```java 80 | class Solution { 81 | private boolean res = true; 82 | public boolean isBalanced(TreeNode root) { 83 | Depth(root); 84 | return res; 85 | } 86 | private int Depth (TreeNode root) { 87 | if (root == null) return 0; 88 | int l = Depth(root.left); 89 | int r = Depth(root.right); 90 | if (Math.abs(l - r) > 1) res = false;; 91 | return 1 + Math.max(l , r); 92 | } 93 | } 94 | ``` 95 | 96 | -------------------------------------------------------------------------------- /Java/alg/lc/112.路径总和.md: -------------------------------------------------------------------------------- 1 | # 112. 二叉树的最小深度 2 | 3 | [url](https://leetcode-cn.com/problems/path-sum/) 4 | 5 | 6 | ## 题目 7 | 8 | 给你二叉树的根节点 `root` 和一个表示目标和的整数 `targetSum` ,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 `targetSum `。 9 | 10 | **叶子节点**是指没有子节点的节点。 11 | 12 | ![lc-pathsum1-tLI8c2](https://cdn.jsdelivr.net/gh/DreamCats/imgs@main/uPic/lc-pathsum1-tLI8c2.jpg) 13 | 14 | ``` 15 | 输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22 16 | 输出:true 17 | ``` 18 | 19 | ## 方法 20 | 21 | 可递归,当然也可迭代 22 | 23 | ## code 24 | 25 | ### js 26 | 27 | ```js 28 | let hasPathSum = (root, sum) => { 29 | if (root === null) return false; 30 | if (root.left === null && root.right === null && root.val === sum) return true; 31 | return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val); 32 | } 33 | ``` 34 | 35 | ### go 36 | 37 | ```go 38 | func hasPathSum(root *TreeNode, sum int) bool { 39 | if root == nil { 40 | return false 41 | } 42 | if root.Left == nil && root.Right == nil && root.Val == sum { 43 | return true 44 | } 45 | return hasPathSum(root.Left, sum - root.Val) || hasPathSum(root.Right, sum -root.Val) 46 | } 47 | ``` 48 | 49 | ### java 50 | 51 | ```java 52 | class Solution { 53 | public boolean hasPathSum(TreeNode root, int sum) { 54 | if (root == null) return false; 55 | if (root.left == null && root.right == null && root.val == sum) return true; 56 | return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val); 57 | } 58 | } 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /Java/alg/lc/118.杨辉三角.md: -------------------------------------------------------------------------------- 1 | # 118. 杨辉三角 2 | 3 | [url](https://leetcode-cn.com/problems/pascals-triangle/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。 9 | 10 | 在杨辉三角中,每个数是它左上方和右上方的数的和。 11 | 12 | 13 | ![lc-PascalTriangleAnimated2-1C8mXY](https://cdn.jsdelivr.net/gh/DreamCats/imgs@main/uPic/lc-PascalTriangleAnimated2-1C8mXY.gif) 14 | 15 | ``` 16 | 输入: 5 17 | 输出: 18 | [ 19 | [1], 20 | [1,1], 21 | [1,2,1], 22 | [1,3,3,1], 23 | [1,4,6,4,1] 24 | ] 25 | ``` 26 | 27 | ## 方法 28 | 29 | 杨辉三角注意观察图上的规律即可 30 | 31 | ## code 32 | 33 | ### js 34 | 35 | ```js 36 | ``` 37 | 38 | ### go 39 | 40 | ```go 41 | ``` 42 | 43 | ### java 44 | 45 | ```java 46 | class Solution { 47 | public List> generate(int numRows) { 48 | List> ans = new ArrayList<>(); 49 | for(int i = 0; i < numRows; i++) { 50 | List curRow = new ArrayList<>(); 51 | for(int j = 0; j <= i; j++) { 52 | if(j == 0 || j == i) { 53 | curRow.add(1); 54 | continue; 55 | } 56 | if(i == 0 || i == 1) { 57 | continue; 58 | } 59 | List preRow = ans.get(i - 1); 60 | int value = preRow.get(j - 1) + preRow.get(j); 61 | curRow.add(value); 62 | } 63 | ans.add(curRow); 64 | } 65 | return ans; 66 | } 67 | } 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /Java/alg/lc/121.买卖股票的最佳时机.md: -------------------------------------------------------------------------------- 1 | # 121. 买卖股票的最佳时机 2 | 3 | [url](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个数组 `prices` ,它的第` i `个元素 `prices[i] `表示一支给定股票第` i `天的价格。 9 | 10 | 你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。设计一个算法来计算你所能获取的最大利润。 11 | 12 | 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。 13 | 14 | 15 | ``` 16 | 输入:[7,1,5,3,6,4] 17 | 输出:5 18 | 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 19 | 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 20 | 21 | 输入:prices = [7,6,4,3,1] 22 | 输出:0 23 | 解释:在这种情况下, 没有交易完成, 所以最大利润为 0。 24 | ``` 25 | 26 | ## 方法 27 | 28 | 动态规划 29 | 30 | - 迭代过程当中,比较`min`和当前价格,取最小,目的是为了获取最大利润的嘛 31 | - 其次将当前价格与`min`的差和最大差值进行比较,保留最大差值,最后该差值就是最大利润 32 | 33 | ## code 34 | 35 | ### js 36 | 37 | ```js 38 | let maxProfit = prices => { 39 | if (prices === null || prices.length === 0) return -1; 40 | let min = prices[0]; 41 | let max = 0; 42 | for (let i = 1; i < prices.length; i++) { 43 | min = prices[i] < min ? prices[i] : min; 44 | max = Math.max(max, prices[i] - min); 45 | } 46 | return max; 47 | } 48 | 49 | console.log(maxProfit([7,1,5,3,6,4])); 50 | console.log(maxProfit([7,6,4,3,1])); 51 | ``` 52 | 53 | ### go 54 | 55 | ```go 56 | func maxProfit(prices []int) int { 57 | if prices == nil || len(prices) == 0 { 58 | return -1 59 | } 60 | min, max := prices[0], 0 61 | for i := 1; i < len(prices); i++ { 62 | if prices[i] < min { 63 | min = prices[i] 64 | } 65 | max = Max(max, prices[i] - min) 66 | } 67 | return max 68 | } 69 | ``` 70 | 71 | ### java 72 | 73 | ```java 74 | class Solution { 75 | public int maxProfit(int[] prices) { 76 | if (prices == null || prices.length == 0) return -1; 77 | int min = prices[0]; 78 | int max = 0; 79 | for (int i = 1; i < prices.length; i++) { 80 | min = prices[i] < min ? prices[i] : min; 81 | max = Math.max(max, prices[i] - min); 82 | } 83 | return max; 84 | } 85 | } 86 | ``` 87 | 88 | -------------------------------------------------------------------------------- /Java/alg/lc/122.买卖股票的最佳时机2.md: -------------------------------------------------------------------------------- 1 | # 122. 买卖股票的最佳时机2 2 | 3 | [url](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个数组,它的第` i `个元素是一支给定股票第` i `天的价格。 9 | 10 | 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 11 | 12 | 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 13 | 14 | 15 | 16 | ``` 17 | 输入: [7,1,5,3,6,4] 18 | 输出: 7 19 | 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 20 |   随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 21 | 输入: [1,2,3,4,5] 22 | 输出: 4 23 | 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 24 |   注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 25 |   因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 26 | 输入: [7,6,4,3,1] 27 | 输出: 0 28 | 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 29 | ``` 30 | 31 | ## 方法 32 | 33 | 贪心 34 | 35 | - 只要当天的价格比前一天的高,那就可以卖出,累加 36 | 37 | ## code 38 | 39 | ### js 40 | 41 | ```js 42 | let maxProfit1 = prices => { 43 | // 贪心 44 | let profit = 0; 45 | for (let i = 1; i < prices.length; i++) { 46 | if (prices[i] > prices[i - 1]) 47 | profit += prices[i] - prices[i - 1]; 48 | } 49 | return profit; 50 | } 51 | console.log(maxProfit1([7,1,5,3,6,4])); 52 | console.log(maxProfit1([1,2,3,4,5])); 53 | console.log(maxProfit1([7,6,4,3,1])); 54 | ``` 55 | 56 | ### go 57 | 58 | ```go 59 | func maxProfit1(prices []int) int { 60 | // 贪心 61 | profit := 0 62 | for i := 1; i < len(prices); i++ { 63 | if prices[i] > prices[i - 1] { 64 | profit += prices[i] - prices[i - 1] 65 | } 66 | } 67 | return profit 68 | } 69 | ``` 70 | 71 | ### java 72 | 73 | ```java 74 | class Solution { 75 | public int maxProfit(int[] prices) { 76 | // 贪心:只要我当前数比前一个数大, 就xxx 77 | int profit = 0; 78 | for (int i = 1; i < prices.length; i++) { 79 | if (prices[i] > prices[i - 1]) profit += prices[i]- prices[i - 1]; 80 | } 81 | return profit; 82 | } 83 | } 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /Java/alg/lc/136.只出现一次的数字.md: -------------------------------------------------------------------------------- 1 | # 136. 只出现一次的数字 2 | 3 | [url](https://leetcode-cn.com/problems/single-number/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 9 | 10 | 说明: 11 | 12 | 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 13 | 14 | ``` 15 | 输入: [2,2,1] 16 | 输出: 1 17 | 输入: [4,1,2,1,2] 18 | 输出: 4 19 | ``` 20 | 21 | ## 方法 22 | 23 | 24 | 两种方法: 25 | 1. 哈希->最容易想到的方法 26 | 2. 异或 27 | 28 | ## code 29 | 30 | ### js 31 | 32 | ```js 33 | let singleNumber = nums => { 34 | let ret = 0; 35 | for (let i = 0; i < nums.length; i++) { 36 | ret = ret ^ nums[i]; 37 | } 38 | return ret; 39 | } 40 | console.log(singleNumber([2, 2, 1])); 41 | console.log(singleNumber([4,1,2,1,2])); 42 | ``` 43 | 44 | ### go 45 | 46 | ```go 47 | func singleNumber(nums []int) int { 48 | ret := 0 49 | for _, num := range nums { 50 | ret = ret ^ num 51 | } 52 | return ret 53 | } 54 | func singleNumber1(nums []int) int { 55 | // 哈希? 56 | m := make(map[int]int, len(nums)) 57 | for _, num := range nums { 58 | if value, ok := m[num]; ok { 59 | m[num] = value + 1 60 | } else { 61 | m[num] = 1 62 | } 63 | } 64 | for k, v := range m { 65 | if v == 1 { 66 | return k 67 | } 68 | } 69 | return -1 70 | } 71 | ``` 72 | 73 | ### java 74 | 75 | ```java 76 | class Solution { 77 | public int singleNumber(int[] nums) { 78 | int ret = 0; 79 | for (int num : nums) ret = ret ^ num; 80 | return ret; 81 | } 82 | } 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /Java/alg/lc/14.最长公共前缀.md: -------------------------------------------------------------------------------- 1 | # 14. 最长公共前缀 2 | 3 | [url](https://leetcode-cn.com/problems/longest-common-prefix/) 4 | 5 | ## 题目 6 | 7 | 编写一个函数来查找字符串数组中的最长公共前缀。 8 | 9 | 如果不存在公共前缀,返回空字符串 ""。 10 | 11 | ``` 12 | 输入:strs = ["flower","flow","flight"] 13 | 输出:"fl" 14 | 15 | 输入:strs = ["dog","racecar","car"] 16 | 输出:"" 17 | 解释:输入不存在公共前缀。 18 | ``` 19 | 20 | ## 方法 21 | 22 | 看到这道题,找到几个字符串公共前缀,比如 23 | `strs = ["flower","flow","flight"]` 24 | 25 | - 我们肯定一眼就能找到fl是公共前缀 26 | - 假设我们取第一个字符串,flower,分别和flow与flight去对比 27 | - 对比啥呢,对比其他字符串是否包含flower,如果包含,则是公共前缀,如果不是呢?那就截取掉最后一个字符,即flowe,再分别和flow与flight去对比 28 | - 以此类推,打截取到flow的时候,flow字符串正好与该字符串相等,但和flight不匹配,那么继续截取,然后从flight所在的索引循环 29 | - 直到fl符合所有要求 30 | 31 | ## code 32 | 33 | ### js 34 | 35 | ```js 36 | let longestCommonPrefix = function(strs) { 37 | if (strs.length === 0) 38 | return ""; 39 | let str = strs[0] 40 | for (let i = 1; i < strs.length; i++) { 41 | while(strs[i].indexOf(str) !== 0) { 42 | str = str.substring(0, str.length - 1); 43 | } 44 | } 45 | return str; 46 | }; 47 | ``` 48 | 49 | ### go 50 | 51 | ```go 52 | func longestCommonPrefix(strs []string) string { 53 | if strs == nil || len(strs) == 0 { 54 | return "" 55 | } 56 | str := strs[0] 57 | for _, v := range strs[1:] { 58 | for strings.Index(v, str) != 0 { 59 | str = str[0:len(str) - 1] 60 | } 61 | } 62 | return str 63 | } 64 | ``` 65 | 66 | ### java 67 | 68 | ```java 69 | class Solution { 70 | public String longestCommonPrefix(String[] strs) { 71 | if (strs == null || strs.length == 0) 72 | return ""; 73 | String str = strs[0]; 74 | for (int i = 1; i < strs.length; i++){ 75 | while (strs[i].indexOf(str) != 0) { 76 | str = str.substring(0, str.length() - 1); 77 | } 78 | } 79 | return str; 80 | } 81 | } 82 | ``` 83 | 84 | -------------------------------------------------------------------------------- /Java/alg/lc/141.环形链表.md: -------------------------------------------------------------------------------- 1 | # 141. 环形链表 2 | 3 | [url](https://leetcode-cn.com/problems/linked-list-cycle/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个链表,判断链表中是否有环。 9 | 10 | 如果链表中有某个节点,可以通过连续跟踪 `next` 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 `pos` 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 `pos` 是 -1,则在该链表中没有环。注意:`pos` 不作为参数进行传递,仅仅是为了标识链表的实际情况。 11 | 12 | 如果链表中存在环,则返回 `true` 。 否则,返回 `false` 。 13 | 14 | 你能用 O(1)(即,常量)内存解决此问题吗? 15 | 16 | ![circularlinkedlist-lc-q9kmLK](https://cdn.jsdelivr.net/gh/DreamCats/imgs@main/uPic/circularlinkedlist-lc-q9kmLK.png) 17 | ``` 18 | 输入:head = [3,2,0,-4], pos = 1 19 | 输出:true 20 | 解释:链表中有一个环,其尾部连接到第二个节点。 21 | ``` 22 | 23 | ![circularlinkedlist_test2-lc-i5Xwi1](https://cdn.jsdelivr.net/gh/DreamCats/imgs@main/uPic/circularlinkedlist_test2-lc-i5Xwi1.png) 24 | ``` 25 | 输入:head = [1,2], pos = 0 26 | 输出:true 27 | 解释:链表中有一个环,其尾部连接到第一个节点。 28 | ``` 29 | 30 | ## 方法 31 | 32 | 33 | 挺简单的:快慢指针 34 | 35 | ## code 36 | 37 | ### js 38 | 39 | ```js 40 | let hasCycle = head => { 41 | if (head === null) return false; 42 | let l1 = head, l2 = head.next; 43 | while (l2 !== null && l2.next !== null) { 44 | if (l1 === l2) 45 | return true; 46 | l1 = l1.next; 47 | l2 = l2.next.next; 48 | } 49 | return false; 50 | } 51 | ``` 52 | 53 | ### go 54 | 55 | ```go 56 | func hasCycle(head *ListNode) bool { 57 | if head == nil { 58 | return false 59 | } 60 | l1, l2 := head, head.Next 61 | for l2 != nil && l2.Next != nil { 62 | if l1 == l2 { 63 | return true 64 | } 65 | l1 = l1.Next 66 | l2 = l2.Next.Next 67 | } 68 | return false; 69 | } 70 | ``` 71 | 72 | ### java 73 | 74 | ```java 75 | public class Solution { 76 | public boolean hasCycle(ListNode head) { 77 | if (head == null) { 78 | return false; 79 | } 80 | ListNode l1 = head, l2 = head.next; 81 | while (l1 != null && l2 != null && l2.next != null) { 82 | if (l1 == l2) { 83 | return true; 84 | } 85 | l1 = l1.next; 86 | l2 = l2.next.next; 87 | } 88 | return false; 89 | } 90 | } 91 | ``` 92 | 93 | -------------------------------------------------------------------------------- /Java/alg/lc/172.阶乘后的零.md: -------------------------------------------------------------------------------- 1 | # 172. 阶乘后的零 2 | 3 | [url](https://leetcode-cn.com/problems/factorial-trailing-zeroes/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个整数 n,返回 n! 结果尾数中零的数量。 9 | 10 | ``` 11 | 输入: 3 12 | 输出: 0 13 | 解释: 3! = 6, 尾数中没有零。 14 | 输入: 5 15 | 输出: 1 16 | 解释: 5! = 120, 尾数中有 1 个零. 17 | ``` 18 | 19 | ## 方法 20 | 21 | 算一下该数与5的相除的个数 22 | 23 | ## code 24 | 25 | ### js 26 | 27 | ```js 28 | let trailingZeros = n => { 29 | return n === 0 ? 0 : Math.floor(n / 5) + trailingZeros(Math.floor(n / 5)); 30 | }; 31 | console.log(railingZeros(3)); 32 | console.log(trailingZeros(5)); 33 | ``` 34 | 35 | ### go 36 | 37 | ```go 38 | func trailingZeroes(n int) int { 39 | if n == 0 { 40 | return 0 41 | } else { 42 | return n / 5 + trailingZeroes(n / 5) 43 | } 44 | } 45 | ``` 46 | 47 | ### java 48 | 49 | ```java 50 | class Solution { 51 | public int trailingZeroes(int n) { 52 | return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5); 53 | } 54 | } 55 | ``` 56 | 57 | -------------------------------------------------------------------------------- /Java/alg/lc/19.删除链表的倒数第N个结点.md: -------------------------------------------------------------------------------- 1 | # 19. 删除链表的倒数第 N 个结点 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/) 5 | 6 | ## 题目 7 | 8 | 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 9 | 10 | 进阶:你能尝试使用一趟扫描实现吗? 11 | 12 | ![](https://assets.leetcode.com/uploads/2020/10/03/remove_ex1.jpg) 13 | 14 | ``` 15 | 输入:head = [1,2,3,4,5], n = 2 16 | 输出:[1,2,3,5] 17 | 输入:head = [1], n = 1 18 | 输出:[] 19 | 输入:head = [1,2], n = 1 20 | 输出:[1] 21 | ``` 22 | 23 | ## 方法 24 | 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let removeNthFromEnd = (head, n) => { 32 | let fast = head; 33 | while (n-- > 0) { 34 | fast = fast.next; 35 | } 36 | if (fast === null) 37 | return head.next; 38 | let slow = head; 39 | while (fast.next !== null) { 40 | fast = fast.next; 41 | slow = slow.next; 42 | } 43 | slow.next = slow.next.next; 44 | return head; 45 | }; 46 | ``` 47 | 48 | ### go 49 | 50 | ```go 51 | func removeNthFromEnd(head *ListNode, n int) *ListNode { 52 | fast := head 53 | for n > 0 { 54 | n-- 55 | fast = fast.Next 56 | } 57 | if fast == nil { 58 | return head.Next 59 | } 60 | slow := head 61 | for fast.Next != nil { 62 | fast = fast.Next 63 | slow = slow.Next 64 | } 65 | slow.Next = slow.Next.Next 66 | return head 67 | } 68 | ``` 69 | 70 | ### java 71 | 72 | ```java 73 | class Solution { 74 | public ListNode removeNthFromEnd(ListNode head, int n) { 75 | ListNode fast = head; 76 | while (n-- > 0) { 77 | fast = fast.next; 78 | } 79 | if (fast == null) return head.next; 80 | ListNode slow = head; 81 | while (fast.next != null) { 82 | fast = fast.next; 83 | slow = slow.next; 84 | } 85 | slow.next = slow.next.next; 86 | return head; 87 | } 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /Java/alg/lc/191.位1的个数.md: -------------------------------------------------------------------------------- 1 | # 191. 位1的个数 2 | 3 | [url](https://leetcode-cn.com/problems/number-of-1-bits/) 4 | 5 | 6 | ## 题目 7 | 8 | 编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量)。 9 | 10 | 提示: 11 | 12 | - 请注意,在某些语言(如 `Java`)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。 13 | - 在 `Java` 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 `3` 中,输入表示有符号整数 `-3`。 14 | 15 | 16 | ``` 17 | 输入:00000000000000000000000000001011 18 | 输出:3 19 | 解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。 20 | 输入:00000000000000000000000010000000 21 | 输出:1 22 | 解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。 23 | 输入:11111111111111111111111111111101 24 | 输出:31 25 | 解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。 26 | ``` 27 | 提示: 28 | 29 | - 输入必须是长度为 `32` 的 二进制串 。 30 | 31 | ## 方法 32 | 33 | 常用:`n &= (n - 1)` 去掉二进制中的一个1 34 | 35 | ``` 36 | 比如:10 & (10 - 1) 37 | 1010 - 0001 = 1001 38 | 1010 & 1001 = 1000 39 | 1000 - 0001 = 0111 40 | 1000 & 0111 = 0000 41 | ``` 42 | 43 | ## code 44 | 45 | ### js 46 | 47 | ```js 48 | let hammingWeight = n => { 49 | // toString(2)把十进制数字转换为二进制字符串,replace删除0,返回剩余长度 50 | return n.toString(2).replace(/0/g, '').length; 51 | }; 52 | let hammingWeight1 = n => { 53 | let sum = 0; 54 | while (n !== 0) { 55 | sum++; 56 | n &= (n - 1); 57 | } 58 | return sum; 59 | } 60 | 61 | console.log(hammingWeight(00000000000000000000000000001011)); 62 | console.log(hammingWeight(00000000000000000000000010000000)); 63 | console.log(hammingWeight(11111111111111111111111111111101)); 64 | ``` 65 | 66 | ### go 67 | 68 | ```go 69 | func hammingWeight(n int) int { 70 | ans := 0 71 | for n != 0 { 72 | n &= n - 1 73 | ans++ 74 | } 75 | return ans 76 | } 77 | ``` 78 | 79 | ### java 80 | 81 | ```java 82 | public class Solution { 83 | // you need to treat n as an unsigned value 84 | public int hammingWeight(int n) { 85 | int ans = 0; 86 | while(n != 0) { 87 | n &= n - 1; 88 | ans++; 89 | } 90 | return ans; 91 | } 92 | } 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- /Java/alg/lc/204.计数质数.md: -------------------------------------------------------------------------------- 1 | # 204. 计数质数 2 | 3 | [url](https://leetcode-cn.com/problems/count-primes/) 4 | 5 | 6 | ## 题目 7 | 8 | 统计所有小于非负整数 n 的质数的数量。 9 | 10 | 11 | ``` 12 | 输入:n = 10 13 | 输出:4 14 | 解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。 15 | 输入:n = 0 16 | 输出:0 17 | 输入:n = 1 18 | 输出:0 19 | ``` 20 | 提示: 21 | 22 | 23 | ## 方法 24 | 25 | 质数的概念:质数又称**素数**。 一个大于1的**自然数**,除了**1和它自身外**,**不能被其他自然数整除的数叫做质数**;否则称为合数(**规定1既不是质数也不是合数**)。 26 | 27 | 所以,嵌套for的条件`let j = 2 * i; j < n + 1; j += i` 28 | 29 | ## code 30 | 31 | ### js 32 | 33 | ```js 34 | let coutnPrimes = n => { 35 | if (n < 2) return 0; 36 | let nums = Array(n + 1).fill(false); 37 | let count = 0; 38 | for (let i = 2; i < n; i++) { 39 | if (nums[i] === false) { 40 | count++; 41 | } 42 | for (let j = 2 * i; j < n + 1; j += i) { 43 | nums[j] = true; 44 | } 45 | } 46 | return count; 47 | }; 48 | 49 | console.log(coutnPrimes(10)); 50 | console.log(coutnPrimes(0)); 51 | console.log(coutnPrimes(1)); 52 | ``` 53 | 54 | ### go 55 | 56 | ```go 57 | func countPrimes(n int) int { 58 | if n < 2 { 59 | return 0 60 | } 61 | nums := make([]bool, n + 1) 62 | count := 0 63 | for i := 2; i < n; i++ { 64 | if nums[i] == false { 65 | count++ 66 | } 67 | for j := 2 * i; j < n + 1; j += i { 68 | nums[j] = true 69 | } 70 | } 71 | return count 72 | } 73 | ``` 74 | 75 | ### java 76 | 77 | ```java 78 | class Solution { 79 | public int countPrimes(int n) { 80 | if (n < 2) return 0; 81 | boolean[] num = new boolean[n + 1]; 82 | int count = 0; 83 | for (int i = 2; i < n; i++) { 84 | if (num[i] == false) { 85 | count++; 86 | } 87 | for (int j = 2 * i; j < n + 1; j += i) { 88 | num[j] = true; 89 | } 90 | } 91 | return count; 92 | } 93 | } 94 | ``` 95 | 96 | -------------------------------------------------------------------------------- /Java/alg/lc/217.存在重复元素.md: -------------------------------------------------------------------------------- 1 | # 217. 存在重复元素 2 | 3 | [url](https://leetcode-cn.com/problems/contains-duplicate/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个整数数组,判断是否存在重复元素。 9 | 10 | 如果存在一值在数组中出现至少两次,函数返回 `true` 。如果数组中每个元素都不相同,则返回 `false` 。 11 | 12 | 13 | ``` 14 | 输入: [1,2,3,1] 15 | 输出: true 16 | 输入: [1,2,3,4] 17 | 输出: false 18 | 输入: [1,1,1,3,3,4,3,2,4,2] 19 | 输出: true 20 | ``` 21 | 22 | 23 | ## 方法 24 | 25 | 哈希 26 | 27 | ## code 28 | 29 | ### js 30 | 31 | ```js 32 | let containsDuplicate = nums => { 33 | let m = {}; 34 | for (let i = 0; i < nums.length; i++) { 35 | if (m[nums[i]] !== undefined) { 36 | return true; 37 | } else { 38 | m[nums[i]] = 1; 39 | } 40 | } 41 | return false; 42 | }; 43 | console.log(containsDuplicate([1, 2, 3, 1])); 44 | console.log(containsDuplicate([1, 2, 3, 4])); 45 | console.log(containsDuplicate([1,1,1,3,3,4,3,2,4,2])); 46 | ``` 47 | 48 | ### go 49 | 50 | ```go 51 | func containsDuplicate(nums []int) bool { 52 | m := make(map[int]int, len(nums)) 53 | for _, num := range nums { 54 | if _, ok := m[num]; ok { 55 | return true 56 | } else { 57 | m[num] = 1 58 | } 59 | } 60 | return false 61 | } 62 | ``` 63 | 64 | ### java 65 | 66 | ```java 67 | class Solution { 68 | public boolean containsDuplicate(int[] nums) { 69 | HashSet set = new HashSet<>(); 70 | for (int num : nums) { 71 | if (!set.add(num)) { 72 | return true; 73 | } 74 | } 75 | return false; 76 | } 77 | } 78 | ``` 79 | 80 | -------------------------------------------------------------------------------- /Java/alg/lc/219.存在重复元素2.md: -------------------------------------------------------------------------------- 1 | # 219. 存在重复元素 II 2 | 3 | [url](https://leetcode-cn.com/problems/contains-duplicate-ii/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个整数数组和一个整数 `k`,判断数组中是否存在两个不同的索引 `i` 和 `j`,使得 `nums [i] = nums [j]`,并且 `i` 和 `j` 的差的绝对值至多为 `k`。 9 | 10 | ``` 11 | 输入: nums = [1,2,3,1], k = 3 12 | 输出: true 13 | 输入: nums = [1,0,1,1], k = 1 14 | 输出: true 15 | 输入: nums = [1,2,3,1,2,3], k = 2 16 | 输出: false 17 | ``` 18 | 19 | 20 | ## 方法 21 | 22 | 哈希 23 | 24 | ## code 25 | 26 | ### js 27 | 28 | ```js 29 | let containsNearByDuplicate = (nums, k) => { 30 | let m = new Set() 31 | for (let i = 0; i < nums.length; i++) { 32 | if (m.has(nums[i])) { 33 | return true; 34 | } 35 | m.add(nums[i]); 36 | if (m.size > k) { 37 | m.delete(nums[i - k]); 38 | } 39 | } 40 | return false; 41 | }; 42 | console.log(containsNearByDuplicate([1, 2, 3, 1], 3)); 43 | console.log(containsNearByDuplicate([1, 0, 1 ,1], 1)); 44 | console.log(containsNearByDuplicate([1, 2, 3, 1, 2, 3], 2)); 45 | ``` 46 | 47 | ### go 48 | 49 | ```go 50 | func containsNearbyDuplicate(nums []int, k int) bool { 51 | m := make(map[int]int, len(nums)) 52 | for i := 0; i < len(nums); i++ { 53 | if _, ok := m[nums[i]]; ok { 54 | return true 55 | } 56 | m[nums[i]] = 1 57 | if len(m) > k { 58 | delete(m, nums[i - k]) 59 | } 60 | } 61 | return false 62 | } 63 | ``` 64 | 65 | ### java 66 | 67 | ```java 68 | class Solution { 69 | public boolean containsNearbyDuplicate(int[] nums, int k) { 70 | HashSet set = new HashSet<>(); 71 | for (int i = 0; i < nums.length; i++) { 72 | if(set.contains(nums[i])) { 73 | return true; 74 | } 75 | set.add(nums[i]); 76 | // 关键这里 77 | if (set.size() > k) { 78 | set.remove(nums[i - k]); 79 | } 80 | } 81 | return false; 82 | } 83 | } 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /Java/alg/lc/22.括号生成.md: -------------------------------------------------------------------------------- 1 | # 22. 括号生成 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/generate-parentheses/) 5 | 6 | ## 题目 7 | 8 | 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 9 | 10 | 11 | ``` 12 | 输入:n = 3 13 | 输出:["((()))","(()())","(())()","()(())","()()()"] 14 | 输入:n = 1 15 | 输出:["()"] 16 | ``` 17 | 18 | ## 方法 19 | 20 | 21 | ## code 22 | 23 | ### js 24 | 25 | ```js 26 | let generateParenthesis = n => { 27 | let dfs = (ans, cnt1, cnt2, n) => { 28 | if (cnt1 > n || cnt2 > n) 29 | return; 30 | if (cnt1 === n && cnt2 === n) 31 | ret.push(ans); 32 | if (cnt1 >= cnt2){ 33 | dfs(ans + "(", cnt1 + 1, cnt2, n); 34 | dfs(ans + ")", cnt1, cnt2 + 1, n); 35 | } 36 | }; 37 | let ret = [] 38 | dfs("", 0, 0, n); 39 | return ret; 40 | }; 41 | ``` 42 | 43 | ### go 44 | 45 | ```go 46 | func generateParenthesis(n int) []string { 47 | var res []string 48 | var dfs func(ans string, cnt1 int, cnt2 int, n int) 49 | dfs = func (ans string, cnt1 int, cnt2 int, n int) { 50 | if cnt1 > n || cnt2 > n { 51 | return 52 | } 53 | if cnt1 == n && cnt2 == n { 54 | res = append(res, ans) 55 | } 56 | if cnt1 >= cnt2 { 57 | //ans1 := ans 58 | dfs(ans + "(", cnt1 + 1, cnt2, n) 59 | dfs(ans + ")", cnt1, cnt2 + 1, n) 60 | } 61 | } 62 | dfs("", 0, 0, n) 63 | return res 64 | } 65 | ``` 66 | 67 | ### java 68 | 69 | ```java 70 | class Solution { 71 | List ret = new ArrayList<>(); 72 | public List generateParenthesis(int n) { 73 | dfs("", 0, 0, n); 74 | return ret; 75 | } 76 | 77 | public void dfs(String ans, int cnt1, int cnt2, int n){ 78 | if (cnt1 > n || cnt2 > n) 79 | return; 80 | if (cnt1 == n && cnt2 == n) 81 | ret.add(ans); 82 | if (cnt1 >= cnt2){ 83 | String ans1 = new String(ans); 84 | dfs(ans + "(", cnt1 + 1, cnt2, n); 85 | dfs(ans + ")", cnt1, cnt2 + 1, n); 86 | } 87 | } 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /Java/alg/lc/226.翻转二叉树.md: -------------------------------------------------------------------------------- 1 | # 226. 翻转二叉树 2 | 3 | [url](https://leetcode-cn.com/problems/invert-binary-tree/) 4 | 5 | 6 | ## 题目 7 | 8 | 翻转一棵二叉树。 9 | 10 | 示例 11 | ``` 12 | 4 13 | / \ 14 | 2 7 15 | / \ / \ 16 | 1 3 6 9 17 | ``` 18 | 19 | 输出 20 | 21 | ``` 22 | 4 23 | / \ 24 | 7 2 25 | / \ / \ 26 | 9 6 3 1 27 | ``` 28 | 29 | 30 | ## 方法 31 | 32 | 递归 33 | 34 | ## code 35 | 36 | ### js 37 | 38 | ```js 39 | let invertTree = root => { 40 | if (root == null) return null; 41 | let left = root.left; 42 | root.left = invertTree(root.right); 43 | root.right = invertTree(left); 44 | return root; 45 | }; 46 | ``` 47 | 48 | ### go 49 | 50 | ```go 51 | func invertTree(root *TreeNode) *TreeNode { 52 | if root == nil { 53 | return nil 54 | } 55 | left := root.Left 56 | root.Left = invertTree(root.Right) 57 | root.Right = invertTree(left) 58 | return root 59 | } 60 | ``` 61 | 62 | ### java 63 | 64 | ```java 65 | class Solution { 66 | public TreeNode invertTree(TreeNode root) { 67 | if (root == null) return null; 68 | TreeNode left = root.left; 69 | root.left = invertTree(root.right); 70 | root.right = invertTree(left); 71 | return root; 72 | } 73 | } 74 | ``` 75 | 76 | -------------------------------------------------------------------------------- /Java/alg/lc/231.2的幂.md: -------------------------------------------------------------------------------- 1 | # 231. 2的幂 2 | 3 | [url](https://leetcode-cn.com/problems/power-of-two/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个整数,编写一个函数来判断它是否是 `2` 的幂次方。 9 | 10 | 示例 11 | ``` 12 | 输入: 1 13 | 输出: true 14 | 解释: 2的0次方 = 1 15 | 输入: 16 16 | 输出: true 17 | 解释: 2的4次方 = 16 18 | 输入: 218 19 | 输出: false 20 | ``` 21 | 22 | 23 | ## 方法 24 | 25 | 计算该数在二进制1的个数 26 | - 如果等于1,则是2的幂 27 | - 否则不是 28 | 29 | ## code 30 | 31 | ### js 32 | 33 | ```js 34 | let isPowerOfTwo = n => { 35 | return n > 0 && countOf1(n) === 1; 36 | }; 37 | // 手写,不就是计算1的个数嘛 38 | let countOf1 = n => { 39 | let cnt = 0; 40 | while (n !== 0) { 41 | cnt++; 42 | n &= (n - 1); 43 | } 44 | return cnt; 45 | }; 46 | console.log(isPowerOfTwo(1)); 47 | console.log(isPowerOfTwo(16)); 48 | console.log(isPowerOfTwo(218)); 49 | ``` 50 | 51 | ### go 52 | 53 | ```go 54 | func isPowerOfTwo(n int) bool { 55 | return n > 0 && bits.OnesCount32(uint32(n)) == 1 56 | } 57 | ``` 58 | 59 | ### java 60 | 61 | ```java 62 | class Solution { 63 | public boolean isPowerOfTwo(int n) { 64 | return n > 0 && Integer.bitCount(n) == 1; 65 | } 66 | } 67 | ``` 68 | 69 | -------------------------------------------------------------------------------- /Java/alg/lc/237.删除链表中的节点.md: -------------------------------------------------------------------------------- 1 | # 237. 删除链表中的节点 2 | 3 | [url](https://leetcode-cn.com/problems/delete-node-in-a-linked-list/) 4 | 5 | 6 | ## 题目 7 | 8 | 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。 9 | 10 | 现有一个链表 `-- head = [4,5,1,9]`,它可以表示为: 11 | 12 | ![237_example-lc-mhWRJh](https://cdn.jsdelivr.net/gh/DreamCats/imgs@main/uPic/237_example-lc-mhWRJh.png) 13 | 14 | ``` 15 | 输入:head = [4,5,1,9], node = 5 16 | 输出:[4,1,9] 17 | 解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9. 18 | 输入:head = [4,5,1,9], node = 1 19 | 输出:[4,5,9] 20 | 解释:给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9. 21 | 22 | ``` 23 | 24 | 25 | ## 方法 26 | 27 | 28 | 29 | ## code 30 | 31 | ### js 32 | 33 | ```js 34 | let deleteNode = node => { 35 | node.val = node.next.val; 36 | node.next = node.next.next; 37 | }; 38 | ``` 39 | 40 | ### go 41 | 42 | ```go 43 | func deleteNode(node *ListNode) { 44 | node.Val = node.Next.Val 45 | node.Next = node.Next.Next 46 | } 47 | ``` 48 | 49 | ### java 50 | 51 | ```java 52 | class Solution { 53 | public void deleteNode(ListNode node) { 54 | node.val = node.next.val; 55 | node.next = node.next.next; 56 | } 57 | } 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /Java/alg/lc/24.两两交换链表中的节点.md: -------------------------------------------------------------------------------- 1 | # 24. 两两交换链表中的节点 2 | 3 | 4 | 5 | [url](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 6 | 7 | ## 题目 8 | 9 | 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 10 | 11 | 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 12 | 13 | ![](https://assets.leetcode.com/uploads/2020/10/03/swap_ex1.jpg) 14 | ``` 15 | 输入:head = [1,2,3,4] 16 | 输出:[2,1,4,3] 17 | 输入:head = [] 18 | 输出:[] 19 | 输入:head = [1] 20 | 输出:[1] 21 | ``` 22 | 23 | ## 方法 24 | 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let swapPairs = head => { 32 | if (head === null) 33 | return head; 34 | let node = new ListNode(); 35 | node.next = head; 36 | let pre = node; 37 | while (pre.next !== null && pre.next.next !== null) { 38 | let l1 = pre.next, l2 = pre.next.next; 39 | let next = l2.next; 40 | l1.next = next; 41 | l2.next = l1; 42 | pre.next = l2; 43 | pre = l1; 44 | } 45 | return node.next; 46 | }; 47 | ``` 48 | 49 | ### go 50 | 51 | ```go 52 | func swapPairs(head *ListNode) *ListNode { 53 | if head == nil { 54 | return head 55 | } 56 | node := &ListNode{Val: 0} 57 | node.Next = head 58 | pre := node 59 | for pre.Next != nil && pre.Next.Next != nil { 60 | l1, l2 := pre.Next, pre.Next.Next 61 | next := l2.Next 62 | l1.Next = next 63 | l2.Next = l1 64 | pre.Next = l2 65 | pre = l1 66 | } 67 | return node.Next 68 | } 69 | ``` 70 | 71 | ### java 72 | 73 | ```java 74 | class Solution { 75 | public ListNode swapPairs(ListNode head) { 76 | if (head == null) 77 | return head; 78 | ListNode node = new ListNode(0); 79 | node.next = head; 80 | ListNode pre = node; 81 | while (pre.next != null && pre.next.next != null) { 82 | ListNode l1 = pre.next, l2 = pre.next.next; 83 | ListNode next = l2.next; 84 | l1.next = next; 85 | l2.next = l1; 86 | pre.next = l2; 87 | pre = l1; 88 | } 89 | return node.next; 90 | } 91 | } 92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /Java/alg/lc/242.有效的字母异位词.md: -------------------------------------------------------------------------------- 1 | # 242. 有效的字母异位词 2 | 3 | [url](https://leetcode-cn.com/problems/valid-anagram/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定两个字符串 `s` 和 `t` ,编写一个函数来判断 `t` 是否是 `s` 的字母异位词。 9 | 10 | ``` 11 | 输入: s = "anagram", t = "nagaram" 12 | 输出: true 13 | 输入: s = "rat", t = "car" 14 | 输出: false 15 | ``` 16 | 17 | 18 | ## 方法 19 | 20 | 其实就是判断两个字符串中的对应的字符数量 21 | 22 | - 遍历s,对应的字符++ 23 | - 遍历t,对应的字符-- 24 | - 遍历桶,如果存在不为0,则返回false 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let isAnagram = (s, t) => { 32 | let cnts = Array(256).fill(0); 33 | s = s.split(""); 34 | t = t.split(""); 35 | s.forEach(item => cnts[item.charCodeAt(0)]++); 36 | t.forEach(item => cnts[item.charCodeAt(0)]--); 37 | for (let value of cnts) { 38 | if (value !== 0) return false; 39 | } 40 | return true; 41 | }; 42 | console.log(isAnagram("anagram", "nagaram")) 43 | console.log(isAnagram("rat", "car")) 44 | ``` 45 | 46 | ### go 47 | 48 | ```go 49 | func isAnagram(s, t string) bool { 50 | cnts := make([]int, 256) 51 | for _, c := range s{ 52 | cnts[c]++ 53 | } 54 | for _, c := range t{ 55 | cnts[c]-- 56 | } 57 | for _, c := range cnts { 58 | if c != 0 { 59 | return false 60 | } 61 | } 62 | return true 63 | } 64 | ``` 65 | 66 | ### java 67 | 68 | ```java 69 | class Solution { 70 | public boolean isAnagram(String s, String t) { 71 | int[] cnts = new int[26]; 72 | for (char c : s.toCharArray()) { 73 | cnts[c - 'a']++; 74 | } 75 | for (char c : t.toCharArray()) { 76 | cnts[c - 'a']--; 77 | } 78 | for (int c : cnts) { 79 | if (c != 0) return false; 80 | } 81 | return true; 82 | } 83 | } 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /Java/alg/lc/26.删除排序数组中的重复项.md: -------------------------------------------------------------------------------- 1 | # 26. 删除排序数组中的重复项 2 | 3 | [url](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/) 4 | 5 | ## 题目 6 | 7 | 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 8 | 9 | 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 10 | 11 | 给定数组 `nums = [1,1,2]`, 12 | 13 | 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 14 | 15 | 你不需要考虑数组中超出新长度后面的元素。 16 | 17 | 18 | ## 方法 19 | 20 | 双指针,其实就是一个指针固定第一个元素上,第二个指针遍历数组。 21 | - 比如,1,2,2。 用一个变量p在1元素上的索引,另一个变量i在元素2的索引上 22 | - 将索引i的元素和索引p的索引上的元素进行比较 23 | - 如果元素不相同,就将p索引+1,并之后将索引i的元素转移到索引p的位置上 24 | - 如果相同,就i依然跟着数组遍历 25 | 26 | 27 | ## code 28 | 29 | ### js 30 | 31 | ```js{cmd="node"} 32 | let removeDuplicates = nums => { 33 | let p = 0; 34 | for (let i = 1; i < nums.length; i++) { 35 | if (nums[i] !== nums[p]) { 36 | nums[++p] = nums[i]; 37 | } 38 | } 39 | console.log(nums); 40 | return p + 1; 41 | } 42 | 43 | console.log(removeDuplicates([1, 1, 2])); 44 | ``` 45 | 46 | ### go 47 | 48 | ```go 49 | func removeDuplicates(nums []int) int { 50 | // 单指针 51 | p := 0 52 | for i := 1; i < len(nums); i++ { 53 | if nums[i] != nums[p] { 54 | p += 1 55 | nums[p] = nums[i] 56 | } 57 | } 58 | return p + 1 59 | } 60 | ``` 61 | 62 | 63 | 64 | ### java 65 | 66 | ```java 67 | class Solution { 68 | public int removeDuplicates(int[] nums) { 69 | // 单指针 70 | int p = 0; 71 | for(int i = 1; i < nums.length; i++){ 72 | if (nums[i] != nums[p]) { 73 | nums[++p] = nums[i]; 74 | } 75 | } 76 | return p + 1; 77 | } 78 | } 79 | ``` 80 | 81 | -------------------------------------------------------------------------------- /Java/alg/lc/268.丢失的数字.md: -------------------------------------------------------------------------------- 1 | # 268. 丢失的数字 2 | 3 | [url](https://leetcode-cn.com/problems/valid-anagram/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个包含 `[0, n]` 中 `n` 个数的数组 `nums` ,找出 `[0, n]` 这个范围内没有出现在数组中的那个数。 9 | 10 | ``` 11 | 输入:nums = [3,0,1] 12 | 输出:2 13 | 解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。 14 | 输入:nums = [0,1] 15 | 输出:2 16 | 解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。 17 | 输入:nums = [9,6,4,2,3,5,7,0,1] 18 | 输出:8 19 | 解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。 20 | 输入:nums = [0] 21 | 输出:1 22 | 解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。 23 | ``` 24 | 25 | 26 | ## 方法 27 | 28 | 还是异或 29 | 30 | ## code 31 | 32 | ### js 33 | 34 | ```js 35 | let missingNumber = nums => { 36 | let ret = 0; 37 | nums.forEach((index, item) => ret = ret ^ index ^ item); 38 | return ret ^ nums.length; 39 | }; 40 | console.log(missingNumber([3, 0 ,1 ])) 41 | console.log(missingNumber([0, 1])) 42 | console.log(missingNumber([9,6,4,2,3,5,7,0,1])) 43 | console.log(missingNumber([0])) 44 | ``` 45 | 46 | ### go 47 | 48 | ```go 49 | func missingNumber(nums []int) int { 50 | ret := 0 51 | for i, v := range nums{ 52 | ret = ret ^ i ^ v 53 | } 54 | return ret ^ len(nums) 55 | } 56 | ``` 57 | 58 | ### java 59 | 60 | ```java 61 | class Solution { 62 | public int missingNumber(int[] nums) { 63 | int ret = 0; 64 | for (int i = 0; i < nums.length; i++) { 65 | ret = ret ^ i ^ nums[i]; 66 | } 67 | return ret ^ nums.length; 68 | } 69 | } 70 | ``` 71 | 72 | -------------------------------------------------------------------------------- /Java/alg/lc/27.移除元素.md: -------------------------------------------------------------------------------- 1 | # 27. 移除元素 2 | 3 | [url](https://leetcode-cn.com/problems/remove-element/) 4 | 5 | ## 题目 6 | 7 | ``` 8 | 给你一个数组 `nums `和一个值 `val`,你需要 原地 移除所有数值等于 `val` 的元素,并返回移除后数组的新长度。 9 | 10 | 不要使用额外的数组空间,你必须仅使用 `O(1) `额外空间并 原地修改输入数组。 11 | 12 | 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 13 | 14 | 给定 `nums = [3,2,2,3], val = 3`, 15 | 16 | 函数应该返回新的长度 2, 并且 `nums` 中的前两个元素均为 2。 17 | 18 | 你不需要考虑数组中超出新长度后面的元素。 19 | 20 | 给定 `nums = [0,1,2,2,3,0,4,2], val = 2`, 21 | 22 | 函数应该返回新的长度 5, 并且 `nums` 中的前五个元素为 `0, 1, 3, 0, 4`。 23 | 24 | 注意这五个元素可为任意顺序。 25 | 26 | 你不需要考虑数组中超出新长度后面的元素。 27 | ``` 28 | 29 | 30 | 31 | 32 | ## 方法 33 | 34 | 双指针,其实就是一个指针固定第一个元素上,第二个指针遍历数组。 35 | - 比如,3,2,2,3。 用一个变量p和i在3元素上的索引, 36 | - 将索引i的元素和val的值进行比较 37 | - 如果值不相同,将索引i的元素转移到索引p的位置上,并且将p++ 38 | - 如果相同,就i依然跟着数组遍历 39 | 40 | 41 | ## code 42 | 43 | ### js 44 | 45 | ```js{cmd="node"} 46 | let removeElement = (nums, val) => { 47 | let p = 0; 48 | for (let i = 0; i < nums.length; i++) { 49 | if (nums[i] !== val) 50 | nums[p++] = nums[i]; 51 | } 52 | console.log(nums); 53 | return p; 54 | } 55 | 56 | console.log(removeElement([3, 2, 2, 3], 3)); 57 | ``` 58 | 59 | ### go 60 | 61 | ```go 62 | func removeElement(nums []int, val int) int { 63 | p := 0 64 | for i := 0; i < len(nums); i++ { 65 | if nums[i] != val { 66 | nums[p] = nums[i] 67 | p++ 68 | } 69 | } 70 | fmt.Println(nums) 71 | return p 72 | } 73 | ``` 74 | 75 | 76 | 77 | ### java 78 | 79 | ```java 80 | class Solution { 81 | public int removeElement(int[] nums, int val) { 82 | int p = 0; 83 | for (int i = 0; i < nums.length; i++){ 84 | if (nums[i] != val) 85 | nums[p++] = nums[i]; 86 | } 87 | return p; 88 | } 89 | } 90 | ``` 91 | 92 | -------------------------------------------------------------------------------- /Java/alg/lc/278.第一个错误的版本.md: -------------------------------------------------------------------------------- 1 | # 278. 第一个错误的版本 2 | 3 | [url](https://leetcode-cn.com/problems/first-bad-version/) 4 | 5 | 6 | ## 题目 7 | 8 | 你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。 9 | 10 | 假设你有 `n` 个版本 `[1, 2, ..., n]`,你想找出导致之后所有版本出错的第一个错误的版本。 11 | 12 | 你可以通过调用 `bool isBadVersion(version)` 接口来判断版本号 `version` 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 `API` 的次数。 13 | 14 | 15 | ``` 16 | 给定 n = 5,并且 version = 4 是第一个错误的版本。 17 | 调用 isBadVersion(3) -> false 18 | 调用 isBadVersion(5) -> true 19 | 调用 isBadVersion(4) -> true 20 | 所以,4 是第一个错误的版本。  21 | ``` 22 | 23 | 24 | ## 方法 25 | 26 | 二分法 27 | 28 | ## code 29 | 30 | ### js 31 | 32 | ```js 33 | let firstBadVersion = n => { 34 | let l = 1, h = n; 35 | while (l < h) { 36 | let m = l + Math.floor((h - l) / 2); 37 | if isBadVersion(m) { 38 | h = m; 39 | } else { 40 | l = m + 1; 41 | } 42 | } 43 | return l; 44 | }; 45 | ``` 46 | 47 | ### go 48 | 49 | ```go 50 | func firstBadVersion(n int) int { 51 | l, h := 1, n 52 | for l < h { 53 | m := l + (h - l) / 2 54 | if isBadVersion(m) { 55 | h = m 56 | } else { 57 | l = m + 1 58 | } 59 | } 60 | return l 61 | } 62 | ``` 63 | 64 | ### java 65 | 66 | ```java 67 | public class Solution extends VersionControl { 68 | public int firstBadVersion(int n) { 69 | int l = 1, h = n; 70 | while (l < h) { 71 | int mid = l + (h - l) / 2; 72 | if (isBadVersion(mid)) { 73 | h = mid; 74 | } else { 75 | l = mid + 1; 76 | } 77 | } 78 | return l; 79 | } 80 | } 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /Java/alg/lc/28.实现strStr.md: -------------------------------------------------------------------------------- 1 | # 28. 实现 strStr() 2 | 3 | [url](https://leetcode-cn.com/problems/implement-strstr/) 4 | 5 | ## 题目 6 | 7 | 实现 strStr() 函数。 8 | 9 | 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。 10 | 11 | ``` 12 | 输入: haystack = "hello", needle = "ll" 13 | 输出: 2 14 | 15 | 输入: haystack = "aaaaa", needle = "bba" 16 | 输出: -1 17 | ``` 18 | 19 | ## 方法 20 | 21 | 滑动窗口 22 | - 以needle的字符串长度在haystack字符串上滑动 23 | - 不断与needle进行匹配,如果相等,则返回窗口的开始索引 24 | 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js{cmd="node"} 31 | let strStr = (haystack, needle) => { 32 | let l = haystack.length; 33 | let r = needle.length; 34 | for (let i = 0; i < l - r + 1; i++) { 35 | if (haystack.substring(i , i + r) === needle) 36 | return i; 37 | } 38 | return -1; 39 | } 40 | 41 | console.log(strStr("hello", "ll")); 42 | console.log(strStr("aaaaa", "bba")); 43 | ``` 44 | 45 | ### go 46 | 47 | ```go 48 | func strStr(haystack, needle string) int { 49 | l := len(haystack) 50 | r := len(needle) 51 | for i := 0; i < l - r + 1; i++ { 52 | if haystack[i:i + r] == needle { 53 | return i 54 | } 55 | } 56 | return -1 57 | } 58 | ``` 59 | 60 | 61 | 62 | ### java 63 | 64 | ```java 65 | class Solution { 66 | public int strStr(String haystack, String needle) { 67 | int l = haystack.length(), r = needle.length(); 68 | for (int i = 0; i < l - r + 1; i++) { 69 | if (haystack.substring(i, i + r).equals(needle)) 70 | return i; 71 | } 72 | return -1; 73 | } 74 | } 75 | ``` 76 | 77 | -------------------------------------------------------------------------------- /Java/alg/lc/283.移动零.md: -------------------------------------------------------------------------------- 1 | # 283. 移动零 2 | 3 | [url](https://leetcode-cn.com/problems/move-zeroes/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 9 | 10 | ``` 11 | 输入: [0,1,0,3,12] 12 | 输出: [1,3,12,0,0] 13 | ``` 14 | 15 | 16 | ## 方法 17 | 18 | - 将所有的非0向前移动 19 | - 从非0的最后位置开始遍历填充0 20 | 21 | ## code 22 | 23 | ### js 24 | 25 | ```js 26 | let moveZeros = nums => { 27 | let idx = 0; 28 | nums.forEach(num => {if (num !== 0) nums[idx++] = num}); 29 | while (idx < nums.length) { 30 | nums[idx++] = 0; 31 | } 32 | return nums; 33 | }; 34 | console.log(moveZeros([0,1,0,3,12])) 35 | ``` 36 | 37 | ### go 38 | 39 | ```go 40 | func moveZeroes(nums []int) { 41 | idx := 0 42 | for _, num := range nums { 43 | if num != 0 { 44 | nums[idx] = num 45 | idx++ 46 | } 47 | } 48 | for idx < len(nums) { 49 | nums[idx] = 0 50 | idx++ 51 | } 52 | fmt.Println(nums) 53 | } 54 | ``` 55 | 56 | ### java 57 | 58 | ```java 59 | class Solution { 60 | public void moveZeroes(int[] nums) { 61 | int idx = 0; 62 | for (int num : nums) { 63 | if (num != 0) nums[idx++] = num; 64 | } 65 | while (idx < nums.length) { 66 | nums[idx++] =0; 67 | } 68 | } 69 | } 70 | ``` 71 | 72 | -------------------------------------------------------------------------------- /Java/alg/lc/3.无重复字符的最长子串.md: -------------------------------------------------------------------------------- 1 | # 3. 无重复字符的最长子串 2 | 3 | [url](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) 4 | 5 | ## 题目 6 | 7 | 8 | ``` 9 | 输入: s = "abcabcbb" 10 | 输出: 3 11 | 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 12 | 输入: s = "bbbbb" 13 | 输出: 1 14 | 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 15 | 输入: s = "pwwkew" 16 | 输出: 3 17 | 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 18 |   请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 19 | ``` 20 | 21 | ## 方法 22 | 23 | 24 | ## code 25 | 26 | ### js 27 | 28 | ```js 29 | let lengthOfLongestSubstring = s => { 30 | if (s === null || s.length === 0) 31 | return -1; 32 | let n = s.length, max = 0; 33 | let map = new Map(); 34 | for (let i = 0, j = 0; j < n; j++) { 35 | let c = s[j]; 36 | if (map.has(c)) { 37 | i = Math.max(i, map.get(c)); 38 | } 39 | max = Math.max(max, j - i + 1); 40 | map.set(c, j + 1); 41 | } 42 | return max; 43 | }; 44 | ``` 45 | 46 | ### go 47 | 48 | ```go 49 | func lengthOfLongestSubstring(s string) int { 50 | Max := func(a int, b int) int { 51 | if a < b { 52 | return b 53 | } else { 54 | return a 55 | } 56 | } 57 | if len(s) == 0 { 58 | return -1 59 | } 60 | n := len(s) 61 | max := 0 62 | m := make(map[byte]int, 0) 63 | for i, j := 0, 0; j < n; j++ { 64 | c := s[j] 65 | if v, ok := m[c]; ok { 66 | i = Max(i, v) 67 | } 68 | max = Max(max, j - i + 1) 69 | m[c] = j + 1 70 | } 71 | return max 72 | } 73 | ``` 74 | 75 | ### java 76 | 77 | ```java 78 | class Solution { 79 | public int lengthOfLongestSubstring(String s) { 80 | if (s == null || s.length() == 0) 81 | return -1; 82 | int n = s.length(); 83 | int max = 0; 84 | Map map = new HashMap<>(); 85 | for (int i = 0, j = 0; j < n; j++){ 86 | char c = s.charAt(j); 87 | if (map.containsKey(c)){ 88 | i = Math.max(i, map.get(c)); 89 | } 90 | max = Math.max(max, j - i + 1); 91 | map.put(c, j + 1); 92 | } 93 | return max; 94 | } 95 | } 96 | ``` 97 | 98 | -------------------------------------------------------------------------------- /Java/alg/lc/326.3的幂.md: -------------------------------------------------------------------------------- 1 | # 326. 3的幂 2 | 3 | [url](https://leetcode-cn.com/problems/power-of-three/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定一个整数,写一个函数来判断它是否是 `3` 的幂次方。如果是,返回 `true` ;否则,返回 `false` 。 9 | 10 | 整数 `n` 是 3` 的幂次方需满足:存在整数 `x` 使得 `n == 3x` 11 | 12 | 13 | ``` 14 | 输入:n = 27 15 | 输出:true 16 | 输入:n = 0 17 | 输出:false 18 | 输入:n = 9 19 | 输出:true 20 | 输入:n = 45 21 | 输出:false 22 | ``` 23 | 24 | 25 | ## 方法 26 | 27 | 28 | ## code 29 | 30 | ### js 31 | 32 | ```js 33 | let isPowerOfThree = n => { 34 | if (n < 1) return false; 35 | while (n > 1) { 36 | if (n % 3 !== 0) { 37 | return false; 38 | } 39 | n /= 3; 40 | } 41 | }; 42 | ``` 43 | 44 | ### go 45 | 46 | ```go 47 | func isPowerOfThree(n int) bool { 48 | if n < 1 { 49 | return false 50 | } 51 | for n > 1 { 52 | if n % 3 != 0 { 53 | return false 54 | } 55 | n /= 3 56 | } 57 | return true 58 | } 59 | ``` 60 | 61 | ### java 62 | 63 | ```java 64 | class Solution { 65 | public boolean isPowerOfThree(int n) { 66 | if (n < 1) return false; 67 | while(n > 1) { 68 | if (n % 3 != 0) return false; 69 | n /= 3; 70 | } 71 | return true; 72 | } 73 | } 74 | ``` 75 | 76 | -------------------------------------------------------------------------------- /Java/alg/lc/344.反转字符串.md: -------------------------------------------------------------------------------- 1 | # 344. 反转字符串 2 | 3 | [url](https://leetcode-cn.com/problems/reverse-string/) 4 | 5 | 6 | ## 题目 7 | 8 | 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 `char[]` 的形式给出。 9 | 10 | 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 `O(1) `的额外空间解决这一问题。 11 | 12 | 你可以假设数组中的所有字符都是 `ASCII` 码表中的可打印字符。 13 | 14 | 15 | 16 | ``` 17 | 输入:["h","e","l","l","o"] 18 | 输出:["o","l","l","e","h"] 19 | 输入:["H","a","n","n","a","h"] 20 | 输出:["h","a","n","n","a","H"] 21 | ``` 22 | 23 | 24 | ## 方法 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let reverseString = s => { 32 | let swap = (s, i, j) => { 33 | let temp = s[i]; 34 | s[i] = s[j]; 35 | s[j] = temp; 36 | }; 37 | let p1 = 0, p2 = s.length - 1; 38 | while (p1 < p2) { 39 | swap(s, p1++, p2--); 40 | } 41 | return s; 42 | }; 43 | console.log(reverseString(["h","e","l","l","o"])) 44 | ``` 45 | 46 | ### go 47 | 48 | ```go 49 | func reverseString(s []byte) { 50 | swap := func (s []byte, i, j int) { 51 | temp := s[i] 52 | s[i] = s[j] 53 | s[j] = temp 54 | } 55 | p1, p2 := 0, len(s) - 1 56 | for p1 < p2 { 57 | swap(s, p1, p2) 58 | p1++ 59 | p2-- 60 | } 61 | fmt.Println(s) 62 | } 63 | ``` 64 | 65 | ### java 66 | 67 | ```java 68 | class Solution { 69 | public void reverseString(char[] s) { 70 | int p1 = 0, p2 = s.length - 1; 71 | while(p1 < p2 ){ 72 | swap(s, p1++, p2--); 73 | } 74 | } 75 | public void swap(char[] s, int i, int j) { 76 | char temp = s[i]; 77 | s[i] = s[j]; 78 | s[j] = temp; 79 | } 80 | } 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /Java/alg/lc/350.两个数组的交集2.md: -------------------------------------------------------------------------------- 1 | # 350. 两个数组的交集 II 2 | 3 | [url](https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/) 4 | 5 | 6 | ## 题目 7 | 8 | 给定两个数组,编写一个函数来计算它们的交集。 9 | 10 | 11 | 12 | ``` 13 | 输入:nums1 = [1,2,2,1], nums2 = [2,2] 14 | 输出:[2,2] 15 | 输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 16 | 输出:[4,9] 17 | ``` 18 | 19 | 20 | ## 方法 21 | 22 | ## code 23 | 24 | ### js 25 | 26 | ```js 27 | let intersect = (nums1, nums2) => { 28 | let list = []; 29 | for (let value of nums2) { 30 | if (nums1.includes(value)) { 31 | list.push(value); 32 | nums1.splice(nums1.findIndex(item => item === value), 1); 33 | } 34 | } 35 | return list; 36 | }; 37 | console.log(intersect([1,2,2,1], [2, 2])) 38 | console.log(intersect([4,9,5], [9,4,9,8,4])) 39 | ``` 40 | 41 | ### go 42 | 43 | ```go 44 | func intersect(nums1 []int, nums2 []int) []int { 45 | containInt := func (nums []int, item int) (int, bool) { 46 | for i, num := range nums { 47 | if num == item { 48 | return i, true 49 | } 50 | } 51 | return 0, false 52 | } 53 | var res []int 54 | for _, value := range nums2 { 55 | if i, ok := containInt(nums1, value); ok { 56 | res = append(res, value) 57 | nums1 = append(nums1[:i], nums1[i+1:]...) 58 | } 59 | } 60 | return res 61 | } 62 | ``` 63 | 64 | ### java 65 | 66 | ```java 67 | class Solution { 68 | public int[] intersect(int[] nums1, int[] nums2) { 69 | ArrayList list1 = new ArrayList<>(); 70 | for (int num : nums1) { 71 | list1.add(num); 72 | } 73 | ArrayList list2 = new ArrayList<>(); 74 | for (int num : nums2) { 75 | if (list1.contains(num)) { 76 | list2.add(num); 77 | list1.remove(num); 78 | } 79 | } 80 | return list2.stream().mapToInt(Integer::valueOf).toArray(); 81 | // int[] ans = new int[list2.size()]; 82 | // for (int i = 0; i < list2.size(); i++) { 83 | // ans[i] = list2.get(i); 84 | // } 85 | // return ans; 86 | } 87 | } 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /Java/alg/lc/367.有效的完全平方数.md: -------------------------------------------------------------------------------- 1 | # 367. 有效的完全平方数 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/valid-perfect-square/) 5 | 6 | 7 | ## 题目 8 | 9 | 给定一个正整数 `num`,编写一个函数,如果 `num` 是一个完全平方数,则返回 `True`,否则返回 `False`。 10 | 11 | 说明:不要使用任何内置的库函数,如  `sqrt`。 12 | 13 | 14 | 15 | 16 | ``` 17 | 输入:16 18 | 输出:True 19 | 输入:14 20 | 输出:False 21 | ``` 22 | 23 | 24 | ## 方法 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let isPerfectSquare = num => { 32 | let subNum = 1; 33 | while (num > 0) { 34 | num -= subNum; 35 | subNum += 2; 36 | } 37 | return num === 0; 38 | }; 39 | console.log(isPerfectSquare(16)) 40 | console.log(isPerfectSquare(14)) 41 | ``` 42 | 43 | ### go 44 | 45 | ```go 46 | func isPerfectSquare(num int) bool { 47 | subNum := 1 48 | for num > 0 { 49 | num -= subNum 50 | subNum += 2 51 | } 52 | return num == 0 53 | } 54 | ``` 55 | 56 | ### java 57 | 58 | ```java 59 | class Solution { 60 | public boolean isPerfectSquare(int num) { 61 | int subNum = 1; 62 | while (num > 0) { 63 | num -= subNum; 64 | subNum += 2; 65 | } 66 | return num == 0; 67 | } 68 | } 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- /Java/alg/lc/371.两整数之和.md: -------------------------------------------------------------------------------- 1 | # 371. 两整数之和 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/sum-of-two-integers/) 5 | 6 | 7 | ## 题目 8 | 9 | 不使用运算符 `+` 和 `-` ​​​​​​​,计算两整数 `​​​​​​​a` 、`b `​​​​​​​之和。 10 | 11 | 12 | 13 | 14 | ``` 15 | 输入: a = 1, b = 2 16 | 输出: 3 17 | 输入: a = -2, b = 3 18 | 输出: 1 19 | ``` 20 | 21 | 22 | ## 方法 23 | 24 | ## code 25 | 26 | ### js 27 | 28 | ```js 29 | let getSum = (a, b) => { 30 | return b === 0 ? a : getSum((a ^ b), (a & b) << 1); 31 | }; 32 | console.log(getSum(1, 2)); 33 | console.log(getSum(-2, 3)); 34 | ``` 35 | 36 | ### go 37 | 38 | ```go 39 | func getSum(a int, b int) int { 40 | if b == 0 { 41 | return a 42 | } else { 43 | return getSum(a ^ b, (a & b) << 1) 44 | } 45 | } 46 | ``` 47 | 48 | ### java 49 | 50 | ```java 51 | class Solution { 52 | public int getSum(int a, int b) { 53 | return b == 0 ? a : getSum((a ^ b), (a & b) << 1); 54 | } 55 | } 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /Java/alg/lc/387.字符串中的第一个唯一字符.md: -------------------------------------------------------------------------------- 1 | # 387. 字符串中的第一个唯一字符 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/first-unique-character-in-a-string/) 5 | 6 | 7 | ## 题目 8 | 9 | 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。 10 | 11 | 12 | 13 | 14 | ``` 15 | s = "leetcode" 16 | 返回 0 17 | s = "loveleetcode" 18 | 返回 2 19 | ``` 20 | 21 | 22 | ## 方法 23 | 24 | ## code 25 | 最容易想到的就是哈希,速战速决 26 | 27 | ### js 28 | 29 | ```js 30 | let firstUniqChar = s => { 31 | let map = new Map(); 32 | s = s.split(""); 33 | s.forEach(c => { 34 | map.has(c) ? map.set(c, map.get(c) + 1) : map.set(c, 1); 35 | }); 36 | for (const [i, v] of s.entries()) { 37 | if (map.get(v) === 1) { 38 | return i; 39 | } 40 | } 41 | return -1; 42 | }; 43 | console.log(firstUniqChar("leetcode")) 44 | console.log(firstUniqChar("loveleetcode")) 45 | ``` 46 | 47 | ### go 48 | 49 | ```go 50 | func firstUniqChar(s string) int { 51 | m := make(map[rune]int, len(s)) 52 | for _, c := range s { 53 | if count, ok := m[c]; ok { 54 | m[c] = count + 1 55 | } else { 56 | m[c] = 1 57 | } 58 | } 59 | for i, c := range s { 60 | if m[c] == 1 { 61 | return i 62 | } 63 | } 64 | return -1 65 | } 66 | ``` 67 | 68 | ### java 69 | 70 | ```java 71 | class Solution { 72 | public int firstUniqChar(String s) { 73 | HashMap map = new HashMap<>(); 74 | for (char c : s.toCharArray()){ 75 | map.put(c, map.getOrDefault(c, 0) + 1); 76 | } 77 | for (int i = 0; i < s.length(); i++) { 78 | if(map.get(s.charAt(i)) == 1) { 79 | return i; 80 | } 81 | } 82 | return -1; 83 | } 84 | } 85 | ``` 86 | 87 | -------------------------------------------------------------------------------- /Java/alg/lc/392.判断子序列.md: -------------------------------------------------------------------------------- 1 | # 392. 判断子序列 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/is-subsequence/) 5 | 6 | 7 | ## 题目 8 | 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 9 | 10 | 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 11 | 12 | 13 | ``` 14 | 输入:s = "abc", t = "ahbgdc" 15 | 输出:true 16 | 输入:s = "axc", t = "ahbgdc" 17 | 输出:false 18 | ``` 19 | 20 | 21 | ## 方法 22 | 23 | 循环计算索引和子串 24 | 25 | ## code 26 | 27 | ### js 28 | 29 | ```js 30 | let isSubsequence = (s, t) => { 31 | let idx = -1; 32 | for (const value of s) { 33 | idx = t.indexOf(value, idx + 1); 34 | if (idx === -1) 35 | return false; 36 | } 37 | return true; 38 | } 39 | console.log(isSubsequence("abc", "ahbqdc")) 40 | console.log(isSubsequence("axc", "ahbqdc")) 41 | ``` 42 | 43 | ### go 44 | 45 | ```go 46 | func isSubsequence(s string, t string) bool { 47 | idx := -1 48 | for _, c := range s { 49 | tmp := t[idx + 1:] 50 | idx = strings.IndexRune(tmp, c) 51 | if idx == -1 { 52 | return false 53 | } 54 | } 55 | return true 56 | } 57 | ``` 58 | 59 | ### java 60 | 61 | ```java 62 | class Solution { 63 | public boolean isSubsequence(String s, String t) { 64 | // 这里用到了String到indexof 65 | int inx = -1; 66 | for (char c : s.toCharArray()) { 67 | inx = t.indexOf(c, inx + 1); 68 | if (inx == -1) return false; 69 | } 70 | return true; 71 | } 72 | } 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /Java/alg/lc/409.最长回文串.md: -------------------------------------------------------------------------------- 1 | # 409. 最长回文串 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/longest-palindrome/) 5 | 6 | 7 | ## 题目 8 | 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。 9 | 10 | 在构造过程中,请注意区分大小写。比如 "Aa" 不能当做一个回文字符串。 11 | 12 | 13 | ``` 14 | 输入: 15 | "abccccdd" 16 | 输出: 17 | 7 18 | 解释: 19 | 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。 20 | ``` 21 | 22 | 23 | ## 方法 24 | 提示:分析回文串的特征 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let longestPalindrome = s => { 32 | let cnts = Array(256).fill(0); 33 | s = s.split(""); 34 | s.forEach(c => cnts[c.charCodeAt(0)]++); 35 | let palindrome = 0; 36 | cnts.forEach(cnt => palindrome += Math.floor(cnt / 2) * 2); 37 | if (palindrome < s.length) 38 | palindrome++; 39 | return palindrome; 40 | }; 41 | console.log(longestPalindrome("abccccdd")) 42 | ``` 43 | 44 | ### go 45 | 46 | ```go 47 | func longestPalindrome(s string) int { 48 | cnts := make([]int, 256) 49 | for _, c := range s { 50 | cnts[c]++ 51 | } 52 | palindrome := 0 53 | for _, cnt := range cnts { 54 | palindrome += (cnt / 2) * 2 55 | } 56 | if palindrome < len(s) { 57 | palindrome++ 58 | } 59 | return palindrome 60 | } 61 | ``` 62 | 63 | ### java 64 | 65 | ```java 66 | class Solution { 67 | public int longestPalindrome(String s) { 68 | int[] cnts = new int[256]; 69 | for (char c : s.toCharArray()) { 70 | cnts[c]++; 71 | } 72 | int palindrome = 0; 73 | for (int cnt : cnts) { 74 | palindrome += (cnt / 2) * 2; 75 | } 76 | if (palindrome < s.length()) { 77 | palindrome++; 78 | } 79 | return palindrome; 80 | } 81 | } 82 | ``` 83 | 84 | -------------------------------------------------------------------------------- /Java/alg/lc/415.字符串相加.md: -------------------------------------------------------------------------------- 1 | # 415. 字符串相加 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/add-strings/) 5 | 6 | 7 | ## 题目 8 | 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。 9 | 10 | 11 | ## 方法 12 | 13 | 14 | ## code 15 | 16 | ### js 17 | 18 | ```js 19 | let addStrings = (num1, num2) => { 20 | let res = '' 21 | let carry = 0, i = num1.length - 1, j = num2.length - 1 22 | while (carry === 1 || i >= 0 || j >= 0) { 23 | let x = i < 0 ? 0 : num1[i--] - '0' 24 | let y = j < 0 ? 0 : num2[j--] - '0' 25 | res += Math.floor((x + y + carry) % 10) 26 | carry = Math.floor((x + y + carry) / 10) 27 | } 28 | return res.split("").reverse().toString() 29 | } 30 | 31 | console.log(addStrings('123', '123')) 32 | ``` 33 | 34 | ### go 35 | 36 | ```go 37 | func addStrings(num1 string, num2 string) string { 38 | reverseStr := func (s string) string { 39 | runes := []rune(s) 40 | i, j := 0, len(runes) - 1 41 | for i < j { 42 | runes[i], runes[j] = runes[j], runes[i] 43 | i++ 44 | j-- 45 | } 46 | return string(runes) 47 | } 48 | x := 0 49 | y := 0 50 | carry := 0 51 | i, j := len(num1) - 1, len(num2) - 1 52 | var res string 53 | for carry == 1 || i >= 0 || j >= 0 { 54 | if i < 0 { 55 | x = 0 56 | } else { 57 | x, _ = strconv.Atoi(string(num1[i])) 58 | } 59 | if j < 0 { 60 | y = 0 61 | } else { 62 | y, _ = strconv.Atoi(string(num2[j])) 63 | } 64 | res += strconv.Itoa((x + y + carry) % 10) 65 | carry = (x + y + carry) / 10 66 | i-- 67 | j-- 68 | } 69 | return reverseStr(res) 70 | } 71 | ``` 72 | 73 | ### java 74 | 75 | ```java 76 | class Solution { 77 | public String addStrings(String num1, String num2) { 78 | StringBuilder str = new StringBuilder(); 79 | int carry = 0, i = num1.length() - 1, j = num2.length() - 1; 80 | while (carry == 1 || i >= 0 || j >= 0) { 81 | int x = i < 0 ? 0 : num1.charAt(i--) - '0'; 82 | int y = j < 0 ? 0 : num2.charAt(j--) - '0'; 83 | str.append((x + y + carry) % 10); 84 | carry = (x + y + carry) / 10; 85 | } 86 | return str.reverse().toString(); 87 | } 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /Java/alg/lc/434.字符串中的单词数.md: -------------------------------------------------------------------------------- 1 | # 434. 字符串中的单词数 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/number-of-segments-in-a-string/) 5 | 6 | 7 | ## 题目 8 | 统计字符串中的单词个数,这里的单词指的是连续的不是空格的字符。 9 | 10 | 请注意,你可以假定字符串里不包括任何不可打印的字符。 11 | 12 | ``` 13 | 输入: "Hello, my name is John" 14 | 输出: 5 15 | 解释: 这里的单词是指连续的不是空格的字符,所以 "Hello," 算作 1 个单词。 16 | ``` 17 | 18 | ## 方法 19 | 20 | 21 | ## code 22 | 23 | ### js 24 | 25 | ```js 26 | let countSegments = s => { 27 | let ss = s.split(" "); 28 | let cnt = 0; 29 | for (let value of ss) { 30 | if (value === " " || value.length === 0) continue; 31 | cnt++; 32 | } 33 | return cnt; 34 | }; 35 | ``` 36 | 37 | ### go 38 | 39 | ```go 40 | func countSegments(s string) int { 41 | ss := strings.Split(s, " ") 42 | cnt := 0 43 | for _, str := range ss { 44 | if str == " " || len(str) == 0{ 45 | continue 46 | } 47 | cnt++ 48 | } 49 | return cnt 50 | } 51 | ``` 52 | 53 | ### java 54 | 55 | ```java 56 | class Solution { 57 | public int countSegments(String s) { 58 | String[] strs = s.split(" "); 59 | int len = 0; 60 | for(String t : strs) { 61 | if(t.equals(" ") || t.isEmpty()) continue; 62 | len++; 63 | } 64 | return len; 65 | } 66 | } 67 | ``` 68 | 69 | -------------------------------------------------------------------------------- /Java/alg/lc/455.分发饼干.md: -------------------------------------------------------------------------------- 1 | # 455. 分发饼干 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/assign-cookies/) 5 | 6 | 7 | ## 题目 8 | 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 9 | 10 | 对每个孩子 i,都有一个胃口值 `g[i]`,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 `j`,都有一个尺寸 `s[j]` 。如果 `s[j] >= g[i]`,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 11 | 12 | 13 | ``` 14 | 输入: g = [1,2,3], s = [1,1] 15 | 输出: 1 16 | 解释: 17 | 你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。 18 | 虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。 19 | 所以你应该输出1。 20 | 输入: g = [1,2], s = [1,2,3] 21 | 输出: 2 22 | 解释: 23 | 你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。 24 | 你拥有的饼干数量和尺寸都足以让所有孩子满足。 25 | 所以你应该输出2. 26 | ``` 27 | 28 | ## 方法 29 | 排序+贪心 30 | 31 | ## code 32 | 33 | ### js 34 | 35 | ```js 36 | let findContentChildren = (g, s) => { 37 | if (g.length === 0 || s.length === 0) 38 | return 0; 39 | let gn = g.length, sn = s.length; 40 | // 排序 41 | g.sort(); 42 | s.sort(); 43 | let gi = 0, si = 0; 44 | while (gi < gn && si < sn) { 45 | if (g[gi] <= s[si]) 46 | gi++; 47 | si++; 48 | } 49 | return gi; 50 | }; 51 | console.log(findContentChildren([1,2,3], [1, 1])); 52 | console.log(findContentChildren([1,2], [1, 2, 3])); 53 | ``` 54 | 55 | ### go 56 | 57 | ```go 58 | func findContentChildren(g []int, s []int) int { 59 | if len(g) == 0 || len(s) == 0 { 60 | return 0 61 | } 62 | gn, sn := len(g), len(s) 63 | // 排序 64 | sort.Ints(g) 65 | sort.Ints(s) 66 | gi, si := 0, 0 67 | for gi < gn && si < sn { 68 | if g[gi] <= s[si] { 69 | gi++ 70 | } 71 | si++ 72 | } 73 | return gi 74 | } 75 | ``` 76 | 77 | ### java 78 | 79 | ```java 80 | class Solution { 81 | public int findContentChildren(int[] g, int[] s) { 82 | if (g.length == 0 || s.length == 0) return 0; 83 | // 长度 84 | int gn = g.length, sn = s.length; 85 | // 排序 86 | Arrays.sort(g); 87 | Arrays.sort(s); 88 | // 双指针,贪心 89 | int gi = 0, si = 0; 90 | while (gi < gn && si < sn) { 91 | if (g[gi] <= s[si]) gi++; 92 | si++; 93 | } 94 | return gi; 95 | } 96 | } 97 | ``` 98 | 99 | -------------------------------------------------------------------------------- /Java/alg/lc/461.汉明距离.md: -------------------------------------------------------------------------------- 1 | # 461. 汉明距离 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/hamming-distance/) 5 | 6 | 7 | ## 题目 8 | 两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。 9 | 10 | 给出两个整数 `x` 和 `y`,计算它们之间的汉明距离。 11 | 12 | 13 | ``` 14 | 输入: x = 1, y = 4 15 | 输出: 2 16 | 解释: 17 | 1 (0 0 0 1) 18 | 4 (0 1 0 0) 19 | ↑ ↑ 20 | 上面的箭头指出了对应二进制位不同的位置。 21 | ``` 22 | 23 | ## 方法 24 | 排序+贪心 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let hammingDistance = (x, y) => { 32 | let z = x ^ y; 33 | let cnt = 0; 34 | while (z !== 0) { 35 | z &= (z - 1); 36 | cnt++; 37 | } 38 | return cnt; 39 | }; 40 | console.log(hammingDistance(1, 4)); 41 | ``` 42 | 43 | ### go 44 | 45 | ```go 46 | func hammingDistance(x int, y int) int { 47 | z := x ^ y 48 | cnt := 0 49 | for z != 0 { 50 | z &= z - 1 51 | cnt++ 52 | } 53 | return cnt 54 | } 55 | ``` 56 | 57 | ### java 58 | 59 | ```java 60 | class Solution { 61 | public int hammingDistance(int x, int y) { 62 | int z = x ^ y; 63 | int cnt = 0; 64 | while (z != 0) { 65 | z &= (z - 1); 66 | cnt++; 67 | } 68 | return cnt; 69 | } 70 | } 71 | ``` 72 | 73 | -------------------------------------------------------------------------------- /Java/alg/lc/485.最大连续1的个数.md: -------------------------------------------------------------------------------- 1 | # 485. 最大连续 1 的个数 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/max-consecutive-ones/) 5 | 6 | 7 | ## 题目 8 | 给定一个二进制数组, 计算其中最大连续 `1` 的个数。 9 | 10 | 11 | ``` 12 | 输入:[1,1,0,1,1,1] 13 | 输出:3 14 | 解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3. 15 | ``` 16 | 17 | ## 方法 18 | 19 | 20 | ## code 21 | 22 | ### js 23 | 24 | ```js 25 | let findMaxConsecutiveOnes = nums => { 26 | let max = 0, cur = 0; 27 | nums.forEach(num => { 28 | cur = num === 0 ? 0 : cur + 1; 29 | max = Math.max(max, cur); 30 | }); 31 | return max; 32 | }; 33 | console.log(findMaxConsecutiveOnes([1, 1, 0, 1, 1, 1])); 34 | ``` 35 | 36 | ### go 37 | 38 | ```go 39 | func findMaxConsecutiveOnes(nums []int) int { 40 | Max := func(a, b int) int { 41 | if a > b { 42 | return a 43 | } else { 44 | return b 45 | } 46 | } 47 | max, cur := 0, 0 48 | for _, num := range nums { 49 | if num == 0 { 50 | cur = 0 51 | } else { 52 | cur = cur + 1 53 | } 54 | max = Max(cur, max) 55 | } 56 | return max 57 | } 58 | ``` 59 | 60 | ### java 61 | 62 | ```java 63 | class Solution { 64 | public int findMaxConsecutiveOnes(int[] nums) { 65 | int max = 0, cur = 0; 66 | for (int x : nums) { 67 | cur = x == 0 ? 0 : cur + 1; 68 | max = Math.max(max, cur); 69 | } 70 | return max; 71 | } 72 | } 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /Java/alg/lc/509.斐波那契数.md: -------------------------------------------------------------------------------- 1 | # 509. 斐波那契数 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/fibonacci-number/) 5 | 6 | 7 | ## 题目 8 | 斐波那契数,通常用 `F(n)` 表示,形成的序列称为 斐波那契数列 。该数列由 `0` 和 `1` 开始,后面的每一项数字都是前面两项数字的和。也就是: 9 | 10 | ``` 11 | F(0) = 0,F(1) = 1 12 | F(n) = F(n - 1) + F(n - 2),其中 n > 1 13 | ``` 14 | 15 | ``` 16 | 输入:2 17 | 输出:1 18 | 解释:F(2) = F(1) + F(0) = 1 + 0 = 1 19 | 输入:3 20 | 输出:2 21 | 解释:F(3) = F(2) + F(1) = 1 + 1 = 2 22 | 输入:4 23 | 输出:3 24 | 解释:F(4) = F(3) + F(2) = 2 + 1 = 3 25 | ``` 26 | 27 | ## 方法 28 | 从下往上的思想 29 | 30 | ## code 31 | 32 | ### js 33 | 34 | ```js 35 | let fib = n => { 36 | if (n <= 1) 37 | return n; 38 | let pre2 = 0, pre1 = 1; 39 | for (let i = 2; i <= n; i++) { 40 | let sum = pre2 + pre1; 41 | pre2 = pre1; 42 | pre1 = sum; 43 | } 44 | return pre1; 45 | }; 46 | console.log(fib(2)); 47 | console.log(fib(3)); 48 | console.log(fib(4)); 49 | ``` 50 | 51 | ### go 52 | 53 | ```go 54 | func fib(n int) int { 55 | if n <= 1 { 56 | return n 57 | } 58 | pre2, pre1 := 0, 1 59 | for i := 2; i <= n; i++ { 60 | sum := pre2 + pre1 61 | pre2 = pre1 62 | pre1 = sum 63 | } 64 | return pre1 65 | } 66 | ``` 67 | 68 | ### java 69 | 70 | ```java 71 | class Solution { 72 | public int fib(int N) { 73 | if (N <= 1) return N; 74 | int pre2 = 0, pre1 = 1; 75 | for (int i = 2; i <= N; i++) { 76 | int sum = pre2 + pre1; 77 | pre2 = pre1; 78 | pre1 = sum; 79 | } 80 | return pre1; 81 | } 82 | } 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /Java/alg/lc/543.二叉树的直径.md: -------------------------------------------------------------------------------- 1 | # 543. 二叉树的直径 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/diameter-of-binary-tree/) 5 | 6 | 7 | ## 题目 8 | 给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。 9 | 10 | 11 | ``` 12 | 1 13 | / \ 14 | 2 3 15 | / \ 16 | 4 5 17 | ``` 18 | 返回 3, 它的长度是路径 `[4,2,1,3]` 或者 `[5,2,1,3]`。 19 | 20 | ## 方法 21 | 从下往上的思想 22 | 23 | ## code 24 | 25 | ### js 26 | 27 | ```js 28 | let max = 0; 29 | let diameterOfBinaryTree = root => { 30 | depth(root); 31 | return max; 32 | }; 33 | let depth = node => { 34 | if (node === null) 35 | return 0; 36 | let l = depth(node.left); 37 | let r = depth(node.right); 38 | max = Math.max(max, (l + r)); 39 | return Math.max(l, r) + 1; 40 | }; 41 | ``` 42 | 43 | ### go 44 | 45 | ```go 46 | func diameterOfBinaryTree(root *TreeNode) int { 47 | Max := func(a int, b int) int { 48 | if a < b { 49 | return b 50 | } else { 51 | return a 52 | } 53 | } 54 | var max = 0 55 | var depth func(node *TreeNode) int 56 | depth = func(node *TreeNode) int { 57 | if node == nil { 58 | return 0 59 | } 60 | l := depth(node.Left) 61 | r := depth(node.Right) 62 | max = Max(max, l+r) 63 | return Max(l, r) + 1 64 | } 65 | depth(root) 66 | return max 67 | } 68 | 69 | ``` 70 | 71 | ### java 72 | 73 | ```java 74 | class Solution { 75 | int max = 0; 76 | public int diameterOfBinaryTree(TreeNode root) { 77 | depth(root); 78 | return max; 79 | } 80 | public int depth(TreeNode node) { 81 | if (node == null) 82 | return 0; 83 | int l = depth(node.left); 84 | int r = depth(node.right); 85 | max = Math.max(max, (l + r)); 86 | return Math.max(l, r) + 1; 87 | } 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /Java/alg/lc/55.跳跃游戏.md: -------------------------------------------------------------------------------- 1 | # 55. 跳跃游戏 2 | 3 | [url](https://leetcode-cn.com/problems/jump-game/) 4 | 5 | ## 题目 6 | 7 | 给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。 8 | 9 | 数组中的每个元素代表你在该位置可以跳跃的最大长度。 10 | 11 | 判断你是否能够到达最后一个下标。 12 | 13 | ``` 14 | 输入:nums = [2,3,1,1,4] 15 | 输出:true 16 | 解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。 17 | 输入:nums = [3,2,1,0,4] 18 | 输出:false 19 | 解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。 20 | ``` 21 | 22 | ## 方法 23 | 24 | ## code 25 | 26 | ### js 27 | 28 | ```js 29 | let canJump = nums => { 30 | if (nums === null || nums.length === 0) return false; 31 | let max = 0; 32 | for (let i = 0; i < nums.length - 1; i++) { 33 | if (i <= max) 34 | max = Math.max(max, nums[i] + 1); 35 | else 36 | break; 37 | } 38 | return max >= nums.length - 1; 39 | } 40 | ``` 41 | 42 | ### go 43 | 44 | ```go 45 | func canJump(nums []int) bool { 46 | if len(nums) == 0 { 47 | return false 48 | } 49 | Max := func(a int, b int) int { 50 | if a > b { 51 | return a 52 | } else { 53 | return b 54 | } 55 | } 56 | max := 0 57 | for i := 1; i < len(nums); i++ { 58 | if i <= max { 59 | max = Max(max, nums[i] + 1) 60 | } else { 61 | break 62 | } 63 | } 64 | return max >= len(nums) - 1 65 | } 66 | ``` 67 | 68 | 69 | 70 | ### java 71 | 72 | ```java 73 | class Solution { 74 | public boolean canJump(int[] nums) { 75 | if (nums == null || nums.length == 0) 76 | return false; 77 | int max = 0; 78 | for (int i = 1; i < nums.length - 1; i++){ 79 | if (i <= max) 80 | max = Math.max(max, nums[i] + 1); 81 | else 82 | break; 83 | } 84 | return max >= nums.length - 1; 85 | } 86 | } 87 | ``` 88 | 89 | -------------------------------------------------------------------------------- /Java/alg/lc/566.重塑矩阵.md: -------------------------------------------------------------------------------- 1 | # 566. 重塑矩阵 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/reshape-the-matrix/) 5 | 6 | 7 | ## 题目 8 | 在MATLAB中,有一个非常有用的函数 reshape,它可以将一个矩阵重塑为另一个大小不同的新矩阵,但保留其原始数据。 9 | 10 | 给出一个由二维数组表示的矩阵,以及两个正整数r和c,分别表示想要的重构的矩阵的行数和列数。 11 | 12 | 重构后的矩阵需要将原始矩阵的所有元素以相同的行遍历顺序填充。 13 | 14 | 如果具有给定参数的reshape操作是可行且合理的,则输出新的重塑矩阵;否则,输出原始矩阵。 15 | 16 | 17 | 18 | ``` 19 | 输入: 20 | nums = 21 | [[1,2], 22 | [3,4]] 23 | r = 1, c = 4 24 | 输出: 25 | [[1,2,3,4]] 26 | 解释: 27 | 行遍历nums的结果是 [1,2,3,4]。新的矩阵是 1 * 4 矩阵, 用之前的元素值一行一行填充新矩阵。 28 | 输入: 29 | nums = 30 | [[1,2], 31 | [3,4]] 32 | r = 2, c = 4 33 | 输出: 34 | [[1,2], 35 | [3,4]] 36 | 解释: 37 | 没有办法将 2 * 2 矩阵转化为 2 * 4 矩阵。 所以输出原矩阵。 38 | ``` 39 | 40 | ## 方法 41 | 从下往上的思想 42 | 43 | ## code 44 | 45 | ### js 46 | 47 | ```js 48 | ``` 49 | 50 | ### go 51 | 52 | ```go 53 | func matrixReshape(nums [][]int, r int, c int) [][]int { 54 | m, n := len(nums), len(nums[0]) 55 | if m*n != r*c { 56 | return nums 57 | } 58 | reshapednums := make([][]int, 0) 59 | idx := 0 60 | for i := 0; i < r; i++ { 61 | for j := 0; j < c; j++ { 62 | reshapednums[i][j] = nums[idx / n][idx % n] 63 | idx++ 64 | } 65 | } 66 | return reshapednums 67 | } 68 | ``` 69 | 70 | ### java 71 | 72 | ```java 73 | class Solution { 74 | public int[][] matrixReshape(int[][] nums, int r, int c) { 75 | int m = nums.length, n = nums[0].length; 76 | if (m * n != r * c) return nums; 77 | int[][] reshapedNums = new int[r][c]; 78 | int idx = 0; 79 | for (int i = 0; i < r; i++) { 80 | for (int j = 0; j < c; j++) { 81 | reshapedNums[i][j] = nums[idx / n][idx % n]; 82 | idx++; 83 | } 84 | } 85 | return reshapedNums; 86 | } 87 | } 88 | ``` 89 | 90 | -------------------------------------------------------------------------------- /Java/alg/lc/572.另一个树的子树.md: -------------------------------------------------------------------------------- 1 | # 572. 另一个树的子树 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/subtree-of-another-tree/) 5 | 6 | 7 | ## 题目 8 | 给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。 9 | 10 | 11 | ``` 12 | s: 13 | 3 14 | / \ 15 | 4 5 16 | / \ 17 | 1 2 18 | t: 19 | 4 20 | / \ 21 | 1 2 22 | ``` 23 | 返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。 24 | 25 | ## 方法 26 | 从下往上的思想 27 | 28 | ## code 29 | 30 | ### js 31 | 32 | ```js 33 | let isSubtree = (s, t) => { 34 | if (s === null) 35 | return false; 36 | return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t); 37 | }; 38 | let isSubtreeWithRoot = (s, t) => { 39 | if (t === null && s === null) 40 | return true; 41 | if (t === null || s === null) 42 | return false; 43 | if (t.val !== s.val) 44 | return false; 45 | return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right); 46 | }; 47 | ``` 48 | 49 | ### go 50 | 51 | ```go 52 | func isSubtree(s *TreeNode, t *TreeNode) bool { 53 | if s == nil { 54 | return false 55 | } 56 | return isSubtreeWithRoot(s, t) || isSubtree(s.Left, t) || isSubtree(s.Right, t) 57 | } 58 | func isSubtreeWithRoot(s *TreeNode, t*TreeNode) bool { 59 | if t == nil && s == nil { 60 | return true 61 | } 62 | if t == nil || s == nil { 63 | return false 64 | } 65 | if t.Val != s.Val { 66 | return false 67 | } 68 | return isSubtreeWithRoot(s.Left, t.Left) && isSubtreeWithRoot(s.Right, t.Right) 69 | } 70 | 71 | ``` 72 | 73 | ### java 74 | 75 | ```java 76 | class Solution { 77 | public boolean isSubtree(TreeNode s, TreeNode t) { 78 | if (s == null) return false; 79 | return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t); 80 | } 81 | 82 | private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) { 83 | if (t == null && s == null) return true; 84 | if (t == null || s == null) return false; 85 | if (t.val != s.val) return false; 86 | return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right); 87 | } 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /Java/alg/lc/58.最后一个单词的长度.md: -------------------------------------------------------------------------------- 1 | # 58. 最后一个单词的长度 2 | 3 | [url](https://leetcode-cn.com/problems/length-of-last-word/) 4 | 5 | ## 题目 6 | 7 | 给定一个仅包含大小写字母和空格 ' ' 的字符串 s,返回其最后一个单词的长度。如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。 8 | 9 | 如果不存在最后一个单词,请返回 0 。 10 | 11 | 说明:一个单词是指仅由字母组成、不包含任何空格字符的 最大子字符串。 12 | 13 | ## 方法 14 | 15 | 这道题,感觉没啥可说的,一段单词,你split数组即可 16 | 17 | ## code 18 | 19 | ### js 20 | 21 | ```js 22 | let lengthOfLastWord = s => { 23 | let strs = s.split(" "); 24 | if (strs.length === 0) 25 | return 0; 26 | return strs[strs.length - 1].length; 27 | } 28 | 29 | console.log(lengthOfLastWord("Hello World")); 30 | ``` 31 | 32 | ### go 33 | 34 | ```go 35 | func lengthOfLastWord(s string) int { 36 | strs := strings.Split(s, " ") 37 | if len(strs) == 0 { 38 | return 0 39 | } 40 | return len(strs[len(strs) - 1]) 41 | } 42 | ``` 43 | 44 | 45 | 46 | ### java 47 | 48 | ```java 49 | class Solution { 50 | public int lengthOfLastWord(String s) { 51 | String[] strs = s.split(" "); 52 | if(strs.length == 0) return 0; 53 | return strs[strs.length-1].length(); 54 | } 55 | } 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /Java/alg/lc/617.合并二叉树.md: -------------------------------------------------------------------------------- 1 | # 617. 合并二叉树 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/merge-two-binary-trees/) 5 | 6 | 7 | ## 题目 8 | 给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 9 | 10 | 你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。 11 | 12 | ``` 13 | 输入: 14 | Tree 1 Tree 2 15 | 1 2 16 | / \ / \ 17 | 3 2 1 3 18 | / \ \ 19 | 5 4 7 20 | 输出: 21 | 合并后的树: 22 | 3 23 | / \ 24 | 4 5 25 | / \ \ 26 | 5 4 7 27 | ``` 28 | 29 | 30 | ## 方法 31 | 32 | 33 | ## code 34 | 35 | ### js 36 | 37 | ```js 38 | let mergeTrees = (l1, l2) => { 39 | if (l1 === null && l2 === null) 40 | return null; 41 | if (l1 === null) 42 | return l2; 43 | if (l2 === null) 44 | return l1; 45 | let root = TreeNode(l1.val + l2.val); 46 | root.left = mergeTrees(l1.left, l2.left); 47 | root.right = mergeTrees(l1.right, l2.right); 48 | return root; 49 | }; 50 | ``` 51 | 52 | ### go 53 | 54 | ```go 55 | func mergeTrees(t1 *TreeNode, t2 *TreeNode) *TreeNode { 56 | if t1 == nil && t2 == nil { 57 | return nil 58 | } 59 | if t1 == nil { 60 | return t2 61 | } 62 | if t2 == nil { 63 | return t1 64 | } 65 | root := &TreeNode{Val: t1.Val + t2.Val} 66 | root.Left = mergeTrees(t1.Left, t2.Left) 67 | root.Right = mergeTrees(t1.Right, t2.Right) 68 | return root 69 | } 70 | ``` 71 | 72 | ### java 73 | 74 | ```java 75 | class Solution { 76 | public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { 77 | if (t1 == null && t2 == null) return null; 78 | if (t1 == null) return t2; 79 | if (t2 == null) return t1; 80 | TreeNode root = new TreeNode(t1.val + t2.val); 81 | root.left = mergeTrees(t1.left, t2.left); 82 | root.right = mergeTrees(t1.right, t2.right); 83 | return root; 84 | } 85 | } 86 | ``` 87 | 88 | -------------------------------------------------------------------------------- /Java/alg/lc/62.不同路径.md: -------------------------------------------------------------------------------- 1 | # 62. 不同路径 2 | 3 | [url](https://leetcode-cn.com/problems/unique-paths/) 4 | 5 | ## 题目 6 | 7 | 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 8 | 9 | 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 10 | 11 | 问总共有多少条不同的路径? 12 | 13 | ![](https://assets.leetcode.com/uploads/2018/10/22/robot_maze.png) 14 | 15 | ``` 16 | 输入:m = 3, n = 7 17 | 输出:28 18 | 输入:m = 3, n = 2 19 | 输出:3 20 | 解释: 21 | 从左上角开始,总共有 3 条路径可以到达右下角。 22 | 1. 向右 -> 向下 -> 向下 23 | 2. 向下 -> 向下 -> 向右 24 | 3. 向下 -> 向右 -> 向下 25 | 输入:m = 7, n = 3 26 | 输出:28 27 | ``` 28 | 29 | ## 方法 30 | 31 | ## code 32 | 33 | ### js 34 | 35 | ```js 36 | let uniquePaths = (m, n) => { 37 | let dp = Array(n).fill(1); 38 | for (let i = 1; i < m; i++) { 39 | for (let j = 1; j < n; j++) { 40 | dp[j] += dp[j - 1]; 41 | } 42 | } 43 | return dp[n - 1]; 44 | } 45 | ``` 46 | 47 | ### go 48 | 49 | ```go 50 | func uniquePaths(m, n int) int { 51 | dp := make([]int, n) 52 | for i := 0; i < n; i++ { 53 | dp[i] = 1 54 | } 55 | for i := 1; i < m; i++ { 56 | for j := 1; j < n; j++ { 57 | dp[j] += dp[j - 1] 58 | } 59 | } 60 | return dp[n-1] 61 | } 62 | ``` 63 | 64 | 65 | ### java 66 | 67 | ```java 68 | class Solution { 69 | public int uniquePaths(int m, int n) { 70 | int[] dp = new int[n]; 71 | Arrays.fill(dp, 1); 72 | for (int i = 1; i < m; i++) { 73 | for (int j = 1; j < n; j++) { 74 | dp[j] += dp[j - 1]; 75 | } 76 | } 77 | return dp[n - 1]; 78 | } 79 | } 80 | ``` 81 | 82 | -------------------------------------------------------------------------------- /Java/alg/lc/645.错误的集合.md: -------------------------------------------------------------------------------- 1 | # 645. 错误的集合 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/set-mismatch/) 5 | 6 | 7 | ## 题目 8 | 集合 s 包含从 1 到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个数字复制了成了集合里面的另外一个数字的值,导致集合 丢失了一个数字 并且 有一个数字重复 。 9 | 10 | 给定一个数组 nums 代表了集合 S 发生错误后的结果。 11 | 12 | 请你找出重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。 13 | 14 | 15 | ``` 16 | 输入:nums = [1,2,2,4] 17 | 输出:[2,3] 18 | 输入:nums = [1,1] 19 | 输出:[1,2] 20 | ``` 21 | 22 | 23 | ## 方法 24 | 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let findErrorNums = nums => { 32 | // 桶计数 33 | let res = []; 34 | let temp = Array(nums.length + 1).fill(0); 35 | nums.forEach(num => temp[num]++); 36 | for (const [idx, value] of nums.entries()) { 37 | if (value === 1) 38 | continue; 39 | if (value === 2) 40 | res[0] = idx; 41 | else 42 | res[1] = idx; 43 | } 44 | return res; 45 | }; 46 | console.log(findErrorNums([1, 2, 2, 4])) 47 | ``` 48 | 49 | ### go 50 | 51 | ```go 52 | func findErrorNums(nums []int) []int { 53 | // 桶计数 54 | res := make([]int, 2) 55 | temp := make([]int, len(nums) + 1) 56 | for _, v := range nums { 57 | temp[v]++ 58 | } 59 | for i := 1; i < len(temp); i++ { 60 | if temp[i] == 1 { 61 | continue 62 | } 63 | if temp[i] == 2 { 64 | res[0] = i 65 | } else { 66 | res[1] = i 67 | } 68 | } 69 | return res 70 | } 71 | ``` 72 | 73 | ### java 74 | 75 | ```java 76 | class Solution { 77 | public int[] findErrorNums(int[] nums) { 78 | // 桶计数 79 | int[] res = new int[2]; 80 | int[] temp = new int[nums.length + 1]; 81 | for (int num : nums) { 82 | temp[num]++; 83 | } 84 | for (int i = 1; i < temp.length; i++) { 85 | if (temp[i] == 1) continue; 86 | if (temp[i] == 2) res[0] = i; 87 | else res[1] = i; 88 | } 89 | return res; 90 | } 91 | } 92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /Java/alg/lc/674.最长连续递增序列.md: -------------------------------------------------------------------------------- 1 | # 674. 最长连续递增序列 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence/) 5 | 6 | 7 | ## 题目 8 | 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。 9 | 10 | 连续递增的子序列 可以由两个下标 `l` 和 `r(l < r`)确定,如果对于每个 `l <= i < r`,都有 `nums[i] < nums[i + 1]` ,那么子序列 `[nums[l], nums[l + 1], ..., nums[r - 1], nums[r]]` 就是连续递增子序列。 11 | 12 | ``` 13 | 输入:nums = [1,3,5,4,7] 14 | 输出:3 15 | 解释:最长连续递增序列是 [1,3,5], 长度为3。 16 | 尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。 17 | 输入:nums = [2,2,2,2,2] 18 | 输出:1 19 | 解释:最长连续递增序列是 [2], 长度为1。 20 | ``` 21 | 22 | 23 | ## 方法 24 | 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let findLengthOfLCIS = nums => { 32 | if (nums === null || nums.length === 0) 33 | return 0; 34 | let d = 0, max = 1; 35 | for (let i = 1; i < nums.length; i++) { 36 | if (nums[i] > nums[i - 1]) 37 | max = Math.max(i - d + 1, max); 38 | else 39 | d = i; 40 | } 41 | return max; 42 | }; 43 | console.log(findLengthOfLCIS([1, 3, 5, 4, 7])); 44 | console.log(findLengthOfLCIS([2, 2, 2, 2, 2])); 45 | ``` 46 | 47 | ### go 48 | 49 | ```go 50 | func findLengthOfLCIS(nums []int) int { 51 | Max := func (a int, b int) int { 52 | if a > b { 53 | return a 54 | } else { 55 | return b 56 | } 57 | } 58 | if nums == nil || len(nums) == 0 { 59 | return 0 60 | } 61 | d := 0 62 | max := 1 63 | for i := 1; i < len(nums); i++ { 64 | if nums[i] > nums[i-1] { 65 | max = Max(i - d + 1, max) 66 | } else { 67 | d = i 68 | } 69 | } 70 | return max 71 | } 72 | ``` 73 | 74 | ### java 75 | 76 | ```java 77 | class Solution { 78 | public int findLengthOfLCIS(int[] nums) { 79 | if (nums == null || nums.length == 0) 80 | return 0; 81 | int d = 0; 82 | int max = 1; 83 | for (int i = 1; i < nums.length; i++) { 84 | if (nums[i] > nums[i - 1]) 85 | max = Math.max(i - d + 1, max); 86 | else 87 | d = i; 88 | } 89 | return max; 90 | } 91 | } 92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /Java/alg/lc/69.x的平方根.md: -------------------------------------------------------------------------------- 1 | # 69. x 的平方根 2 | 3 | [url](https://leetcode-cn.com/problems/sqrtx/) 4 | 5 | ## 题目 6 | 7 | 实现 `int sqrt(int x)` 函数。 8 | 9 | 计算并返回 x 的平方根,其中 x 是非负整数。 10 | 11 | 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 12 | 13 | ``` 14 | 输入: 4 15 | 输出: 2 16 | 输入: 8 17 | 输出: 2 18 | 说明: 8 的平方根是 2.82842..., 19 | 由于返回类型是整数,小数部分将被舍去。 20 | ``` 21 | 22 | ## 方法 23 | 24 | 二分法的思想 25 | 26 | - 在`(1, x)`的区间中找一个res可以满足题意。 27 | - 左点为1,右点为x,中点m为`l + (r - l) / 2` 28 | - 乘法可能会溢出,因此改为`m`和`x / m`的判断 29 | - 如果`m >= x / m`,则说明res在左边,那么另`r = m`,限定r的范围 30 | - 如果`m < x / m`,则说明res在右边,那么另`l = m + 1` 31 | - 最后判断`m`和`x / m`的值,若大于后者,则res为`l - 1`, 否则为`l` 32 | 33 | ## code 34 | 35 | ### js 36 | 37 | ```js 38 | let mySqrt = x => { 39 | let l = 1, r = x; 40 | while (l < r) { 41 | let m = Math.floor(l + (r - l) / 2); 42 | if (m >= Math.floor(x / m)) { 43 | r = m; 44 | } else { 45 | l = m + 1; 46 | } 47 | } 48 | return l > Math.floor(x / l) ? (l - 1) : l; 49 | } 50 | 51 | console.log(mySqrt(4)); 52 | console.log(mySqrt(8)); 53 | ``` 54 | 55 | ### go 56 | 57 | ```go 58 | func mySqrt(x int) int { 59 | l, r := 1, x 60 | for l < r { 61 | m := l + (r - l) / 2 62 | if m >= x/m { 63 | r = m 64 | } else { 65 | l = m + 1 66 | } 67 | } 68 | if l > x / l { 69 | return l - 1 70 | } else { 71 | return l 72 | } 73 | } 74 | ``` 75 | 76 | 77 | 78 | ### java 79 | 80 | ```java 81 | class Solution { 82 | public int mySqrt(int x) { 83 | int l = 1, r = x; 84 | while(l= x / mid){ // 乘法可能会溢出, 改为除法 87 | r = mid; 88 | }else{ 89 | l = mid+1; 90 | } 91 | } 92 | return l>x/l ? (l-1):l; // 乘法可能会溢出, 改为除法 93 | } 94 | } 95 | ``` 96 | 97 | -------------------------------------------------------------------------------- /Java/alg/lc/693.交替位二进制数.md: -------------------------------------------------------------------------------- 1 | # 693. 交替位二进制数 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/binary-number-with-alternating-bits/) 5 | 6 | 7 | ## 题目 8 | 给定一个正整数,检查它的二进制表示是否总是 0、1 交替出现:换句话说,就是二进制表示中相邻两位的数字永不相同。 9 | 10 | ``` 11 | 输入:n = 5 12 | 输出:true 13 | 解释:5 的二进制表示是:101 14 | 输入:n = 7 15 | 输出:false 16 | 解释:7 的二进制表示是:111. 17 | 输入:n = 11 18 | 输出:false 19 | 解释:11 的二进制表示是:1011. 20 | 输入:n = 10 21 | 输出:true 22 | 解释:10 的二进制表示是:1010. 23 | 输入:n = 3 24 | 输出:false 25 | ``` 26 | 27 | 28 | ## 方法 29 | 30 | 31 | ## code 32 | 33 | ### js 34 | 35 | ```js 36 | let hasAlternatingBits = n => { 37 | let a = (n ^ (n >> 1)); 38 | return (a & (a + 1)) === 0; 39 | }; 40 | console.log(hasAlternatingBits(5)) 41 | console.log(hasAlternatingBits(7)) 42 | console.log(hasAlternatingBits(11)) 43 | console.log(hasAlternatingBits(10)) 44 | ``` 45 | 46 | ### go 47 | 48 | ```go 49 | func hasAlternatingBits(n int) bool { 50 | a := n ^ (n >> 1) 51 | return (a & (a + 1)) == 0 52 | } 53 | ``` 54 | 55 | ### java 56 | 57 | ```java 58 | class Solution { 59 | public boolean hasAlternatingBits(int n) { 60 | int a = (n ^ (n >> 1)); 61 | return (a & (a + 1)) == 0; 62 | } 63 | } 64 | ``` 65 | 66 | -------------------------------------------------------------------------------- /Java/alg/lc/696.计数二进制子串.md: -------------------------------------------------------------------------------- 1 | # 696. 计数二进制子串 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/count-binary-substrings/) 5 | 6 | 7 | ## 题目 8 | 给定一个字符串 s,计算具有相同数量 0 和 1 的非空(连续)子字符串的数量,并且这些子字符串中的所有 0 和所有 1 都是连续的。 9 | 10 | 重复出现的子串要计算它们出现的次数。 11 | 12 | ``` 13 | 输入: "00110011" 14 | 输出: 6 15 | 解释: 有6个子串具有相同数量的连续1和0:“0011”,“01”,“1100”,“10”,“0011” 和 “01”。 16 | 请注意,一些重复出现的子串要计算它们出现的次数。 17 | 另外,“00110011”不是有效的子串,因为所有的0(和1)没有组合在一起。 18 | 19 | 输入: "10101" 20 | 输出: 4 21 | 解释: 有4个子串:“10”,“01”,“10”,“01”,它们具有相同数量的连续1和0。 22 | ``` 23 | 24 | 25 | ## 方法 26 | 27 | 28 | ## code 29 | 30 | ### js 31 | 32 | ```js 33 | let countBinarySubstrings = s => { 34 | let preLen = 0, curLen = 1, count = 0; 35 | for (let i = 1; i < s.length; i++) { 36 | if (s[i] === s[i - 1]) 37 | curLen++; 38 | else { 39 | preLen = curLen; 40 | curLen = 1; 41 | } 42 | if (preLen >= curLen) 43 | count++; 44 | } 45 | return count; 46 | }; 47 | console.log(countBinarySubstrings("00110011")) 48 | ``` 49 | 50 | ### go 51 | 52 | ```go 53 | func countBinarySubstrings(s string) int { 54 | preLen, curLen, count := 0, 1, 0 55 | for i := 1; i < len(s); i++ { 56 | if s[i] == s[i-1] { 57 | curLen++ 58 | } else { 59 | preLen = curLen 60 | curLen = 1 61 | } 62 | if preLen >= curLen { 63 | count++ 64 | } 65 | } 66 | return count 67 | } 68 | ``` 69 | 70 | ### java 71 | 72 | ```java 73 | class Solution { 74 | public int countBinarySubstrings(String s) { 75 | int preLen = 0, curLen = 1, count = 0; 76 | for (int i = 1; i < s.length(); i++) { 77 | if (s.charAt(i) == s.charAt(i - 1)) curLen++; 78 | else { 79 | preLen = curLen; 80 | curLen = 1; 81 | } 82 | if (preLen >= curLen) { 83 | count++; 84 | } 85 | } 86 | return count; 87 | } 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /Java/alg/lc/7.整数反转.md: -------------------------------------------------------------------------------- 1 | # 7.整数反转 2 | 3 | [url](https://leetcode-cn.com/problems/reverse-integer/) 4 | 5 | ## 题目 6 | 7 | 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。 8 | 9 | ``` 10 | 输入: 123 11 | 输出: 321 12 | 13 | 输入: -123 14 | 输出: -321 15 | 16 | 输入: 120 17 | 输出: 21 18 | ``` 19 | 20 | ## 方法 21 | 22 | y = y * 10 + x % 10 23 | x /= 10 24 | 25 | 二者使用效果极佳 26 | 27 | - 这个题很多方法的,就用数学公式解答吧 28 | - 你可以举个例子,假如123,怎么才能成为321呢?字符串的话,反转即可,关键这不是字符串了,当然你想说,先转成字符串,然后字符串反转,然后变为int,岂不是很方便?嘿嘿,的确很方便。 29 | - 利用y = y * 10 + x % 10,123 % 10 = 3,是不是将3提炼出来了?那么利用x /= 10,是不是x=12了? y = 3, 假如为再来一次这样的操作呢?12 % 10 = 2, x = 1, y = 30 + 2,不用继续往下说了吧? 30 | 31 | 32 | ## code 33 | 34 | ### js 35 | 36 | ```js{cmd="node"} 37 | let reverse = function(x) { 38 | let ret = 0; 39 | while (x !== 0) { 40 | ret = ret * 10 + x % 10; 41 | x = (x / 10) | 0; // x / 10 去除末位,| 0 强制转换为32位有符号整数。 42 | // 通过 | 0 取整,无论正负,只移除小数点部分(正数向下取整,负数向上取整)。 43 | } 44 | return (ret | 0) === ret ? ret : 0; // ret | 0 超过32位的整数转换结果不等于自身,可用作溢出判断。 45 | }; 46 | console.log(reverse(123)); 47 | ``` 48 | 49 | ### go 50 | 51 | ```go 52 | func reverse(x int) int { 53 | ret := 0 54 | for x != 0 { 55 | ret = ret * 10 + x % 10 56 | x /= 10 57 | } 58 | return ret 59 | } 60 | ``` 61 | 62 | ### java 63 | 64 | ```java 65 | class Solution { 66 | public int reverse(int x) { 67 | long ret = 0; 68 | while (x != 0) { 69 | ret = ret * 10 + x % 10; 70 | x /= 10; 71 | } 72 | return (int) ret == ret ? (int) ret : 0; 73 | } 74 | } 75 | ``` 76 | 77 | -------------------------------------------------------------------------------- /Java/alg/lc/70.爬楼梯.md: -------------------------------------------------------------------------------- 1 | # 70. 爬楼梯 2 | 3 | [url](https://leetcode-cn.com/problems/climbing-stairs/) 4 | 5 | ## 题目 6 | 7 | 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 8 | 9 | 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 10 | 11 | 注意:给定 n 是一个正整数。 12 | 13 | ``` 14 | 输入: 2 15 | 输出: 2 16 | 解释: 有两种方法可以爬到楼顶。 17 | 1. 1 阶 + 1 阶 18 | 2. 2 阶 19 | 输入: 3 20 | 输出: 3 21 | 解释: 有三种方法可以爬到楼顶。 22 | 1. 1 阶 + 1 阶 + 1 阶 23 | 2. 1 阶 + 2 阶 24 | 3. 2 阶 + 1 阶 25 | ``` 26 | 27 | ## 方法 28 | 29 | 从下往上迭代 30 | 31 | - 当`n=1,n=2`的时候,结果为`1,2`。 32 | - 从`n=3`开始,计算`cur = pre2 + pre1` 33 | - 接着替换pre2,pre1 34 | - 迭代完毕,返回pre1 35 | 36 | 37 | ## code 38 | 39 | ### js 40 | 41 | ```js 42 | let climbStairs = n => { 43 | if (n <= 2) return n; 44 | let pre2 = 1, pre1 = 2; 45 | for (let i = 3; i <= n; i++) { 46 | let cur = pre2 + pre1; 47 | pre2 = pre1; 48 | pre1 = cur; 49 | } 50 | return pre1; 51 | } 52 | console.log(climbStairs(2)); 53 | console.log(climbStairs(3)); 54 | console.log(climbStairs(4)); 55 | ``` 56 | 57 | ### go 58 | 59 | ```go 60 | func climbStairs(n int) int { 61 | if n <= 2 { 62 | return n 63 | } 64 | pre2, pre1 := 1, 2 65 | for i := 3; i <= n; i++ { 66 | cur := pre2 + pre1 67 | pre2 = pre1 68 | pre1 = cur 69 | } 70 | return pre1 71 | } 72 | ``` 73 | 74 | 75 | 76 | ### java 77 | 78 | ```java 79 | class Solution { 80 | public int climbStairs(int n) { 81 | if (n <= 2) return n; 82 | int pre2 = 1, pre1 = 2; 83 | for (int i = 3; i <= n; i++) { 84 | int cur = pre2 + pre1; 85 | pre2 = pre1; 86 | pre1 = cur; 87 | } 88 | return pre1; 89 | } 90 | } 91 | ``` 92 | 93 | -------------------------------------------------------------------------------- /Java/alg/lc/704.二分查找.md: -------------------------------------------------------------------------------- 1 | # 704. 二分查找 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/binary-search/) 5 | 6 | 7 | ## 题目 8 | 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 9 | 10 | ``` 11 | 输入: nums = [-1,0,3,5,9,12], target = 9 12 | 输出: 4 13 | 解释: 9 出现在 nums 中并且下标为 4 14 | 输入: nums = [-1,0,3,5,9,12], target = 2 15 | 输出: -1 16 | 解释: 2 不存在 nums 中因此返回 -1 17 | ``` 18 | 19 | 20 | ## 方法 21 | 22 | 23 | ## code 24 | 25 | ### js 26 | 27 | ```js 28 | let search = (nums, target) => { 29 | if (nums === null || nums.length === 0) 30 | return -1; 31 | let l = 0, h = nums.length - 1; 32 | while (l <= h) { 33 | let m = l + Math.floor((h - l) / 2); 34 | if (nums[m] === target) 35 | return m; 36 | else if (nums[m] < target) 37 | l = m + 1; 38 | else 39 | h = m - 1; 40 | } 41 | return -1; 42 | }; 43 | console.log(search([-1,0,3,5,9,12], 9)) 44 | console.log(search([-1,0,3,5,9,12], 2)) 45 | ``` 46 | 47 | ### go 48 | 49 | ```go 50 | func search(nums []int, target int) int { 51 | if nums == nil || len(nums) == 0 { 52 | return -1 53 | } 54 | l, h := 0, len(nums) - 1 55 | for l <= h { 56 | m := l + (h - l) / 2 57 | if nums[m] == target { 58 | return m 59 | } else if nums[m] < target { 60 | l = m + 1 61 | } else { 62 | h = m - 1 63 | } 64 | } 65 | return -1 66 | } 67 | ``` 68 | 69 | ### java 70 | 71 | ```java 72 | class Solution { 73 | public int search(int[] nums, int target) { 74 | if (nums == null || nums.length == 0) return -1; 75 | int l = 0, h = nums.length - 1; 76 | while (l <= h) { 77 | int m = l + (h - l) / 2; 78 | if (nums[m] == target) return m; 79 | else if (nums[m] < target) l = m + 1; 80 | else h = m - 1; 81 | } 82 | return -1; 83 | } 84 | } 85 | ``` 86 | 87 | -------------------------------------------------------------------------------- /Java/alg/lc/724.寻找数组的中心下标.md: -------------------------------------------------------------------------------- 1 | # 724. 寻找数组的中心下标 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/find-pivot-index/) 5 | 6 | 7 | ## 题目 8 | 给你一个整数数组 nums,请编写一个能够返回数组 “中心下标” 的方法。 9 | 10 | 数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。 11 | 12 | 如果数组不存在中心下标,返回 -1 。如果数组有多个中心下标,应该返回最靠近左边的那一个。 13 | 14 | 注意:中心下标可能出现在数组的两端。 15 | 16 | 17 | ``` 18 | 输入:nums = [1, 7, 3, 6, 5, 6] 19 | 输出:3 20 | 解释: 21 | 中心下标是 3 。 22 | 左侧数之和 (1 + 7 + 3 = 11), 23 | 右侧数之和 (5 + 6 = 11) ,二者相等。 24 | 输入:nums = [1, 2, 3] 25 | 输出:-1 26 | 解释: 27 | 数组中不存在满足此条件的中心下标。 28 | 输入:nums = [2, 1, -1] 29 | 输出:0 30 | 解释: 31 | 中心下标是 0 。 32 | 下标 0 左侧不存在元素,视作和为 0 ; 33 | 右侧数之和为 1 + (-1) = 0 ,二者相等。 34 | ``` 35 | 36 | 37 | ## 方法 38 | 39 | 40 | ## code 41 | 42 | ### js 43 | 44 | ```js 45 | let pivotIndex = nums => { 46 | let sum = 0, leftSum = 0; 47 | nums.forEach(num => sum += num); 48 | for (const [idx, val] of nums.entries()) { 49 | if (sum - val === leftSum * 2) 50 | return idx; 51 | else 52 | leftSum += val; 53 | } 54 | return -1; 55 | }; 56 | console.log(pivotIndex([1, 7, 3, 6, 5, 6])) 57 | console.log(pivotIndex([1, 2, 3])) 58 | ``` 59 | 60 | ### go 61 | 62 | ```go 63 | func pivotIndex(nums []int) int { 64 | sum ,leftSum := 0, 0 65 | for _, v := range nums { 66 | sum += v 67 | } 68 | for i, v := range nums { 69 | if sum-v == leftSum*2 { 70 | return i 71 | } else { 72 | leftSum += v 73 | } 74 | } 75 | return -1 76 | } 77 | ``` 78 | 79 | ### java 80 | 81 | ```java 82 | class Solution { 83 | public int pivotIndex(int[] nums) { 84 | int sum = 0, leftSum = 0; 85 | for (int num : nums) sum += num; 86 | for (int i = 0; i < nums.length; i++) { 87 | if(sum - nums[i] == leftSum * 2) { 88 | return i; 89 | } else { 90 | leftSum += nums[i]; 91 | } 92 | } 93 | return -1; 94 | } 95 | } 96 | ``` 97 | 98 | -------------------------------------------------------------------------------- /Java/alg/lc/747.至少是其他数字两倍的最大数.md: -------------------------------------------------------------------------------- 1 | # 747. 至少是其他数字两倍的最大数 2 | 3 | 4 | [url](https://leetcode-cn.com/problems/largest-number-at-least-twice-of-others/) 5 | 6 | 7 | ## 题目 8 | 在一个给定的数组nums中,总是存在一个最大元素 。 9 | 10 | 查找数组中的最大元素是否至少是数组中每个其他数字的两倍。 11 | 12 | 如果是,则返回最大元素的索引,否则返回-1。 13 | 14 | 15 | ``` 16 | 输入: nums = [3, 6, 1, 0] 17 | 输出: 1 18 | 解释: 6是最大的整数, 对于数组中的其他整数, 19 | 6大于数组中其他元素的两倍。6的索引是1, 所以我们返回1. 20 | 输入: nums = [1, 2, 3, 4] 21 | 输出: -1 22 | 解释: 4没有超过3的两倍大, 所以我们返回 -1. 23 | ``` 24 | 25 | 26 | ## 方法 27 | 28 | 29 | ## code 30 | 31 | ### js 32 | 33 | ```js 34 | let dominantIndex = nums => { 35 | // 将第二大的元素的两倍与最大值做比较 36 | // 如果最大值大,那么就能证明最大值大于所有元素的两倍 37 | let max = 0, idx = 0, less = 0; 38 | for (const [i, val] of nums.entries()) { 39 | if (val > max) { 40 | less = max; 41 | max = val; 42 | idx = i; 43 | } else if (val > less && val !== max) { 44 | less = val; 45 | } 46 | } 47 | return max >= (less * 2) ? idx : -1; 48 | }; 49 | console.log(dominantIndex([3, 6, 1, 0])) 50 | console.log(dominantIndex([1, 2, 3, 4])) 51 | ``` 52 | 53 | ### go 54 | 55 | ```go 56 | func dominantIndex(nums []int) int { 57 | max, idx, less := 0 , 0, 0 58 | for i, v := range nums { 59 | if v > max { 60 | less = max 61 | max = v 62 | idx = i 63 | } else if v > less && v != max { 64 | less = v 65 | } 66 | } 67 | if max >= (less * 2) { 68 | return idx 69 | } else { 70 | return -1 71 | } 72 | } 73 | ``` 74 | 75 | ### java 76 | 77 | ```java 78 | class Solution { 79 | public int dominantIndex(int[] nums) { 80 | // 将第二大的元素的两倍与最大值做比较 81 | // 如果最大值大,那么就能证明最大值大于所有元素的两倍 82 | int max = 0, idx = 0, less = 0; 83 | for (int i = 0; i < nums.length; i++) { 84 | if (nums[i] > max) { 85 | less = max; 86 | max = nums[i]; 87 | idx = i; 88 | } else if(nums[i] > less && nums[i] != max) { 89 | less = nums[i]; 90 | } 91 | } 92 | return max >= (less * 2) ? idx : -1; 93 | } 94 | } 95 | ``` 96 | 97 | -------------------------------------------------------------------------------- /Java/alg/lc/75.颜色分类.md: -------------------------------------------------------------------------------- 1 | # 75. 颜色分类 2 | 3 | [url](https://leetcode-cn.com/problems/sort-colors/) 4 | 5 | ## 题目 6 | 7 | 给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 8 | 9 | 10 | ``` 11 | 输入:nums = [2,0,2,1,1,0] 12 | 输出:[0,0,1,1,2,2] 13 | 输入:nums = [2,0,1] 14 | 输出:[0,1,2] 15 | 输入:nums = [0] 16 | 输出:[0] 17 | ``` 18 | 19 | 20 | 21 | 22 | ## 方法 23 | 24 | 25 | 26 | 27 | ## code 28 | 29 | ### js 30 | 31 | ```js 32 | let sortColors = nums => { 33 | let swap = (a, i, j) => { 34 | let t = a[i]; 35 | a[i] = a[j]; 36 | a[j] = t; 37 | } 38 | let zero = -1, one = 0, two = nums.length; 39 | while (one < two) { 40 | if (nums[one] === 0) 41 | swap(nums, ++zero, one++); 42 | else if (nums[one] === 2) 43 | swap(nums, --two, one); 44 | else 45 | ++one; 46 | } 47 | } 48 | ``` 49 | 50 | ### go 51 | 52 | ```go 53 | func sortColors(nums []int) { 54 | swap := func(a []int, i, j int) { 55 | a[i], a[j] = a[j], a[i] 56 | } 57 | zero, one, two := -1, 0, len(nums) 58 | for one < two { 59 | if nums[one] == 0 { 60 | zero++ 61 | swap(nums, zero, one) 62 | one++ 63 | } else if nums[one] == 2 { 64 | two-- 65 | swap(nums, two, one) 66 | } else { 67 | one++ 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | 74 | 75 | ### java 76 | 77 | ```java 78 | class Solution { 79 | public void sortColors(int[] nums) { 80 | int zero = -1, one = 0, two = nums.length; 81 | while (one < two) { 82 | if (nums[one] == 0) { 83 | swap(nums, ++zero, one++); 84 | } else if (nums[one] == 2){ 85 | swap(nums, --two, one); 86 | } else { 87 | ++one; 88 | } 89 | } 90 | } 91 | private void swap(int[] a, int i, int j) { 92 | int t = a[i]; 93 | a[i] = a[j]; 94 | a[j] = t; 95 | } 96 | } 97 | ``` 98 | 99 | -------------------------------------------------------------------------------- /Java/alg/lc/82.删除排序链表中的重复元素2.md: -------------------------------------------------------------------------------- 1 | # 82. 删除排序链表中的重复元素 II 2 | 3 | [url](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) 4 | 5 | ## 题目 6 | 7 | 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。 8 | 9 | 返回同样按升序排列的结果链表。 10 | 11 | ![](https://assets.leetcode.com/uploads/2021/01/04/linkedlist1.jpg) 12 | 13 | 14 | ``` 15 | 输入:head = [1,2,3,3,4,4,5] 16 | 输出:[1,2,5] 17 | ``` 18 | 19 | 20 | 21 | 22 | ## 方法 23 | 24 | 25 | 26 | 27 | ## code 28 | 29 | ### js 30 | 31 | ```js 32 | let deleteDuplicates = head => { 33 | if (head === null || head.next === null) 34 | return head; 35 | let next = head.next; 36 | if (head.val === next.val) { 37 | while (next !== null && head.val === next.val) 38 | next = next.next; 39 | return deleteDuplicates(head); 40 | } else { 41 | head.next = deleteDuplicates(head.next); 42 | return head; 43 | } 44 | } 45 | ``` 46 | 47 | ### go 48 | 49 | ```go 50 | func deleteDuplicates2(head *ListNode) *ListNode { 51 | if head == nil || head.Next == nil { 52 | return head 53 | } 54 | next := head.Next 55 | if head.Val == next.Val { 56 | for next != nil && head.Val == next.Val { 57 | next = next.Next 58 | } 59 | return deleteDuplicates2(next) 60 | } else { 61 | head.Next = deleteDuplicates2(head.Next) 62 | return head 63 | } 64 | } 65 | ``` 66 | 67 | 68 | 69 | ### java 70 | 71 | ```java 72 | class Solution { 73 | public ListNode deleteDuplicates(ListNode head) { 74 | if (head == null || head.next == null) 75 | return head; 76 | ListNode next = head.next; 77 | if (head.val == next.val) { 78 | while (next != null && head.val == next.val) 79 | next = next.next; 80 | return deleteDuplicates(next); 81 | } else { 82 | head.next = deleteDuplicates(head.next); 83 | return head; 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | -------------------------------------------------------------------------------- /Java/alg/lc/83.删除排序链表中的重复元素.md: -------------------------------------------------------------------------------- 1 | # 83. 删除排序链表中的重复元素 2 | 3 | [url](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) 4 | 5 | ## 题目 6 | 7 | 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 8 | 9 | ``` 10 | 输入: 1->1->2 11 | 输出: 1->2 12 | 输入: 1->1->2->3->3 13 | 输出: 1->2->3 14 | ``` 15 | 16 | ## 方法 17 | 18 | 递归 19 | 20 | - 递归结束条件:要么为空,要么`head.next`为空 21 | - 递归到最后,返回时候判断当前节点与下一个节点的val是否相等 22 | - 如果相等,则返回下一个节点 23 | - 如果不相等,则返回当前节点 24 | 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let deleteDuplicates = head => { 32 | if (head === undefined || head.next == null) return head; 33 | head.next = deleteDuplicates(head.next); 34 | return head.val === head.next.val ? head.next : head; 35 | } 36 | ``` 37 | 38 | ### go 39 | 40 | ```go 41 | func deleteDuplicates(head *ListNode) *ListNode { 42 | if head == nil || head.Next == nil{ 43 | return head 44 | } 45 | head.Next = deleteDuplicates(head.Next) 46 | if head.Val == head.Next.Val { 47 | return head.Next 48 | } 49 | return head 50 | } 51 | ``` 52 | 53 | 54 | 55 | ### java 56 | 57 | ```java 58 | class Solution { 59 | public ListNode deleteDuplicates(ListNode head) { 60 | if (head == null || head.next == null) return head; 61 | head.next = deleteDuplicates(head.next); 62 | return head.val == head.next.val ? head.next : head; 63 | } 64 | } 65 | ``` 66 | 67 | -------------------------------------------------------------------------------- /Java/alg/lc/836.矩形重叠.md: -------------------------------------------------------------------------------- 1 | # 836. 矩形重叠 2 | 3 | 4 | 5 | [url](https://leetcode-cn.com/problems/rectangle-overlap/) 6 | 7 | 8 | ## 题目 9 | 矩形以列表 `[x1, y1, x2, y2]` 的形式表示,其中 `(x1, y1)` 为左下角的坐标,`(x2, y2)` 是右上角的坐标。矩形的上下边平行于 `x` 轴,左右边平行于 `y` 轴。 10 | 11 | 如果相交的面积为 正 ,则称两矩形重叠。需要明确的是,只在角或边接触的两个矩形不构成重叠。 12 | 13 | 给出两个矩形 `rec1` 和 `rec2` 。如果它们重叠,返回 `true`;否则,返回 `false` 。 14 | 15 | 16 | ``` 17 | 输入:rec1 = [0,0,2,2], rec2 = [1,1,3,3] 18 | 输出:true 19 | 输入:rec1 = [0,0,1,1], rec2 = [1,0,2,1] 20 | 输出:false 21 | 输入:rec1 = [0,0,1,1], rec2 = [2,2,3,3] 22 | 输出:false 23 | ``` 24 | 25 | 26 | ## 方法 27 | 28 | 29 | ## code 30 | 31 | ### js 32 | 33 | ```js 34 | let isRectangleOverlap = (rec1, rec2) => { 35 | if (rec2[1] >= rec1[3] || rec1[1] >= rec2[3]) 36 | return false; 37 | return !(rec1[0] >= rec2[2] || rec1[2] <= rec2[0]); 38 | 39 | }; 40 | console.log(isRectangleOverlap([0,0,2,2], [1,1,3,3])) 41 | console.log(isRectangleOverlap([0,0,1,1], [1,0,2,1])) 42 | console.log(isRectangleOverlap([0,0,1,1], [2,2,3,3])) 43 | ``` 44 | 45 | ### go 46 | 47 | ```go 48 | func isRectangleOverlap(rec1 []int, rec2 []int) bool { 49 | if rec2[1] >= rec1[3] || rec1[1] >= rec2[3] { 50 | return false 51 | } 52 | if rec1[0] >= rec2[2] || rec1[2] <= rec2[0] { 53 | return false 54 | } 55 | return true 56 | } 57 | ``` 58 | 59 | ### java 60 | 61 | ```java 62 | class Solution { 63 | public boolean isRectangleOverlap(int[] rec1, int[] rec2) { 64 | if (rec2[1] >= rec1[3] || rec1[1] >= rec2[3]) { 65 | return false; 66 | } 67 | if (rec1[0] >= rec2[2] || rec1[2] <= rec2[0]) { 68 | return false; 69 | } 70 | return true; 71 | } 72 | } 73 | ``` 74 | 75 | -------------------------------------------------------------------------------- /Java/alg/lc/876.链表的中间结点.md: -------------------------------------------------------------------------------- 1 | # 876. 链表的中间结点 2 | 3 | 4 | 5 | [url](https://leetcode-cn.com/problems/middle-of-the-linked-list/) 6 | 7 | 8 | ## 题目 9 | 给定一个头结点为 head 的非空单链表,返回链表的中间结点。 10 | 11 | 如果有两个中间结点,则返回第二个中间结点。 12 | 13 | 14 | ``` 15 | 输入:[1,2,3,4,5] 16 | 输出:此列表中的结点 3 (序列化形式:[3,4,5]) 17 | 返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。 18 | 注意,我们返回了一个 ListNode 类型的对象 ans,这样: 19 | ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. 20 | 输入:[1,2,3,4,5,6] 21 | 输出:此列表中的结点 4 (序列化形式:[4,5,6]) 22 | 由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。 23 | ``` 24 | 25 | 26 | ## 方法 27 | 28 | 29 | ## code 30 | 31 | ### js 32 | 33 | ```js 34 | let middleNode = head => { 35 | let p = head, q = head; 36 | while (q !== null && q.next !== null) { 37 | q = q.next.next; 38 | p = p.next; 39 | } 40 | return p; 41 | }; 42 | ``` 43 | 44 | ### go 45 | 46 | ```go 47 | func middleNode(head *ListNode) *ListNode { 48 | p, q := head, head 49 | for q != nil && q.Next != nil { 50 | q = q.Next.Next 51 | p = p.Next 52 | } 53 | return p 54 | } 55 | ``` 56 | 57 | ### java 58 | 59 | ```java 60 | class Solution { 61 | public ListNode middleNode(ListNode head) { 62 | ListNode p = head, q = head; 63 | while (q != null && q.next != null) { 64 | q = q.next.next; 65 | p = p.next; 66 | } 67 | return p; 68 | } 69 | } 70 | ``` 71 | 72 | -------------------------------------------------------------------------------- /Java/alg/lc/9.回文数.md: -------------------------------------------------------------------------------- 1 | # 9. 回文数 2 | 3 | [url](https://leetcode-cn.com/problems/palindrome-number/) 4 | 5 | ## 题目 6 | 7 | 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 8 | 9 | ``` 10 | 输入: 121 11 | 输出: true 12 | 13 | 输入: -121 14 | 输出: false 15 | 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 16 | 17 | 输入: 10 18 | 输出: false 19 | 解释: 从右向左读, 为 01 。因此它不是一个回文数。 20 | ``` 21 | 22 | ## 方法 23 | 24 | y = y * 10 + x % 10 25 | x /= 10 26 | 27 | 二者使用效果极佳 28 | - 和整数反转差不多的思想 29 | 30 | ## code 31 | 32 | ### js 33 | 34 | ```js 35 | let isPalindrome = x => { 36 | if (x === 0) 37 | return true; 38 | if (x < 0 || x % 10 === 0) 39 | return false; 40 | let right = 0; 41 | while (x > right) { // 核心步骤 42 | right = right * 10 + x % 10; 43 | x = (x / 10) | 0; 44 | } 45 | return x === right ? x === right : x === ((right / 10) | 0) 46 | }; 47 | ``` 48 | 49 | ### go 50 | 51 | ```go 52 | func isPalindrome(x int) bool { 53 | if x == 0 { 54 | return true 55 | } 56 | if x < 0 || x % 10 == 0 { 57 | return false 58 | } 59 | right := 0 60 | for x > right { 61 | right = right * 10 + x % 10 62 | x /= 10 63 | } 64 | if x == right { 65 | return true 66 | } else { 67 | return x == right / 10 68 | } 69 | } 70 | ``` 71 | 72 | ### java 73 | 74 | ```java 75 | class Solution { 76 | public boolean isPalindrome(int x) { 77 | if (x == 0) 78 | return true; 79 | if (x < 0 || x % 10 == 0) 80 | return false; 81 | int right = 0; 82 | while (x > right) { 83 | right = right * 10 + x % 10; 84 | x /= 10; 85 | } 86 | return x == right ? x == right : x == right / 10; 87 | } 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /Java/alg/lc/94.二叉树的中序遍历.md: -------------------------------------------------------------------------------- 1 | # 94.二叉树的中序遍历 2 | 3 | [url](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) 4 | 5 | ## 题目 6 | 7 | 给定一个二叉树的根节点 `root` ,返回它的 **中序** 遍历。 8 | 9 | ![](https://assets.leetcode.com/uploads/2020/09/15/inorder_1.jpg) 10 | 11 | 12 | ``` 13 | 输入:root = [1,null,2,3] 14 | 输出:[1,3,2] 15 | ``` 16 | 17 | ## 方法 18 | 19 | 20 | ## code 21 | 22 | ### js 23 | 24 | ```js 25 | var inorderTraversal = function(root) { 26 | let ret = []; 27 | if (root === null) 28 | return ret; 29 | let stack = []; 30 | let cur = root; 31 | while (cur !== null || stack.length !== 0) { 32 | while (cur !== null) { 33 | stack.push(cur); 34 | cur = cur.left; 35 | } 36 | let t = stack.pop(); 37 | ret.push(t.val) 38 | cur = t.right; 39 | } 40 | return ret; 41 | }; 42 | ``` 43 | 44 | ### go 45 | 46 | ```go 47 | func inorderTraversal(root *TreeNode) []int { 48 | var ret []int 49 | if root == nil { 50 | return ret 51 | } 52 | var stack []*TreeNode 53 | cur := root 54 | for cur != nil || len(stack) != 0 { 55 | for cur != nil { 56 | stack = append(stack, cur) 57 | cur = cur.Left 58 | } 59 | t := stack[len(stack)-1] 60 | stack = stack[:len(stack)-1] 61 | ret = append(ret, t.Val) 62 | cur = t.Right 63 | } 64 | return ret 65 | } 66 | ``` 67 | 68 | ### java 69 | 70 | ```java 71 | class Solution { 72 | public List inorderTraversal(TreeNode root) { 73 | List ret = new ArrayList<>(); 74 | if (root == null) 75 | return ret; 76 | Stack stack = new Stack<>(); 77 | 78 | TreeNode cur = root; 79 | 80 | while (cur != null || !stack.isEmpty()) { 81 | while (cur != null) { 82 | stack.push(cur); 83 | cur = cur.left; 84 | } 85 | TreeNode t = stack.pop(); 86 | ret.add(t.val); 87 | cur = t.right; 88 | } 89 | return ret; 90 | } 91 | } 92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /Java/alg/lc/96.不同的二叉搜索树.md: -------------------------------------------------------------------------------- 1 | # 96. 不同的二叉搜索树 2 | 3 | [url](https://leetcode-cn.com/problems/unique-binary-search-trees/) 4 | 5 | ## 题目 6 | 7 | 给定一个整数 *n*,求以 1 ... *n* 为节点组成的二叉搜索树有多少种? 8 | 9 | 10 | ``` 11 | 输入: 3 12 | 输出: 5 13 | 解释: 14 | 给定 n = 3, 一共有 5 种不同结构的二叉搜索树: 15 | 16 | 1 3 3 2 1 17 | \ / / / \ \ 18 | 3 2 1 1 3 2 19 | / / \ \ 20 | 2 1 2 3 21 | ``` 22 | 23 | ## 方法 24 | 25 | 26 | ## code 27 | 28 | ### js 29 | 30 | ```js 31 | let numTrees = n => { 32 | let dp = Array(n + 1).fill(0); 33 | dp[0] = 1; 34 | dp[1] = 1; 35 | for (let i = 2; i <= n; i++) { 36 | for (let j = 1; j <= i; j++) { 37 | dp[i] += dp[j-1] * dp[i-j]; 38 | } 39 | } 40 | return dp[n]; 41 | } 42 | ``` 43 | 44 | ### go 45 | 46 | ```go 47 | func numTrees(n int) int { 48 | dp := make([]int, n+1) 49 | dp[0], dp[1] = 1, 1 50 | for i := 2; i <= n; i++ { 51 | for j := 1; j <= i; j++ { 52 | dp[i] += dp[j-1] * dp[i-j] 53 | } 54 | } 55 | return dp[n] 56 | } 57 | ``` 58 | 59 | ### java 60 | 61 | ```java 62 | class Solution { 63 | public int numTrees(int n) { 64 | int[] dp = new int[n + 1]; 65 | dp[0] = 1; 66 | dp[1] = 1; 67 | for (int i = 2; i <=n; i++){ 68 | for (int j = 1; j <= i; j++){ 69 | dp[i] += dp[j - 1] * dp[i - j]; 70 | } 71 | } 72 | return dp[n]; 73 | } 74 | } 75 | ``` 76 | 77 | -------------------------------------------------------------------------------- /Java/alg/lc/98.验证二叉搜索树.md: -------------------------------------------------------------------------------- 1 | # 98. 验证二叉搜索树 2 | 3 | [url](https://leetcode-cn.com/problems/unique-binary-search-trees/) 4 | 5 | ## 题目 6 | 7 | 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 8 | 9 | 假设一个二叉搜索树具有如下特征: 10 | 11 | - 节点的左子树只包含小于当前节点的数。 12 | - 节点的右子树只包含大于当前节点的数。 13 | - 所有左子树和右子树自身必须也是二叉搜索树。 14 | 15 | 16 | ``` 17 | 输入: 18 | 2 19 | / \ 20 | 1 3 21 | 输出: true 22 | 输入: 23 | 5 24 | / \ 25 | 1 4 26 |   / \ 27 |   3 6 28 | 输出: false 29 | 解释: 输入为: [5,1,4,null,null,3,6]。 30 |   根节点的值为 5 ,但是其右子节点值为 4 。 31 | ``` 32 | 33 | ## 方法 34 | 35 | 36 | ## code 37 | 38 | ### js 39 | 40 | ```js 41 | let isValidBST = root => { 42 | let validate = (node, min, max) => { 43 | if (node === null) 44 | return true; 45 | if (node.val <= min || node.val >= max) 46 | return false; 47 | return validate(node.left, min, node.val) && validate(node.right, node.val, max); 48 | } 49 | return validate(root, Number.MIN_VALUE, Number.MAX_VALUE); 50 | } 51 | ``` 52 | 53 | ### go 54 | 55 | ```go 56 | func isValidBST(root *TreeNode) bool { 57 | const INT_MAX = int(^uint(0) >> 1) 58 | const INT_MIN = ^INT_MAX 59 | var validate func(node *TreeNode, min, max int) bool 60 | validate = func(node *TreeNode, min, max int) bool { 61 | if node == nil { 62 | return true 63 | } 64 | if node.Val <= min || node.Val >= max { 65 | return false 66 | } 67 | return validate(node.Left, min, node.Val) && validate(node.Right, node.Val, max) 68 | } 69 | return validate(root, INT_MIN, INT_MAX) 70 | } 71 | ``` 72 | 73 | ### java 74 | 75 | ```java 76 | class Solution { 77 | public boolean isValidBST(TreeNode root) { 78 | return validate(root, Long.MIN_VALUE, Long.MAX_VALUE); 79 | } 80 | 81 | public boolean validate(TreeNode node, long min, long max) { 82 | if (node == null) 83 | return true; 84 | if (node.val <= min || node.val >= max) 85 | return false; 86 | return validate(node.left, min, node.val) && validate(node.right, node.val, max); 87 | } 88 | } 89 | ``` 90 | 91 | -------------------------------------------------------------------------------- /Java/bishi/README.md: -------------------------------------------------------------------------------- 1 | > 秋招的时候,会有笔试的,其实大部分笔试出的难度,基本是2-4道编程题,其中都会有一道简单题,题的分类无非就是动态规划、贪心、二分法、DFS、图、字符串、排序等,可以去牛客网按照公司的的套题做,并且还能熟悉一下输入输出,我这里就列举一点点公司的笔试题。 2 | 3 | - [牛客](niuke.md) 一不小心把内容错删掉了,去官网边做边总结把... 4 | - [腾讯](tx.md) 5 | - [贝壳](beike.md) 6 | - [拼多多](pdd.md) 7 | - [美团](meituan.md) 8 | - [大疆](dajiang.md) 9 | - [老虎](laohu.md) 10 | - [shopee](Shopee.md) 11 | - [网易](wangyi.md) 12 | 13 | 14 | -------------------------------------------------------------------------------- /Java/bishi/ali.md: -------------------------------------------------------------------------------- 1 | 2 | 这给我难的... 3 | 4 | 智力题 5 | 6 | ```java 7 | import java.util.Arrays; 8 | import java.util.Scanner; 9 | 10 | public class Main { 11 | public static void main(String[] args) { 12 | Scanner sc = new Scanner(System.in); 13 | int T = sc.nextInt(); 14 | for (int i = 0; i < T; i++) { 15 | int n = sc.nextInt(); 16 | int[] a = new int[n]; 17 | for (int j = 0; j < n; j++) { 18 | a[j] = sc.nextInt(); 19 | } 20 | Arrays.sort(a); 21 | System.out.println(min(a, n - 1)); 22 | } 23 | } 24 | 25 | public static long min(int[] a, int end) { 26 | if (end + 1 == 1) 27 | return 0; 28 | if (end + 1 <= 2) 29 | return a[1]; 30 | if (end + 1 == 3) 31 | return a[2] + a[1] + a[0]; 32 | if (a[0] + a[end - 1] > 2 * a[1]) 33 | return a[1] * 2 + a[0] + a[end] + min(a, end - 2); 34 | else 35 | return a[end] + a[0] + min(a, end - 2); 36 | } 37 | } 38 | 39 | ``` 40 | 41 | 42 | ```python 43 | 44 | # 容易想到的是过河方案就是最轻的人作为摆渡人,一趟一趟运 45 | # 还有一种是,最轻的人把第二轻的送到对岸,自己回来,再让最重的两个人过去,第二轻的再把船划回来 46 | # 一直比较这两种方案,直到人数小于3 47 | def first(): 48 | T = int(input()) 49 | for _ in range(T): 50 | n = int(input()) 51 | weights = list(map(int, input().split())) 52 | sorted(weights) 53 | res = 0 54 | while n > 3: 55 | res += min(weights[1] * 2 + weights[0] + weights[-1], weights[0] * 2 + weights[-1] + weights[-2]) 56 | weights.pop() 57 | weights.pop() 58 | n -=2 59 | if n == 1 or n == 3: 60 | res += sum(weights) 61 | elif n == 2: 62 | res += max(weights) 63 | print(res) 64 | 65 | first() 66 | 67 | ``` -------------------------------------------------------------------------------- /Java/bishi/niuke.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DreamCats/java-notes/c4cf3bbbb6bc95daaf6deff734642ea51c7cec9c/Java/bishi/niuke.md -------------------------------------------------------------------------------- /Java/bus/README.md: -------------------------------------------------------------------------------- 1 | - 项目地址:[微服务班车在线预约系统](https://github.com/DreamCats/SchoolBus) 2 | - [环境搭建文档](环境搭建文档.md) 3 | - [Redis绑定Token分析文档](Redis绑定Token.md) 4 | - [用户服务所有接口分析文档](用户服务.md) 5 | - [班车服务所有接口分析文档](班车服务.md) 6 | - [订单服务所有接口分析文档](订单服务.md) 7 | - [支付服务所有接口分析文档](支付服务.md) 8 | - [添加订单、支付和退款的业务结合消息队列](RocketMQ最终一致性.md) 9 | - [Redis的key过期事件结合自动取消订单业务](Redis的key过期事件.md) 10 | - [SQL语句调优](业务逻辑SQL语句.md) 11 | - [Zookeeper的bug之一](上线遇到的bug.md) 12 | 13 | -------------------------------------------------------------------------------- /Java/classify/basis/IO.md: -------------------------------------------------------------------------------- 1 | # IO 2 | ## BIO 3 | **BIO (Blocking I/O)**:**同步阻塞I/O模式**,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 4 | 5 | ## NIO 6 | **NIO (New I/O)**:NIO是一种**同步非阻塞的I/O模型**,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 `Channel` , `Selector`,`Buffer`等抽象。NIO中的N可以理解为`Non-blocking`,不单纯是New。它支持**面向缓冲**的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。 7 | 8 | [NIO底层原理](https://blog.csdn.net/u013857458/article/details/82424104) 9 | 10 | ## AIO 11 | **AIO (Asynchronous I/O)**: AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是**异步非阻塞的IO模型**。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。 -------------------------------------------------------------------------------- /Java/classify/basis/Object.md: -------------------------------------------------------------------------------- 1 | # Object 2 | 3 | ```java 4 | public final native Class getClass(); 5 | public native int hashCode(); // 返回对象的哈希代码值。 6 | public boolean equals(Object obj) 7 | protected native Object clone() // 创建并返回此对象的副本。 8 | public String toString() // 返回对象的字符串表示形式。 9 | public final native void notify(); // 唤醒正在该对象的监视器上等待的单个线程。 10 | public final native void notifyAll(); // 唤醒正在该对象的监视器上等待的全部线程。 11 | public final native void wait(); // 使当前线程等待,直到另一个线程调用此对象的方法或方法。 12 | protected void finalize(); // 当垃圾回收确定不再有对对象的引用时,由对象上的垃圾回收器调用。 13 | ``` 14 | 15 | -------------------------------------------------------------------------------- /Java/classify/basis/final.md: -------------------------------------------------------------------------------- 1 | # final 2 | 3 | 面试官:有木有想过为什么String的char前面加了final,有什么好处? 4 | 5 | 我:答这个问题,你要先说final是干啥的 6 | 7 | final关键字主要用在三个地方:变量、方法、类。 8 | 9 | 10 | - 对于一个final变量,如果是**基本数据类型的变量,则其数值一旦在初始化之后便不能更改**;如果是引用类型的变量,则在对其初始化之后便**不能再让其指向另一个对象**。 11 | 12 | - 当用final修饰一个类时,表明**这个类不能被继承**。final类中的所有成员方法都会被隐式地指定为final方法。 13 | 14 | - 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 15 | 16 | **final修饰有啥好处**:(面试官想听这三点) 17 | 18 | - final的关键字**提高了性能**,JVM和java应用会**缓存final变量**; 19 | 20 | - final变量可以在多线程环境下保持**线程安全**; 21 | 22 | - 使用final的关键字提高了性能,JVM会对方法变量类进行优化; -------------------------------------------------------------------------------- /Java/classify/basis/static.md: -------------------------------------------------------------------------------- 1 | # static 2 | - **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()` 3 | - **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次. 4 | - **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。 5 | - **静态导包(用来导入类中的静态资源,1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。 -------------------------------------------------------------------------------- /Java/classify/basis/基本类型.md: -------------------------------------------------------------------------------- 1 | # 基本类型 2 | 3 | > 简单说一下 4 | 5 | 6 | ## 8大基本类型 7 | 8 | ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg) 9 | 10 | 11 | > 特别注意Boolean 未精确定义字节。Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位。 12 | 13 | 14 | ## 装箱和拆箱 15 | 16 | - 装箱:将基本类型用它们对应的**引用类型**包装起来; 17 | - 拆箱:将包装类型转换为**基本数据类型**; 18 | 19 | ## 注意初始化 20 | 21 | ```java 22 | int a; 23 | System.out.println(a); // 这里会报错,因为a没有初始化 24 | 25 | Integer b = new Integer(); // 构造函数必须传递一个默认值,要不然提示报错,无法初始化 26 | // 看一下Integer的源码的value, 27 | public Integer(int value) { 28 | this.value = value; 29 | } 30 | // 而value,这么以来,创建对象必须初始化咯 31 | private final int value; 32 | ``` 33 | 34 | ## 注意字面量 35 | 36 | ```java 37 | float a = 3.14; // 字面量3.14 是double类型, 所以这里会报错 38 | float a = 3.14f; // 这样就不会报错了,需要加个f 39 | 40 | // 转型 41 | short a = 1; // short类型 42 | a = a + 1; // short = short + int 会报错, int无法像下转型,但是short可以向上,这样右边就是Int,无法向下 43 | a++; // 这里是a = (short) (a + 1); 44 | ``` 45 | 46 | ## 注意Integer.valueOf 47 | 48 | ```java 49 | // This method will always cache values in the range -128 to 127, 50 | public static Integer valueOf(int i) { 51 | if (i >= IntegerCache.low && i <= IntegerCache.high) // 条件 52 | return IntegerCache.cache[i + (-IntegerCache.low)];// 取缓存, 53 | // Integeer的源码中: 54 | // static final int low = -128; IntegerCache.low = -128 55 | return new Integer(i); 56 | } 57 | ``` -------------------------------------------------------------------------------- /Java/classify/basis/序列化.md: -------------------------------------------------------------------------------- 1 | > 很多rpc框架都要考虑序列化的问题,但是我没有过于深入 2 | 3 | # 序列化 4 | 1. 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。 5 | 2. 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。 6 | 3. 如果想让某个变量不被序列化,使用transient修饰。 7 | 4. 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。 8 | 5. 反序列化时必须有序列化对象的class文件。 9 | 6. 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。 10 | 7. 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。 11 | 8. 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。 12 | 9. 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。 13 | 14 | [参考链接](https://juejin.im/post/5ce3cdc8e51d45777b1a3cdf#heading-9) -------------------------------------------------------------------------------- /Java/classify/basis/泛型.md: -------------------------------------------------------------------------------- 1 | 2 | > 我也没有过多的深入这一块 3 | 4 | ## 泛型 5 | 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。 6 | 7 | **好处**: 8 | - **类型安全**,提供编译期间的类型检测 9 | - **前后兼容** 10 | - **泛化代码,代码可以更多的重复利用** 11 | - **性能较高**,用GJ(泛型JAVA)编写的代码可以为java编译器和虚拟机带来更多的类型信息,这些信息对java程序做进一步优化提供条件 12 | 13 | [泛型擦除原理](https://www.jianshu.com/p/328efeb01940) -------------------------------------------------------------------------------- /Java/classify/basis/深浅拷贝.md: -------------------------------------------------------------------------------- 1 | # 深浅拷贝 2 | 3 | - **浅拷贝**:对**基本数据类型进行值传递**,对**引用数据类型进行引用传递般的拷贝**,此为浅拷贝。 4 | - **深拷贝**:对**基本数据类型进行值传递**,对**引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝** 5 | - 也就二者对引用数据类型有区别 6 | 7 | 8 | 知道结论即可,要想测试,可以[我博客-先占坑]() 9 | 10 | -------------------------------------------------------------------------------- /Java/classify/dis/CAP和BASE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## CAP 4 | - C(一致性):对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。 5 | - A(可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。 6 | - P(分区容错性):当出现网络分区后,系统能够继续工作。打个比方,集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。 7 | 8 | - CP:对于CP来说,放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致。 9 | - AP:对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展。 10 | 11 | ## BASE 12 | BASE是BasicallyAvailable(基本可用)、Softstate(软状态)和Eventuallyconsistent(最终一致性)三个短语的缩写。是对CAP中AP的一个扩展-基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。 13 | - 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。 14 | - 最终一致:**最终一致是指经过一段时间后,所有节点数据都将会达到一致**。BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和ACID是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。 -------------------------------------------------------------------------------- /Java/classify/dis/Sentinel.md: -------------------------------------------------------------------------------- 1 | 2 | ## 为什么选择Sentinel? 3 | Sentinel是一个面向分布式架构的轻量级服务保护框架,主要以流量控制、熔断降级、系统负载保护等多个维度。 4 | 5 | 隔离策略:信号量隔离(并发线程数限流) 6 | 7 | 熔断策略: 8 | 1. 基于响应时间 9 | 2. 异常比率 10 | 3. 异常数 11 | 12 | 限流:基于QPS限流 13 | 14 | 控制台:查看秒级监控、机器发现等。 15 | 16 | ## 服务限流 17 | 当**系统资源不够,不足以应对大量请求**,对系统按照预设的规则进行流量限制或功能限制 18 | 19 | ## 服务熔断 20 | 当**调用目标服务的请求和调用大量超时或失败,服务调用方为避免造成长时间的阻塞造成影响其他服务**,后续对该服务接口的调用不再经过进行请求,直接执行本地的默认方法 21 | 22 | ## 服务降级 23 | **为了保证核心业务在大量请求下能正常运行,根据实际业务情况及流量,对部分服务降低优先级**,有策略的不处理或用简单的方式处理 24 | 25 | ## 为什么熔断降级 26 | 系统承载的访问量是有限的,如果不做流量控制,会导致系统资源占满,服务超时,从而所有用户无法使用,通过服务限流控制请求的量,服务降级省掉非核心业务对系统资源的占用,最大化利用系统资源,尽可能服务更多用户 27 | 28 | ## 和Hystrix对比 29 | ![sentinel和hystrix-qb3wFi](https://gitee.com/dreamcater/blog-img/raw/master/uPic/sentinel和hystrix-qb3wFi.png) 30 | 31 | **值得补充的是**:相比 Hystrix 基于线程池隔离进行限流,这种方案**虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响**。 32 | 33 | Sentinel 并发线程数限流不负责创建和管理线程池,而是**简单统计当前请求上下文的线程数目,如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离**。 34 | 35 | [官网补充](http://dubbo.apache.org/zh-cn/blog/sentinel-introduction-for-dubbo.html) -------------------------------------------------------------------------------- /Java/classify/dis/zookeeper.md: -------------------------------------------------------------------------------- 1 | 2 | ## 什么是Zookeeper? 3 | ZooKeeper 是一个开源的分布式协调服务 4 | 5 | ## Zookeeper使用场景? 6 | 1. 数据发布/订阅、 7 | 2. 负载均衡、 8 | 3. 命名服务、 9 | 4. 分布式协调/通知、 10 | 5. 集群管理、 11 | 6. Master 选举、 12 | 7. 分布式锁和分布式队列等功能。 13 | 14 | ## Zookeeper的特点 15 | - 顺序一致性: 从同一客户端发起的事务请求,最终将会严格地按照顺序被应用到 ZooKeeper 中去。 16 | - 原子性: 所有事务请求的处理结果在整个集群中所有机器上的应用情况是一致的,也就是说,要么整个集群中所有的机器都成功应用了某一个事务,要么都没有应用。 17 | - 单一系统映像 : 无论客户端连到哪一个 ZooKeeper 服务器上,其看到的服务端数据模型都是一致的。 18 | - 可靠性: 一旦一次更改请求被应用,更改的结果就会被持久化,直到被下一次更改覆盖。 19 | 20 | ## Zookeeper的原理? 21 | ZAB 协议&Paxos算法 22 | ZAB协议包括两种基本的模式:**崩溃恢复**和**消息广播**。当整个 Zookeeper 集群刚刚启动或**者Leader服务器宕机**、**重启**或者网络故障导致**少于过半的服务器与 Leader 服务器保持正常通信**时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。当集群中超过**半数机器与该 Leader 服务器完成数据同步**之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。 23 | 24 | ## 选择算法和流程 25 | 26 | 27 | 先说: 28 | > 每次投票会包含所推举的服务器的 myid 和 ZXID、epoch,使用(myid, ZXID,epoch)来表示 29 | > zxid,也就是事务 id,为了保证事务的顺序一致性,zookeeper 采用了递增的事务 id 号(zxid)来标识事务。 30 | > PK 1. 优先检查 ZXID。ZXID 比较大的服务器优先作为Leader 2. 如果 ZXID 相同,那么就比较 myid。myid 较大的服务器作为 Leader 服务器。 31 | 32 | 目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下: 33 | 34 | 1. 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。 35 | 2. 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。 36 | 3. 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为leader,服务器1,2成为follower。 37 | 4. 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为follower。 38 | 5. 服务器5启动,后面的逻辑同服务器4成为follower。 39 | 40 | [https://www.cnblogs.com/wuzhenzhao/p/9983231.html](https://www.cnblogs.com/wuzhenzhao/p/9983231.html) -------------------------------------------------------------------------------- /Java/classify/dis/分布式一致性算法.md: -------------------------------------------------------------------------------- 1 | 2 | ## paxos算法 3 | > 要讲这个算法,还要先扯背景:在常见的分布式系统中,总会发生诸如机器宕机或网络异常(等情况。Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确地在集群内部对某个数据的值达成一致,并且保证不论发生以上任何异常,都不会破坏整个系统的一致性。 4 | 5 | > 其实在整个提议和投票过程当中,主要的角色就是“提议者”和“接受者” 6 | 7 | 该算法大致流程:其实分为两个阶段 8 | 9 | 1. 因为存在多个“提议者”Proposer,如果都提意见,那么“接受者”Acceptor不就炸掉了嘛?到底接受谁啊?所以,要先明确哪个“提议者”是领袖,最厉害的那个,先把这个给挑出来。尽早的让意见统一,并且早点形成多数派。 10 | 2. 由上阶段选出的意见领袖提出提议,“接受者”反馈意见。如果多数“接受者”接受了一个提议,那么这个提议就通过了。 11 | 12 | [例子](https://ocavue.com/paxos.html#%E8%8A%82%E7%82%B9%E6%95%85%E9%9A%9C%E7%9A%84%E4%BE%8B%E5%AD%90) 13 | 14 | ## ZAB 15 | - ZAB协议包括两种基本的模式:**崩溃恢复**和**消息广播**。 16 | - 当整个 Zookeeper 集群刚刚启动或**者Leader服务器宕机**、**重启**或者网络故障导致**少于过半的服务器与 Leader 服务器保持正常通信**时,所有服务器进入崩溃恢复模式,首先选举产生新的 Leader 服务器,然后集群中 Follower 服务器开始与新的 Leader 服务器进行数据同步。 17 | - 当集群中超过**半数机器与该 Leader 服务器完成数据同步**之后,退出恢复模式进入消息广播模式,Leader 服务器开始接收客户端的事务请求生成事物提案来进行事务请求处理。 18 | 19 | ## zk的leader选举算法和流程 20 | 目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下: 21 | 22 | 1. 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking。 23 | 2. 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大所以服务器2胜出,但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING。 24 | 3. 服务器3启动,给自己投票,同时与之前启动的服务器1,2交换信息,由于服务器3的编号最大所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为leader,服务器1,2成为follower。 25 | 4. 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为follower。 26 | 5. 服务器5启动,后面的逻辑同服务器4成为follower。 27 | 28 | [https://www.cnblogs.com/wuzhenzhao/p/9983231.html](https://www.cnblogs.com/wuzhenzhao/p/9983231.html) 29 | 30 | 31 | ## raft 32 | 33 | [https://zhuanlan.zhihu.com/p/66441389](https://zhuanlan.zhihu.com/p/66441389) 34 | 35 | 36 | 37 | ## 分布式一致性哈希 38 | 39 | [https://zhuanlan.zhihu.com/p/24440059](https://zhuanlan.zhihu.com/p/24440059) -------------------------------------------------------------------------------- /Java/classify/dis/分布式概念.md: -------------------------------------------------------------------------------- 1 | ## 集群、分布式和微服务的概念 2 | 微服务是一种架构风格,一个大型复杂软件应用由**一个或多个微服务组成**。系统中的**各个微服务可被独立部署,各个微服务之间是松耦合的**。**每个微服务仅关注于完成一件任务并很好地完成该任务**。在所有情况下,每个任务代表着一个小的业务能力。 3 | 4 | - 分布式将一个大的系统划分为多个业务模块,**业务模块分别部署到不同的机器上**,各个业务模块之间通过接口进行数据交互。区别**分布式的方式是根据不同机器不同业务**。 5 | - 集群模式是**不同服务器部署同一套服务对外访问,实现服务的负载均衡**。区别集群的方式是根据部署多台服务器业务是否相同。 6 | - 微服务的设计是为了不因为某个模块的升级和BUG影响现有的系统业务。微服务与分布式的细微差别是,**微服务的应用不一定是分散在多个服务器上,他也可以是同一个服务器**。 7 | 8 | ## 什么是RPC? 9 | RPC(RemoteProcedureCall)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务A、B部署在两台不同的机器上,那么服务A如果想要调用服务B中的某个方法该怎么办呢?使用HTTP请求当然可以,但是可能会比较慢而且一些优化做的并不好。RPC的出现就是为了解决这个问题。 -------------------------------------------------------------------------------- /Java/classify/dis/布隆过滤器.md: -------------------------------------------------------------------------------- 1 | ## 布隆过滤器原理 2 | - 布隆过滤器可以检查值是 “可能在集合中” 还是 “绝对不在集合中”。 3 | - 布隆过滤器(Bloom Filter)本质上是由长度为 m 的位向量或位列表(仅包含 0 或 1 位值的列表)组成,最初所有的值均设置为 0 4 | - 为了将数据项添加到布隆过滤器中,我们会提供 K 个不同的哈希函数,并将结果位置上对应位的值置为 “1” 5 | 6 | 总结:当我们搜索一个值的时候,若该值经过 K 个哈希函数运算后的任何一个索引位为 ”0“,那么该值肯定不在集合中。但如果所有哈希索引值均为 ”1“,则只能说该搜索的值可能存在集合中。 7 | [5分钟搞懂布隆过滤器,亿级数据过滤算法你值得拥有!](https://juejin.im/post/5de1e37c5188256e8e43adfc) -------------------------------------------------------------------------------- /Java/classify/dis/限流算法.md: -------------------------------------------------------------------------------- 1 | 2 | > 在大型电商等系统中,我们回努力提升API的吞吐量和QPS(Query Per Second 每秒查询量),但总归有上限.为了应对巨大流量的瞬间提交,我们会做对应的限流处理. 3 | 4 | 常见的限流算法有计数器,漏桶,令牌桶. 5 | 6 | ## 计数器 7 | 8 | 计数器限流方式比较粗暴,**一次访问设置一次计数**,在系统内设置每秒的访问量,**超过访问量的访问直接丢弃**,实现访问限流.这种算法的弊端就是,在开始的时间,访问量被使用完后,1S内会有长时间的真空期是处于接口不可用的状态的. 9 | 10 | 实现方式和拓展方式很多.比如可以使用redis进行1S的100次访问计数,来一个流量100-1当数量到达0时,拒绝后续的访问.也可以不拒绝而是将请求放入缓存队列,根据实际业务情况选择不同的实现方式. 11 | 12 | ## 漏斗 13 | 14 | 在计数器算法中我们看到,当使用了所有的访问量后,接口会完全处于不可用状态.有些系统不喜欢这样的处理方式,可以选择漏斗算法进行限流. 漏斗算法的原理就像名字,是一个漏斗,**访问量从漏斗的大口进入,从漏斗的小口进入系统**.这样不管是多大的访问量进入漏斗,最后进入系统的访问量都是固定的.漏斗的好处就是,大批量访问进入时,漏斗有容量,不超过容量(容量的设计=固定处理的访问量*可接受等待时长)的数据都可以排队等待处理,超过的才会丢弃. 15 | 16 | 实现方式可以使用队列,队列设置容量,访问可以大批量塞入队列,满队列后丢弃后续访问量.队列的出口以固定速率拿去访问量处理. 17 | 18 | 这种方案由于出口速率是固定的,那么当就无法应对短时间的突发流量. 19 | 20 | ## 令牌桶 21 | 22 | 令牌桶算法算是漏斗算法的改进版,为了处理短时间的突发流量而做了优化,令牌桶算法主要由三部分组成令牌流、数据流、令牌桶. 23 | 24 | - 令牌流:流通令牌的管道,用于生成的令牌的流通,放入令牌桶中. 25 | - 数据流:进入系统的数据流量 26 | - 令牌桶:保存令牌的区域,可以理解为一个缓存区.令牌保存在这里用于使用. 27 | 28 | 令牌桶算法会**按照一定的速率生成令牌放入令牌桶,访问要进入系统时,需要从令牌桶获取令牌,有令牌的可以进入,没有的被抛弃**.**由于令牌桶的令牌是源源不断生成的,当访问量小时,可以留存令牌达到令牌桶的上限,这样当短时间的突发访问量来时,积累的令牌数可以处理这个问题.当访问量持续大量流入时,由于生成令牌的速率是固定的,最后也就变成了类似漏斗算法的固定流量处理.** 29 | 30 | 实现方式和漏斗也比较类似,可以使用一个队列保存令牌,一个定时任务用等速率生成令牌放入队列,访问量进入系统时,从队列获取令牌再进入系统. 31 | 32 | Google开源的guava包中RateLimiter类实现了令牌桶算法,不过这是单机的.集群可以按照上面的实现方式实现,队列使用中间件MQ实现,配合负载均衡算法,考虑集群各个服务器的承压情况做对应服务器的队列是比较建议的做法. 33 | 34 | [参考](https://zhuanlan.zhihu.com/p/95066428) -------------------------------------------------------------------------------- /Java/classify/jvm/类加载过程.md: -------------------------------------------------------------------------------- 1 | # 类加载过程 2 | 3 | 面试官:谈一谈类加载过程 4 | 5 | 我:加载->验证->准备->解析->初始化 6 | 7 | ![类加载过程](https://gitee.com/dreamcater/blog-img/raw/master/uPic/类加载过程-5A2XIO.png) 8 | 9 | ## 加载 10 | 11 | 类加载过程的第一步,主要完成下面3件事情: 12 | 13 | - 通过**全类名**获取定义此类的**二进制字节流** 14 | - 将字节流所代表的**静态存储结构**转换为方法区的**运行时数据结构** 15 | - 在内存中生成一个代表该类的 **Class 对象**,作为**方法区这些数据的访问入口** 16 | 17 | ## 验证 18 | 19 | - 文件格式验证:主要验证Class文件**是否规范**等。 20 | - 元数据验证:对字节码描述的信息**语义分析**等。 21 | - 字节码验证:确保语义是ok的。 22 | - 符号引用验证:确保解析动作能执行。 23 | 24 | ## 准备 25 | 26 | **准备阶段是正式为类变量分配内存并设置类变量初始值的阶段**,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意: 27 | 28 | - 这时候进行内存分配的仅包括**类变量**(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在 Java 堆中。 29 | - 这里所设置的初始值"通常情况"下是数据类型默认的**零值**(如0、0L、null、false等),比如我们定义了`public static int value=111` ,那么 value 变量在准备阶段的初始值就是 0 而不是111(初始化阶段才会复制)。特殊情况:比如给 value 变量加上了 **fianl 关键字**`public static final int value=111` ,那么准备阶段 value 的值就被复制为 111。 30 | 31 | ## 解析 32 | 33 | 解析阶段是虚拟机将常量池内的**符号引用替换为直接引用**的过程,也就是得到**类或者字段、方法在内存中的指针或者偏移量。** 34 | 35 | ## 初始化 36 | 37 | 初始化是类加载的最后一步,也是真正执行类中定义的 **Java 程序代码**(字节码),初始化阶段是执行**类构造器** ` ()`方法的过程。 38 | 39 | -------------------------------------------------------------------------------- /Java/classify/jvm/逃逸分析.md: -------------------------------------------------------------------------------- 1 | # 逃逸分析 2 | 3 | > 这一块知识还是要知道的呀,它是Java虚拟机中比较前沿优化的技术。 4 | 5 | 面试官:你了解逃逸分析吗? 6 | 7 | 我:算是了解。逃逸分析的基本行为就是分析**对象动态作用域**:当一个对**象在方法中被定义后,它可能被外部方法引用,例如作为调用参数传递到其他方法中,称为方法逃逸**。甚至还有可能**被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸**。如果能证明**一个对象不会逃逸到方法或线程之外**,也就是**别的方法或线程无法通过任何途径访问到这个对象**,则可能为这个变量进行一些高效的优化: 8 | 9 | 1. 栈上分配 10 | 11 | Java虚拟机中,**如果确定一个对象不会逃逸出方法之外,那让这个对象在栈上分配内存**将会是一个很不错的主意,**对象所占用的内存空间就可以随栈帧出栈而销毁**。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那**大量的对象就会随着方法的结束而自动销毁了**,垃圾收集系统的压力将会小很多。 12 | 13 | 2. 同步消除 14 | 15 | **线程同步本身是一个相对耗时的过程**,**如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问**,那这个变量的**读写肯定就不会有竞争**,对这个变量实施的同步措施也就可以消除。 16 | 17 | 3. 标量替换 18 | 19 | 标量是指一**个数据已经无法再分解成更小的数据来表示了**,Java虚拟机的原始数据类型都不能再进一步分解,它们就可以称为标量。如果逃逸分析证明**一个对象不会被外部访问**,并且**这个对象可以被拆散的话**,那程序真正执行的时候将**可能不创建这个对象**,而改**为直接创建它的若干个被这个方法使用的成员变量来代替**。除了可以让对象的成员变量在栈上(栈上存储的数据,有很大的概率会被虚拟机分配到物理机器高速寄存器中存储)分配和读写之外,还可以为后续进一步的优化手段创建条件。 -------------------------------------------------------------------------------- /Java/classify/mysql/MySQL是如何执行一条SQL的.md: -------------------------------------------------------------------------------- 1 | 2 | ## SQL执行顺序 3 | SQL的执行顺序:from---where--group by---having---select---order by 4 | 5 | ## MySQL是如何执行一条SQL的 6 | ![SQL执行的全部过程-MlS1d5](https://gitee.com/dreamcater/blog-img/raw/master/uPic/SQL执行的全部过程-MlS1d5.png) 7 | 8 | **MySQL内部可以分为服务层和存储引擎层两部分:** 9 | 10 | 1. **服务层包括连接器、查询缓存、分析器、优化器、执行器等**,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。 11 | 2. **存储引擎层负责数据的存储和提取**,其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。现在最常用的存储引擎是InnoDB,它从MySQL 5.5.5版本开始成为了默认的存储引擎。 12 | 13 | **Server层按顺序执行sql的步骤为**: 14 | 客户端请求: 15 | - **连接器**(验证用户身份,给予权限) 16 | - **查询缓存**(存在缓存则直接返回,不存在则执行后续操作) 17 | - **分析器**(对SQL进行词法分析和语法分析操作) 18 | - **优化器**(主要对执行的sql优化选择最优的执行方案方法) 19 | - **执行器**(执行时会先看用户是否有执行权限,有才去使用这个引擎提供的接口) 20 | - **去引擎层获取数据返回**(如果开启查询缓存则会缓存查询结果) -------------------------------------------------------------------------------- /Java/classify/mysql/MySQL的锁.md: -------------------------------------------------------------------------------- 1 | # MySQL的锁 2 | 3 | > MySQL的锁,其实跟Java差不了,一个思想。 4 | 5 | 面试官:MySQL的锁,介绍一下 6 | 7 | 我: 8 | 9 | MyISAM:MyISAM只有表锁,其中又分为共享读锁和独占写锁。 10 | 11 | - MyISAM表的读操作,不会阻塞其他用户对同一个表的读请求,但会阻塞对同一个表的写请求。 12 | - MyISAM表的写操作,会阻塞其他用户对同一个表的读和写操作。 13 | - MyISAM表的读、写操作之间、以及写操作之间是串行的。 14 | 15 | Innodb行锁:共享锁,排他锁 16 | 17 | - 对于UPDATE、DELETE、INSERT语句,Innodb会自动给涉及的数据集加排他锁(X);对于普通SELECT语句,Innodb不会加任何锁。 18 | 19 | ```sql 20 | //显示共享锁(S) : 21 | SELECT * FROM table_name WHERE .... LOCK IN SHARE MODE 22 | //显示排他锁(X): 23 | SELECT * FROM table_name WHERE .... FOR UPDATE. 24 | ``` 25 | 26 | - 记录锁(Record Locks):记录锁是封锁记录,记录锁也叫行锁,注意:行锁是针对索引的,如果表中没有索引,那么就会锁整张表 27 | - 间隙锁(GAP)对于键值在条件范围内但并不存在的记录,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。 28 | - 临键锁(Next-Key Lock):(Record Locks+GAP),锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。 29 | 30 | 面试官:给你张表怎么用cas实现高并发下的update操作 31 | 32 | 我: 33 | 34 | 第一种: 35 | 36 | ```xml 37 | // cas, 期望值和数据表中的旧值一致,才更新。 38 | # newStock = oldStock-desStock; 39 | 40 | UPDATE t_order SET stock=#{newStock} WHERE id=#{orderId} AND stock=#{oldStock} 41 | 42 | ``` 43 | 44 | ```java 45 | // orderId:订单id 46 | // getStock:库存:旧值 47 | // desStock:可以是期望值,但这里预减值 48 | int result = orderManager.desStockByCas(orderId, orderDo.getStock(), desStock); 49 | ``` 50 | 51 | 第二种: 52 | 53 | ```xml 54 | // 用版本号 55 | // 我期望的版本号, 和旧版本号一致才更新,并且版本号累加... 56 | 57 | UPDATE t_order SET stock=stock-#{desStock}, version=version+1 58 | WHERE id=#{orderId} AND version=#{oldVersion} 59 | 60 | ``` 61 | 62 | 如 63 | 64 | ```java 65 | // orderId:订单id 66 | // getVersion:获取数据库版本号,旧版本 67 | // desStock:可以是期望值,但这里预减值 68 | int result = orderManager.desStockByOptimistic(orderId, orderDo.getVersion(), desStock); 69 | ``` -------------------------------------------------------------------------------- /Java/classify/net/DNS.md: -------------------------------------------------------------------------------- 1 | 2 | ## DNS 3 | 4 | ### DNS是什么 5 | 6 | **官方解释**:DNS(Domain Name System,域名系统),因特网上作为**域名和IP地址相互映射**的一个**分布式数据库**,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。 7 | 8 | **通俗的讲**,我们更习惯于记住一个网站的名字,比如www.baidu.com,而不是记住它的ip地址,比如:167.23.10.2。 9 | 10 | ### 谈谈DNS解析过程 11 | ![DNS解析过程-eiVd6a](https://gitee.com/dreamcater/blog-img/raw/master/uPic/DNS解析过程-eiVd6a.png) 12 | 13 | - 请求一旦发起,若是chrome浏览器,先在浏览器找之前**有没有缓存过的域名所对应的ip地址**,有的话,直接跳过dns解析了,若是没有,就会**找硬盘的hosts文件**,看看有没有,有的话,直接找到hosts文件里面的ip 14 | 15 | [字节问了修改hosts,浏览器会变吗?](https://blog.csdn.net/woshizhangliang999/article/details/51457864) 16 | 17 | - 如果本地的hosts文件没有能的到对应的ip地址,浏览器会发出一个**dns请求到本地dns服务器**,**本地dns服务器一般都是你的网络接入服务器商提供**,比如中国电信,中国移动等。 18 | - 查询你输入的网址的DNS请求到达本地DNS服务器之后,**本地DNS服务器会首先查询它的缓存记录**,如果缓存中有此条记录,就可以直接返回结果,此过程是**递归的方式进行查询**。如果没有,本地DNS服务器还要向**DNS根服务器**进行查询。 19 | - 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。 20 | - 最后,本地DNS服务器向**域名的解析服务器**发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。 21 | 22 | ### DNS查询方式 23 | 24 | #### 递归解析 25 | 26 | 当局部DNS服务器自己不能回答客户机的DNS查询时,它就需要向其他DNS服务器进行查询。此时有两种方式。**局部DNS服务器自己负责向其他DNS服务器进行查询,一般是先向该域名的根域服务器查询,再由根域名服务器一级级向下查询**。最后得到的查询结果返回给局部DNS服务器,再由局部DNS服务器返回给客户端。 27 | 28 | #### 迭代解析 29 | 30 | 当局部DNS服务器自己不能回答客户机的DNS查询时,也可以通过迭代查询的方式进行解析。局部DNS服务器不是自己向其他DNS服务器进行查询,**而是把能解析该域名的其他DNS服务器的IP地址返回给客户端DNS程序**,客户端DNS程序再继续向这些DNS服务器进行查询,直到得到查询结果为止。也就是说,迭代解析只是帮你找到相关的服务器而已,而不会帮你去查。比如说:baidu.com的服务器ip地址在192.168.4.5这里,你自己去查吧,本人比较忙,只能帮你到这里了。 31 | 32 | ### DNS负载均衡 33 | 34 | 当一个网站有足够多的用户的时候,假如每次请求的资源都位于同一台机器上面,那么这台机器随时可能会蹦掉。处理办法就是用DNS负载均衡技术,它的原理是在**DNS服务器中为同一个主机名配置多个IP地址,在应答DNS查询时,DNS服务器对每个查询将以DNS文件中主机记录的IP地址按顺序返回不同的解析结果,将客户端的访问引导到不同的机器上去,使得不同的客户端访问不同的服务器**,从而达到负载均衡的目的。例如可以根据每台机器的负载量,该机器离用户地理位置的距离等等。 35 | 36 | ### 为什么域名解析用UDP协议? 37 | 38 | 因为UDP快啊!UDP的DNS协议只要一个请求、一个应答就好了。而使用基于TCP的DNS协议要三次握手、发送数据以及应答、四次挥手。但是UDP协议传输内容不能超过512字节。不过客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。 39 | 40 | ### 为什么区域传送用TCP协议? 41 | 42 | 因为TCP协议可靠性好啊!你要从主DNS上复制内容啊,你用不可靠的UDP? 因为TCP协议传输的内容大啊,你用最大只能传512字节的UDP协议?万一同步的数据大于512字节,你怎么办? -------------------------------------------------------------------------------- /Java/classify/net/网络模型.md: -------------------------------------------------------------------------------- 1 | 2 | ## 网络模型 3 | 4 | ![分层模型-EiHhGW](https://gitee.com/dreamcater/blog-img/raw/master/uPic/分层模型-EiHhGW.png) 5 | 6 | ### 简要概括 7 | 8 | - 物理层:底层数据传输,如网线;网卡标准。 9 | 10 | - 数据链路层:定义数据的基本格式,如何传输,如何标识;如网卡MAC地址。 11 | 12 | - 网络层:定义IP编址,定义路由功能;如不同设备的数据转发。 13 | 14 | - 传输层:端到端传输数据的基本功能;如 TCP、UDP。 15 | 16 | - 会话层:控制应用程序之间会话能力;如不同软件数据分发给不同软件。 17 | 18 | - 标识层:数据格式标识,基本压缩加密功能。 19 | 20 | - 应用层:各种应用软件,包括 Web 应用。 21 | 22 | ### 流程 23 | 24 | 比如,计算机 A 和 计算机 B 要进行信息交互,比如 A 上开发了一个网页,需要 B 去访问。B 发出一个请求给 A,那么请求数据从 B 的 **应用层开始向下传到表示层、再从表示层传到会话层直到物理层,通过物理层传递到 A,A 的物理层接到请求后将请求向上传递到自己的应用层,应用层再将要请求的数据向自己的物理层方向传递然后 B 接到数据传递数据到自己的应用层**。 25 | 26 | 说明: 27 | 28 | - 在四层,既传输层数据被称作**段**(Segments); 29 | - 三层网络层数据被称做**包**(Packages); 30 | - 二层数据链路层时数据被称为**帧**(Frames); 31 | - 一层物理层时数据被称为**比特流**(Bits)。 32 | 33 | ### 常见的端口号和协议号 34 | 35 | ![常见端口号-PemUq1](https://gitee.com/dreamcater/blog-img/raw/master/uPic/常见端口号-PemUq1.png) 36 | 37 | ### 总结 38 | 39 | - 网络七层模型是一个标准,而非实现。 40 | - 网络四层模型是一个实现的应用模型。 41 | - 网络四层模型由七层模型简化合并而来。 42 | 43 | ### ping命令基于哪一层协议的原理是什么? 44 | 45 | ping命令基于网络层的命令,是基于ICMP协议工作的。 46 | 47 | ### ARP 48 | ARP是一种解决地址问题的协议。以目标IP地址为线索,用来定位下一个应该接收数据分包的网络设备对应的MAC地址。 49 | 起初要通过广播发送一个ARP请求包,这个包里存放了其MAC地址的主机IP地址,由于广播的包可以被同一个链路上所有的主机或路由器接收,因此ARP的请求包也就会被这同一个链路上所有的主机和路由器进行解析。如果ARP请求包中的目标IP地址与自己的IP地址的一致,那么这个节点就将自己的MAC地址塞入ARP响应包返回给主机A。 -------------------------------------------------------------------------------- /Java/classify/redis/Redis分布式锁.md: -------------------------------------------------------------------------------- 1 | # 分布式锁 2 | 3 | > 毕竟判断和绑定座位(或者下单)非原子性,为了降低锁的粒度,可以将判断和绑定座位锁在一个事务里。集群:Redisson 4 | 5 | - Key为xx_座位号,过期时间为随机1-5s(用setex的命令,该命令是key和过期时间是原子性的) 6 | - 每次先Redis中判断该key存在不存在,如果存在,要么阻塞,要么就返回给用户,座位已被选择。 7 | - 如果不存在,先上锁,然后再判断和绑定座位(或者下单)。其实这里有个隐藏的问题。如果绑定座位非常耗时,超过了过期时间1-5s,就凉凉了。其实这里设置过期时间,就是防止一直因为某种原因阻塞而不释放锁 8 | - 前三步,少了个签证value,如果不设置,那么当锁过期了,业务逻辑才走完,准备删除的时候,B客户端获取到了该锁,但是A把B的key锁删除了,然而B还不知道。 9 | - 因此,要解决这个问题,可以设置value签证,结束的时候判断一次,该value是不是自己的value,这样就不会误删。 10 | 11 | ## RedLock算法流程 12 | 13 | 首先有这样的问题: 14 | 15 | 1. 客户端 A 从 Master 上获取锁。 16 | 2. 在锁未被复制到某 Slave 节点的时候,Master 节点 Down 掉了。 17 | 3. 某 Slave 节点成为新的 Master。 18 | 4. 客户端 B 可从新 Master 上获取锁。 19 | 20 | 假设有5个实例 21 | 22 | 1. 比如过期时间为TTL:10min 23 | 2. 记录当前时间:比如T1 = 12:00 24 | 3. 客户端分别向5个实例获取锁,比如申请锁的时间依次为:12:01...12:05,最后获取实例的锁为T2:12:05(获取锁的超时时间要远远小于过期时间,防止死等。) 25 | 4. 如果获取锁的实例大于3个(过半机制),那么就相当于获取到锁了,该锁的真正的有效时间为TTL-(T2-T1) = 5min 26 | 5. 如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例;因为可能已经获取了小于3个锁,必须释放,否则影响其他client获取锁 27 | 28 | [https://juejin.im/post/5cc165816fb9a03202221dd5](https://juejin.im/post/5cc165816fb9a03202221dd5) 29 | 30 | ## zk实现分布式锁 31 | 32 | [补充-Zookeeper锁的实现](https://juejin.im/post/5c01532ef265da61362232ed) -------------------------------------------------------------------------------- /Java/classify/redis/Redis持久化.md: -------------------------------------------------------------------------------- 1 | 2 | 面试官:Redis的持久化了解嘛? 3 | 4 | 我:了解,Redis的持久化分为两种:**RDB和AOF** 5 | 6 | ## RDB 7 | 8 | **RDB**是一种**快照存储持久化**方式,具体就是将`Redis`某一时刻的内存数据保存到硬盘的文件当中,默认保存的文件名为`dump.rdb`。在`Redis`服务器启动时,会重新加载`dump.rdb`文件的数据到内存当中恢复数据。 9 | 10 | 优点: 11 | 12 | 1. RDB会生成多个数据文件,**每个数据文件都代表了某一个时刻中redis的数据**,这种多个数据文件的方式,非常适合**做冷备**。 13 | 2. RDB对redis对外提供读写服务的时候,影像非常小,因为redis 主进程只需要fork一个子进程出来,让子进程对磁盘io来进行rdb持久化 14 | 3. **RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快**。 15 | 16 | 缺点: 17 | 18 | 1. **如果redis要故障时要尽可能少的丢失数据,RDB没有AOF好**,例如1:00进行的快照,在1:10又要进行快照的时候宕机了,这个时候就会丢失10分钟的数据。 19 | 2. RDB每次fork出子进程来执行RDB快照生成文件时,如果文件特别大,可能会导致客户端提供服务暂停数毫秒或者几秒 20 | 21 | ![rdb-uc03rm](https://gitee.com/dreamcater/blog-img/raw/master/uPic/rdb-uc03rm.png) 22 | 23 | ## AOF 24 | 25 | **AOF**:把所有的**对Redis的服务器进行修改的命令都存到一个文件里,命令的集合**。 使用AOF做持久化,每一个写命令都通过write函数追加到appendonly.aof中。aof的默认策略是每秒钟fsync一次,在这种配置下,就算发生故障停机,也最多丢失一秒钟的数据。 缺点是对于相同的数据集来说,AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略,AOF的速度可能会慢于RDB。 Redis默认是快照RDB的持久化方式。对于主从同步来说,主从刚刚连接的时候,进行全量同步(RDB);全同步结束后,进行增量同步(AOF)。 26 | 27 | 优点: 28 | 29 | 1. **AOF可以更好的保护数据不丢失,一般AOF会以每隔1秒**,通过后台的一个线程去执行一次fsync操作,如果redis进程挂掉,最多丢失1秒的数据。 30 | 2. **AOF以appen-only的模式写入,所以没有任何磁盘寻址的开销,写入性能非常高**。 31 | 3. AOF日志文件的命令通过非常可读的方式进行记录,这个非常适合做灾难性的误删除紧急恢复,如果某人不小心用flushall命令清空了所有数据,只要这个时候还没有执行rewrite,那么就可以将日志文件中的flushall删除,进行恢复。 32 | 33 | 缺点: 34 | 35 | 1. 对于同一份文件**AOF文件比RDB数据快照要大**。 36 | 2. AOF开启后支持写的QPS会比RDB支持的写的QPS低,因为AOF一般会配置成每秒fsync操作,每秒的fsync操作还是很高的 37 | 3. **数据恢复比较慢,不适合做冷备**。 38 | 39 | ![aof-ElLyYr](https://gitee.com/dreamcater/blog-img/raw/master/uPic/aof-ElLyYr.png) 40 | 41 | **如何选择:** 42 | 43 | 如何选择: 44 | 45 | 综合AOF和RDB两种持久化方式,**用AOF来保证数据不丢失,作为恢复数据的第一选择**;**用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,可以使用RDB进行快速的数据恢复**。 46 | 47 | -------------------------------------------------------------------------------- /Java/classify/redis/Redis数据结构.md: -------------------------------------------------------------------------------- 1 | 2 | ## String 3 | 4 | String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 常规key-value缓存应用; **常规计数:微博数,粉丝数**等。 5 | 6 | ## Hash 7 | 8 | Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来**存储用户信息,商品信息**等等。 9 | 10 | 简单说一下结构 11 | 12 | - 字典被广泛用于实现Redis的各种功能,其中包括数据库和哈希键。 13 | - Redis中的字典使用哈希表作为底层结构实现,每个字典带有两个哈希表,一个平时使用,另一个仅在进行rehash时使用。 14 | - Redis使用MurmurHash2算法来计算键的哈希值。 15 | - 哈希表使用链地址法来解决键冲突。 16 | 17 | 注意:这里和Java的HashMap不同的rehash过程 18 | 19 | 1. Redis的rehash过程是扩展和收缩,而且还是渐进式的rehash 20 | 2. Redis的字典有两个哈希表ht[0]和ht[1] 21 | 3. 为字典的ht[1]哈希表分配空间,如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used *2的2^n;如果执行的是收缩操作,那么ht[1]的大小第一个大于等于ht[0].used的2^n。(举个例子,ht[0]的长度为10,那么扩展就是2^5的32,如果是压缩的话2^4=16) 22 | 4. 如果ht[0]的键值非常多的话,一次性转移过去,是一个非常耗时的操作哦,因此并非一次性,采取渐进式rehash转移。 23 | 24 | ## List 25 | 26 | list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如**微博的关注列表,粉丝列表, 消息列表**等功能都可以用Redis的 list 结构来实现。 27 | 28 | Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 29 | 30 | 另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。 31 | 32 | ## Set 33 | 34 | set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。 35 | 36 | 当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在 一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。 37 | 38 | 比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常 方便的实现如**共同关注、共同粉丝、共同喜好**等功能。这个过程也就是求交集的过程,具体命令如下:`sinterstore key1 key2 key3`将交集存在key1内 39 | 40 | 41 | ## Zset 42 | 43 | 和set相比,sorted set增加了一个**权重参数score**,使得集合中的元素能够按score进行**有序**排列。 44 | 45 | 举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各种**礼物排行榜**,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。 46 | 47 | 跳跃表,暂时先放一个链接[https://zhuanlan.zhihu.com/p/53975333](https://zhuanlan.zhihu.com/p/53975333) 48 | 49 | - 简单来说跳跃表是一种有序数据结构,它通过在**每个节点中维持多个指向其他节点的指针**,从而达到快速访问节点的目的。 50 | - 跳跃表平均O(longN),最坏O(N)复杂度的节点查找 51 | - 跳跃表有个层的概念:层带有两个属性:**前进指针和跨度**,前进指针用于**访问位于表尾方向的其他节点**,而跨度则记录了**前进指针所指向节点和当前节点的距离**。一般情况下,层越多,查找效率越高。 -------------------------------------------------------------------------------- /Java/classify/redis/Redis模型.md: -------------------------------------------------------------------------------- 1 | 2 | ### Redis模型 3 | 4 | 面试官:Redis为什么快? 5 | 6 | 我:内心:不知道为什么一直问这个问题。 7 | 8 | 1. 纯内存操作 9 | 2. 单线程操作,避免了**频繁的上下文切换** 10 | 3. 合理高效的数据结构 11 | 4. 采用了**非阻塞I/O多路复用**机制 12 | 13 | 实际上Redis服务器是一个事件驱动程序,分为**文件事件**和**时间事件**,就主要讲一下文件事件。 14 | 15 | Redis基于Reactor模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器 16 | 17 | - 文件事件处理器使用**I/O多路复用程序来同时监听多个套接字**,并根据套接字目前执行的任务来为套接字**关联不同的事件处理器** 18 | - 当被监听的套接字准备好执行连接**应答、读取、写入、关闭**等操作时,与操作相对于的文件事件就会产生,这时**文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件**。 19 | - 简单点:**就是一堆套接字请求,被一个叫做I/O多路复用程序监听,通过文件事件分派器一个一个和事件处理器绑定在一起去处理**。 20 | 21 | I/O多路复用程序是有常见的select、epoll等系统调用所实现的。有个小故事,自行理解BIO、NIO、select、poll、epoll等 22 | 23 | 故事情节为:**老李去买火车票,三天后买到一张退票。参演人员(老李,黄牛,售票员,快递员),往返车站耗费1小时。** 24 | 25 | **往返车站可以看成系统调用,调用一次一小时** 26 | 27 | ### 1. 阻塞I/O模型 28 | 29 | 老李去火车站买票,排队三天买到一张退票。 30 | 31 | 耗费:在车站吃喝拉撒睡 3天,其他事一件没干。 32 | 33 | ### 2. 非阻塞I/O模型 34 | 35 | 老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。 36 | 37 | 耗费:往返车站6次,路上6小时,其他时间做了好多事。 38 | 39 | 2比1多了个自己轮训调用 40 | 41 | ### 3. I/O复用模型 42 | 43 | 1. select/poll 44 | 45 | 老李去火车站买票,委托黄牛,然后每隔6小时电话**黄牛**询问,黄牛三天内买到票,然后老李去火车站交钱领票。 46 | 47 | 耗费:往返车站2次,路上2小时,黄牛手续费100元,打电话17次 48 | 49 | 实际上,就是自己不断调select(像个船一样,装了很多描述符)询问哪些描述符可读可写,比如又一个可读了,咱就调用可读系统调用就ok了 50 | 51 | 2. epoll 52 | 53 | 老李去火车站买票,委托黄牛,**黄牛买到后即通知老李去领**,然后老李去火车站交钱领票。 54 | 耗费:往返车站2次,路上2小时,黄牛手续费100元,无需打电话 55 | 56 | 实际上,自己不用管了,当有可读的时候,直接中断你,然后你自己去读 57 | 58 | ### 4. 信号驱动I/O模型 59 | 60 | 老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李,然后老李去火车站交钱领票。 61 | 62 | 耗费:往返车站2次,路上2小时,免黄牛费100元,无需打电话 63 | 64 | 不要黄牛了,省了这个单线程,系统通知你,你收到以后自己去读 65 | 66 | ### 5. 异步I/O模型 67 | 68 | 老李去火车站,告诉售票员要买票,售票员买到票之后,打电话通知老李把票放在某某储物箱,老李根据储物箱地址自己去取票。 69 | 70 | 耗费:往返车站1次,路上1小时,免黄牛费100元,无需打电话 71 | 72 | 只需要注册一次,得到消息之后,就去另外一个地址上取走票 73 | 74 | 黄牛是多路复用,他不仅可以帮你买票,还可以其他人买票,还可以买飞机票,高铁票等。 75 | 76 | -------------------------------------------------------------------------------- /Java/classify/redis/内存淘汰机制.md: -------------------------------------------------------------------------------- 1 | # 内存淘汰机制 2 | 3 | 面试官:先讲一下Redis的过期时间 4 | 5 | 我: 6 | 7 | 定期删除+惰性删除 8 | 9 | - 定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载! 10 | - 惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈! 11 | 12 | 面试官:如果定期删除漏掉了很多过期 key,然后你也没及时去查, 也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? 13 | 14 | 我:**redis 内存淘汰机制。** 15 | 16 | redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略。redis 提供 6种数据淘汰策略: 17 | 18 | - volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 19 | - volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰 20 | - volatile-random:从已设置过期时间的数据集中任意选择数据淘汰 21 | - allkeys-lru:从数据集中挑选最近最少使用的数据淘汰 22 | - allkeys-random:从数据集中任意选择数据淘汰 23 | - no-enviction(驱逐):禁止驱逐数据 -------------------------------------------------------------------------------- /Java/classify/redis/缓存穿透和缓存雪崩.md: -------------------------------------------------------------------------------- 1 | > 感觉这个被问烂了 2 | 3 | 面试官:聊聊什么是缓存穿透和雪崩 4 | 5 | 我:ok 6 | 7 | **缓存穿透**: 8 | 9 | 一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。 10 | 11 | 1. 在接口做校验 12 | 2. 存null值(缓存击穿加锁) 13 | 3. 布隆过滤器拦截: 将所有可能的查询key先映射到布隆过滤器中,查询时先判断key是否存在布隆过滤器中,存在才继续向下执行,如果不存在,则直接返回。布隆过滤器将值进行多次哈希bit存储,布隆过滤器说某个元素在,可能会被误判。布隆过滤器说某个元素不在,那么一定不在。 14 | 15 | **缓存雪崩:** 16 | 17 | 缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。 18 | 19 | 1. 使用 Redis 高可用架构:使用 Redis 集群来保证 Redis 服务不会挂掉 20 | 2. 缓存时间不一致,给缓存的失效时间,加上一个随机值,避免集体失效 21 | 3. 限流降级策略:有一定的备案,比如个性推荐服务不可用了,换成热点数据推荐服务 -------------------------------------------------------------------------------- /Java/classify/spring/SpringBoot.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Springboot自动装配原理 4 | 5 | SpringBootApplication的注解 6 | [https://www.jianshu.com/p/943650ab7dfd](https://www.jianshu.com/p/943650ab7dfd) 7 | 8 | - @SpringBootConfiguration:允许在上下文中注册额外的bean或导入其他配置类 9 | - @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制 10 | - @ComponentScan: 扫描常用的注解 11 | 12 | 其中 @EnableAutoConfiguration 是实现自动配置的入口,该注解又通过 @Import 注解导入了AutoConfigurationImportSelector,在该类中加载 META-INF/spring.factories 的配置信息。然后筛选出以 EnableAutoConfiguration 为 key 的数据,加载到 IOC 容器中,实现自动配置功能! 13 | 14 | 15 | ## @Resource和@Autowired区别 16 | 17 | ### 共同点 18 | 两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。 19 | 20 | ```java 21 | public class TestServiceImpl { 22 | // 下面两种@Autowired只要使用一种即可 23 | @Autowired 24 | private UserDao userDao; // 用于字段上 25 | 26 | @Autowired 27 | public void setUserDao(UserDao userDao) { // 用于属性的方法上 28 | this.userDao = userDao; 29 | } 30 | } 31 | ``` 32 | 33 | ### 不同点 34 | 35 | 1. @Autowired 36 | 37 | @Autowired注解是**按照类型**(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用**按照名称**(byName)来装配,可以结合**@Qualifier注解**一起使用。(通过类型匹配找到多个candidate,在没有@Qualifier、@Primary注解的情况下,会使用对象名作为最后的fallback匹配)如下: 38 | 39 | ```java 40 | public class TestServiceImpl { 41 | @Autowired 42 | @Qualifier("userDao") 43 | private UserDao userDao; 44 | } 45 | ``` 46 | 47 | 2. @Resource 48 | 49 | @Resource默认按照ByName自动注入。@Resource有两个重要的属性:**name和type**,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。**所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略** -------------------------------------------------------------------------------- /Java/classify/spring/SpringMVC.md: -------------------------------------------------------------------------------- 1 | 2 | # SpringMVC 3 | 4 | ![springmvc工作原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49790288.jpg) 5 | 6 | 1. 客户端(浏览器)发送请求,直接请求到 `DispatcherServlet`。 7 | 2. `DispatcherServlet` 根据请求信息调用 `HandlerMapping`,解析请求对应的 `Handler`。 8 | 3. 解析到对应的 `Handler`(也就是我们平常说的 `Controller` 控制器)后,开始由 `HandlerAdapter` 适配器处理。 9 | 4. `HandlerAdapter` 会根据 `Handler`来调用真正的处理器开处理请求,并处理相应的业务逻辑。 10 | 5. 处理器处理完业务后,会返回一个 `ModelAndView` 对象,`Model` 是返回的数据对象,`View` 是个逻辑上的 `View`。 11 | 6. `ViewResolver` 会根据逻辑 `View` 查找实际的 `View`。 12 | 7. `DispaterServlet` 把返回的 `Model` 传给 `View`(视图渲染)。 13 | 8. 把 `View` 返回给请求者(浏览器) -------------------------------------------------------------------------------- /Java/classify/sys/操作系统内存管理方式.md: -------------------------------------------------------------------------------- 1 | 2 | ## 分页管理 3 | 4 | 分页存储管理是将一个进程的逻辑地址空间分成若干个大小相等的片,称为页面或页,并为各页加以编号,从0开始,如第0页、第1页等。相应地,也把内存空间分成与页面相同大小的若干个存储块,称为(物理)块或页框(frame),也同样为它们加以编号,如0#块、1#块等等。在为进程分配内存时,以块为单位将进程中的若干个页分别装入到多个可以不相邻接的物理块中。由于进程的最后一页经常装不满一块而形成了不可利用的碎片,称之为“页内碎片”。 5 | 6 | **优缺点**:**没有外部碎片,内存利用率高。但各页中内容没有关联,不利于编程和共享**。 7 | 8 | ## 分段管理 9 | 10 | 程序通过分段(segmentation)划分为多个模块,如代码段、数据段、共享段。内存每段的大小都匹配程序段,不会产生内部碎片。 11 | **优缺点**: 可以针对不同类型的段采取不同的保护。 可以按段为单位来进行共享,包括通过动态链接进行代码共享。 **不会产生内部碎片,但会产生外部碎片,内存利用率比分页低**。 12 | 13 | ## 段页式管理 14 | 15 | 一个进程中所包含的具有独立逻辑功能的程序或数据仍被划分为段,并有各自的段号s。这反映相继承了段式管理的特征。其次,对于段s中的程序或数据,则按照一定的大小将其划分为不同的页。和页式系统一样,最后不足一页的部分仍占一页。这反映了段页式管理中的页式特征。从而,段页式管理时的进程的虚拟地址空间中的虚拟地址由三部分组成:即段号s,页号P和页内相对地址d。虚拟空间的最小单位是页而不是段,从而内存可用区也就被划分成为若干个大小相等的页面,且每段所拥有的程序和数据在内存中可以分开存放。分段的大小也不再受内存可用区的限制。 16 | **优缺点:**既有具有独立逻辑功能的段,又以大小相同的页为内存分配单位进而不会产生外部碎片。但仍会有内部碎片。 17 | 18 | [操作系统如管理内存](https://blog.csdn.net/hguisu/article/details/5713164) 19 | 20 | ## 内存分配过程 21 | 22 | [https://blog.csdn.net/edonlii/article/details/22601043](https://blog.csdn.net/edonlii/article/details/22601043) -------------------------------------------------------------------------------- /Java/classify/sys/死锁.md: -------------------------------------------------------------------------------- 1 | ## 死锁的条件 2 | 3 | 所以:死锁的条件? 4 | 5 | - **互斥条件**:该资源任意一个时刻只由一个线程占用。(同一时刻,这个碗是我的,你不能碰) 6 | - **请求与保持条件**:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(我拿着这个碗一直不放) 7 | - **不剥夺条件**:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。(我碗中的饭没吃完,你不能抢,释放权是我自己的,我想什么时候放就什么时候放) 8 | - **循环等待条件**:若干进程之间形成一种头尾相接的循环等待资源关系。(我拿了A碗,你拿了B碗,但是我还想要你的B碗,你还想我的A碗)比喻成棒棒糖也阔以。 9 | 10 | 面试官:所以解决死锁的办法是? 11 | 12 | 我:好的,没毛病 13 | 14 | - 预防死锁: 15 | - **资源一次性分配**:破坏请求和保持条件。 16 | - **可剥夺资源**:当进程新申请的资源不满足时,释放已经分配的资源。破坏不可剥夺条件 17 | - **资源有序分配**:系统给进程编号,按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。 18 | - 避免死锁:银行家算法:分配资源前先评估风险,会不会在分配后导致死锁。 即分配给一个进程资源的时候,该进程能否全部返还占用的资源。 19 | - 检测死锁:建立资源分配表和进程等待表。 20 | - 解除死锁:可以直接撤销死锁进程,或撤销代价最小的进程。 -------------------------------------------------------------------------------- /Java/classify/sys/系统进程调度.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | - FCFS(先来先服务,队列实现,非抢占的):先请求CPU的进程先分配到CPU 4 | - SJF(最短作业优先调度算法):平均等待时间最短,但难以知道下一个CPU区间长度 5 | - 优先级调度算法(可以是抢占的,也可以是非抢占的):优先级越高越先分配到CPU,相同优先级先到先服务,存在的主要问题是:低优先级进程无穷等待CPU,会导致无穷阻塞或饥饿;解决方案:老化 6 | - 时间片轮转调度算法(可抢占的):队列中没有进程被分配超过一个时间片的CPU时间,除非它是唯一可运行的进程。如果进程的CPU区间超过了一个时间片,那么该进程就被抢占并放回就绪队列。 7 | - 多级队列调度算法:将就绪队列分成多个独立的队列,每个队列都有自己的调度算法,队列之间采用固定优先级抢占调度。其中,一个进程根据自身属性被永久地分配到一个队列中。 8 | - 多级反馈队列调度算法:与多级队列调度算法相比,其允许进程在队列之间移动:若进程使用过多CPU时间,那么它会被转移到更低的优先级队列;在较低优先级队列等待时间过长的进程会被转移到更高优先级队列,以防止饥饿发生。 -------------------------------------------------------------------------------- /Java/classify/sys/虚拟内存.md: -------------------------------------------------------------------------------- 1 | 2 | > 为什么操作系统引进虚拟内存?说白了,直接让一个进程全部放在主内存当中,太占用内存啦,以前的计算机的内存可没有那么大,并且程序也很多。所以在进程和主内存之间存在虚拟内存。可以[参考](https://draveness.me/whys-the-design-os-virtual-memory/) 3 | 4 | 5 | 虚拟内存允许执行进程不必完全在内存中。虚拟内存的基本思想是: 6 | - 每个进程拥有独立的地址空间,这个空间被分为大小相等的多个块,称为页(Page),每个页都是一段连续的地址。 7 | - 这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。 8 | - 当程序引用到一部分在物理内存中的地址空间时,由硬件立刻进行必要的映射;当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的命令。这样,对于进程而言,逻辑上似乎有很大的内存空间,实际上其中一部分对应物理内存上的一块(称为帧,通常页和帧大小相等),还有一些没加载在内存中的对应在硬盘上。 9 | 10 | 举个小例子: 11 | 吃饭。 12 | 13 | 比如你和同学去聚餐,根据菜谱点了一堆的菜,有汤,有肉,有素材等。 大可不必一下子全做完一起送过来(如果桌子不给力,比较小,并且时间成本,饭菜都凉了等),比如,同学想吃肉哈?不想先喝汤,那就把肉类端上来,对吧?等同学又想喝汤了,就把汤做一下端过来。 14 | -------------------------------------------------------------------------------- /Java/classify/sys/进程与线程.md: -------------------------------------------------------------------------------- 1 | # 线程与进程的区别 2 | 面试官:说一下线程与进程的区别 3 | 4 | 我:好的,如下: 5 | 6 | - 根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位 7 | 8 | - 资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。 9 | 10 | - 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的 11 | 12 | - 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。 13 | 14 | - 与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程 15 | 16 | 举例子:比如,main函数启动了JVM进程,同时main就是其中的线程,并且启动了JVM进程,那么还有垃圾回收等线程。 17 | 18 | 或者这样的例子:做个简单的比喻:进程=火车,线程=车厢 19 | 20 | - 线程在进程下行进(单纯的车厢无法运行) 21 | - 一个进程可以包含多个线程(一辆火车可以有多个车厢) 22 | - 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘) 23 | - 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易) 24 | - 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源) 25 | - 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢) 26 | - 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上) 27 | - 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁" 28 | - 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量” 29 | 30 | 31 | 32 | --- 33 | 34 | ## 协程 35 | 36 | 协程: 37 | 38 | 协程是一种用户态的轻量级线程, 我们的server的发展如下:IO密集型应用:多进程 -> 多线程 ->事件驱动 ->协程 39 | 40 | 协程拥有自己的寄存器上下文和栈. 协程调度切换时,将寄存器上下文和栈保存到其他地方,然后去做其他工作,当你的IO解除之后切回原来的状态,恢复先前保存的寄存器上下文和栈。 41 | 42 | 43 | 优点: 44 | 45 | - 跨平台 46 | - 无需线程上下文切换的开销 47 | - 无需原子操作锁定及同步的开销 48 | - 方便切换控制流,简化编程模型 49 | - 高并发+高扩展行+低成本: 一个CPU支持上万的协程都不是问题,所以很适合用于高并发处理 50 | 51 | 缺点: 52 | 53 | - 无法利用多核资源:协程的本质是一个单线程,它不能同时将单个CPU的多个核作用上,协程需要和进程配合才能运行在多CPU上. 54 | - 进行阻塞(Blocking)操作会阻塞到整个程序; 这一点和事件驱动一样,可以使用异步IO操作来解决. -------------------------------------------------------------------------------- /Java/classify/sys/进程间通信.md: -------------------------------------------------------------------------------- 1 | 2 | # 进程间通信 3 | 4 | > 首先要知道进程之间为什么要通信。 进程是一个独立的资源分配单元,不同进程(这里所说的进程通常指的是用户进程)之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源(例如打开的文件描述符)。 但是,进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程之间的通信。 5 | 6 | ## 进程通信的目的 7 | 8 | - 数据传输:一个进程需要将它的数据发送给另一个进程。 9 | - 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。 10 | - 资源共享:多个进程之间共享同样的资源。为了做到这一点,需要内核提供互斥和同步机制。 11 | - 进程控制:有些进程希望完全控制另一个进程的执行(如 Debug 进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。 12 | 13 | 14 | ## 通信方式 15 | 16 | ### 管道 17 | 18 | - 普通管道:通常有两种限制,一是单工,只能单向传输;二是只能在父子或者兄弟进程间使用. 19 | - 流管道:去除了第一种限制,为半双工,只能在父子或兄弟进程间使用,可以双向传输. 20 | - 命名管道:去除了第二种限制,可以在许多并不相关的进程之间进行通讯. 21 | 22 | ### 信号量 23 | 24 | - 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。 25 | - 它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。 26 | - 因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 27 | 28 | ### 消息队列 29 | 30 | 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 31 | 32 | ### 信号 33 | 34 | - 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。 35 | 36 | ### 共享内存 37 | 38 | 共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。 39 | 40 | #### 共享内存的实现(mmap) 41 | mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。 42 | 43 | #### 系统调用mmap共享内存的两种方式 44 | - 使用普通文件提供的内存映射:适用于任何进程之间; 45 | - 使用特殊文件提供匿名内存映射:适用于具有亲缘关系的进程之间; 由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区域进行通信了。 46 | 47 | ### socket 48 | 49 | 这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。 50 | 51 | ## 线程之间的通信 52 | 53 | 1. 锁机制:包括互斥锁、条件变量、读写锁 54 | 2. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量 55 | 3. 信号机制(Signal):类似进程间的信号处理 56 | 57 | > 线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。 -------------------------------------------------------------------------------- /Java/classify/sys/页面置换算法.md: -------------------------------------------------------------------------------- 1 | > 地址映射过程中,若在页面中发现所要访问的页面不再内存中,则产生缺页中断。当发生缺页中断时操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。常见的置换算法有: 2 | 3 | - 最佳置换算法(OPT):所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。 4 | - 先进先出置换算法(FIFO):选择换出的页面是最先进入的页面。该算法会将那些经常被访问的页面换出,导致缺页率升高。 5 | - 第二次机会算法:当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。 6 | - 最近最久未使用(LRU)算法:为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。 7 | 8 | -------------------------------------------------------------------------------- /Java/classify/thread/AQS.md: -------------------------------------------------------------------------------- 1 | # AQS 2 | 3 | > 这个先留着,这个内容有点多,待我这周结束之后来写。 4 | 5 | 面试官:AQS是什么? 6 | 7 | ## 原理 8 | 我:AbstractQueuedSynchronizer抽象同步队列。说白了,就是个FIFO双向队列。其内部通过节点head和tail记录对首和队尾元素。 9 | 10 | **state**:在AQS中维持了一个单一的状态信息state,可以通过**getState**、**setState**、**compareAndSetState**函数修改其值。 11 | 12 | - **ReentrantLock**:state可以用来表示当前线程获取锁的可重入次数。 13 | 14 | - **ReentrantReadWriteLock**:state的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数。 15 | - **semaphore**:state用来表示当前可用信号的个数。 16 | - **CountDownlatch**:state用来表示计数器当前的值。 17 | 18 | 对于AQS来讲,线程同步的关键是对状态值**state**进行操作。 19 | 20 | ## 方法 21 | 22 | 在独占方式下获取和释放资源使用的方法: 23 | 24 | ```java 25 | void acquire(int arg); 26 | void acquireInterruptibly(int arg); 27 | boolean release(int arg); 28 | ``` 29 | 30 | 在共享方式获取和释放资源使用的方法: 31 | 32 | ```java 33 | void acquireShared(int arg); 34 | void acquireSharedInterruptibly(int arg); 35 | boolean releaseShared(int arg); 36 | ``` 37 | 38 | 使用独占方式获取的资源是与**具体线程绑定**的,就是说如果一个线程获取到了资源,就会标记是这个线程获取到了,其他线程再尝试操作state获取资源时会**发现当前该资源不是自己持有的**,就会在**获取失败后被阻塞**。(比如ReentrantLock) 39 | 40 | 对应的共享方式的资源与具体线程是不相关的,当多个线程去请求资源时通过CAS方式竞争获取资源,当一个线程获取到了资源后,另外一个线程再次去获取时如果当前资源还能满足它的资源,则当前线程只需要使用CAS方式进行获取即可。(比如semaphore) 41 | 42 | 看一下**acquire**方法: 43 | 44 | ```java 45 | // tryAcquire 具体的子类去实现,并维护state的状态 46 | public final void acquire(int arg) { 47 | if (!tryAcquire(arg) && 48 | acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 如果失败标记状态,入队 49 | selfInterrupt(); 50 | } 51 | ``` 52 | 53 | 看一下**release**方法: 54 | 55 | ```java 56 | // tryRelease 具体的子类是实现,并设置state的状态 57 | public final boolean release(int arg) { 58 | if (tryRelease(arg)) { 59 | Node h = head; 60 | if (h != null && h.waitStatus != 0) 61 | unparkSuccessor(h); // 调用unpark唤醒队列的线程,并调用tryAcquire尝试,看是否需要,如果不需要,继续挂起 62 | return true; 63 | } 64 | return false; 65 | } 66 | ``` 67 | 68 | **acquireShared和releaseShared**和上面的方法的思想差不多 69 | 70 | -------------------------------------------------------------------------------- /Java/classify/thread/CountDownLatch.md: -------------------------------------------------------------------------------- 1 | # CountDownLatch 2 | 3 | > 我们经常在主线程中开启多个线程去并行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。知道你们要说join,但是join不够灵活。 4 | 5 | 面试官:讲讲CountDownLatch原理 6 | 7 | 我:我试试 8 | 9 | ## 原理 10 | 11 | 首先状态变量state:**state用来表示计数器当前的值**,当线程调用CountDownLatch对象的await方法后, 当前线程会被阻塞,直到下面的情况之一发生才返回:当所有线程都调用了CountDownLatch对象的countDown方法后,也就是**计数器的值为0**时:其他线程调用了**当前线程的interrupt()方法中断了当前线程**,当前线程就会抛出InterruptedException异常。 12 | 13 | ## 方法 14 | 15 | 所以,我们看一下await方法: 16 | 17 | ```java 18 | public final void acquireSharedInterruptibly(int arg) 19 | throws InterruptedException { 20 | if (Thread.interrupted()) // 线程可中断 21 | throw new InterruptedException(); 22 | if (tryAcquireShared(arg) < 0) // 如果等于-1, 说明还在挂起 23 | doAcquireSharedInterruptibly(arg); 24 | } 25 | protected int tryAcquireShared(int acquires) { 26 | return (getState() == 0) ? 1 : -1; // 如果为0,则返回1,不为0,则返回-1 27 | } 28 | ``` 29 | 30 | 看一下countDown方法: 31 | 32 | ```java 33 | public final boolean releaseShared(int arg) { 34 | if (tryReleaseShared(arg)) { 35 | doReleaseShared(); // 激活被阻塞的线程 36 | return true; 37 | } 38 | return false; 39 | } 40 | protected boolean tryReleaseShared(int releases) { // 尝试释放锁 41 | // Decrement count; signal when transition to zero 42 | for (;;) { 43 | int c = getState(); 44 | if (c == 0) 45 | return false; // 如果等于0, 返回false,不用释放 46 | int nextc = c-1; 47 | if (compareAndSetState(c, nextc)) // 更新state 48 | return nextc == 0; // nextc如果等于0了,说明资源释放成功,但是不管成功与否,都会退出循环 49 | // 并且去激活被await阻塞的线程 50 | } 51 | } 52 | ``` 53 | 54 | -------------------------------------------------------------------------------- /Java/classify/thread/JMM内存模型.md: -------------------------------------------------------------------------------- 1 | ![volatile保证内存可见性和避免重排-WDKMZd](https://gitee.com/dreamcater/blog-img/raw/master/uPic/volatile保证内存可见性和避免重排-WDKMZd.png) 2 | 3 | 4 | -------------------------------------------------------------------------------- /Java/classify/thread/死锁.md: -------------------------------------------------------------------------------- 1 | # 死锁 2 | 3 | 面试官:知道死锁嘛? 4 | 5 | 我:知道,那我就谈一下Java的死锁吧,其实原理都一样。 6 | 7 | 先看个例子: 8 | 9 | ```java 10 | public class Test { 11 | private static Object res1 = new Object(); 12 | private static Object res2 = new Object(); 13 | 14 | public static void main(String[] args) { 15 | new Thread(() -> { 16 | synchronized (res1) { 17 | System.out.println(Thread.currentThread().getName() + " res1"); 18 | // 延迟一下, 确保B拿到了res2 19 | try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } 20 | synchronized (res2) { 21 | System.out.println(Thread.currentThread().getName() + " res2"); 22 | } 23 | } 24 | }, "ThreadA").start(); 25 | 26 | new Thread(() -> { 27 | synchronized (res2) { 28 | System.out.println(Thread.currentThread().getName() + " res2"); 29 | // 延迟一下,确保A拿到了res1 30 | synchronized (res1) { 31 | System.out.println(Thread.currentThread().getName() + " res1"); 32 | } 33 | } 34 | }, "ThreadB").start(); 35 | } 36 | } 37 | ``` 38 | 39 | ## 死锁的条件 40 | 41 | 所以:死锁的条件? 42 | 43 | - **互斥条件**:该资源任意一个时刻只由一个线程占用。(同一时刻,这个碗是我的,你不能碰) 44 | - **请求与保持条件**:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(我拿着这个碗一直不放) 45 | - **不剥夺条件**:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。(我碗中的饭没吃完,你不能抢,释放权是我自己的,我想什么时候放就什么时候放) 46 | - **循环等待条件**:若干进程之间形成一种头尾相接的循环等待资源关系。(我拿了A碗,你拿了B碗,但是我还想要你的B碗,你还想我的A碗)比喻成棒棒糖也阔以。 47 | 48 | 面试官:所以解决死锁的办法是? 49 | 50 | 我:好的,没毛病 51 | 52 | - 预防死锁: 53 | - **资源一次性分配**:破坏请求和保持条件。 54 | - **可剥夺资源**:当进程新申请的资源不满足时,释放已经分配的资源。破坏不可剥夺条件 55 | - **资源有序分配**:系统给进程编号,按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。 56 | - 避免死锁:银行家算法:分配资源前先评估风险,会不会在分配后导致死锁。 即分配给一个进程资源的时候,该进程能否全部返还占用的资源。 57 | - 检测死锁:建立资源分配表和进程等待表。 58 | - 解除死锁:可以直接撤销死锁进程,或撤销代价最小的进程。 59 | 60 | ## 找死锁的步骤 61 | 62 | 所以:找死锁的步骤: 63 | 64 | 1. 我们通过jps确定当前执行任务的进程号 65 | 66 | 2. 然后执行jstack命令查看当前进程堆栈信息 67 | 68 | 3. 然后将会看到`Found a total of 1 deadlock` 69 | 70 | -------------------------------------------------------------------------------- /Java/crazy/README.md: -------------------------------------------------------------------------------- 1 | - **个人吐血系列-总结Java基础**: [本地阅读](Java基础.md)->[掘金阅读](https://juejin.im/post/5e7e0615f265da795568754b) 2 | - **个人吐血系列-总结Java集合**: [本地阅读](Java集合.md)->[掘金阅读](https://juejin.im/post/5e801e29e51d45470b4fce1c) 3 | - **个人吐血系列-总结Java多线程**: [本地阅读](Java多线程)->[掘金阅读-1](https://juejin.im/post/5e7e0e4ce51d4546cd2fcc7c) [掘金阅读-2](https://juejin.im/post/5e7e0e4ce51d4546cd2fcc7c) 4 | - **个人吐血系列-总结JVM**: [本地阅读](JVM.md)->[掘金阅读](https://juejin.im/post/5e8344486fb9a03c786ef885) 5 | - **个人吐血系列-总结Spring**: [本地阅读](Spring.md)->[掘金阅读](https://juejin.im/post/5e846a4a6fb9a03c42378bc1) 6 | - **个人吐血系列-总结Mybatis**: [本地阅读](Mybatis.md)->[掘金阅读](https://juejin.im/post/5e889b196fb9a03c875c8f50) 7 | - **个人吐血系列-总结MySQL**: [本地阅读](MySQL.md)->[掘金阅读](https://juejin.im/post/5e94116551882573b86f970f) 8 | - **个人吐血系列-总结Redis**: [本地阅读](Redis.md)->[掘金阅读](https://juejin.im/post/5e9d6a9ff265da47e34c0e8a) 9 | - **个人吐血系列-总结计算机网络**: [本地阅读](计算机网络.md)->[掘金阅读](https://juejin.im/post/5ea383c251882573716ab496) 10 | - **个人吐血系列-Dubbo**: [本地阅读](Dubbo.md)->[掘金阅读](https://juejin.im/post/5eb11127f265da7bb46bce26) 11 | - **个人吐血系列-RocketMQ**: [本地阅读](RocketMQ)-> [掘金阅读](https://juejin.im/post/5ecf1f716fb9a047f338b972) 12 | 13 | -------------------------------------------------------------------------------- /Java/jdk/InvocationHandler源码.md: -------------------------------------------------------------------------------- 1 | > 代理经常用到,回调方法,很多框架也的到。 2 | > 3 | > 不过这仅仅是动态代理 4 | 5 | ## 类 6 | 7 | ```java 8 | // 接口 9 | public interface InvocationHandler 10 | ``` 11 | 12 | ## invoke 13 | 14 | ```java 15 | // proxy:调用方法的代理实例 16 | // method:代理实例的接口方法 17 | // args:参数 18 | public Object invoke(Object proxy, Method method, Object[] args) 19 | throws Throwable; 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /Java/jdk/README.md: -------------------------------------------------------------------------------- 1 | > 说明:JDK1.8版本,我只看了一些重要且部分的源码,目前害没有看完,JUC部分的源码一定要看。看源码能带来很多思维上的好处,闲的时候多看看,能发现其中的原理,不分前后,都挺重要的。 2 | 3 | - [Object](Object源码.md) 4 | - [Integer](Integer源码.md) 5 | - [String](String源码.md) 6 | - [StringBuilder](StringBuilder源码.md) 7 | - [Thread](Thread源码.md) 8 | - [ThreadLocal](ThreadLocal源码.md) 9 | - [WeakReference](WeakReference源码.md) 10 | - [Proxy](Proxy源码.md) 11 | - [Class](Class源码.md) 12 | - [InvocationHandler](InvocationHandler源码.md) 13 | - [ArrayList](ArrayList源码.md) 14 | - [LinkedList](LinkedList源码.md) 15 | - [HashSet-HashMap](HashSet-HashMap源码.md) 16 | - [LinkedHashSet](LinkedHashSet源码.md) 17 | - [Stack-Queue](Stack-Queue源码.md) 18 | - [PriorityQueue](PriorityQueue源码.md) 19 | - [TreeSet-TreeMap](TreeSet-TreeMap源码.md) 20 | 21 | 22 | -------------------------------------------------------------------------------- /Java/jdk/SoftReference源码.md: -------------------------------------------------------------------------------- 1 | ## 类 2 | 3 | ```java 4 | // 继承了Reference 5 | public class SoftReference extends Reference 6 | ``` 7 | 8 | ## 参数 9 | 10 | ```java 11 | // 时间戳时钟,由垃圾收集器更新 12 | static private long clock; 13 | // 通过每次调用get方法更新时间戳。当选择要清除的软引用时,VM可以使用这个字段,但这不是必需的。 14 | private long timestamp; 15 | ``` 16 | 17 | ## 方法 18 | 19 | ### get() 20 | 21 | ```java 22 | // 返回该引用对象的引用。 23 | // 如果该引用对象已经被程序或垃圾收集器清除,则该方法返回null。 24 | public T get() { 25 | T o = super.get(); 26 | if (o != null && this.timestamp != clock) 27 | this.timestamp = clock; 28 | return o; 29 | } 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /Java/jdk/ThreadLocal源码.md: -------------------------------------------------------------------------------- 1 | ## 类 2 | 3 | ```java 4 | // 什么也没有,就有个泛型 5 | public class ThreadLocal 6 | ``` 7 | 8 | 但是Entry 9 | 10 | ```java 11 | // 12 | static class Entry extends WeakReference> { 13 | /** The value associated with this ThreadLocal. */ 14 | Object value; 15 | 16 | Entry(ThreadLocal k, Object v) { 17 | super(k); // key super了, 弱引用 18 | value = v; 19 | } 20 | } 21 | ``` 22 | 23 | 24 | 25 | ## 变量 26 | 27 | ```java 28 | // hashcode 是指定一个固定值,开始自增 29 | private static AtomicInteger nextHashCode = 30 | new AtomicInteger(); 31 | // 连续生成的哈希码之间的区别——将隐式顺序线程本地id转换为接近最优扩散的倍增哈希值,用于两级幂的表。 32 | private static final int HASH_INCREMENT = 0x61c88647; 33 | ``` 34 | 35 | ## 方法 36 | 37 | ### get() 38 | 39 | ```java 40 | // 依托Thread 41 | public T get() { 42 | // 获取线程 43 | Thread t = Thread.currentThread(); 44 | // 得到定制的map 45 | ThreadLocalMap map = getMap(t); 46 | if (map != null) { 47 | ThreadLocalMap.Entry e = map.getEntry(this); 48 | if (e != null) { 49 | @SuppressWarnings("unchecked") 50 | T result = (T)e.value; 51 | return result; 52 | } 53 | } 54 | return setInitialValue(); 55 | } 56 | ``` 57 | 58 | ### set() 59 | 60 | ```java 61 | // 依然是map 62 | public void set(T value) { 63 | // 获取线程 64 | Thread t = Thread.currentThread(); 65 | // 得到map 66 | ThreadLocalMap map = getMap(t); 67 | if (map != null) 68 | // set 69 | map.set(this, value); 70 | else 71 | // 没有就创建 72 | createMap(t, value); 73 | } 74 | ``` 75 | 76 | -------------------------------------------------------------------------------- /Java/jdk/WeakReference源码.md: -------------------------------------------------------------------------------- 1 | ## 类 2 | 3 | ```java 4 | 5 | // 弱引用对象,它不阻止将其引用变为可结束、结束,然后再回收。 6 | // 弱引用最常用于实现规范化映射。 7 | public class WeakReference extends Reference 8 | ``` 9 | 10 | -------------------------------------------------------------------------------- /Java/mianjing/README.md: -------------------------------------------------------------------------------- 1 | 个人建议: 2 | 3 | > 首先自己先有个知识点体系,不管是思维导图也好,还是大纲也好。 4 | > 那么其次看大量的面经,可以将每个面经当作一次面试,查看自己是否能讲出来,查漏补缺! 5 | > 最后,不断沉淀即可。祝好运!!! 6 | 7 | 文档说明: 8 | 9 | > 秋招后期,有些面试我没有总结,大同小异,仅供参考,可能自己想偷懒了,还有一些有2面3面的,我也懒的总结了,工作量不小,不过大概差不多,面经就是需要复盘,查漏补缺,面试官问的大部分跟你的简历有关系,从简历问的又深又广,不仅仅局限于简历哦,提示:这里的面经是关于Java后端的。 10 | 11 | ## 牛客网面经 12 | 13 | - [拼多多所有问题汇总](拼多多所有问题汇总.md) 14 | - [京东所有问题汇总](京东所有问题汇总.md) 15 | - [美团所有问题汇总](美团所有问题汇总.md) 16 | - [阿里所有问题汇总](阿里所有问题汇总.md) 17 | - [百度所有问题汇总](百度所有问题汇总.md) 18 | - [字节所有问题汇总](字节所有问题汇总.md) 19 | - [招银所有问题汇总](招银所有问题汇总.md) 20 | - [网易所有问题汇总](网易所有问题汇总.md) 21 | - [远景所有问题汇总](远景所有问题汇总.md) 22 | - [猿辅导所有问题汇总](猿辅导所有问题汇总.md) 23 | 24 | ## 自己 25 | 26 | - [shein](myshein.md) 27 | - [京东](my京东.md) 28 | - [作业帮](my作业帮.md) 29 | - [字节](my字节.md) 30 | - [招银](my招银.md) 31 | - [猿辅导](my猿辅导.md) 32 | - [用友](my用友.md) 33 | - [百度](my百度.md) 34 | - [网易](my网易.md) 35 | - [腾讯](my腾讯.md) 36 | - [贝壳](my贝壳.md) -------------------------------------------------------------------------------- /Java/mianjing/myshein.md: -------------------------------------------------------------------------------- 1 | 一面 2 | 时间:8.14 14:30 时长:40 3 | 4 | 1. 自我介绍 5 | 2. 介绍项目(20min) 6 | 3. 为什么用Dubbo 7 | 4. 为什么用JWT 8 | 5. 为什么用Redis 9 | 6. 为什么用RocketMQ 10 | 7. RocketMQ事务模型 11 | 7. 为什么用分布式锁 12 | 8. MyISAM和InnoDB的区别 13 | 9. B和B+树的区别 14 | 10. 什么会锁整张表 15 | 11. Explain都有啥玩意 16 | 12. 如果是你,如何慢查询优化 17 | 18 | 二面 19 | 时间:8.24 19:50 时长:25min 20 | 21 | 1. 自我介绍 22 | 2. 简单说一下微服务 23 | 3. 为什么用索引 24 | 4. 什么情况下用索引 25 | 5. 家庭情况 26 | 6. 反问:如何看待消息队列产生 -------------------------------------------------------------------------------- /Java/mianjing/my京东.md: -------------------------------------------------------------------------------- 1 | 2 | 第一个offer,虽然我记录的比较晚。 3 | 因为我在等字节的意向书 4 | 5 | 时间:7月14日 10:30 时长:40min 6 | 7 | 小插曲:和面试官聊了北京和成都的自我感受 8 | 9 | 1. String、StringBuilder、StringBuffer的区别 10 | 2. String不可变的优点 11 | 3. 装箱和拆箱的区别 12 | 4. Integer和int有哪些不同 13 | 5. 重载和重写的区别 14 | 6. 接口和抽象类的区别 15 | 7. ArrayList和LinkedList的区别 16 | 8. HashTable和HashMap的区别 17 | 9. 有哪些同步方法,讲一下 18 | 10. 线程的创建方式 19 | 11. 线程池的原理 20 | 12. 简单说一下Spring 21 | 13. 简单说一下Dubbo 22 | 14. MQ的特点 23 | 15. 为什么用RocketMQ 24 | 16. 说一下Redis的分布式锁 25 | 17. 讲一下RedLock算法 26 | 18. 解决消息幂等的方案 27 | 19. SSO是什么? 28 | 20. 说一下Token和Redis交互的过程 29 | 21. Dubbo指定特定的客户端方案 30 | 22. Dubbo的负载均衡都有哪些 31 | 23. 实现一下其中一个负载均衡 32 | 33 | 34 | 时间:7月16日 10:00 时长:24min 35 | 36 | 1. 自我介绍 37 | 2. HashTable和HashMap的区别 38 | 3. 项目主要做什么 39 | 4. 班车到点更改状态的业务逻辑 40 | 5. 未来发展 41 | 6. 反问 42 | 43 | 时间:7月21日 14:20 时长:20min 44 | 45 | hr 46 | 47 | 1. 自我介绍 48 | 2. 你觉得自己有什么吸引人的地方 49 | 3. 手里还有其他的offer嘛? 50 | 4. 对成都和北京有想法? 51 | 5. 对自己将来有什么打算? 52 | 6. 反问 53 | 54 | 时间:7月31日 17:30 55 | 56 | 已获offer -------------------------------------------------------------------------------- /Java/mianjing/my作业帮.md: -------------------------------------------------------------------------------- 1 | 一面 2 | 时间:8.27 19:00 时长:45min 3 | 4 | 1. 自我介绍 5 | 2. 谈一谈Java的面向对象的特性 6 | 3. 谈一谈多态 7 | 4. 讲一讲并发的同步方法 8 | 5. 讲一下synchroniezd和lock的区别 9 | 6. 什么是乐观锁和悲观锁 10 | 7. juc都有哪些? 11 | 8. AQS的底层原理 12 | 9. MySQL的索引是什么? 13 | 10. 如果优化慢查询 14 | 11. 如何优化数据库 15 | 12. jdbc的线程池 16 | 13. MySQL,Mongodb和ES各自的场景 17 | 14. 讲一下缓存穿透,缓存雪崩以及方案 18 | 15. 谈一谈SpringIOC和AOP 19 | 16. 控制反转前后的优缺点 20 | 17. 谈一谈微服务 21 | 18. 谈一谈中间件 22 | 19. MQ如何的特性 23 | 20. 什么是幂等性,如何解决 24 | 21. 反问 -------------------------------------------------------------------------------- /Java/mianjing/my招银.md: -------------------------------------------------------------------------------- 1 | 时间:6月29日 16:32 时长:20min 2 | 3 | 1. Mybatis的依赖pom包 4 | 2. maven如何解决循环pom依赖 5 | 3. Java的8大基础类型和所占字节 6 | 4. 类加载器 7 | 5. Redis的缓存穿透和缓存雪崩 8 | 6. Redis的key键过期事件 9 | 7. Redis的延迟队列 10 | 8. RocketMQ的消息最终一致性 11 | 9. 分布式事务(cap,2pc,3pc,tcc) 12 | 10. 平时如何学习技术 13 | 11. 未来发展 14 | 15 | 16 | 17 | 时间:7月8日 15.55 时长:45min左右 18 | 19 | 1. 介绍集合 20 | 2. list的并发问题 21 | 3. set是否有序?去重原理?如何有序? 22 | 4. 异常机制 23 | 5. 线程的5大状态 24 | 6. 线程的创建方式 25 | 7. 线程池原理 26 | 8. 谈谈双亲委派机制 27 | 9. 垃圾回收算法介绍一下 28 | 10. 谈谈Spring 29 | 11. 前端发来一条请求,Spring是怎么处理的(我讲了计算机网络的各个步骤和SpringMVC,讲很细) 30 | 12. Redis的OOM溢出原因和解决方案 31 | 13. Redis的缓存穿透 32 | 14. 为什么用RocketMQ,为什么不用其他的MQ? 33 | 15. RocketMQ的分布式事务模型 34 | 16. Dubbo的原理 35 | 17. 反问 36 | 37 | 时间问题,没让我手撕代码(手撕代码紧张的一批 38 | 39 | 40 | 41 | 时间:7月14日 17.40 时长:40min 42 | 43 | 女面试官,我还有这种运气?碰见女面试官!!!! ,简直不要太爽 44 | 45 | 1. 讲一波买姓的由来(居然对我的姓氏这么感兴趣。。。 阔以),,卡卡,讲了10分钟,从元朝baba的讲到现代 46 | 2. 项目的由来? 47 | 3. 为什么要做微服务? 48 | 4. 为什么用Dubbo? 49 | 5. 为什么分为四个服务? 50 | 6. 谈谈Spring cloud 51 | 7. 为什么项目里面自己没有用多线程? 52 | 8. 哪些用了多线程? 53 | 9. 在你遇到什么困难的情况下,为什么用了Redis? 54 | 10. 为什么用RocketMQ? 55 | 11. 为什么不用2pc、3pc、tcc? 56 | 12. 你1个多月做这个项目,代码量不少吧?而且头一次做,怎么做完的?(我熬夜做完的。。,。。。。。。。。。) 57 | 13. 项目上线了吗?用什么方案部署上的?了解热部署吗? 58 | 14. 为什么没有实习? 59 | 15. 项目不问了, 来, 给我讲一下GC的所有垃圾回收器 60 | 16. G1什么版本的时候出来的。。。。。。。。。。 61 | 17. 给我讲讲Redis的五大结构的底层结构,尤其是跳跃表 62 | 18. 有什么想问的?(我今天表现如何?) 63 | 64 | -------------------------------------------------------------------------------- /Java/mianjing/my猿辅导.md: -------------------------------------------------------------------------------- 1 | 一面: 2 | 时间 8.28:15:00 时长50min 3 | 4 | 1. 自我介绍 5 | 2. 介绍一下项目 6 | 3. MySQL的索引 7 | 4. 慢查询优化 8 | 5. Redis的缓存穿透和雪崩 9 | 6. Dubbo的负载均衡策略 10 | 7. 分布式一致性哈希的原理 11 | 8. zk和redis实现分布式锁的原理 12 | 9. MQ的特性 13 | 10. RocketMQ事务消息的模型 14 | 11. 如何维持幂等性 15 | 12. final的关键字的作用 16 | 13. final关键字修饰引用类型,那么在GC有什么特点 17 | 14. 写题:非递归的对称二叉树 18 | -------------------------------------------------------------------------------- /Java/mianjing/my用友.md: -------------------------------------------------------------------------------- 1 | 2 | 时间:7月21日 11:00 时长:35min 3 | 4 | 1. 自我介绍 5 | 2. 介绍一下项目 6 | 3. 讲一下为什么写这个项目,初衷? 7 | 4. 为什么用dubbo 8 | 5. 为什么用MQ 9 | 6. 讲一下分布式事务 10 | 7. 如果下游服务宕机了怎么办? 11 | 8. 讲一下分布式锁 12 | 9. 讲一下RedLock算法 13 | 10. 讲一下zk的分布式锁的实现 14 | 11. 反问 15 | 16 | -------------------------------------------------------------------------------- /Java/mianjing/my网易.md: -------------------------------------------------------------------------------- 1 | 一面 2 | 时间:8.13 14:50 时长:35min 3 | 4 | 5 | 1. 自我介绍 6 | 2. Redis的集群如何管理,如何选举 7 | 3. G1垃圾收集器如何查找块的 8 | 4. Using Index和Using Where的区别 9 | 5. 如何分库分表? 10 | 6. int[10]和int[11]的区别 11 | 7. int[10]和int[10][10]的区别 12 | 8. newarray和anewarray的区别 13 | 9. RocketMQ的消息事务模型原理 14 | 10. RocketMQ的存储为什么那么块? 15 | 11. 熟悉分布式事务嘛?(熟悉这个词语吓到我了,直接说了解) 16 | 12. 谈一谈分布式如何优化 17 | 13. 限流算法都有哪些? 18 | 14. 平时如何学习的? 19 | 20 | 21 | 二面 22 | 时间:8.14 19:20 时长:35min 23 | 24 | 1. 自我介绍 25 | 2. 谈一谈深度学习 26 | 3. 简单聊以下项目 27 | 4. 为什么用分布式事务 28 | 5. 为何选择Java 29 | 6. 如何学习Java 30 | 7. Java都看过哪些源码 31 | 8. 谈一谈HashMap 32 | 9. 谈一谈ConcurrentHashMap 33 | 10. 为何选择网易 34 | 11. JavaSPI机制 35 | 12. Spring SPI机制 36 | 13. 如何是你,你会怎么设计SPI 37 | 14. 怎么学习Dubbo和RocketMQ的 38 | 15. 从源码上讲以下Spring bean的初始化前后所有过程(我没讲循环依赖,可惜了) 39 | 16. AOP源码 40 | 17. Spring事务原理,以及失效 41 | 18. 如何也让受检异常,事务不失效 42 | 19. for和iterator的区别,并且for会出什么错误 43 | 20. 有没有看过iterator的源码 44 | 21. 反问 45 | 46 | -------------------------------------------------------------------------------- /Java/mianjing/my腾讯.md: -------------------------------------------------------------------------------- 1 | 一面 2 | 时间:8.20 16:00 时长:130min 3 | 4 | 当时问我熟悉不熟悉计算机网络和操作系统之类的,我说的是了解。。。 5 | 那边技术栈不用java的,基本问的java不是特别深入 6 | 7 | 8 | 1. hashmap的底层结构 9 | 2. hashmap的一些参数介绍 10 | 3. hashmap在1.8版改进的地方 11 | 4. hashmap什么情况下查询复杂度高 12 | 5. 红黑树的特点 13 | 6. 红黑树的复杂度 14 | 7. 并发安全的map类 15 | 8. 都有哪些排序算法 16 | 9. 介绍快排 17 | 10. 介绍堆排 18 | 11. 手写单例 19 | 12. 谈一谈volatile 20 | 13. 谈一谈synchroinzed 21 | 14. mutex如何实现的?过程是什么? 22 | 15. 如果是你,你如何实现mutex? 23 | 16. 谈一谈gc 24 | 17. JVM如何调优 25 | 18. cap是什么 26 | 19. zab算法 27 | 20. 实现paxos算法的工程还有哪个?raft... 28 | 21. 哪些中间件用了raft? 29 | 22. 为什么zk不用raft? 30 | 23. paxos的有哪些缺点 31 | 24. mongodb的底层结构是什么 32 | 25. mysql宕机了,数据怎么办? 33 | 26. 两个客户端去修改同一个id的字段,mysql会发生什么? 34 | 27. 写一道题:股票 35 | 28. select和epoll的区别 36 | 29. Redis实现分布式锁的过程 37 | 30. 写一道题:翻转序列,求前n项和(我用了个简单方法,但复杂度高,后来面试管提示我用数学方法,他挺好的,就是我太笨了。) 38 | 31. 线程和协程的区别是什么? 39 | 32. 如果是你,你如何设计协程 40 | 33. 反问 41 | 42 | 总结:算法题写的不好,估计是凉了,我都不好意思耽误面试官那么长时间。 43 | 44 | 大概率凉凉 -------------------------------------------------------------------------------- /Java/mianjing/my贝壳.md: -------------------------------------------------------------------------------- 1 | 一面 2 | 时间:8.15 12:00 时长:40min 3 | 4 | 1. 写题:数组中的第K个最大元素 5 | 2. Object所有的方法以及作用 6 | 3. CMS收集器的原理和过程及优缺点 7 | 4. HashMap和ConcurrentHashMap的区别(扯细一点) 8 | 5. 项目(20min,我太熟悉了) 9 | 6. 反问 10 | 11 | 12 | 二面 13 | 时间:8.15 14:00 时长:40min 14 | 15 | 1. 自我介绍 16 | 2. 项目(30min,这里我讲的很熟悉,把所有为什么都讲出来了,可以看我git) 17 | 3. 数据库结构如何优化 18 | 4. Spring IOC流程 19 | 5. 如何解决循环依赖(按照源码讲) 20 | 6. 反问 21 | 22 | 23 | hr 24 | 时间:8.15 15:00 时长:25min 25 | 1. 自我介绍 26 | 2. 怎么学习的 27 | 3. 最近看的哪一本书,感受如何 28 | 4. 别人对你如何评价的 29 | 5. 老家在哪 30 | 6. 如何选城市的? 31 | 7. 面了几家公司,都有哪些offer(这里,永远都不知道如何回答) 32 | 8. 你是怎么看待薪资的 33 | 34 | -------------------------------------------------------------------------------- /Java/mianjing/远景所有问题汇总.md: -------------------------------------------------------------------------------- 1 | ## java 2 | - springboot了解多少:2 3 | - Spring MVC:2 4 | - bean生命周期:1 5 | - 为什么用MQ:1 6 | - JVM内存区域:2 7 | - 双亲委派模型:1 8 | - sychronized:1 9 | - volatile:1 10 | - ArrayList和LinkedList的区别 11 | 12 | 13 | ## Redis 14 | - Redis的数据结构:1 15 | - 缓存穿透:1 16 | - 缓存雪崩:1 17 | - Redis为什么快:1 18 | - IO多路复用:1 19 | 20 | 21 | 22 | ## 计算机网络 23 | - 三次握手:1 24 | - TCP和UDP:2 25 | - nginx负载均衡的几种方式:1 -------------------------------------------------------------------------------- /Java/mind/README.md: -------------------------------------------------------------------------------- 1 | - [总体架构](https://www.processon.com/view/link/5e170217e4b0bcfb733ce553) **这边就不放图了,放图的字体小,放大可能模糊。该图还在持续总结中...** 2 | - [Java常见基础问题](https://www.processon.com/view/link/5e457c32e4b05d0fd4e94cad) **常见的基础问题,这是必须要掌握。** 3 | - [Java常见集合问题]() **还没总结,后续总结...** 4 | - [Java常见多线程问题](https://www.processon.com/view/link/5e4ab92de4b0996b2ba505bf) **常见的多线程问题,也是必须掌握...** 5 | - [JVM常见问题](https://www.processon.com/view/link/5e4c0704e4b00aefb7e74f44) **常见的JVM要掌握的点...** 6 | - [Spring常见问题](https://www.processon.com/view/link/5e846de9e4b07b16dcdb63f0) **常见的Spring面试的问题...** 7 | - [Mybatis常见问题](https://www.processon.com/view/link/5e4e3b7ae4b0369b916b2e71) **常见的Mybatis面试的问题...** 8 | - [MySQL常见问题](https://www.processon.com/view/link/5e9b0cb15653bb1a686e17ea) **常见的MySQL面试的问题...** 9 | - [Redis常见问题](https://www.processon.com/view/link/5ea2da5907912948b0d89a0a) **常见的Redis面试的问题...** 10 | - [计算机网络常见问题](https://www.processon.com/view/link/5eb8c93be401fd16f42b5f77) **常见的计算机网络面试的问题...** 11 | - [Dubbo常见问题](https://www.processon.com/view/link/5eb8c9715653bb6f2aff7c11) **常见的Dubbo的问题...** 12 | - [RocketMQ常见问题](https://www.processon.com/view/link/5ecf208f7d9c08156c6c37e3) **常见的RocketMQ的问题...** 13 | - [微服务班车在线预约系统](https://github.com/DreamCats/SchoolBus) 个人撸的项目是基于微服务架构的班车预约系统,采用**springboot+mybatis+dubbo+rocketmq+mysql+redis等**。当然,该项目也是前后端分离,前端采用比较流行的vue框架。 -------------------------------------------------------------------------------- /Java/mode/README.md: -------------------------------------------------------------------------------- 1 | - [单例](mode/单例模式.md) 2 | - [工厂](mode/工厂模式.md) 3 | - [代理](mode/代理模式.md) 4 | - [模板方法](mode/模板方法模式.md) 5 | - [观察者](mode/观察者模式.md) 6 | - [装饰器](mode/装饰器模式.md) -------------------------------------------------------------------------------- /Java/mode/代理模式.md: -------------------------------------------------------------------------------- 1 | ## 代理模式 2 | 3 | > 所谓代理模式是指客户端并不直接调用实际的对象,而是通过调用代理,来间接的调用实际的对象。 4 | 一般是因为客户端不想直接访问实际的对象,或者访问实际的对象存在困难,因此通过一个代理对象来完成间接的访问。 5 | 6 | ### 静态代理 7 | 8 | > 定义主题 9 | 10 | ```java 11 | interface Subject { 12 | void visit(); 13 | } 14 | ``` 15 | 16 | > 实现subject的两个类 17 | 18 | ```java 19 | class RealSubject implements Subject { 20 | private String name = "dreamcat"; 21 | @Override 22 | public void visit() { 23 | System.out.println(name); 24 | } 25 | } 26 | ``` 27 | > 代理类 28 | ```java 29 | class ProxySubject implements Subject { 30 | 31 | private Subject subject; 32 | 33 | public ProxySubject(Subject subject) { 34 | this.subject = subject; 35 | } 36 | 37 | @Override 38 | public void visit() { 39 | subject.visit(); 40 | } 41 | } 42 | ``` 43 | 缺点: 44 | > 代理类接受一个Subject接口的对象,任何实现该接口的对象,都可以通过代理类进行代理,增加了通用性。 45 | 每一个代理类都必须实现一遍委托类(也就是realsubject)的接口,如果接口增加方法,则代理类也必须跟着修改。 46 | 代理类每一个接口对象对应一个委托对象,如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。 47 | 48 | ### 动态代理 49 | > 动态代理是实现方式,是通过反射来实现的,借助Java自带的java.lang.reflect.Proxy,通过固定的规则生成。 50 | 51 | > 动态代理 52 | 53 | ```java 54 | class DynamicProxy implements InvocationHandler { 55 | 56 | private Object object; 57 | 58 | public DynamicProxy(Object object) { 59 | this.object = object; 60 | } 61 | 62 | @Override 63 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 64 | Object result = method.invoke(object, args); 65 | return result; 66 | } 67 | } 68 | ``` 69 | 70 | ### 测试 71 | ```java 72 | public class ProxyMode { 73 | public static void main(String[] args) { 74 | // 静态代理 75 | ProxySubject proxySubject = new ProxySubject(new RealSubject()); 76 | proxySubject.visit(); 77 | 78 | // 动态代理 79 | RealSubject realSubject = new RealSubject(); 80 | DynamicProxy dynamicProxy = new DynamicProxy(realSubject); 81 | ClassLoader classLoader = realSubject.getClass().getClassLoader(); 82 | Subject subject = (Subject) Proxy.newProxyInstance(classLoader, new Class[]{Subject.class}, dynamicProxy); 83 | subject.visit(); 84 | } 85 | } 86 | ``` 87 | -------------------------------------------------------------------------------- /Java/mode/单例模式.md: -------------------------------------------------------------------------------- 1 | ## 单例模式 2 | > 单例就不多说了 3 | 4 | ### 饿汉 5 | 6 | ```java 7 | public class Singleton { 8 | private static Singleton instance = new Singleton(); 9 | private Singleton(){} 10 | 11 | public static Singleton getInstance() { 12 | return instance; 13 | } 14 | } 15 | ``` 16 | ### 饿汉变种 17 | 18 | ```java 19 | class Singleton { 20 | private static Singleton instance = null; 21 | static { 22 | instance = new Singleton(); 23 | } 24 | private Singleton() {} 25 | 26 | public static Singleton getInstance() { 27 | return instance; 28 | } 29 | } 30 | ``` 31 | 32 | ### 懒汉(线程不安全) 33 | 34 | ```java 35 | class Singleton { 36 | private static Singleton instance = null; 37 | private Singleton(){} 38 | 39 | public static Singleton getInstance() { 40 | if (instance == null){ 41 | instance = new Singleton(); 42 | } 43 | return instance; 44 | 45 | } 46 | } 47 | ``` 48 | 49 | ### 懒汉(线程安全,但消耗资源较为严重) 50 | 51 | ```java 52 | class Singleton { 53 | private static Singleton instance = null; 54 | 55 | private Singleton() { 56 | } 57 | 58 | public static synchronized Singleton getInstance() { 59 | if (instance == null) { 60 | instance = new Singleton(); 61 | } 62 | return instance; 63 | } 64 | } 65 | ``` 66 | 67 | ### 懒汉(线程安全,双重校验) 68 | 69 | ```java 70 | class Singleton { 71 | private static volatile Singleton instance = null; 72 | 73 | private Singleton() { 74 | } 75 | 76 | public static Singleton getInstance() { 77 | if (instance == null) { 78 | synchronized (Singleton.class) { 79 | if (instance == null) { 80 | instance = new Singleton(); 81 | } 82 | } 83 | } 84 | return instance; 85 | } 86 | } 87 | ``` -------------------------------------------------------------------------------- /Java/mode/工厂模式.md: -------------------------------------------------------------------------------- 1 | ## 工厂模式 2 | > 大概意思就不要说了,直接举个例子,看例子讲解就知道是什么意思了。 3 | 4 | ### 举例子 5 | 6 | > 定义一个面条抽象类 7 | 8 | ```java 9 | abstract class INoodles { 10 | /** 11 | * 描述每种面条长什么样的... 12 | */ 13 | public abstract void desc(); 14 | } 15 | ``` 16 | 17 | > 定义一份兰州拉面(具体产品) 18 | 19 | ```java 20 | class LzNoodles extends INoodles { 21 | 22 | @Override 23 | public void desc() { 24 | System.out.println("兰州拉面,成都的好贵 家里的才5-6块钱一碗"); 25 | } 26 | } 27 | ``` 28 | 29 | > 定义一份泡面(程序员挺喜欢的) 30 | 31 | ```java 32 | class PaoNoodles extends INoodles { 33 | 34 | @Override 35 | public void desc() { 36 | System.out.println("泡面可还行..."); 37 | } 38 | } 39 | ``` 40 | 41 | > 不得不说家乡的杂酱面了,好吃得不得了 42 | 43 | ```java 44 | class ZaNoodles extends INoodles { 45 | 46 | @Override 47 | public void desc() { 48 | System.out.println("杂酱面,嗯? 真香..."); 49 | } 50 | } 51 | ``` 52 | 53 | > 重头戏,开面条馆了。(工厂) 54 | 55 | ```java 56 | class SimpleNoodlesFactory { 57 | public static final int TYPE_LZ = 1; // 兰州拉面 58 | public static final int TYPE_PAO = 2; // 泡面撒 59 | public static final int TYPE_ZA = 3; // 杂酱面 60 | // 提供静态方法 61 | public static INoodles createNoodles(int type) { 62 | switch (type) { 63 | case TYPE_LZ:return new LzNoodles(); 64 | case TYPE_PAO:return new PaoNoodles(); 65 | case TYPE_ZA:return new ZaNoodles(); 66 | default:return new ZaNoodles(); 67 | } 68 | } 69 | } 70 | ``` 71 | 72 | > 测试 73 | 74 | ```java 75 | public class FactoryMode { 76 | public static void main(String[] args) { 77 | INoodles noodles = SimpleNoodlesFactory.createNoodles(SimpleNoodlesFactory.TYPE_ZA); 78 | noodles.desc(); 79 | } 80 | } 81 | ``` -------------------------------------------------------------------------------- /Java/mode/模板方法模式.md: -------------------------------------------------------------------------------- 1 | ## 模板方法模式 2 | 3 | > 定义一个操作中的算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的 4 | 5 | 例如: 6 | > 去银行办业务,银行给我们提供了一个模板就是:先取号,排对,办理业务(核心部分我们子类完成),给客服人员评分,完毕。 7 | 这里办理业务是属于子类来完成的,其他的取号,排队,评分则是一个模板。 8 | 9 | 再例如: 10 | > 去餐厅吃饭,餐厅给提供的一套模板就是:先点餐,等待,吃饭(核心部分我们子类完成),买单 11 | 这里吃饭是属于子类来完成的,其他的点餐,买单则是餐厅提供给我们客户的一个模板。 12 | 13 | 所以: 14 | > 实现一些操作时,整体步骤很固定,但是呢。就是其中一小部分容易变,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。 15 | 16 | 17 | ### 举例子 18 | > 银行办理业务抽象类 19 | 20 | ```java 21 | abstract class BankTemplateMethod { 22 | // 1. 取号排队 23 | public void takeNumber() { 24 | System.out.println("取号排队..."); 25 | } 26 | 27 | // 2. 每个子类不同的业务实现,各由子类来实现 28 | abstract void transact(); 29 | 30 | // 3. 评价 31 | public void evaluate() { 32 | System.out.println("反馈评价..."); 33 | } 34 | 35 | public void process() { 36 | takeNumber(); 37 | transact(); 38 | evaluate(); 39 | } 40 | } 41 | ``` 42 | 43 | > 具体的业务,比如取钱 44 | ```java 45 | class DrawMoney extends BankTemplateMethod { 46 | 47 | @Override 48 | void transact() { 49 | System.out.println("我要取款..."); 50 | } 51 | } 52 | ``` 53 | 54 | ### 测试 55 | 56 | ```java 57 | public class TemplateMode { 58 | public static void main(String[] args) { 59 | BankTemplateMethod drawMoney = new DrawMoney(); 60 | drawMoney.process(); 61 | } 62 | } 63 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/1.xml注解bean方式.md: -------------------------------------------------------------------------------- 1 | # 1.xml注解bean方式 2 | 3 | ```xml 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/11.@Value的使用.md: -------------------------------------------------------------------------------- 1 | # @Value的使用 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: value的用法 7 | * @author: mf 8 | * @create: 2020/01/29 21:04 9 | */ 10 | 11 | package org.example.config; 12 | 13 | import org.example.bean.Person; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.PropertySource; 17 | 18 | //使用@Value赋值; 19 | //1、基本数值 20 | //2、可以写SpEL; #{} 21 | //3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值) 22 | @Configuration 23 | @PropertySource(value = {"classpath:/person.properties"}) 24 | public class MainConfigOfValue { 25 | 26 | @Bean 27 | public Person person() { 28 | return new Person(); 29 | } 30 | } 31 | 32 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/12.@PropertySource的使用.md: -------------------------------------------------------------------------------- 1 | # 12.@PropertySource的使用 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: value的用法 7 | * @author: mf 8 | * @create: 2020/01/29 21:04 9 | */ 10 | 11 | package org.example.config; 12 | 13 | import org.example.bean.Person; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.PropertySource; 17 | 18 | //使用@Value赋值; 19 | //1、基本数值 20 | //2、可以写SpEL; #{} 21 | //3、可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值) 22 | @Configuration 23 | @PropertySource(value = {"classpath:/person.properties"}) 24 | public class MainConfigOfValue { 25 | 26 | @Bean 27 | public Person person() { 28 | return new Person(); 29 | } 30 | } 31 | 32 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/18.BeanFactoryPostProcessor.md: -------------------------------------------------------------------------------- 1 | # 18.BeanFactoryPostProcessor 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: MyBeanFactoryPostProcessor 7 | * @author: mf 8 | * @create: 2020/02/02 20:52 9 | */ 10 | 11 | package org.example.ext; 12 | 13 | import org.springframework.beans.BeansException; 14 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 15 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 16 | import org.springframework.stereotype.Component; 17 | 18 | import java.util.Arrays; 19 | 20 | @Component 21 | public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { 22 | 23 | /** 24 | * 重写该方法,目的研究一下它的作用,何时启动 25 | * @param configurableListableBeanFactory 26 | * @throws BeansException 27 | */ 28 | public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { 29 | System.out.println("MyBeanFactoryPostProcessor"); 30 | int count = configurableListableBeanFactory.getBeanDefinitionCount(); 31 | String[] names = configurableListableBeanFactory.getBeanDefinitionNames(); 32 | System.out.println(count+" Bean"); 33 | for (String name : names) { 34 | System.out.println(name); 35 | } 36 | } 37 | } 38 | 39 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/19.BeanDefinitionRegistryPostProcessor.md: -------------------------------------------------------------------------------- 1 | # 19.BeanDefinitionRegistryPostProcessor 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: MyBeanDefinitionRegistryPostProcessor 7 | * @author: mf 8 | * @create: 2020/02/02 23:18 9 | */ 10 | 11 | package org.example.ext; 12 | 13 | import org.springframework.beans.BeansException; 14 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 15 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 16 | import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; 17 | import org.springframework.stereotype.Component; 18 | 19 | @Component 20 | public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor { 21 | public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException { 22 | System.out.println("MyBeanDefinitionRegistryPostProcessor...bean的数量:"+beanDefinitionRegistry.getBeanDefinitionCount()); 23 | } 24 | 25 | //BeanDefinitionRegistry Bean定义信息的保存中心,以后BeanFactory就是按照BeanDefinitionRegistry里面保存的每一个bean定义信息创建bean实例; 26 | public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { 27 | System.out.println("postProcessBeanDefinitionRegistry...bean的数量:"+configurableListableBeanFactory.getBeanDefinitionCount()); 28 | // 自己也可以在这里添加额外的bean 29 | } 30 | } 31 | 32 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/2.xml注册包扫描方式.md: -------------------------------------------------------------------------------- 1 | # 2.xml注册包扫描方式 2 | 3 | ```xml 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/20.ApplicationListener.md: -------------------------------------------------------------------------------- 1 | # 20.ApplicationListener 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: MyApplicationListener 7 | * @author: mf 8 | * @create: 2020/02/02 23:34 9 | */ 10 | 11 | package org.example.ext; 12 | 13 | import org.springframework.context.ApplicationEvent; 14 | import org.springframework.context.ApplicationListener; 15 | import org.springframework.stereotype.Component; 16 | 17 | @Component 18 | public class MyApplicationListener implements ApplicationListener { 19 | //当容器中发布此事件以后,方法触发 20 | public void onApplicationEvent(ApplicationEvent event) { 21 | System.out.println("收到事件:" + event); 22 | } 23 | } 24 | 25 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/3.注解注册bean方式.md: -------------------------------------------------------------------------------- 1 | # 3.注解注册bean方式 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: 配置类 7 | * @author: mf 8 | * @create: 2020/01/26 17:10 9 | */ 10 | 11 | package org.example.config; 12 | 13 | import org.example.bean.Person; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.ComponentScan; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | /** 19 | * 以前是配置文件,现在是配置类... 配置文件==配置类 20 | */ 21 | 22 | @Configuration // 告诉spring,这是一个配置文件 23 | @ComponentScan("org.example") 24 | //@ComponentScan value:指定要扫描的包 25 | //excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件 26 | //includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件 27 | //FilterType.ANNOTATION:按照注解 28 | //FilterType.ASSIGNABLE_TYPE:按照给定的类型; 29 | //FilterType.ASPECTJ:使用ASPECTJ表达式 30 | //FilterType.REGEX:使用正则指定 31 | //FilterType.CUSTOM:使用自定义规则 32 | public class MainConfig { 33 | 34 | // 给容器注册一个bean,类型为返回值的类型,id默认为方法名 35 | @Bean("person") 36 | public Person person01() { 37 | return new Person("class", 18); 38 | } 39 | } 40 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/4.注解注册包扫描方式.md: -------------------------------------------------------------------------------- 1 | # 4.注解注册包扫描方式 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: 配置类 7 | * @author: mf 8 | * @create: 2020/01/26 17:10 9 | */ 10 | 11 | package org.example.config; 12 | 13 | import org.example.bean.Person; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.ComponentScan; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | /** 19 | * 以前是配置文件,现在是配置类... 配置文件==配置类 20 | */ 21 | 22 | @Configuration // 告诉spring,这是一个配置文件 23 | @ComponentScan("org.example") 24 | //@ComponentScan value:指定要扫描的包 25 | //excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件 26 | //includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件 27 | //FilterType.ANNOTATION:按照注解 28 | //FilterType.ASSIGNABLE_TYPE:按照给定的类型; 29 | //FilterType.ASPECTJ:使用ASPECTJ表达式 30 | //FilterType.REGEX:使用正则指定 31 | //FilterType.CUSTOM:使用自定义规则 32 | public class MainConfig { 33 | 34 | // 给容器注册一个bean,类型为返回值的类型,id默认为方法名 35 | @Bean("person") 36 | public Person person01() { 37 | return new Person("class", 18); 38 | } 39 | } 40 | 41 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/5.@Filter的使用.md: -------------------------------------------------------------------------------- 1 | # 5.@Filter的使用 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: 过滤注解 7 | * @author: mf 8 | * @create: 2020/01/28 22:19 9 | */ 10 | 11 | package org.example.config; 12 | 13 | import org.example.service.BookService; 14 | import org.springframework.context.annotation.ComponentScan; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.FilterType; 17 | import org.springframework.stereotype.Controller; 18 | 19 | @Configuration 20 | @ComponentScan(value = "org.example", includeFilters = { 21 | @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BookService.class), 22 | @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class) 23 | }, useDefaultFilters = false) 24 | //@ComponentScan value:指定要扫描的包 25 | //excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件 26 | //includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件 27 | //FilterType.ANNOTATION:按照注解 28 | //FilterType.ASSIGNABLE_TYPE:按照给定的类型; 29 | //FilterType.ASPECTJ:使用ASPECTJ表达式 30 | //FilterType.REGEX:使用正则指定 31 | //FilterType.CUSTOM:使用自定义规则 32 | public class MainConfigOfFilter { 33 | } 34 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/6.@Scope的使用.md: -------------------------------------------------------------------------------- 1 | # 6.@Scope的使用 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: Scope的使用 7 | * @author: mf 8 | * @create: 2020/01/28 22:30 9 | */ 10 | 11 | package org.example.config; 12 | 13 | import org.example.bean.Person; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.Lazy; 17 | import org.springframework.context.annotation.Scope; 18 | 19 | @Configuration 20 | public class MainConfigOfScope { 21 | 22 | //默认是单实例的 23 | /** 24 | * ConfigurableBeanFactory#SCOPE_PROTOTYPE 25 | * @see ConfigurableBeanFactory#SCOPE_SINGLETON 26 | * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request 27 | * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssion 28 | * @return\ 29 | * @Scope:调整作用域 30 | * prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。 31 | * 每次获取的时候才会调用方法创建对象; 32 | * singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。 33 | * 以后每次获取就是直接从容器(map.get())中拿, 34 | * request:同一次请求创建一个实例 35 | * session:同一个session创建一个实例 36 | * 37 | * 懒加载: 38 | * 单实例bean:默认在容器启动的时候创建对象; 39 | * 懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化; 40 | * 41 | */ 42 | // @Scope("prototype") 43 | @Lazy 44 | @Bean 45 | public Person person() { 46 | System.out.println("给容器添加person"); 47 | return new Person("Maifeng", 18); 48 | } 49 | } 50 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/7.@Lazy的使用.md: -------------------------------------------------------------------------------- 1 | # 7.@Lazy的使用 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: Scope的使用 7 | * @author: mf 8 | * @create: 2020/01/28 22:30 9 | */ 10 | 11 | package org.example.config; 12 | 13 | import org.example.bean.Person; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.Lazy; 17 | import org.springframework.context.annotation.Scope; 18 | 19 | @Configuration 20 | public class MainConfigOfScope { 21 | 22 | //默认是单实例的 23 | /** 24 | * ConfigurableBeanFactory#SCOPE_PROTOTYPE 25 | * @see ConfigurableBeanFactory#SCOPE_SINGLETON 26 | * @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST request 27 | * @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION sesssion 28 | * @return\ 29 | * @Scope:调整作用域 30 | * prototype:多实例的:ioc容器启动并不会去调用方法创建对象放在容器中。 31 | * 每次获取的时候才会调用方法创建对象; 32 | * singleton:单实例的(默认值):ioc容器启动会调用方法创建对象放到ioc容器中。 33 | * 以后每次获取就是直接从容器(map.get())中拿, 34 | * request:同一次请求创建一个实例 35 | * session:同一个session创建一个实例 36 | * 37 | * 懒加载: 38 | * 单实例bean:默认在容器启动的时候创建对象; 39 | * 懒加载:容器启动不创建对象。第一次使用(获取)Bean创建对象,并初始化; 40 | * 41 | */ 42 | // @Scope("prototype") 43 | @Lazy 44 | @Bean 45 | public Person person() { 46 | System.out.println("给容器添加person"); 47 | return new Person("Maifeng", 18); 48 | } 49 | } 50 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/8.@Conditional的使用.md: -------------------------------------------------------------------------------- 1 | # 8.@Conditional的使用 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: Conditional 7 | * @author: mf 8 | * @create: 2020/01/28 23:03 9 | */ 10 | 11 | package org.example.config; 12 | 13 | import org.example.bean.Person; 14 | import org.example.condition.ManCondition; 15 | import org.example.condition.WomanCondition; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Conditional; 18 | import org.springframework.context.annotation.Configuration; 19 | 20 | /** 21 | * @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean 22 | * 23 | * 如果man,给容器中注册("Maifeng") 24 | * 如果woman,给容器中注册("Liumeng") 25 | */ 26 | @Configuration 27 | public class MainConfigOfConditional { 28 | 29 | @Bean("isMan") 30 | public Person person() { 31 | return new Person(); 32 | } 33 | 34 | @Conditional(ManCondition.class) 35 | @Bean("Man") 36 | public Person person01() { 37 | return new Person("Maifeng", 18); 38 | } 39 | 40 | @Conditional(WomanCondition.class) 41 | @Bean("Woman") 42 | public Person person02() { 43 | return new Person("Liumeng", 18); 44 | } 45 | } 46 | ``` -------------------------------------------------------------------------------- /Java/spring-books/docs/9.@Import的使用.md: -------------------------------------------------------------------------------- 1 | # 9.@Import的使用 2 | 3 | ```java 4 | /** 5 | * @program SpringBooks 6 | * @description: Import的使用 7 | * @author: mf 8 | * @create: 2020/01/28 23:12 9 | */ 10 | 11 | package org.example.config; 12 | 13 | import org.example.bean.Color; 14 | import org.example.bean.Person; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.context.annotation.Import; 17 | 18 | @Configuration 19 | @Import({Person.class, Color.class}) 20 | public class MainConfigOfImport { 21 | 22 | /** 23 | * 给容器中注册组件; 24 | * 1)、包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)[自己写的类] 25 | * 2)、@Bean[导入的第三方包里面的组件] 26 | * 3)、@Import[快速给容器中导入一个组件] 27 | * 1)、@Import(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名 28 | * 2)、ImportSelector:返回需要导入的组件的全类名数组; 29 | * 3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中 30 | * 4)、使用Spring提供的 FactoryBean(工厂Bean); 31 | * 1)、默认获取到的是工厂bean调用getObject创建的对象 32 | * 2)、要获取工厂Bean本身,我们需要给id前面加一个& 33 | * &colorFactoryBean 34 | */ 35 | } 36 | 37 | ``` -------------------------------------------------------------------------------- /Java/spring-books/spring-aop2/src/Advice.java: -------------------------------------------------------------------------------- 1 | import java.lang.reflect.InvocationHandler; 2 | 3 | /** 4 | * 继承了 InvocationHandler 接口 5 | */ 6 | public interface Advice extends InvocationHandler { 7 | } 8 | -------------------------------------------------------------------------------- /Java/spring-books/spring-aop2/src/AfterAdvice.java: -------------------------------------------------------------------------------- 1 | import java.lang.reflect.Method; 2 | 3 | /** 4 | * @program SpringBooks 5 | * @description: 后置通知 6 | * @author: mf 7 | * @create: 2020/02/04 20:23 8 | */ 9 | 10 | public class AfterAdvice implements Advice { 11 | 12 | private Object bean; 13 | 14 | private MethodInvocation methodInvocation; 15 | 16 | public AfterAdvice(Object bean, MethodInvocation methodInvocation) { 17 | this.bean = bean; 18 | this.methodInvocation = methodInvocation; 19 | } 20 | 21 | @Override 22 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 23 | // 业务逻辑调用 24 | Object invoke = method.invoke(bean, args); 25 | // 后置通知调用 26 | methodInvocation.invoke(); 27 | return invoke; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Java/spring-books/spring-aop2/src/BeforeAdvice.java: -------------------------------------------------------------------------------- 1 | import java.lang.reflect.Method; 2 | 3 | /** 4 | * @program SpringBooks 5 | * @description: 前置通知 6 | * @author: mf 7 | * @create: 2020/02/04 20:00 8 | */ 9 | 10 | public class BeforeAdvice implements Advice { 11 | 12 | private Object bean; // bean对象 13 | 14 | private MethodInvocation methodInvocation; // 方法调用 15 | 16 | public BeforeAdvice(Object bean, MethodInvocation methodInvocation) { 17 | this.bean = bean; 18 | this.methodInvocation = methodInvocation; 19 | } 20 | 21 | @Override 22 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 23 | // 在目标方法前调用 24 | methodInvocation.invoke(); 25 | return method.invoke(bean, args); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Java/spring-books/spring-aop2/src/HelloService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @program SpringBooks 3 | * @description: HelloService 4 | * @author: mf 5 | * @create: 2020/02/04 20:05 6 | */ 7 | 8 | public interface HelloService { 9 | 10 | void sayHelloWorld(); 11 | } 12 | -------------------------------------------------------------------------------- /Java/spring-books/spring-aop2/src/HelloServiceImpl.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @program SpringBooks 3 | * @description: HelloServiceImpl 4 | * @author: mf 5 | * @create: 2020/02/04 20:06 6 | */ 7 | 8 | public class HelloServiceImpl implements HelloService { 9 | 10 | @Override 11 | public void sayHelloWorld() { 12 | System.out.println("hello world..."); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Java/spring-books/spring-aop2/src/MethodInvocation.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @program SpringBooks 3 | * @description: 实现类包含了切面逻辑 4 | * @author: mf 5 | * @create: 2020/02/04 19:53 6 | */ 7 | 8 | public interface MethodInvocation { 9 | 10 | void invoke(); 11 | } 12 | -------------------------------------------------------------------------------- /Java/spring-books/spring-aop2/src/SimpleAOP.java: -------------------------------------------------------------------------------- 1 | import java.lang.reflect.Proxy; 2 | 3 | /** 4 | * @program SpringBooks 5 | * @description: AOP的简单实现 6 | * @author: mf 7 | * @create: 2020/02/04 20:02 8 | */ 9 | 10 | public class SimpleAOP { 11 | 12 | public static Object getProxy(Object bean, Advice advice) { 13 | return Proxy.newProxyInstance(SimpleAOP.class.getClassLoader() 14 | , bean.getClass().getInterfaces(), advice); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Java/spring-books/spring-aop2/src/SimpleAOPTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @program SpringBooks 3 | * @description: SimpleAOPTest 4 | * @author: mf 5 | * @create: 2020/02/04 20:06 6 | */ 7 | 8 | public class SimpleAOPTest { 9 | public static void main(String[] args) { 10 | // 1. 创建一个 MethodInvocation 实现类 切面逻辑类 11 | MethodInvocation logTask = () -> System.out.println("log task start"); 12 | MethodInvocation logTaskEnd = () -> System.out.println("log task end"); 13 | 14 | // 业务逻辑类 15 | HelloServiceImpl helloService = new HelloServiceImpl(); 16 | 17 | // 2. 创建一个Advice 切入点 18 | BeforeAdvice beforeAdvice = new BeforeAdvice(helloService, logTask); 19 | AfterAdvice afterAdvice = new AfterAdvice(helloService, logTaskEnd); 20 | 21 | // 3. 为目标对象生成代理 22 | HelloService helloServiceImplProxy = (HelloService) SimpleAOP.getProxy(helloService, beforeAdvice); 23 | HelloService helloServiceImplProxyAfter = (HelloService) SimpleAOP.getProxy(helloService, afterAdvice); 24 | 25 | helloServiceImplProxy.sayHelloWorld(); 26 | helloServiceImplProxyAfter.sayHelloWorld(); 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Java/spring-books/spring-ioc/src/Car.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @program SpringBooks 3 | * @description: Car 4 | * @author: mf 5 | * @create: 2020/02/04 01:58 6 | */ 7 | 8 | public class Car { 9 | 10 | private String name; 11 | 12 | private Wheel wheel; 13 | 14 | 15 | 16 | public String getName() { 17 | return name; 18 | } 19 | 20 | public void setName(String name) { 21 | this.name = name; 22 | } 23 | 24 | public Wheel getWheel() { 25 | return wheel; 26 | } 27 | 28 | public void setWheel(Wheel wheel) { 29 | this.wheel = wheel; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Car{" + 35 | "name='" + name + '\'' + 36 | ", wheel=" + wheel + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Java/spring-books/spring-ioc/src/SimpleIOCTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @program SpringBooks 3 | * @description: SimpleIOCTest 4 | * @author: mf 5 | * @create: 2020/02/04 02:07 6 | */ 7 | 8 | 9 | public class SimpleIOCTest { 10 | 11 | public static void main(String[] args) throws Exception { 12 | System.out.println(SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile()); 13 | String location = SimpleIOC.class.getClassLoader().getResource("ioc.xml").getFile(); 14 | SimpleIOC simpleIOC = new SimpleIOC(location); 15 | Wheel wheel = (Wheel) simpleIOC.getBean("wheel"); 16 | System.out.println(wheel); 17 | Car car = (Car) simpleIOC.getBean("car"); 18 | System.out.println(car); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Java/spring-books/spring-ioc/src/Wheel.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @program SpringBooks 3 | * @description: Wheel 4 | * @author: mf 5 | * @create: 2020/02/04 01:59 6 | */ 7 | 8 | public class Wheel { 9 | 10 | private String brand; 11 | 12 | private String specification; 13 | 14 | 15 | 16 | public String getBrand() { 17 | return brand; 18 | } 19 | 20 | public void setBrand(String brand) { 21 | this.brand = brand; 22 | } 23 | 24 | public String getSpecification() { 25 | return specification; 26 | } 27 | 28 | public void setSpecification(String specification) { 29 | this.specification = specification; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Wheel{" + 35 | "brand='" + brand + '\'' + 36 | ", specification='" + specification + '\'' + 37 | '}'; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Java/spring-books/spring-ioc/src/ioc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------