<?xml version="1.0" encoding="utf-8" ?><rss version="2.0"><channel><title><![CDATA[papilon_的博客]]></title><description><![CDATA[]]></description><link>https://blog.youkuaiyun.com/papilon_</link><language>zh-cn</language><generator>https://blog.youkuaiyun.com/</generator><copyright><![CDATA[Copyright &copy; papilon_]]></copyright><item><title><![CDATA[《Effective Java》第七条：消除过期的对象引用]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/153623614</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/153623614</guid><author>papilon_</author><pubDate>Mon, 20 Oct 2025 11:28:48 +0800</pubDate><description><![CDATA[只要类是自己管理内存，就要警惕内存泄漏问题。该用弱引用就用弱引用。或者Heap剖析工具。]]></description><category></category></item><item><title><![CDATA[《Effective Java》第五条：优先考虑依赖注入来引用资源]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/153395616</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/153395616</guid><author>papilon_</author><pubDate>Thu, 16 Oct 2025 10:44:04 +0800</pubDate><description><![CDATA[《Effective Java》第五条的核心思想是：类不应自行创建其依赖的资源（如数据库连接、网络服务、配置信息等），而应通过外部传入（即 “依赖注入”）的方式获取，以提高灵活性、可测试性和复用性。
 很多类在实现时会 “硬编码” 依赖资源，例如在类内部直接创建数据库连接、实例化特定服务等。这种做法存在三个严重问题：硬编码的本质是 **“类自己创建依赖”，而测试需要的是“替换依赖为测试替身（如 Mock 对象）”**，这两者是矛盾的。假设我们有一个发送短信的服务类，硬编码了具体的短信网关实现：


