[{"authors":null,"categories":null,"content":"LRU缓存机制 前言 LRU（Least Recently Used）即最近最少使用、最近最久未使用，是一种缓存解决方案。\n缓存思想在计算机科学中应用广泛，其核心是以空间换时间，实现方式主要采用特定的数据结构，在计算机存储层次结构中，低一层的存储器都可以看做是高一层的缓存。比如Cache是内存的缓存，内存是硬盘的缓存，硬盘是网络的缓存等等。\n第一次接触到LRU是在操作系统的课程上，在提到OS页面调度算法的时候提到的LRU页面置换算法，页面置换是基于时间和空间局部性原理的基础上，而缓存类似于LRU这样的缓存技术在这样的场景下得到应用。\nLRU在数据库缓存中也有广泛应用，例如Redis缓存淘汰策略。在使用next.js的时候，访问页面时，为了加快访问速度也会用到。\nLRU：如果一个数据最近没有被访问到，那么它将来被访问的几率也很小，也就是说当限定的内存空间已没有其他空间可用时，应该把最久没有访问到的数据去除掉。\nLRU为了实现时间复杂度O(1)，采用HashMap+双向链表的数据结构，实get和put操作，如下图所示。\nJava中可以采用LinkedHashMap源实现LRU，Python中可以调用OrderedDict()实现。\n例题 LeetCode146. LRU 缓存机制 Java实现 下面用是用Java实现的几种方式，分别是\n调用Java的LinkedHashMap实现LRU package LRU; import java.util.*; /** * @author Orust * @create 2021/5/24 16:07 */ // 146. LRU 缓存机制: https://leetcode-cn.com/problems/lru-cache/ // LinkedHashMap源码中写道: This kind of map is well-suited to building LRU caches. // put和get的时间复杂度均为O(1) // 空间复杂度为O(capacity) capacity为LRU缓存大小 // 广泛应用与OS内存页面调度，Redis缓存淘汰策略 // 样例输入 // [\u0026#34;LRUCache_4\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;get\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;get\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;get\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;get\u0026#34;,\u0026#34;get\u0026#34;,\u0026#34;get\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;get\u0026#34;,\u0026#34;put\u0026#34;,\u0026#34;get\u0026#34;] // [[10],[7,28],[7,1],[8,15],[6],[10,27],[8,10],[8],[6,29],[1,9],[6],[10,7],[1],[2],[13],[8,30],[1,5],[1],[13,2],[12]] // 预期结果 // [null,null,null,null,-1,null,null,10,null,null,29,null,9,-1,-1,null,null,5,null,-1] // 调用Java的LinkedHashMap实现LRU public class LRUCache { int capacity; LinkedHashMap\u0026lt;Integer, Integer\u0026gt; cache; public LRUCache(int capacity) { this.capacity = capacity; cache = new LinkedHashMap\u0026lt;\u0026gt;(); } public int get(int key) { if (cache.containsKey(key)) { int num = cache.remove(key); cache.put(key, num); return num; } else return -1; // 未找到当前元素 } public void put(int key, int value) { if (cache.containsKey(key)) { cache.remove(key); } else if (cache.size() == capacity) { // cache.remove(cache.entrySet().iterator().next().getKey()); cache.remove(cache.keySet().iterator().next()); } cache.put(key, value); } public static void main(String[] args) { LRUCache obj = new LRUCache(10); obj.put(7, 28); obj.put(7, 1); obj.put(8, 15); System.out.println(obj.get(6)); obj.put(10, 27); obj.put(8, 10); System.out.println(obj.get(8)); obj.put(6, 29); obj.put(1, 9); System.out.println(obj.get(6)); obj.put(10, 7); System.out.println(obj.get(1)); System.out.println(obj.get(2)); System.out.println(obj.get(13)); obj.put(8, 30); obj.put(1, 5); System.out.println(obj.get(1)); obj.put(13, 2); System.out.println(obj.get(12)); } } HashMap + 定义双向链表 实现LRU package LRU; import java.util.*; /** * @author Orust * @create 2021/5/24 16:18 */ // HashMap + 定义双向链表 实现LRU public class LRUCache_2 { int capacity; HashMap\u0026lt;Integer, Node\u0026gt; map; Node head; Node tail; private static class Node { int key; int value; Node prev; Node next; public Node(int key, int value) { this.key = key; this.value = value; } } public LRUCache_2(int capacity) { this.capacity = capacity; map = new HashMap\u0026lt;\u0026gt;(); } public int get(int key) { if (map.containsKey(key)) { Node node = map.get(key); moveToHead(node); return node.value; } else return -1; } public void put(int key, int value) { if (map.containsKey(key)) { Node node = map.get(key); node.value = value; map.put(key, node); moveToHead(node); } else { if (map.size() == capacity) map.remove(popTail()); Node node = new Node(key, value); map.put(key, node); addToHead(node); } } private void addToHead(Node node) { if (tail == null) { // 当前链表为空 head = node; tail = node; } else { head.prev = node; node.next = head; node.prev = null; head = node; } } private void moveToHead(Node node) { if (node == head) return; // 判断是否是头尾节点并删除节点 node.prev.next = node.next; if (node == tail) tail = tail.prev; else node.next.prev = node.prev; addToHead(node); } private int popTail() { if (tail == null) throw new NullPointerException(\u0026#34;NullPointerException!\u0026#34;); int key = tail.key; if (tail == head) { head = null; tail = null; } else { tail = tail.prev; tail.next = null; } return key; } public static void main(String[] args) { LRUCache_2 obj = new LRUCache_2(10); obj.put(7, 28); obj.put(7, 1); obj.put(8, 15); System.out.println(obj.get(6)); obj.put(10, 27); obj.put(8, 10); System.out.println(obj.get(8)); obj.put(6, 29); obj.put(1, 9); System.out.println(obj.get(6)); obj.put(10, 7); System.out.println(obj.get(1)); System.out.println(obj.get(2)); System.out.println(obj.get(13)); obj.put(8, 30); obj.put(1, 5); System.out.println(obj.get(1)); obj.put(13, 2); System.out.println(obj.get(12)); } } HashMap + 定义静态内部类 MyLinkedList　+　泛型 实现LRU package LRU; import java.util.*; /** * @author Orust * @create 2021/5/24 17:03 */ // HashMap + 定义静态内部类MyLinkedList　+　泛型 实现LRU public class LRUCache_3\u0026lt;K, V\u0026gt; { int capacity; Map\u0026lt;K, Node\u0026lt;K, V\u0026gt;\u0026gt; map = new HashMap\u0026lt;\u0026gt;(); MyLinkedList\u0026lt;K, V\u0026gt; cache = new MyLinkedList\u0026lt;\u0026gt;(); public LRUCache_3(int capacity) { this.capacity = capacity; } public V get(K key) { if (map.containsKey(key)) { Node\u0026lt;K, V\u0026gt; node = map.get(key); cache.moveToHead(node); return node.value; } else return null; } public void put(K key, V value) { if (map.containsKey(key)) { Node\u0026lt;K, V\u0026gt; node = map.get(key); node.value = value; …","date":1621934383,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779972673,"objectID":"432bc76f90028d03e0c26f942c6ef9f0","permalink":"https://orust.cc/post/lru-huan-cun-ji-zhi/","publishdate":"2021-05-25T17:19:43+08:00","relpermalink":"/post/lru-huan-cun-ji-zhi/","section":"post","summary":"LRU缓存机制 前言 LRU（Least Recently Used）即最近最少使用、最近最久未使用，是一种缓存解决方案。 缓存思想在计算机科学中应用广泛，其核心是以空间换时间，实现方式主要采用特定的数据结构，在计算机存储层次结构中，低一层的存储器都可以看做是高一层的缓存。比如Cache是内存的缓存，内存是硬盘的缓存，","tags":["# LRU"],"title":"LRU缓存机制","type":"post"},{"authors":null,"categories":null,"content":"1030. 距离顺序排列矩阵单元格\n解题思路 sorted的lambda表达式排序\nclass Solution: def allCellsDistOrder(self, R: int, C: int, r0: int, c0: int) -\u0026gt; List[List[int]]: res = [[r, c] for r in range(R) for c in range(C)] return sorted(res, key=lambda x:abs(x[0]-r0)+abs(x[1]-c0)) python3中sorted函数自定义排序：\n# 例1: def cmp(a, b): # 数组升序排序 if b \u0026lt; a: return 1 if a \u0026lt; b: return -1 return 0 a = [1, 2, 5, 4] print(sorted(a, key=functools.cmp_to_key(cmp))) # 或者: def cmp(a, b): # 数组升序排序 return a - b a = [1, 2, 5, 4] print(sorted(a, key=functools.cmp_to_key(cmp))) # 例2: a = [[3,1],[9,7],[6,4],[1,0],[1,3],[3,3]] def cmp(x, y): # if x[0] != y[0]: # 如果第一个元素不同 # return x[0] - y[0] # 则按照第一个元素升序排序 # else: # return y[1] - x[1] # 否则按照第二个元素降序排序 return x[0] - y[0] if x[0] != y[0] else y[1] - x[1] print(sorted(a,key=cmp_to_key(cmp))) 代码 class Solution: def allCellsDistOrder(self, R: int, C: int, r0: int, c0: int) -\u0026gt; List[List[int]]: res = [[r, c] for r in range(R) for c in range(C)] def cmp(x, y): # 按d1-d2的大小升序 d1 = abs(x[0] - r0) + abs(x[1] - c0) d2 = abs(y[0] - r0) + abs(y[1] - c0) return d1 - d2 return sorted(res, key=cmp_to_key(cmp)) ","date":1605586473,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779973485,"objectID":"f71462a51d2acfbdebda786169880d30","permalink":"https://orust.cc/post/python3lambda-biao-da-shi-zi-ding-yi-pai-xu/","publishdate":"2020-11-17T12:14:33+08:00","relpermalink":"/post/python3lambda-biao-da-shi-zi-ding-yi-pai-xu/","section":"post","summary":"记录 Python3 中 lambda 表达式、sorted 自定义排序和 cmp_to_key 的用法，并以 LeetCode 1030 为例整理实现思路。","tags":[],"title":"Python3 lambda 表达式、自定义排序","type":"post"},{"authors":null,"categories":null,"content":"模糊关系 ​ 现实世界中,事物之间存在着这样或那样的关系.一些是界限非常明确的关系,如“父子关系”、“大小关系”等等,可以简单地用“是”与“否”或者“1”与“0”来刻画.而更多的则是界限不明显的关系,如“朋友关系”、“相近关系”、“相像关系”等.对于这类关系再用简单的“是”与“否”或者“1”与“0”来刻画显然是不合适的,必须用模糊集理论来进行描述,这就是模糊关系. ​ 模糊关系是模糊集理论重要的内容之一,其应用范围十分广泛,几乎遍及模糊数学的所有应用领域. 本章主要介绍模糊关系的基本理论及其在聚类分析中的应用,内容主要包括：模糊矩阵,模糊关系,模糊聚类分析.\n聚类算法 聚类算法是我们研究时经常遇到的算法,许多文章用其来对给定的数据集进行聚类,从而得到有利结果,以下是聚类算法的汇总.\n1、层次(系统)聚类(Agglomerative Clustering) 1.1 凝聚层次聚类 1.2 分裂层次聚类 2、基于原型的聚类 2.1 K-均值(K-means) 2.2 二分K-均值(bisecting K-means) 2.3 K-中心(K-mediods) 2.4 模糊C均值聚类(FCM) 3、基于密度的聚类 3.1 DBSCAN 3.2 OPTICS 3.3 DENCLUE 4、基于网格的聚类 4.1 STING 4.2 CLIQUE 5、基于图的聚类 5.1 最小生成树(MST)聚类 5.2 OPOSSUM 5.3 Chameleon 6、基于神经网络 6.1 SOM 其中我们熟悉的常见聚类算法有：K-means算法、bisecting K-means等.\n值得注意的是,模糊C均值聚类（FCM）算法的提出,填补了原有K-means算法的的局限性,使得聚类算法应用更加广泛.\n目前许多WRSN的文章采用的聚类算法都是基于K-means的,而且都是以距离作为原始数据进行聚类,但是这样聚类的结果对于网络来说可能并不是最佳的.\n模糊聚类算法 模糊聚类分析按照聚类过程的不同大致可以分为三大类：\n基于模糊关系的分类法：其中包括谱系聚类算法(又称系统聚类法)、基于等价关系的聚类算法、基于相似关系的聚类算法和图论聚类算法等等.它是研究比较早的一种方法,但是由于它不能适用于大数据量的情况,所以在实际中的应用并不广泛. 基于目标函数的模糊聚类算法：该方法把聚类分析归结成一个带约束的非线性规划问题,通过优化求解获得数据集的最优模糊划分和聚类.该方法设计简单、解决问题的范围广,还可以转化为优化问题而借助经典数学的非线性规划理论求解,并易于计算机实现.因此,随着计算机的应用和发展,基于目标函数的模糊聚类算法成为新的研究热点. 基于神经网络的模糊聚类算法：它是兴起比较晚的一种算法,主要是采用竞争学习算法来指导网络的聚类过程. 模糊C均值 (FCM) 上面提到的K-均值聚类,其实它有另一个名字,C-均值聚类(HCM).要讲模糊C-均值,我们先从C-均值,也就是K-均值这个角度谈起.\nHCM 从上面对K-均值算法的介绍中,我们已经知道了,K-均值的目标函数是:\n其中, HCM 的 0/1 隶属函数可写为:\n$$ u_{ij} = \\begin{cases} 1, \u0026amp; d(x_i,c_j) \\le d(x_i,c_k),\\ \\forall k \\ne j \\ 0, \u0026amp; \\text{otherwise} \\end{cases} $$\n也就是说, 样本 $x_i$ 只归属于距离最近的一个聚类中心 $c_j$。\n这也是为什么叫H(Hard)CM.要么隶属该簇,要么不隶属该簇.太硬了. 引入模糊数学的观点,使得每个给定数据点用值在0,1间的隶度来确定其属于各个组的程度,便得到FCM的核心思想.\nFCM\nFCM的目标函数是\n其中介于0,1之间;也就是说,每个样本对各个簇都有一个隶属度,不再像HCM那样要么属于,要么不属于.更”模糊”\n)为控制算法模糊程度的参数.\n而且,对任意样本满足:\n借助拉格朗日乘子,我们将限制条件整合到目标函数中得到新的目标函数\n即在每一轮迭代中,要寻找使得目标函数最小.\n下面我们对其进行求解:\n当为欧氏距离,对中心求偏导并令其为0,有\n对变量求偏导并令其为0,有:\n为了使公式(4)满足条件\n看,我们得到了公式(5),即所需满足的条件.接下来将公式(5)代回到公式(4),我们得到:\n数学推导到这里就结束了,我们关键再看一下公式(2)与公式(6).这两个公式分别决定了簇中心与隶属矩阵的更新.\n模糊C均值的算法流程如下:\n按照上面的流程, FCM 每轮会先依据当前隶属度矩阵更新聚类中心, 再依据新的聚类中心更新每个样本对各簇的隶属度。常用的两个更新式为:\n$$ v_j=\\frac{\\sum_{i=1}^{n}u_{ij}^{m}x_i}{\\sum_{i=1}^{n}u_{ij}^{m}} $$\n$$ u_{ij}=\\frac{1}{\\sum_{k=1}^{c}\\left(\\frac{\\lVert x_i-v_j\\rVert}{\\lVert x_i-v_k\\rVert}\\right)^{\\frac{2}{m-1}}} $$\n当算法收敛,就可以得到聚类中心和各个样本对于各簇的隶属度值,从而完成了模糊聚类划分.如果有需要,还可以将模糊聚类结果进行去模糊化.即用一定规则把模糊聚类划分为确定性分类.\n聚类有效性分析 ​ 与其它基于目标函数的模糊聚类方法一样，FCM 要求在实施算法之前，必须事先指定分类数目。如果我们指定的聚类数不正确，即使使用很好的聚类算法也不会得到优的聚类结果。\n​ 然而，在实际应用中通常事先并不知道或难以确切知道样本集的聚类数。因此，根据给定样本集中数据的内在结构来自动判断样本集应该具有的聚类数(称之为佳聚类数)，是一个非常有意义的课题。这种自动寻求样本集的佳聚类数问题，我们称之为聚类有效性分析，可以借助有效性函数来实现。聚类的有效性函数，均以聚类算法的相关参数为自变量，目的是评价在选定的参数值下，算法所得的分类结果是 否或以多大程度有效地反映了数据本身固有的分类结构。经过深入研究，人们基本取得一致的认识：反映聚类有效性的主要特征是类内的紧致性和类间的分离性，因而人们设计的聚类有效性函数，都始于这一基本出发点。 ​ 已经提出的聚类有效性函数有许多，并且人们还在逐步改进。比较有代表性的有如下几个。\n划分系数\n如果将给定样本集分成 c 类，对应的模糊划分矩阵为 U = ( μ ij)c×n，则相应的划分系数为\n​ Fc的大值对应于佳的聚类数 c*。\n平均模糊熵\n如果将给定样本集分成 c 类，对应的模糊划分矩阵为 U = ( μ ij)c×n，则相应的平均模糊熵为\n​ Hc的小值对应于佳的聚类数 c*。\n变差−−分离度\n如果将给定样本集分成 c 类，对应的模糊划分矩阵为 聚类中心矩阵为 vi为第 i 类的聚类中心向量，则相应的变差−−分离度为\nVw的小值对应于佳的聚类数 c*，其中\n我们称 Var(U, V) 为变差度，其定义如下：\n这里，n(i) 是第 i 类的样本数，d(x, y) 是一个度量，定义为：\n式中\n变差度所反映的是样本集整体的平均类内分散程度，分类的效果越好，类内的数据越紧密，变差度就越小。\n称 Sep(c, U) 为分离度，其定义如下：\n式中\n分离度通过模糊集之间的不相似性来描述各个类之间的隔离程度，分类效果越好，分离度越大。 注：佳聚类数 c* 的求取，通常是对 c = 2, 3, …, cmax 逐次实施模糊聚类算法，然后根据相应的模糊划分矩阵 U 和聚类中心矩阵 V 计算有效性函数值，后比较得出佳的聚类数 c*。在实际应用中，需要处 理的数据集其样本数可能非常大，但真实的分类数并不很大。因而通常取 ，或者根据实际情况将cmax 限定的更小，以减少算法运行的费用。\nFCM代码演示 import numpy as np import matplotlib.pylab as plt from skfuzzy.cluster import cmeans cp = np.random.uniform(1, 100, (100, 2)) train = cp[:50] test = cp[50:] train = train.T center, u, u0, d, jm, p, fpc = cmeans(train, c=3, m=2, error=0.005, maxiter=1000) label = np.argmax(u, axis=0) for i in range(50): if label[i] == 0: plt.scatter(train[0][i], train[1][i], c=\u0026#39;r\u0026#39;) elif label[i] == 1: plt.scatter(train[0][i], train[1][i], c=\u0026#39;g\u0026#39;) elif label[i] == 2: plt.scatter(train[0][i], train[1][i], c=\u0026#39;b\u0026#39;) plt.show() 输出图像：\n参数：\ndata：训练数据，这里需要注意 data 的格式，应为 (特征数目, 数据个数)，与很多训练数据的 shape 正好相反。 c：需要指定的聚类个数。 m：隶属度指数，是一个加权指数，一般为 2。 error：当隶属度的变化小于此值时提前结束迭代。 maxiter：最大迭代次数。 返回值：\ncntr：聚类中心。 u：最后的隶属度矩阵。 u0：初始化的隶属度矩阵。 d：记录每一个点到聚类中心欧式距离的矩阵。 jm：目标函数的优化历史。 p：迭代次数。 fpc：fuzzy partition coefficient，评价分类效果的指标，范围是 0 到 1；越接近 1 表示效果越好，可用于选择聚类个数。 参考链接 https://liangyaorong.github.io/blog/2017/%E8%81%9A%E7%B1%BB%E7%AE%97%E6%B3%95%E5%A4%A7%E7%9B%98%E7%82%B9/#1 https://blog.csdn.net/WWWQ2386466490/article/details/80349239 https://www.pythonf.cn/read/144212 http://grs.dlmu.edu.cn/__local/B/33/BD/9D3B5A6B993880F956A22B52622_B2DB6898_5B08F.pdf https://scikit-fuzzy.github.io/scikit-fuzzy/auto_examples/plot_cmeans.html ","date":1602559508,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779974525,"objectID":"e5b1db7e03dd991260726aad2a941adc","permalink":"https://orust.cc/post/mo-hu-ju-lei-yu-wrsn/","publishdate":"2020-10-13T11:25:08+08:00","relpermalink":"/post/mo-hu-ju-lei-yu-wrsn/","section":"post","summary":"梳理模糊关系、模糊聚类、FCM 目标函数与迭代流程，并记录 FCM 在 WRSN 聚类场景中的理解与代码演示。","tags":["# 模糊聚类"],"title":"模糊聚类与WRSN","type":"post"},{"authors":null,"categories":null,"content":"概览 这里分别给出了三种二叉树的遍历方法与N叉树的前序遍历，及其时空复杂度\n1：递归：直接递归版本、针对不同题目通用递归版本（包括前序、中序、后序）\n2：迭代：最常用版本（常用主要包括前序和层序，即【DFS和BFS】）、【前中后】序遍历通用版本（一个栈的空间）、【前中后层】序通用版本（双倍栈（队列）的空间）\n3：莫里斯遍历：利用线索二叉树的特性进行遍历\n4：N叉树的前序遍历\nLeetCode题目链接 144. 二叉树的前序遍历\n94. 二叉树的中序遍历\n145. 二叉树的后序遍历\n102. 二叉树的层序遍历\n589. N叉树的前序遍历\n主要参考资料 LeetCode官方题解\npow的题解\nGrandyang的博客\nAnnie Kim’s Blog\n维基百科对于线索二叉树的解释\n莫里斯的文章\n代码实现 # Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None # 递归 # 时间复杂度：O(n)，n为节点数，访问每个节点恰好一次。 # 空间复杂度：空间复杂度：O(h)，h为树的高度。最坏情况下需要空间O(n)，平均情况为O(logn) # 递归1：二叉树遍历最易理解和实现版本 class Solution: def preorderTraversal(self, root: TreeNode) -\u0026gt; List[int]: if not root: return [] # 前序递归 return [root.val] + self.preorderTraversal(root.left) + self.preorderTraversal(root.right) # # 中序递归 # return self.inorderTraversal(root.left) + [root.val] + self.inorderTraversal(root.right) # # 后序递归 # return self.postorderTraversal(root.left) + self.postorderTraversal(root.right) + [root.val] # 递归2：通用模板，可以适应不同的题目，添加参数、增加返回条件、修改进入递归条件、自定义返回值 class Solution: def preorderTraversal(self, root: TreeNode) -\u0026gt; List[int]: def dfs(cur): if not cur: return # 前序递归 res.append(cur.val) dfs(cur.left) dfs(cur.right) # # 中序递归 # dfs(cur.left) # res.append(cur.val) # dfs(cur.right) # # 后序递归 # dfs(cur.left) # dfs(cur.right) # res.append(cur.val) res = [] dfs(root) return res # 迭代 # 时间复杂度：O(n)，n为节点数，访问每个节点恰好一次。 # 空间复杂度：O(h)，h为树的高度。取决于树的结构，最坏情况存储整棵树，即O(n) # 迭代1：前序遍历最常用模板（后序同样可以用） class Solution: def preorderTraversal(self, root: TreeNode) -\u0026gt; List[int]: if not root: return [] res = [] stack = [root] # # 前序迭代模板：最常用的二叉树DFS迭代遍历模板 while stack: cur = stack.pop() res.append(cur.val) if cur.right: stack.append(cur.right) if cur.left: stack.append(cur.left) return res # # 后序迭代，相同模板：将前序迭代进栈顺序稍作修改，最后得到的结果反转 # while stack: # cur = stack.pop() # if cur.left: # stack.append(cur.left) # if cur.right: # stack.append(cur.right) # res.append(cur.val) # return res[::-1] # 迭代1：层序遍历最常用模板 class Solution: def levelOrder(self, root: TreeNode) -\u0026gt; List[List[int]]: if not root: return [] cur, res = [root], [] while cur: lay, layval = [], [] for node in cur: layval.append(node.val) if node.left: lay.append(node.left) if node.right: lay.append(node.right) cur = lay res.append(layval) return res # 迭代2：前、中、后序遍历通用模板（只需一个栈的空间） class Solution: def inorderTraversal(self, root: TreeNode) -\u0026gt; List[int]: res = [] stack = [] cur = root # 中序，模板：先用指针找到每颗子树的最左下角，然后进行进出栈操作 while stack or cur: while cur: stack.append(cur) cur = cur.left cur = stack.pop() res.append(cur.val) cur = cur.right return res # # 前序，相同模板 # while stack or cur: # while cur: # res.append(cur.val) # stack.append(cur) # cur = cur.left # cur = stack.pop() # cur = cur.right # return res # # 后序，相同模板 # while stack or cur: # while cur: # res.append(cur.val) # stack.append(cur) # cur = cur.right # cur = stack.pop() # cur = cur.left # return res[::-1] # 迭代3：标记法迭代（需要双倍的空间来存储访问状态）： # 前、中、后、层序通用模板，只需改变进栈顺序或即可实现前后中序遍历， # 而层序遍历则使用队列先进先出。0表示当前未访问，1表示已访问。 class Solution: def preorderTraversal(self, root: TreeNode) -\u0026gt; List[int]: res = [] stack = [(0, root)] while stack: flag, cur = stack.pop() if not cur: continue if flag == 0: # 前序，标记法 stack.append((0, cur.right)) stack.append((0, cur.left)) stack.append((1, cur)) # # 后序，标记法 # stack.append((1, cur)) # stack.append((0, cur.right)) # stack.append((0, cur.left)) # # 中序，标记法 # stack.append((0, cur.right)) # stack.append((1, cur)) # stack.append((0, cur.left)) else: res.append(cur.val) return res # # 层序，标记法 # res = [] # queue = [(0, root)] # while queue: # flag, cur = queue.pop(0) # 注意是队列，先进先出 # if not cur: continue # if flag == 0: # 层序遍历这三个的顺序无所谓，因为是队列，只弹出队首元素 # queue.append((1, cur)) # queue.append((0, cur.left)) # queue.append((0, cur.right)) # else: # res.append(cur.val) # return res # 莫里斯遍历 # 时间复杂度：O(n)，n为节点数，看似超过O(n)，有的节点可能要访问两次，实际分析还是O(n)，具体参考大佬博客的分析。 # 空间复杂度：O(1)，如果在遍历过程中就输出节点值，则只需常数空间就能得到中序遍历结果，空间只需两个指针。 # 如果将结果储存最后输出，则空间复杂度还是O(n)。 # PS：莫里斯遍历实际上是在原有二叉树的结构基础上，构造了线索二叉树， # 线索二叉树定义为：原本为空的右子节点指向了中序遍历顺序之后的那个节点，把所有原本为空的左子节点都指向了中序遍历之前的那个节点 # emmmm，好像大学教材学过，还考过 # 此处只给出中序遍历，前序遍历只需修改输出顺序即可 # 而后序遍历，由于遍历是从根开始的，而线索二叉树是将为空的左右子节点连接到相应的顺序上，使其能够按照相应准则输出 # 但是后序遍历的根节点却已经没有额外的空间来标记自己下一个应该访问的节点， # 所以这里需要建立一个临时节点dump，令其左孩子是root。并且还需要一个子过程，就是倒序输出某两个节点之间路径上的各个节点。 # 具体参考大佬博客 # 莫里斯遍历，借助线索二叉树中序遍历（附前序遍历） class Solution: def inorderTraversal(self, root: TreeNode) -\u0026gt; List[int]: res = [] # cur = pre = TreeNode(None) cur = root while cur: if not cur.left: res.append(cur.val) # print(cur.val) cur = cur.right else: pre = cur.left while pre.right and pre.right != cur: pre = pre.right if not pre.right: # print(cur.val) 这里是前序遍历的代码，前序与中序的唯一差别，只是输出顺序不同 pre.right = cur cur = cur.left else: pre.right = None res.append(cur.val) # print(cur.val) cur = cur.right return res # N叉树遍历 # 时间复杂度：时间复杂度：O(M)，其中 M 是 N 叉树中的节点个数。每个节点只会入栈和出栈各一次。 # 空间复杂度：O(M)。在最坏的情况下，这棵 N 叉树只有 2 层，所有第 2 层的节点都是根节点的孩子。 # 将根节点推出栈后，需要将这些节点都放入栈，共有 M−1个节点，因此栈的大小为 O(M)。 \u0026#34;\u0026#34;\u0026#34; # Definition for a Node. class Node: def __init__(self, val=None, children=None): …","date":1602502163,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779972399,"objectID":"05d8ad915f66087b5b5580275bf90170","permalink":"https://orust.cc/post/er-cha-shu-bian-li-xi-lie-zong-jie/","publishdate":"2020-10-12T19:29:23+08:00","relpermalink":"/post/er-cha-shu-bian-li-xi-lie-zong-jie/","section":"post","summary":"这里分别给出了三种二叉树的遍历方法与N叉树的前序遍历，及其时空复杂度 1：递归：直接递归版本、针对不同题目通用递归版本（包括前序、中序、后序） 2：迭代：最常用版本（常用主要包括前序和层序，即【DFS和BFS】）、【前中后】序遍历通用版本（一个栈的空间）、【前中后层】序通用版本（双倍栈（队列）的空间） 3：莫里斯遍历：","tags":["# Leetcode"],"title":"二叉树遍历系列总结","type":"post"},{"authors":null,"categories":null,"content":"前言 前两天在刷LeetCode每日一题的时候连续遇到了两天有并查集标签的题目，从来没有学过这个算法的我只能硬着头皮去看题，看评论区和题解区大家用并查集都很容易的解决了题目，群里也在讨论一些类似于并查集的优化呀什么的，而我都不知道这是个什么。于是下定决心要学一下这个“神秘的并查集”。\n查找资料 在群里问了一些大佬，怎么学并查集，有人推荐《算法4》这本书，说这本书的开头就是并查集，在coursera上也有公开课，coursera还可以判题。我在网上找到了《算法4》的pdf版，在pdf版中，第一章的第五部分：1.5案例研究：union-ﬁnd算法，讲了并查集的相关知识。 另外群主负雪明烛发了一个《2013年王道论坛计算机考研机试指南》，在这里面，第五章图论的第二部分，也讲到了并查集。 群里的liweiwei大佬的个人刷题笔记gitbooks网站上的第5章-高级数据结构中，也有并查集的总结，大佬给了一个思维导图，让人很好理解。liweiwei的网站。 当然，少不了91算法群主lucifer大佬的个人博客上的总结，群主还给出了两个python的并查集模板，一个是基础版本，一个是优化版本，可以在理解了的基础上，熟练背诵，作为以后刷题的一个板子。 在91算法群主博客的推荐中，给了另一个大佬的leetcode并查集题解链接，发现这是另外一个大佬labuladong的总结（labuladong的公众号老早就关注了），以及在labuladong的算法小抄（修订）.pdf中的第四章高频面试系列中，讲到了union-ﬁnd的详解及其应用。 不怕大佬没总结，就怕大佬总结了你不看。这么多的资料足够掌握“小小并查集”了\n学习 labuladong的表述中说到：Union-Find 算法，也就是常说的并查集算法，主要是解决图论中「动态连通性」问题的。\n所谓并查集，一个并：Union，一个查：Find，还有一个判断节点是否是同一个根节点（同类）的：connected。\n在union中，会将两棵树合到一起，使他们的根节点root相同。平衡性优化是在union函数中实现的，即在类中加入一个size列表[]或者字典{}用来计算某一棵树的重量，即这棵树有多少个节点，比如说size[3] = 5表示，以节点3为根的那棵树，总共有5个节点。最后用一个if语句来判断，将重量小的树加到重量大的树上。在liweiwei的总结中，这种方法叫做基于size的优化方法，其实还有一种叫做基于rank的优化方法，即根据节点的深度来当作if的判定条件。 在find中，会判断某个节点属于哪颗树（即哪一类），即它的根节点root是谁。路径压缩是在find函数中是实现的，在这里，我们掌握普通的压缩就可以了，只需要一行代码self.parent[x] = self.parent[self.parent[x]]即可实现树的压缩，而且压缩之后树的深度最多只有3（包括根节点），这种方法通常称之为隔代压缩（基于循环）。在liweiwei的总结中，详细说明了另外一种压缩：完全压缩（基于递归），这个就偏难理解一点，每轮递归的返回值都是parent[x]，这种压缩方式压缩树之后可以将树的深度压缩到只有2，即所有节点的父节点都是根节点，正常情况下需要用到完全压缩很少，掌握隔代压缩就行了。可能很多人都会注意到这一点：路径压缩后，还需要平衡性优化吗？，这个问题，liweiwei和labuladong都谈到了他们的理解。 liweiwei：我们发现在路径压缩的过程中，我们之前引入的辅助合并的数组，不管是 rank 还是 size，它们的定义都不准确了，因为我们在路径压缩的过程中没有对它们的定义进行维护。其实写到这里，数组 rank 还是数组 size 都不太重要了，我们只用在 find 的时候，做好路径压缩，把孩子结点指向父亲结点即可。\nlabuladong：也就是说，去掉重量平衡，虽然对于单个的 find 函数调用，时间复杂度依然是 O(1)，但是对于 API 调用的整个过程，效率会有一定的下降。当然，好处就是减少了一些空间，不过对于 Big O 表示法来说，时空复杂度都没变。\n所以我的结论是，在路径压缩后，用平衡性优化也行，不用也行，只有很微小的空间和时间的差距，不影响大局。在实际操作过程中可视题目具体的时间和空间的要求加或者不加平衡性优化。\n总结 以上就是我对并查集的学习和总结。实际操作还需要再题目中去训练。 我只看了labuladong、liweiwei、lucifer三位的总结，其中labuladong总结的更基础，让人容易理解和入门这个并查集的概念，而liweiwei总结的更加全面和深刻，在并查集的优化和压缩上讲的更细，至于代码，学习lucifer给的两个Python模板就足够了。另外好像大家都说是《算法4》这本书讲的并查集最全面最好，但是现在我已经会了。\n附代码(lucifer) 这是一个我经常使用的模板，我会根据具体题目做细小的变化，但是大体是不变的。\nclass UF: parent = {} cnt = 0 def __init__(self, M): # 初始化 parent 和 cnt def find(self, x): while x != self.parent[x]: x = self.parent[x] return x def union(self, p, q): if self.connected(p, q): return self.parent[self.find(p)] = self.find(q) self.cnt -= 1 def connected(self, p, q): return self.find(p) == self.find(q) 如果你想要更好的性能，这个模板更适合你，相应地代码稍微有一点复杂。可以根据情况使用不同的模板。\nclass UF: parent = {} size = {} cnt = 0 def __init__(self, M): # 初始化 parent，size 和 cnt def find(self, x): while x != self.parent[x]: x = self.parent[x] # 路径压缩 self.parent[x] = self.parent[self.parent[x]]; return x def union(self, p, q): if self.connected(p, q): return # 小的树挂到大的树上， 使树尽量平衡 leader_p = self.find(p) leader_q = self.find(q) if self.size[leader_p] \u0026lt; self.size[leader_q]: self.parent[leader_p] = leader_q else: self.parent[leader_q] = leader_p self.cnt -= 1 def connected(self, p, q): return self.find(p) == self.find(q) ","date":1591785549,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779972484,"objectID":"b1e60674194309a46eef3976e4772dd0","permalink":"https://orust.cc/post/bing-cha-ji-de-xue-xi-yu-zong-jie/","publishdate":"2020-06-10T18:39:09+08:00","relpermalink":"/post/bing-cha-ji-de-xue-xi-yu-zong-jie/","section":"post","summary":"前言 前两天在刷LeetCode每日一题的时候连续遇到了两天有并查集标签的题目，从来没有学过这个算法的我只能硬着头皮去看题，看评论区和题解区大家用并查集都很容易的解决了题目，群里也在讨论一些类似于并查集的优化呀什么的，而我都不知道这是个什么。于是下定决心要学一下这个“神秘的并查集”。 查找资料 1. 在群里问了一些大佬","tags":["# 并查集"],"title":"并查集的学习与总结","type":"post"},{"authors":null,"categories":null,"content":"Leetcode每日一题 解题思路 先取出最大的数组值，然后用一个for便利比较即可 在这里python返回bool类型的数组也是比较方便\n代码 class Solution: def kidsWithCandies(self, candies: List[int], extraCandies: int) -\u0026gt; List[bool]: maxi = max(candies) judge = [] for i in candies: judge.append(i+extraCandies\u0026gt;=maxi) return judge ","date":1590983029,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779972418,"objectID":"e09941069ea0760883b10c3e786f0fc9","permalink":"https://orust.cc/post/1431-yong-you-zui-duo-tang-guo-de-hai-zi/","publishdate":"2020-06-01T11:43:49+08:00","relpermalink":"/post/1431-yong-you-zui-duo-tang-guo-de-hai-zi/","section":"post","summary":"Leetcode每日一题 解题思路 先取出最大的数组值，然后用一个for便利比较即可 在这里python返回bool类型的数组也是比较方便 代码 class Solution: def kidsWithCandies self, candies: List int , extraCandies: int List bo","tags":["# Leetcode"],"title":"Leetcode-1431.拥有最多糖果的孩子","type":"post"},{"authors":null,"categories":null,"content":"儿童节快乐 解题思路1 python3 一次for循环解决问题 从数组尾部遍历 如果遇到数字不是9就+1，并返回 如果是9，则将当前数字置0，并进入下一轮循环\n考虑特殊情况：9999 此时需要检查最后一次for循环的数字是不是0 即digits[0]是否为0，如果是0，则遇到了特殊情况 此时需要在数组最前面加一个数字1，然后返回即可\n代码1 class Solution: def plusOne(self, digits: List[int]) -\u0026gt; List[int]: for i in range(len(digits)-1, -1, -1): if digits[i] is not 9: digits[i] += 1 return digits else: digits[i] = 0 if digits[0] is 0: digits.insert(0, 1) return digits 解题思路2 python3 赖皮解法 先将数字列表数组转化为数字 然后+1 最后将数字转化为数组，并返回\n代码2 class Solution: def plusOne(self, digits: List[int]) -\u0026gt; List[int]: a = [i *10**index for index,i in enumerate(digits[::-1])] num = sum(a) + 1 return [int(x) for x in str(num)] ","date":1590982793,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779972559,"objectID":"77e3c66c3d82fbb17805d4521da3d49d","permalink":"https://orust.cc/post/leetcode-66jia-yi/","publishdate":"2020-06-01T11:39:53+08:00","relpermalink":"/post/leetcode-66jia-yi/","section":"post","summary":"儿童节快乐 解题思路1 python3 一次for循环解决问题 从数组尾部遍历 如果遇到数字不是9就+1，并返回 如果是9，则将当前数字置0，并进入下一轮循环 考虑特殊情况：9999 此时需要检查最后一次for循环的数字是不是0 即digits 0 是否为0，如果是0，则遇到了特殊情况 此时需要在数组最前面加一个数字1","tags":["# Leetcode"],"title":"Leetcode-66.加一","type":"post"},{"authors":null,"categories":null,"content":"新的一周的下午，拿起新的一篇论文，有点看不下去。打开了我的Gridea…… 谨以此篇文章记录自己在chrome浏览器摸索的经验和技巧。 以下技巧和经验仅作交流和学习使用，请勿用作其他用途。\n进入正题。获取很多人都听说过谷歌浏览器很强大，但是又有人问到底哪里强大了，这不都是一样和普通浏览器一样吗？当然，不同的人用当然不同，或许有的功能根本用不到。其实，它的强大就在于它的开发者模式，可以体验各种各样的插件带来的便利。\n1. 梦开始的地方 以下是我的浏览器界面\n首先，如何安装浏览器的插件？这不是要到谷歌商店才可以？谷歌商店不是要科学？这个确实是个问题，就是这一步，已经有一半以上的人放弃折腾了。 解决方法：集装箱\n集装箱链接 提取码：s0v9\n分享的第一个插件，这是我用过最好用的一个“万能插件”。 使用方法：在下载后，打开浏览器，右上角三个点，更多工具，扩展程序。打开扩展程序界面，或者直接在地址栏输入chrome://extensions/也可以进入。 进入之后，将右上角的开发者模式打开。然后把自己刚刚下载的哪个文件直接用鼠标拖到这个界面里面，然后这个扩展程序就安装上了。在安装好之后，在你的浏览器地址栏右边会有一个“#”的图标，这就是刚刚所安装的东西：集装箱。 PS：如果还是不会，去这个网站看看吧：\n扩展迷\n点击插件打开，界面应该是这样子的\n从图就可以看出来8 当然，不要想太多，这个只是为了能够上谷歌，正常查资料，查文献，装插件就可以了，我想绝大部分人图的也就是个查资料。这个插件的更多用途可以自己探索~回归本文主题： 将启用代理√打上，然后点击谷歌应用商店。\n2. 装饰我的浏览器 看到别人的浏览器都很炫酷，而本人的怎么就这？ 如果按照以上步骤顺利进入chrome商店，则会看到以下界面：\n在左侧，有扩展程序和主题背景两大选项，在主题背景中，你可以看到很多浏览器主题，按照自己喜欢下载安装即可。在这里隆重推出第二个插件，左侧输入：Infinity 新标签页 (Pro) 就是下面这个，点击安装即可，注意看我标记的地方，商店里面有很多山寨插件，看看下载量就能分辨出来哪些是真的。\n没有用过当然不知道，用了之后你就会发现就是最好的，没有之一。依然是自己去探索吧~\n当然，你这里可以直接在商店里面搜索“集装箱”，下载官方版本的，因为刚刚下载的是我分享的离线安装包版本，能在商店里面找到的尽量用商店的，因为插件会更新一些功能。\n我心中的前两名插件就是我前面介绍的，其实“集装箱”这个知道的人不多，这个能上谷歌，能上谷歌学术，关键是没有广告不需要注册的插件，几乎是唯一的一个了。这个插件原理是走的代理，后台自动给你分配服务器，当然这些都不需要了解，用就完事了。\n3. 进阶操作 以上当然都是基本操作，对于本来就会的人来说，没什么有价值的东西。下面，就是我介绍其他插件的时候了，总有一款会让你喜欢。\n1. 暴力猴 即使我不介绍，许多人也知道这个插件：油猴/暴力猴，另一款称得上是“万能插件”的就是这个了，两个是同样的插件功能，暴力猴是在油猴的基础上开发的，建议安装暴力猴。界面就是一只小猴子，提供方是：https://violentmonkey.github.io 记得不要安装错了，山寨的太多，另外一个安装量更多的Tampermonkey是开发者用的，普通人用不到。 这个插件的功能，为什么可以说万能。举一个最常用的例子，当你打开爱奇艺的时候，发现想要看的某个视频还有广告，这时候你就可以点击右上角的这只小猴子，然后选：为此站点查找脚本，会发现以下界面\n还不明白吗！点击任何一个你想安装的，点击安装此脚本，然后你会看到脚本的代码，点击右上角确认安装，然后就安装好了。 当你再一次打开刚刚爱奇艺的网也，你会发现界面多了一个东西，自己探索吧~ 当然不不止爱奇艺，所有的视频网站、百度文库、CSDN……\n2. Chrono下载管理器 这款古老的插件现在已经在商店找不到了，这个几乎可以称为“万能的下载插件”，可以方便的管理从chrome 下载的所有东西，还有资源嗅探器，可以下载网页的各种东西，很实用。 官方下载不了可以去这个网站找：\n扩展迷\n按照我最开始的那种方式，直接拖拽安装即可。\nPS：以下推荐插件不在详细介绍，只介绍插件名字，可以根据个人喜好安装，但都是我一直在用的。\n3. 哔哩哔哩助手：bilibili.com 综合辅助扩展 经常在b站学习的同学怎么能放过这个\n4. bilibili哔哩哔哩下载助手 同上\n5. Free Download Manager（FDM） 网页下载插件，不要问，就是快~ 另一个同样好用的下载插件：IDM：Internet Download Manager 配合软件使用效果更佳～\n6. 捕捉网页截图 - FireShot 网页截图，各种截\n7. 魂签 各种网站自动签到，原本提供默认几个网站，如果想自己搞一个想签到的网站，需要自己用js写\n8. Dark Reader 非常好用的护眼插件，各种网页一键变色，夜间敲代码再也不怕了\n9. FeHelper(前端助手) 玩前端的必备\n10. AdBlock — 最佳广告拦截工具 拦截所有网页的广告\n4. 真正的进阶操作 1. 效率插件： OneTab Plus:标签效率管理扩展 一键管理所有网页，下次可以一键恢复，再也不用担心网页过多啦！\n扩展管理器（Extension Manager） 一键管理所有插件，再也不用担心插件太多不认识啦！\n书签侧边栏 一键，不对，一下子管理所有书签，再也不用担心找不到想要的书签啦！\n2. GitHub插件： Sourcegraph 一下子说不清楚，用了就知道，可以随意查看文件内容\nGitZip for github 只下载某个特定的文件，而不是整个包都下载\n3. 工具： Google学术搜索按钮 一键搜索你想要的文章\nGoogle 翻译 对于页面单词不认识可以选中翻译\n彩云小译 - 网页翻译插件 另一款好用的翻译插件\n九章刷题小助手 leetcode题解里面都有\nThe IP 本人用的最久的一款插件之一，任何网页在右下角或左下角显示服务器IP地址和所属地\n完~\n","date":1586762559,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779973457,"objectID":"4680ce809ee4d3edd404d227e288639f","permalink":"https://orust.cc/post/chrome-liu-lan-qi-de-zheng-que-da-kai-fang-shi/","publishdate":"2020-04-13T15:22:39+08:00","relpermalink":"/post/chrome-liu-lan-qi-de-zheng-que-da-kai-fang-shi/","section":"post","summary":"记录 Chrome 插件安装、主题配置、常用扩展和效率工具的使用经验。","tags":["# 工具分享"],"title":"Chrome浏览器的正确打开方式","type":"post"},{"authors":null,"categories":null,"content":"1. 海豚、识字与阅读 题目 已知：\n能阅读者是识字的。 海豚不识字。 有些海豚是聪明的。 要求：用归结原理证明“有些聪明者并不能阅读”。\n符号约定 R(x)：x 能阅读。 L(x)：x 识字。 D(x)：x 是海豚。 I(x)：x 是聪明的。 谓词公式 已知条件：\nF1: ∀x(R(x) → L(x)) F2: ∀x(D(x) → ¬L(x)) F3: ∃x(D(x) ∧ I(x)) 目标：\nG: ∃x(I(x) ∧ ¬R(x))\n使用归结反证时，需要加入目标的否定：\n¬G: ∀x(¬I(x) ∨ R(x))\n子句集 编号 子句 来源 1 ¬R(x) ∨ L(x) F1 消去蕴含 2 ¬D(y) ∨ ¬L(y) F2 消去蕴含 3 D(a) F3 Skolem 化 4 I(a) F3 Skolem 化 5 ¬I(z) ∨ R(z) ¬G 归结过程 步骤 归结结果 使用子句 置换 6 ¬L(a) 2, 3 {a/y} 7 ¬R(a) 1, 6 {a/x} 8 R(a) 4, 5 {a/z} 9 □ 7, 8 - 推出空子句，说明“已知条件 + 目标的否定”不可满足，因此目标成立：有些聪明者并不能阅读。\n2. 老李、大李与小李 题目 已知：\n如果 x 是 y 的父亲，并且 y 是 z 的父亲，则 x 是 z 的祖父。 老李是大李的父亲。 大李是小李的父亲。 要求：用归结原理求解上述人员中谁和谁是祖孙关系。\n符号约定 F(x, y)：x 是 y 的父亲。 G(x, z)：x 是 z 的祖父。 L：老李。 D：大李。 X：小李。 谓词公式 已知条件：\nF1: F(x,y) ∧ F(y,z) → G(x,z) F2: F(L,D) F3: F(D,X) 为了先证明存在祖孙关系，目标写为：\nG0: ∃u∃vG(u,v)\n归结反证时加入目标的否定：\n¬G0: ¬G(u,v)\n子句集 编号 子句 来源 1 ¬F(x,y) ∨ ¬F(y,z) ∨ G(x,z) F1 消去蕴含 2 F(L,D) F2 3 F(D,X) F3 4 ¬G(u,v) ¬G0 证明存在祖孙关系 步骤 归结结果 使用子句 置换 5 ¬F(D,z) ∨ G(L,z) 1, 2 {L/x, D/y} 6 G(L,X) 3, 5 {X/z} 7 □ 4, 6 {L/u, X/v} 推出空子句，说明已知条件可以推出“存在祖孙关系”。\n求具体答案 为了求出具体是哪两个人，可以用重言式 ¬G(u,v) ∨ G(u,v) 代替目标否定中的单独否定子句。\n步骤 归结结果 使用子句 置换 4 ¬G(u,v) ∨ G(u,v) 重言式 - 5 ¬F(D,z) ∨ G(L,z) 1, 2 {L/x, D/y} 6 G(L,X) 3, 5 {X/z} 7 G(L,X) 4, 6 {L/u, X/v} 最终得到 G(L,X)，即老李是小李的祖父。\n","date":1585711790,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779976527,"objectID":"7957763adc0b559f5c5eeddc9e878df6","permalink":"https://orust.cc/post/ren-gong-zhi-neng-luo-ji-tui-li-ti-1/","publishdate":"2020-04-01T11:29:50+08:00","relpermalink":"/post/ren-gong-zhi-neng-luo-ji-tui-li-ti-1/","section":"post","summary":"两道一阶谓词逻辑归结题：证明“有些聪明者不能阅读”，并求出老李与小李的祖孙关系。","tags":["# 逻辑推理"],"title":"人工智能-逻辑推理题-1","type":"post"},{"authors":null,"categories":null,"content":"题目 Leetcode 4. 寻找两个有序数组的中位数\n给定两个有序数组 nums1 和 nums2，要求找出这两个数组合并后的中位数，并且时间复杂度要求为 O(log(m+n))。\n思路 这段代码使用的是“二分划分”思路。为了让二分范围更小，先保证 nums1 是较短数组。\n设两个数组总长度为 n1 + n2，左半部分需要放入 k = (n1 + n2 + 1) // 2 个元素。我们在 nums1 中二分切分点 m1，并令 m2 = k - m1，也就是 nums2 的切分点。\n当 nums1[m1] \u0026lt; nums2[m2 - 1] 时，说明 nums1 左边取得太少，需要右移 m1；否则收缩右边界。二分结束后，左半部分最大值就是奇数长度时的中位数；如果总长度为偶数，再取右半部分最小值，二者平均即可。\n代码 class Solution(object): def findMedianSortedArrays(self, nums1, nums2): n1 = len(nums1) n2 = len(nums2) if n1 \u0026gt; n2: return self.findMedianSortedArrays(nums2, nums1) k = (n1 + n2 + 1)//2 left = 0 right = n1 while left \u0026lt; right: m1 = left+(right - left)//2 m2 = k - m1 if nums1[m1] \u0026lt; nums2[m2 - 1]: left = m1 + 1 else: right = m1 m1 = left m2 = k - m1 c1 = max(nums1[m1 - 1] if m1 \u0026gt; 0 else float(\u0026#34;-inf\u0026#34;), nums2[m2 - 1] if m2 \u0026gt; 0 else float(\u0026#34;-inf\u0026#34;)) if (n1 + n2) % 2 == 1: return c1 c2 = min(nums1[m1] if m1 \u0026lt; n1 else float(\u0026#34;inf\u0026#34;), nums2[m2] if m2 \u0026lt; n2 else float(\u0026#34;inf\u0026#34;)) return (c1+c2) / 2 ","date":1585710212,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779974371,"objectID":"29d03ea2db7aae39ef5a57f774ffb936","permalink":"https://orust.cc/post/leetcode-4/","publishdate":"2020-04-01T11:03:32+08:00","relpermalink":"/post/leetcode-4/","section":"post","summary":"使用二分划分较短数组，在 O(log(min(m,n))) 时间内寻找两个有序数组合并后的中位数。","tags":["# Leetcode"],"title":"Leetcode-4. 寻找两个有序数组的中位数","type":"post"},{"authors":null,"categories":null,"content":"欢迎来到 Orust’s Blog，很高兴在这里遇见你。\n关于 Orust 我是 Orust。这个博客会用来沉淀平时的学习笔记、技术实践、读文章时的摘录，以及一些阶段性的思考。\n旧博客里曾经有几篇介绍性文章，包括 关于、关于OnAndOn😊 和 Gridea 默认欢迎页。它们现在合并到这一页中，只保留一处入口，避免列表里出现重复的“关于”内容。\n关于这个博客 这里会优先放我自己写下来的内容，而不是博客模板自带的示例文本。当前博客使用 Hugo 和 github-style 风格，文章主要用 Markdown 维护。\n我希望这块地方更像一个长期可检索的个人知识库：内容可以不完美，但要尽量保留当时解决问题的上下文、关键命令、踩坑记录和后续复盘。\n写作约定 技术文章尽量写清楚背景、问题、尝试过程和最终方案。 笔记类内容可以短一些，但要保留链接、日期和必要的上下文。 迁移自旧站的文章会尽量保留原始发布时间，必要时再做格式修复。 如果后续文章有代码，优先使用带语言标识的 Markdown 代码块。 联系 GitHub: Orustown\n","date":1548345600,"expirydate":-62135596800,"kind":"page","lang":"zh-cn","lastmod":1779973457,"objectID":"4f8ede94661089a2bb01b50b2080c5b0","permalink":"https://orust.cc/post/about/","publishdate":"2019-01-25T00:00:00+08:00","relpermalink":"/post/about/","section":"post","summary":"关于 Orust 和 Orust's Blog：这里会整理个人笔记、技术实践、阅读记录和写作约定。","tags":[],"title":"关于","type":"post"}]