如果我们]]></description><category></category></item><item><title><![CDATA[《Effective Java》第四条：通过私有构造器强化不可实例化的能力]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/153344682</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/153344682</guid><author>papilon_</author><pubDate>Wed, 15 Oct 2025 20:33:10 +0800</pubDate><description><![CDATA[主要针对工具类，即仅包含静态方法和静态域的类。]]></description><category></category></item><item><title><![CDATA[《Effective Java》第三条：用私有构造器或者枚举类型强化Singleton属性]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/153250792</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/153250792</guid><author>papilon_</author><pubDate>Tue, 14 Oct 2025 15:35:33 +0800</pubDate><description><![CDATA[静态工厂方法将 “实例创建逻辑” 封装在方法内部，若未来需要修改 “是否返回单例”（如改为池化实例、按需创建），只需修改方法实现，无需改变对外暴露的 API（方法名、参数、返回值），现有调用代码无需调整。单例的核心是 “唯一实例的访问入口”，而直接暴露变量，本质是 “把实例当工具类的静态变量用”，无法引导开发者形成 “通过统一入口获取实例” 的习惯。是 “行为入口”，天然传递 “获取实例” 的动作 —— 它暗示 “这个类是有状态的实例类，不是无状态的工具类”，避免了对 “变量性质” 的误解。]]></description><category></category></item><item><title><![CDATA[《Effective Java》第二条：多构造器参数时考虑使用构建器]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/153194380</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/153194380</guid><author>papilon_</author><pubDate>Mon, 13 Oct 2025 15:22:28 +0800</pubDate><description><![CDATA[在创建类时，若需要初始化多个参数，重叠构造器模式可行，但仍不利于客户端编写，且难以阅读；JavaBeans模式可行，但是会使得类可能处于不一致状态。而构建器Builder能够较好的处理多参数情况。Builder模式在创建类时，需要创建构建器，有性能开销；且代码也比较冗长。《EJ》推荐一般在有4个以上时用它，毕竟谁会为了几个家具请师傅呢？如果将来有添加参数的可能，一开始就使用构建器更好。]]></description><category></category></item><item><title><![CDATA[《Effective Java》第一条：用静态工厂方法代替构造器]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/153051513</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/153051513</guid><author>papilon_</author><pubDate>Sat, 11 Oct 2025 17:23:38 +0800</pubDate><description><![CDATA[《Effective Java》，第一条，用静态工厂方法代替构造器]]></description><category></category></item><item><title><![CDATA[LeetCode HOT 100：3. 无重复字符的最长字串]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/139189550</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/139189550</guid><author>papilon_</author><pubDate>Sat, 25 May 2024 10:32:35 +0800</pubDate><description><![CDATA[3. 以i-1字符为结尾的最长无重复子串的长度tmp与i - left + 1（当前字符与上一个重复字符之间的长度）的关系：如果tmp >= i - left + 1，说明以i-1字符为结尾的无重复最长子串中包含了i字符的重复字符，又因为tmp表示的是i-1无重复，所以tmp可以无痛（隐含的left指针就是i字符前一次出现的地方+1）更新为i - left + 1；4. 根据map中的记录，找到上一个与i字符相同的位置（这里要注意，因为是顺序遍历i，所以在考虑i时，i之前的字符都已经被考虑过了）。]]></description><category></category></item><item><title><![CDATA[代码训练营Day.48 | 198. 打家劫舍、213. 打家劫舍II、337. 打家劫舍III]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135909860</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135909860</guid><author>papilon_</author><pubDate>Tue, 30 Jan 2024 10:18:50 +0800</pubDate><description><![CDATA[因为同时考虑头元素和尾元素的最终答案，在减去头尾中的最小元素后，与nums中去掉头元素而成的基本打家劫舍问题的最终答案，是完完全全的两个概念。即dp[i][j] = dp[i - 1][j] + bdp[i - 2][j - nums[i]] + nums[i]（由于不能在相邻房子中偷盗，所以偷盗当前房子时，应该加到上上一行的数上）3. 单层递归逻辑：计算左右孩子的dp数组，根据左右孩子的dp数组，计算当前节点的dp数组。1. dp数组含义：dp[i]表示包括前i个物品的情况下可盗取的最大金额。]]></description><category></category></item><item><title><![CDATA[代码训练营Day.38 | 509. 斐波那契数列、70. 爬楼梯、746. 使用最小花费爬楼梯]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135697794</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135697794</guid><author>papilon_</author><pubDate>Fri, 19 Jan 2024 15:40:44 +0800</pubDate><description><![CDATA[2. 递推公式：bp[i]取决于两种方案，即（爬到i - 1阶所需最小花费）bp[i - 1] + （从i - 1阶走一步）cost[i - 1]和（爬到i - 2阶所需最小花费）bp[i - 2] + （从i - 2阶走两格）cost[i - 2]中最小的那一个。1. bp数组即下标：参照cost数组，bp[i]表示爬到第i阶所需最小花费。3. bp数组初始化：bp[0] = 0、bp[1] = 0；所以，f(n) = f(n - 1) + f(n - 2);4. 遍历顺序：顺序遍历；]]></description><category></category></item><item><title><![CDATA[代码随想录Day.36 | 435. 无重叠区间、763. 划分字母区间、56. 合并区间]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135649336</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135649336</guid><author>papilon_</author><pubDate>Wed, 17 Jan 2024 17:03:05 +0800</pubDate><description><![CDATA[2. 假如存在另一堆区间，这堆区间有着与上一堆区间不同的最小区间，是否可以证明这两堆区间最终要移除至只剩两个区间？假如有两堆各自有最小公共区间的区间，如果只剩余一个区间a，那么这个区间是有可能与两个最小公共区间重叠的，但是，一定可以找到另外两个区间b，c，其中，b，c和a的公共区间不同。然后根据起始坐标从大到小排列，如果起始坐标相等，按照末尾坐标从大到小排列后，遍历区间，依次吞并可以吞并的区间。3. 所以，使得剩余区间互不重叠的最小移除数目，是区间总数 - 最小重叠区间个数。这就跟上一个的复杂解法类似了。]]></description><category></category></item><item><title><![CDATA[代码训练营Day.35 | 860. 柠檬水找零、406. 根据身高重建队列、452. 用最少数量的箭引爆气球]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135623177</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135623177</guid><author>papilon_</author><pubDate>Tue, 16 Jan 2024 19:36:53 +0800</pubDate><description><![CDATA[每次记录一个范围region，表示当前气球之前的公共区间。此时，可以从后往前考虑，一个人后面的身高一定是比他高的。这时只需要根据当前人的h来判断向后要移动多少步，此时，要考虑该元素前面有多少个相同点，以及他本身的h。那么依次遍历，根据当前人的h，作为其下标，插入到对应下标下，因为当前人之前一定是身高大于等于他的人。将气球按照其起点从小到大排列，若相等，按照其大小从小到大排列。需要考虑的情况很少，直接记录三种面值的数量，一一判断即可。那么，从后往前遍历时，h就是当前人要向后移动的步数。]]></description><category></category></item><item><title><![CDATA[代码训练营Day.34 | 1005. K次取反后最大化的数组和、134. 加油站、135. 分发糖果]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135599577</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135599577</guid><author>papilon_</author><pubDate>Mon, 15 Jan 2024 16:25:43 +0800</pubDate><description><![CDATA[然后，将极小值点设为1（颗糖果），从极小值开始，向左爬升到极大值点，糖果数量依次+1，向右爬升到极大值点，糖果数量一次+1。因为如果在计算局部累加和时，count < 0，则说明其对应累加初始坐标一定不是循环起始点，累加和之间的坐标，也不是！因为start到末尾的累加和中，从来没有小于过0，所以，start一定是第一个。还有更简单的是，记录累加和初始坐标start，这样可以在一遍遍历后得到答案，而且只需一次遍历。3. 从右向左遍历，如果当前孩子评分比后一个高，取他原本的数/后一个糖果数+1中最大的。]]></description><category></category></item><item><title><![CDATA[代码随想录Day.31 | 455. 分发饼干、376. 摆动序列、53. 最大子序和]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135554660</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135554660</guid><author>papilon_</author><pubDate>Fri, 12 Jan 2024 19:59:53 +0800</pubDate><description><![CDATA[有两种情况：1. 该阶段中的负数累加到sum上后的值<0，则刷新累加和sum，和局部累加和sumb。2. 改变连续子区间的起点和终点，在子区间累加和<0后，使子区间累加和=0，相当于重新设置起点；其中，for循环时由饼干的数组控制的，原因在于，如果当前小孩和饼干不匹配的话，即当前饼干不是能满足小孩的最小尺寸饼干，应该用下一块饼干去匹配当前小孩。假如用，下一个小孩去匹配当前饼干，而在当前小孩都不匹配的情况下，下一个胃口更大的小孩更不可能匹配，这就导致在一个for循环下，找不到任何一对匹配的。]]></description><category></category></item><item><title><![CDATA[代码训练营Day.28 | 93. 复原IP地址、78. 子集、90. 子集II]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135472478</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135472478</guid><author>papilon_</author><pubDate>Tue, 09 Jan 2024 19:14:09 +0800</pubDate><description><![CDATA[以下方法直接在s上操作，考虑将三个‘.’插在字符串的哪里。注意第三个点插在最后一位的情况。太简单了，就是组合但是不考虑长度，任何都能压入最终答案中。麻烦的地方在于文本的各种限制条件、剪枝等等。字符串切四刀，最后一刀必须是在末位。有了重复元素，就不得不先排序了。]]></description><category></category></item><item><title><![CDATA[代码训练营Day.27 | 39. 组合总和、40. 组合总和II、131. 分割回文串]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135449302</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135449302</guid><author>papilon_</author><pubDate>Mon, 08 Jan 2024 11:35:37 +0800</pubDate><description><![CDATA[3. 单层递归逻辑。遍历数组，将每一个数当作当前递归层的候选值，然后进一步递归。因为可以重复使用，所以下一层的起点仍然是当前值的位置而不用+1；比较难想到，但仍然是组合问题，组合总和问题是本层递归要选哪个数字为候选值，分割回文串问题是本层递归要分割前几个合为回文串的字符作为候选字符串。与其他组合总和不同的是，数组中含有相同数字，而仍然要求两种答案不可重复。与其他组合总和题目不同的是，这一次数组中的数字可以重复使用。每一层的要割的回文串起点，取决于上一层递归割的终点。注意，在跳过重复数字时，不要额外i++。]]></description><category></category></item><item><title><![CDATA[代码训练营Day.25 | 216. 组合总和III、17. 电话号码和字母组合]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135421663</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135421663</guid><author>papilon_</author><pubDate>Sat, 06 Jan 2024 10:02:27 +0800</pubDate><description><![CDATA[从start到数值9，每个数都充当一遍该层回溯的加项。（按照迭代法来看，就是三指针求和问题，这里有递归来实现）；此层递归的加项考虑完后，将其从result中pop出来，完成回溯。如果(k == 0 && n == 0)则将vector<int> result，push到vector<vector<int>> results。参数：（[1, 9]中的某个数充当遍历）起点start，还剩几个数k，值还差多少n。普普通通组合问题，用回溯，最麻烦的地方是2~9所对应的字母如何映射，我这里的笨办法是分别考虑。]]></description><category></category></item><item><title><![CDATA[代码训练营Day.24 | 77. 组合]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135410753</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135410753</guid><author>papilon_</author><pubDate>Fri, 05 Jan 2024 15:48:46 +0800</pubDate><description><![CDATA[3. 单层递归逻辑。从start开始到n，每个数都充当一次第一项（具体是第几项，取决于递归深度），push到result中。然后在，后序区间找k - 1个数。最后，将result中最后一个数pop出来，完成回溯。vector<int> result 中的数恰好够最初的k个，或者当前递归要找0个数。在[1, n]中找k个数的组合，一般的想法就是取区间首项作为第一个数，然后在剩余数中找(k - 1)个数。然后取区间第二项作第一个数。1. 返回值和参数。参数：区间起始值start，区间结束值n，要找k个数。]]></description><category></category></item><item><title><![CDATA[代码随想录Day.23 | 669. 修剪二叉搜索树、108. 将有序数组转换为二叉搜索树、538. 把二叉搜索树转换为累加树]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135378068</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135378068</guid><author>papilon_</author><pubDate>Thu, 04 Jan 2024 12:32:12 +0800</pubDate><description><![CDATA[找恰好大于low的节点时，即用正常左中右的中序遍历，pre指针指向前一个，判断root是要找的节点条件是(pre!= NULL && pre->val < low && root->val >= low)此时，root根本不是要删除的，而删除pre比较困难，而且可能存在root是pre右子树中的值这种情况，处理起来更麻烦。针对情况3，说明要保留的部分树的根节点，就是rootbenshen。针对情况3，要在root树中，假如某个节点恰好小于low，则要删除该节点及其左子树，即令其右子树替代他；]]></description><category></category></item><item><title><![CDATA[代码训练营Day.22 | 235. 二叉搜索树的最近公共祖先、701. 二叉搜索树中的插入操作、450. 删除二叉搜索树中的节点]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135354573</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135354573</guid><author>papilon_</author><pubDate>Wed, 03 Jan 2024 11:34:14 +0800</pubDate><description><![CDATA[更巧妙的是，删除当前节点后，最合适的候选节点是左子树最右节点或者右子树最左节点。细想的话，其实如果将左子树上位，右子树一定被插在左子树中的最右节点的右节点上；如果让右子树上位，左子树一定被插在最左节点的左节点上。其实这道题目可以看成左子树上位后，将右子树插入左子树，因为两边都是二叉搜索树，且左子树必小于右子树，所以只要简单插入即可。即，root的val在p、q的val所构成的闭区间内。相当于将在遍历至NULL的路上，重新连接原本的树，直到遇到NULL，然后把NULL换成val。参数：当前节点和val。]]></description><category></category></item><item><title><![CDATA[代码训练营Day.21 | 530. 二叉搜索树的最小绝对差、501. 二叉搜索树中的众数、236. 二叉树的最近公共祖先]]></title><link>https://blog.youkuaiyun.com/papilon_/article/details/135331803</link><guid>https://blog.youkuaiyun.com/papilon_/article/details/135331803</guid><author>papilon_</author><pubDate>Tue, 02 Jan 2024 16:19:29 +0800</pubDate><description><![CDATA[就是找到p、q节点。如果，恰好左右节点分别在某节点的左右子树上，直接返回这个节点，即为公共节点。中序遍历，记录前一个指针，并记录前一个指针和当前指针的绝对差值。自己想到的笨办法，自顶向下找，每次都要遍历一遍当前节点之下的节点。从下往上遍历，就用后序遍历，先判断完左右子树，然后根据结果判断当前节点。中序遍历，记录指针、最大出现频率、当前数字累计个数、最终result。]]></description><category></category></item></channel></rss>