├── README.md ├── notes ├── 1.md ├── 10.md ├── 11.md ├── 12.md ├── 13.md ├── 14.md ├── 15.md ├── 2.md ├── 3.md ├── 4.md ├── 5.md ├── 6.md ├── 7.md ├── 8.md ├── 9.md └── images │ ├── 084855b0-c946-11e8-8843-fa163e7a698c.jpg │ ├── 0fe0d024-a306-11e8-8f8e-fa163e7a698c.png │ ├── 1c5df960-c932-11e8-832e-fa163e7a698c.png │ ├── 2cb28482-a305-11e8-8f8e-fa163e7a698c.png │ ├── 313e5700-a306-11e8-bcef-fa163e7a698c.png │ ├── 34efc1aa-a305-11e8-8eb6-fa163e7a698c.png │ ├── 36acd5f4-a306-11e8-8eb6-fa163e7a698c.png │ ├── 3c4c2578-a306-11e8-a6f3-fa163e7a698c.png │ ├── 40ca72bc-a306-11e8-8f8e-fa163e7a698c.png │ ├── 473e0550-a306-11e8-a6f3-fa163e7a698c.png │ ├── 4c13f1d4-a306-11e8-8f8e-fa163e7a698c.png │ ├── 5abe55a4-a305-11e8-b04d-fa163e7a698c.png │ ├── 5b7cd91a-a306-11e8-89f2-fa163e7a698c.png │ ├── 60225484-ca1d-11e8-8843-fa163e7a698c.png │ ├── 8615a224-a306-11e8-89f2-fa163e7a698c.png │ ├── b575ae8e-a305-11e8-b04d-fa163e7a698c.png │ ├── bb21c362-a306-11e8-b04d-fa163e7a698c.png │ ├── be838be4-a306-11e8-8f8e-fa163e7a698c.png │ ├── c2ceddb2-a305-11e8-a6f3-fa163e7a698c.png │ ├── c3beec5c-a306-11e8-89f2-fa163e7a698c.png │ ├── c68e74da-ca2e-11e8-8843-fa163e7a698c.png │ ├── dea686ac-ca29-11e8-8843-fa163e7a698c (1).png │ ├── dea686ac-ca29-11e8-8843-fa163e7a698c.png │ ├── e76b8242-a305-11e8-b04d-fa163e7a698c.png │ ├── ec89f6d2-a305-11e8-bcef-fa163e7a698c.png │ ├── f26efe8a-a305-11e8-89f2-fa163e7a698c.png │ └── fa1374f4-a305-11e8-bcef-fa163e7a698c.png └── pdf ├── 1.pdf ├── 10.pdf ├── 11.pdf ├── 12.pdf ├── 13.pdf ├── 14.pdf ├── 15.pdf ├── 2.pdf ├── 3.pdf ├── 4.pdf ├── 5.pdf ├── 6.pdf ├── 7.pdf ├── 8.pdf └── 9.pdf /README.md: -------------------------------------------------------------------------------- 1 | # 剑指Offer Python题解 2 | offer-coding-interviews-python 3 | 4 | 记录个人的剑指offer python题解 5 | 6 | 在线阅读[请戳这里](https://codingcat.cn/article/57) 7 | 8 | 现已添加PDF版,离线阅读也ok~ 9 | 10 | 在线编码可以去[牛客网专区](https://www.nowcoder.com/ta/coding-interviews) 11 | 12 | **本仓库只收录了Python语言的题解和题目的简单分析,仅供学习交流使用,更多关于面试的知识、算法题的思路,以及一些题目的详细解答和推导,还请参阅《剑指Offer》这本书,多多支持下原作者。** 13 | 14 | ## 目录 15 | 16 | 本repo按照博客的来组织,几个题合成一篇。 17 | 18 | ### 第1篇 19 | 20 | | 数组 | 二维数组中的查找 21 | 22 | 23 | ### 第2篇 24 | 25 | | 字符串 | 替换空格 26 | 27 | | 链表 | 从尾到头打印链表 28 | 29 | ### 第3篇 30 | 31 | | 树 | 重建二叉树 32 | 33 | | 栈和队列 | 用两个栈实现队列 34 | 35 | | 查找和排序 | 旋转数组的最小数字 36 | 37 | ### 第4篇 38 | 39 | | 递归和循环 | 斐波那契数列 40 | 41 | | 递归和循环 | 跳台阶 42 | 43 | | 递归和循环 | 变态跳台阶 44 | 45 | | 递归和循环 | 矩形覆盖 46 | 47 | ### 第5篇 48 | 49 | | 位运算 | 二进制中1的个数 50 | 51 | | 代码的完整性 | 数值的整数次方 52 | 53 | | 代码的完整性 | 调整数组顺序使奇数位于偶数前面 54 | 55 | ### 第6篇 56 | 57 | | 代码的鲁棒性 | 链表中倒数第k个结点 58 | 59 | | 代码的鲁棒性 | 反转链表 60 | 61 | | 代码的鲁棒性 | 合并两个排序的链表 62 | 63 | | 代码的鲁棒性 | 树的子结构 64 | 65 | ### 第7篇 66 | 67 | | 面试思路 | 二叉树的镜像 68 | 69 | | 画图让抽象形象化 | 顺时针打印矩阵 70 | 71 | | 举例让抽象具体化 | 包含min函数的栈 72 | 73 | | 举例让抽象具体化 | 栈的压入、弹出序列 74 | 75 | | 举例让抽象具体化 | 从上往下打印二叉树 76 | 77 | | 举例让抽象具体化 | 二叉搜索树的后序遍历序列 78 | 79 | | 举例让抽象具体化 | 二叉树中和为某一值的路径 80 | 81 | ### 第8篇 82 | 83 | | 分解让复杂问题简单 | 复杂链表的复制 84 | 85 | | 分解让复杂问题简单 | 二叉搜索树与双向链表 86 | 87 | | 分解让复杂问题简单 | 字符串的排列 88 | 89 | ### 第9篇 90 | 91 | | 时间效率 | 数组中出现次数超过一半的数字 92 | 93 | | 时间效率 | 最小的K个数 94 | 95 | | 时间效率 | 连续子数组的最大和 96 | 97 | | 时间效率 | 整数中1出现的次数(从1到n整数中1出现的次数) 98 | 99 | | 时间效率 | 把数组排成最小的数 100 | 101 | ### 第10篇 102 | 103 | | 时间空间效率的平衡 | 丑数 104 | 105 | | 时间空间效率的平衡 | 第一个只出现一次的字符位置 106 | 107 | | 时间空间效率的平衡 | 数组中的逆序对 108 | 109 | | 时间空间效率的平衡 | 两个链表的第一个公共结点 110 | 111 | ### 第11篇 112 | 113 | | 知识迁移能力 | 数字在排序数组中出现的次数 114 | 115 | | 知识迁移能力 | 二叉树的深度 116 | 117 | | 知识迁移能力 | 平衡二叉树 118 | 119 | | 知识迁移能力 | 数组中只出现一次的数字 120 | 121 | | 知识迁移能力 | 和为S的连续正数序列 122 | 123 | | 知识迁移能力 | 和为S的两个数字 124 | 125 | ### 第12篇 126 | 127 | | 知识迁移能力 | 左旋转字符串 128 | 129 | | 知识迁移能力 | 翻转单词顺序列 130 | 131 | | 抽象建模能力 | 扑克牌顺子 132 | 133 | | 抽象建模能力 | 孩子们的游戏(圆圈中最后剩下的数) 134 | 135 | | 发散思维能力 | 求1+2+3+…+n 136 | 137 | | 发散思维能力 | 不用加减乘除做加法 138 | 139 | ### 第13篇 140 | 141 | | 综合 | 把字符串转换成整数 142 | 143 | | 数组 | 数组中重复的数字 144 | 145 | | 数组 | 构建乘积数组 146 | 147 | | 字符串 | 正则表达式匹配 148 | 149 | | 字符串 | 表示数值的字符串 150 | 151 | | 字符串 | 字符流中第一个不重复的字符 152 | 153 | | 链表 | 链表中环的入口结点 154 | 155 | | 链表 | 删除链表中重复的结点 156 | 157 | ### 第14篇 158 | 159 | | 树 | 二叉树的下一个结点 160 | 161 | | 树 | 对称的二叉树 162 | 163 | | 树 | 按之字形顺序打印二叉树 164 | 165 | | 树 | 把二叉树打印成多行 166 | 167 | | 树 | 序列化二叉树 168 | 169 | | 树 | 二叉搜索树的第k个结点 170 | 171 | | 树 | 数据流中的中位数 172 | 173 | ### 第15篇 174 | 175 | | 栈和队列 | 滑动窗口的最大值 176 | 177 | | 回溯法 | 矩阵中的路径 178 | 179 | | 回溯法 | 机器人的运动范围 180 | 181 | 182 | 183 | 184 | ## notes 185 | 186 | 如有遗漏错误,欢迎指正 187 | 188 | 189 | -------------------------------------------------------------------------------- /notes/1.md: -------------------------------------------------------------------------------- 1 | # 第1篇 2 | 3 | > 知识点:查找 4 | > 复习:线性表顺序查找、二分查找 5 | 6 | 7 | # 二维数组的查找 8 | 9 | ## 题目描述 10 | 11 | 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 12 | 13 | ## 分析 14 | 15 | 一上来想到的方法是,先按第一列做查找,找到行首比该数字小的最大一行,就是该数字所在的行,然后按这一行做查找,找到该数字所在的列。这个想法是错误的,因为根本没有认真审题,没有理解题中给出的是什么样的数组。 16 | 17 | `每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序`,数组可能是这样的: 18 | 19 | ``` 20 | 1 2 8 9 21 | 2 4 9 12 22 | 4 7 10 13 23 | 6 8 11 15 24 | ``` 25 | 26 | 在没有具体给出一个数组的情况下,光看文字着急去空想,错误的方法显然是把整个二维数组当作有序,也就是按从左到右从上到下顺序读一遍的有序。 27 | 28 | 正确的解法是,从某一个角开始查找,然后指针向两个方向移动。如果从左上角开始,由于current target时,说明应当向上移动一格,没有歧义。(从右上角开始同理) 29 | 30 | 代码如下: 31 | 32 | ```python 33 | # -*- coding:utf-8 -*- 34 | class Solution: 35 | def Find(self, target, array): 36 | # write code here 37 | if target is None or array is None: 38 | return False 39 | col = 0 40 | row = len(array)-1 41 | while row >= 0 and col < len(array[0]): 42 | if target == array[row][col]: 43 | return True 44 | elif target > array[row][col]: 45 | col += 1 46 | elif target < array[row][col]: 47 | row -= 1 48 | return False 49 | ``` 50 | 51 | # 查找算法 52 | 53 | 54 | **平均查找长度ASL**:需和指定key进行比较的关键字的个数的期望值,称为查找算法在查找成功时的平均查找长度。 55 | 56 | 对于含有n个数据元素的查找表,查找成功的平均查找长度为: `ASL = Pi*Ci`的和。 57 | 58 |   Pi:查找表中第i个数据元素的概率。 59 |   Ci:找到第i个数据元素时已经比较过的次数。 60 | 61 | 62 | ## 顺序查找 63 | 64 | ```python 65 | def Find(target, values): 66 | for i in range(0, len(values)): 67 | if values[i] == target: 68 | return True 69 | return False 70 | ``` 71 | 72 | ASL= 1/n * (1+2+3+…+n) = (n+1)/2 73 | 时间复杂度O(n) 74 | 75 | 76 | ## 二分查找 77 | 78 | 要求元素必须是有序的。 79 | 最坏的情况是查找到最后才找到,也就是经过了k次二分,n/(2^k)=1, n=2^k, k=log(n),时间复杂度 O(log n) 80 | 81 | 通过循环实现: 82 | 83 | ```python 84 | def Binary(target, values): 85 | left = 0 86 | right = len(values) - 1 87 | while left <= right: 88 | mid = (left + right) // 2 89 | if values[mid] == target: 90 | return mid 91 | elif values[mid] < target: 92 | left = mid + 1 93 | else: 94 | right = mid - 1 95 | return -1 96 | ``` 97 | 98 | 通过递归实现: 99 | ```python 100 | def Binary_recursive(target, values, left, right): 101 | if left > right: 102 | return -1 103 | mid = (left + right) // 2 104 | if target == values[mid]: 105 | return mid 106 | elif target > values[mid]: 107 | return Binary_recursive(target, values, mid + 1, right) 108 | else: 109 | return Binary_recursive(target, values, left, mid - 1) 110 | ``` 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | --- 119 | 120 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/10.md: -------------------------------------------------------------------------------- 1 | # 第 10 篇 2 | 3 | ## 丑数 4 | 5 | ### 问题 6 | 把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。 7 | 8 | ### 思路 9 | 首先明确一下丑数的定义:只包含银子2,3和5的数。所以要判断一个数是否是丑数,可以先用它使劲除以2(如果它%2为0,则把它除以2,循环判断和除),使劲除以3,使劲除以5,这一套下来如果最后只剩1,说明它是丑数。 10 | 11 | 既然能够判断丑数,最笨的方法就是从1开始挨个往后找,直到找到第N个丑数。缺点:每个数都判断了一次,但是有的数肯定不是丑数,还是花费大量时间去判断了。 12 | 13 | 根据定义,除了1之外,丑数应当是另一个丑数乘以2或3或5的结果,所以可以保存之前已求出的丑数,用来生成后续的丑数。 14 | 15 | 理一下思路。有一个数组,按大小存放当前已经找到的丑数。假设当前数组中最大的丑数是M。要找一个新的丑数,肯定是根据列表中当前的数x2/3/5生成出来的。把每个丑数乘以2,然后选第一个大于M的;把每个乘以3,然后选第一个大于M的;把每个乘以5,然后选第一个大于M的。从这三个结果中,选出最小的那个,就是下一个找到的丑数。 16 | 17 | 由于列表有序,不必从头把每个数乘以2,只要记下第一个乘以2之后大于M的位置即可,后面再找,可以从该位置继续往后找。所以可以弄3个标记指针,以代表x2的指针为例,x2指针指向当前第一个x2之后大于M的数,同理x3和x5指针也是。 18 | 19 | 20 | ### 代码 21 | 自己写的笨代码: 22 | ```python 23 | def GetUglyNumber_Solution(self, index): 24 | if index < 7: 25 | return index 26 | numbers = [1,] 27 | two = 0 28 | three = 0 29 | five = 0 30 | while(len(numbers) < index): 31 | while two < len(numbers): 32 | cur_two = numbers[two] * 2 33 | if cur_two <= numbers[-1]: 34 | two += 1 35 | else: 36 | break 37 | while three < len(numbers): 38 | cur_three = numbers[three] * 3 39 | if cur_three <= numbers[-1]: 40 | three += 1 41 | else: 42 | break 43 | while five < len(numbers): 44 | cur_five = numbers[five] * 5 45 | if cur_five <= numbers[-1]: 46 | five += 1 47 | else: 48 | break 49 | numbers.append(min(cur_two, cur_three, cur_five)) 50 | return numbers[-1] 51 | ``` 52 | 53 | 更加简洁的代码: 54 | ```python 55 | if index < 7: 56 | return index 57 | result = [1,] 58 | two = 0 59 | three = 0 60 | five = 0 61 | while len(result) < index: 62 | result.append(min(result[two]*2, result[three]*3, result[five]*5)) 63 | if result[-1] == result[two] * 2: 64 | two += 1 65 | if result[-1] == result[three] * 3: 66 | three += 1 67 | if result[-1] == result[five] * 5: 68 | five += 1 69 | return result[-1] 70 | ``` 71 | 72 | ## 第一个只出现一次的字符 73 | 74 | ### 问题 75 | 在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置 76 | 77 | ### 思路 78 | 最笨的办法,从头开始,对每个字符,都把整个字符串扫一遍看看是不是只出现一次,找到第一个即停止。时间复杂度是O(n^2),肯定会有更好的方法。 79 | 80 | 另一种思路是,开一块额外空间做一个出现次数的计数,对这个串扫一遍就能得到各个字符的出现次数了,计数可以通过哈希表来实现(如Python中的字典就是哈希表来实现的)。然后需要找到第一个只出现一次的字符,这就要求字典中key的顺序应当是字符在串中出现的先后顺序,但是哈希表的key是无序的…… 一种可行的做法是,再扫一遍整个串,扫到每个字符时查一下该字符的计数,找到第一个计数为1的就可以了。总共只需要扫两边字符串,但需要开辟额外的一些空间来做计数。 81 | 82 | 哈希表的实现,可以直接用Python的字典,也可以自己来实现一个。由于这里的字符只是英文字母,因此可以用0-255的ASCII码作为键,开辟一个256长度的数组,用很少的空间实现了哈希表。 83 | 84 | ### 代码 85 | ```python 86 | class Solution: 87 | def FirstNotRepeatingChar(self, s): 88 | if not s: 89 | return -1 90 | dic = {} 91 | for char in s: 92 | if char in dic: 93 | dic[char] += 1 94 | else: 95 | dic[char] = 1 96 | # 最近新了解到的做法,Python在for in中可以这样获取到当前下标,而不用自己建一个计数器了 97 | for index, char in enumerate(s): 98 | if dic[char] == 1: 99 | return index 100 | ``` 101 | 提示:Python中获取一个字符的整数型编码,可以用 ord() 函数,例如 ord('中')=20013, ord('A')=65 。 102 | 103 | ### 扩展 104 | 105 | 一些类似解决方法的问题: 106 | 107 | (1)有两个字符串,从第一个字符串中删除在第二个字符串中出现过的字符。 108 | 构建一个哈希表,把第二个中出现过的字符设置为1,那么只需把第一个字符串扫一遍,每个字符去表里查一查,如果为1说明在第二个串中出现过,则删除。 109 | 110 | (2)删除字符串中重复出现的字符,例如 google 删除后剩下 gole 111 | 构建一个哈希表,只需把字符串扫一遍。扫到某个字符时,先去表里查,如果位置为0,没出现过,则把其位置设置为1,如果位置为1,则从串里删掉此字符 112 | 113 | (3)变位词问题,英语中如果两个单词中出现的字母相同,且每个字母出现的次数也相同,则互为变位词。例如 evil和live。判断是否互为变位词。 114 | 构建哈希表,把第一个单词扫一遍,扫到每个字母时其次数+1。把第二个单词扫一遍,扫到每个字母时其次数-1。最后把哈希表的值检查一遍,如果所有位置都是0,则说明是变位词。 115 | 116 | 117 | ## 数组中的逆序对 118 | 119 | ### 问题 120 | 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。 121 | 122 | ### 思路 123 | 直观思路就是从头开始逐个数字处理,读到一个数字就把它和它之后的进行比较,以O(n^2)时间复杂度解决。 124 | 125 | 另一种可行的思路是,分解来处理。以 7564 为例,按下图所示先逐次两半拆分,然后进行合并和计数。 126 | 127 | ![](./images/bb21c362-a306-11e8-b04d-fa163e7a698c.png) 128 | 129 | 例如将7和5合并,读到有1个逆序对,然后75要排序一下以免后续重复统计;将57和46合并时,按照下面的流程来计数: 130 | 131 | ![](./images/be838be4-a306-11e8-8f8e-fa163e7a698c.png) 132 | 133 | 两个指针分别指向两个数组末尾,并比较大小,如果当前指向的第一个子数组中的数字大于第二个子数组中当前指向的数字,则构成逆序对(说明第一个中的当前指针处最大数大于第二个中的当前指针处最大数,逆序对个数为第二个子数组中剩余数字的个数)。如果小于等于,则不构成逆序对。然后将较大的数字复制到辅助数组中,并将指针往前挪一位。依此类推。 134 | 135 | 总结一下过程:先拆分成子数组,统计子数组内部的逆序对,然后统计两个相邻子数组之间逆序对的数目。 136 | 137 | 138 | ### 代码 139 | 用Python在牛客上超时了没通过(笑哭) 140 | ```python 141 | # -*- coding:utf-8 -*- 142 | class Solution: 143 | def InversePairs(self, data): 144 | # write code here 145 | if not data: 146 | return 0 147 | copy = [ x for x in data ] 148 | # 调用递归 149 | count = self.Func(data, copy, 0, len(copy)-1) 150 | return count % 1000000007 151 | 152 | def Func(self, data, copy, start, end): 153 | if start == end: 154 | copy[start] = data[start] 155 | return 0 156 | length = (end - start) // 2 157 | # 先将整个数据拆分成左右两半子数组,递归去先处理子数组 158 | left = self.Func(data, copy, start, start+length) 159 | right = self.Func(data,copy, start+length+1, end) 160 | 161 | # 子数组处理完成后回到这一层,处理两个子数组的合并 162 | # 两个指针指向两个子数组的末尾 163 | i = start + length 164 | j = end 165 | indexCopy = end 166 | count = 0 167 | # while都未读完 168 | while(i>=start and j>=start+length+1): 169 | # 左比右大,说明产生多个逆序对,个数为右边余下的数字个数 170 | if data[i] > data[j]: 171 | copy[indexCopy] = data[i] 172 | indexCopy -= 1 173 | i -= 1 174 | count += j-start-length 175 | else: 176 | # 左小于或等于右,则没有逆序对 177 | copy[indexCopy] = data[j] 178 | indexCopy -= 1 179 | j -= 1 180 | 181 | # 说明左边有剩余,将余下的追加到合并后的数组里 182 | while i>=start: 183 | copy[indexCopy] = data[i] 184 | indexCopy -= 1 185 | i -= 1 186 | # 说明右边有剩余,将余下的追加到合并后的数组里 187 | while j>=start+length+1: 188 | copy[indexCopy] = data[j] 189 | indexCopy -= 1 190 | j -= 1 191 | # 返回的个数为左右子数组各自的逆序对个数,和这一层的逆序对个数 192 | return left + right + count 193 | ``` 194 | 195 | 196 | ## 两个链表的第一个公共结点 197 | 198 | ### 问题 199 | 输入两个链表,找出它们的第一个公共结点。 200 | 201 | 注意:题目所指的公共结点,并不是仅仅值相同的两个不同结点,而是说地址也相同的同一个结点,即这个结点同时位于两个链表中。 202 | 203 | ### 思路 204 | 最直接的蛮力法,遍历第一个链表,每个结点,都把第二个扫一遍,找到公共结点。复杂度O(mn)。 205 | 206 | ![](./images/c3beec5c-a306-11e8-89f2-fa163e7a698c.png) 207 | 208 | 根据公共结点的定义,分析可以知道,两个链表其实是如图所示的这种情况,也就是说,从第一个公共结点开始,之后的结点都是公共结点。 209 | 210 | 那么就有一些思路啦。 211 | (1)从后往前找。借助辅助空间,将两个链表的结点放入两个栈里,然后比较栈顶元素,如果相同则弹出,直到找到最后一个相同的结点,就是第一个公共结点。 212 | 时间复杂度O(m+n),额外需要空间也是O(m+n) 213 | 214 | (2)另一种思路是,首先两个各遍历一遍,得到两个链表的长度。计算长度差,让长的先走一段,走到两个链表余下长度相同时,两个链表同时继续走,并比较元素,就能找到第一个相同元素。 215 | 216 | 第二种思路比第一种思路要好一些,因为不需要额外的空间了,而且只需遍历两个链表两遍,和第一种的时间复杂度一样。 217 | 218 | ### 代码 219 | ```python 220 | # -*- coding:utf-8 -*- 221 | # class ListNode: 222 | # def __init__(self, x): 223 | # self.val = x 224 | # self.next = None 225 | class Solution: 226 | def FindFirstCommonNode(self, pHead1, pHead2): 227 | if not pHead1 or not pHead2: 228 | return None 229 | len1 = 0 230 | len2 = 0 231 | ptr1 = pHead1 232 | ptr2 = pHead2 233 | while ptr1: 234 | len1 += 1 235 | ptr1 = ptr1.next 236 | while ptr2: 237 | len2 += 1 238 | ptr2 = ptr2.next 239 | ptr1 = pHead1 240 | ptr2 = pHead2 241 | while len1 > len2: 242 | ptr1 = ptr1.next 243 | len1 -= 1 244 | while len2 > len1: 245 | ptr2 = ptr2.next 246 | len2 -= 1 247 | while len1 > 0: 248 | if id(ptr1) == id(ptr2): 249 | return ptr1 250 | else: 251 | ptr1 = ptr1.next 252 | ptr2 = ptr2.next 253 | len1 -= 1 254 | ``` 255 | 256 | 257 | 258 | 259 | 260 | ------ 261 | 262 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/11.md: -------------------------------------------------------------------------------- 1 | # 第 11 篇 2 | 3 | 4 | ## 数字在排序数组中出现的次数 5 | 6 | ### 问题 7 | 统计一个数字k在排序数组中出现的次数。 8 | 9 | ### 思路 10 | 11 | 要充分利用数字已排序这个特点。最基本的思路是,二分查找,找到一个k,然后向前后顺序计数,找到所有k的个数。 12 | 13 | 如果要更加高效一点的话,可以变换思路,用二分查找的方式,找到首尾的两个k,首尾下标之间的距离就是k的个数。 14 | 15 | 以查找开头第一个k为例,二分查找分两半,看mid和k的大小比较,如果mid比k大,说明k应该在前一半,则递归去前一半;如果mid比k小,说明k在后一半,则递归去后一半。如果mid和k相等,则判断mid是否是第一个k,就看mid的前一个是否为k,如果不是k,则找到啦,返回mid,如果是,则乖乖去递归前一半。查找最末尾的k同上思路。 16 | 17 | 注意写的时候要留意边界条件。 18 | 19 | ### 代码 20 | 21 | 先皮一下,python中直接对数组 arr.count(k)就能完成这个要求了(捂脸) 22 | 23 | 正经代码: 24 | ```python 25 | # -*- coding:utf-8 -*- 26 | class Solution: 27 | def GetNumberOfK(self, data, k): 28 | length = len(data) 29 | # write code here 30 | if data and k: 31 | first_k = self.get_first_k(data, length, k, 0, length - 1) 32 | last_k = self.get_last_k(data, length, k, 0, length - 1) 33 | if first_k > -1 and last_k > -1: 34 | return last_k - first_k + 1 35 | return 0 36 | 37 | def get_first_k(self, data, length, k, start, end): 38 | if start > end: 39 | return -1 40 | mid = int((start + end) / 2) 41 | if data[mid] < k: 42 | start = mid + 1 43 | elif data[mid] > k: 44 | end = mid - 1 45 | elif data[mid] == k: 46 | if (mid > 0 and data[mid-1] != k) or mid == 0: 47 | return mid 48 | else: 49 | end = mid - 1 50 | return self.get_first_k(data, length, k, start, end) 51 | 52 | def get_last_k(self, data, length, k, start, end): 53 | if start > end: 54 | return -1 55 | mid = int((start + end) / 2) 56 | if data[mid] < k: 57 | start = mid + 1 58 | elif data[mid] > k: 59 | end = mid - 1 60 | elif data[mid] == k: 61 | if (mid < length - 1 and data[mid+1] != k) or mid == length -1: 62 | return mid 63 | else: 64 | start = mid + 1 65 | return self.get_last_k(data, length, k, start, end) 66 | 67 | ``` 68 | 69 | 70 | ## 二叉树的深度 71 | 72 | ### 问题 73 | 输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 74 | 75 | ### 思路 76 | 遍历整个树,记录下遍历的路径,从而找到一个最长路径,得到深度。这种思路需要的代码量稍大,略微复杂一点。 77 | 78 | 换个娇爽分析一下情况,如果树只有1个节点,其深度为1;如果根节点只有左子树,则深度为左子树的深度+1;如果只有右子树,则深度为右子树深度+1;如果左右都有,则深度为左右的深度的较大值+1。 按此思路,可写一个递归的方法。 79 | 80 | ### 代码 81 | ```python 82 | # -*- coding:utf-8 -*- 83 | # class TreeNode: 84 | # def __init__(self, x): 85 | # self.val = x 86 | # self.left = None 87 | # self.right = None 88 | class Solution: 89 | def TreeDepth(self, pRoot): 90 | # write code here 91 | if not pRoot: 92 | return 0 93 | left = self.TreeDepth(pRoot.left) 94 | right = self.TreeDepth(pRoot.right) 95 | return max(left, right) + 1 96 | ``` 97 | 98 | ## 平衡二叉树 99 | ### 问题 100 | 输入一棵二叉树,判断该二叉树是否是平衡二叉树。 101 | 102 | 平衡二叉搜索树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 103 | 104 | ### 思路 105 | 结合上一题,思路是对每个节点,调用 TreeDepth 来判断其左右子树的深度差是否不超过1。 106 | 虽然实现简单,但问题在于,对递归判断每个节点时,调用 TreeDepth 会遍历该节点的每个子节点,导致底层的子节点多次重复访问,效率低。 107 | 108 | 思考是否有只需遍历一次的解法? 上面的思路中,我们是先判断根节点的树是否平衡,再判断子节点是否平衡,会导致判断根节点时访问过子节点了,判断子节点时又重复访问子节点。这实际上是先序遍历!所以,如果变为后序遍历呢?先访问左右子节点判断是否平衡,再判断根节点是否平衡,在访问子节点后记录子节点的深度,根节点时就可以直接获取此深度值,避免了对子节点的重复访问。 109 | 110 | ### 代码 111 | ```python 112 | # -*- coding:utf-8 -*- 113 | # class TreeNode: 114 | # def __init__(self, x): 115 | # self.val = x 116 | # self.left = None 117 | # self.right = None 118 | class Solution: 119 | def IsBalanced_Solution(self, pRoot): 120 | # write code here 121 | if not pRoot: 122 | return True 123 | # 判断左子树是否平衡,不平衡则直接终止判断,返回false 124 | is_left = self.IsBalanced_Solution(pRoot.left) 125 | if not is_left: 126 | return False 127 | # 判断右子树是否平衡,不平衡则直接终止判断,返回false 128 | is_right = self.IsBalanced_Solution(pRoot.right) 129 | if not is_right: 130 | return False 131 | # 左右均平衡,则计算深度,判断当前为根的树是否平衡 132 | left_depth = pRoot.left.depth if pRoot.left else 0 133 | right_depth = pRoot.right.depth if pRoot.right else 0 134 | pRoot.depth = max(left_depth, right_depth) + 1 135 | return abs(left_depth - right_depth) <= 1 136 | ``` 137 | 138 | 139 | ## 数组中只出现一次的数字 140 | 141 | ### 问题 142 | 一个整型数组里除了两个数字之外,其他的数字都出现了偶数次。请写程序找出这两个只出现一次的数字。要求:时间复杂度 O(n),空间复杂度 O(1) 143 | 144 | ### 思路 145 | 容易想到的方法,用list或set来记住两个数字,把数组读一遍,遇到当前在list里数字就把它去掉,最后剩下的就是两个只出现一次的数字。但这样导致空间复杂度超出限制了。 146 | 147 | 原书中的思路是,利用了二进制位运算xor的特性。同一个数字的二进制和它自己进行xor,最终一定会相互抵消(=0),所以说,假如这题是只有一个只出现了一次的数字,则可以从头对每个数累计进行xor,由于出现了两次的数都抵消了,最后剩下的就是那个只出现了一次的数字了。 148 | 149 | 那现在有两个只出现了一次的数字,该如何操作?由于这两个数字肯定不相同,所以对全部数字进行一遍xor之后,结果的值必不为0,而且结果是这两个数字进行xor之后的结果。所以,在结果二进制中,找到为1的一位,说明这两个数字的二进制,在这一位上必定不相同。因此,就根据这一位是1或是0,把整个数组划分为两个小数组,能够保证这两个数字分别出现在两个数组中(其他出现两次的数字,两个相同数字必定会被划到同一个子数组中,也就能抵消),然后两个小数组再各进行一次全面xor,就可以得到这两个数字了。 150 | 151 | 总共需要先把大数组xor一遍,找到哪一位二进制可用于区分,然后不必物理上把大数组划开,第二趟仍然可以把大数组过一遍,xor时根据这一位是0还是1,分别与不同的累积进行xor即可。 152 | 153 | (感觉这个方法也挺折腾的……) 154 | 155 | ### 代码 156 | 偷懒就用简单的方法来写了…… 157 | ```python 158 | # -*- coding:utf-8 -*- 159 | class Solution: 160 | # 返回[a,b] 其中ab是出现一次的两个数字 161 | def FindNumsAppearOnce(self, array): 162 | data = set() 163 | for d in array: 164 | if d in data: 165 | data.remove(d) 166 | else: 167 | data.add(d) 168 | return list(data) 169 | ``` 170 | 171 | ## 和为s的两个数字 172 | ### 问题 173 | 输入一个递增排序的数组,和一个数字s,在数组中查找两个数,使它们的和正好是s。如果有多对和为s,输出两个数的乘积最小的。 174 | 175 | ### 思路 176 | 最基本的是从前往后,固定住某一位,然后判断它后面各位与它的和是否等于s,是的话就找到了一对。 177 | 178 | 但由于这个题只要求找一对即可,不用找全,所以可以有更优化的方法。两个指针分别指向首尾,两个值相加如果大于s,则让后面的指针往前走一走,如果小于s则让前面的指针往后走一走,直到找到和为s,或者指针重合也没找到。 179 | 180 | 为什么这样可行呢?如果值小于s,为什么不让后面的指针往后走,和也能变大呢?这是因为,后面的指针如果能够往后走(说明它曾经往前走过),是因为它加上数组最小的值之后还是大于s了,所以这时如果让后面指针往后走,结果必定大于s。如果值大于s,为什么不让前面的指针往前走呢?这是因为,如果前面的指针能往前走(说明它曾经往后走过),是因为它加上数组最大的值之后还是小于s了,这时往前走了,结果也必定小于s,没有必要。所以,上述方法虽然看起来略让人怀疑,但是可行的。 181 | 182 | ### 代码 183 | ```python 184 | # -*- coding:utf-8 -*- 185 | class Solution: 186 | def FindNumbersWithSum(self, array, tsum): 187 | if not array or not tsum: 188 | return [] 189 | front = 0 190 | rear = len(array) - 1 191 | while front < rear: 192 | added = array[front] + array[rear] 193 | if added == tsum: 194 | return [array[front], array[rear]] 195 | elif added < tsum: 196 | front += 1 197 | else: 198 | rear -= 1 199 | return [] 200 | ``` 201 | 202 | ### 补充 203 | 为什么从两端往中间找,找到的第一组满足条件的,就是乘积最小的呢? 204 | 试想 205 | ``` 206 | xy - (x-a)*(y+b) = xy - xy + bx - ay - ab = -b(a-x) - ay 207 | ``` 208 | 肯定小于0,也就是说xy比xy向里逼近一点的乘积要更小。 209 | 210 | 如果要找乘积最大的一对,用变量存下当前找到的一对,继续往里直到front和rear重合,每找到新的就覆盖变量,就能找到最里面的满足条件的一对。 211 | 212 | 213 | ## 和为s的连续正数序列 214 | 215 | ### 问题 216 | 有了前面的问题,再变得难一点: 不再局限为两个数字的和了,也不局限在给定数组了,而是要在1,2,3……的正数序列中,找到连续的子序列(至少包含两个数),使得其中的数之和为s;而且不光要找到一对,要找到所有的和为s的连续正数序列。 217 | 218 | ### 思路 219 | 原始的容易想到的思路就是,从头开始,对每一个数字,从它开始往后找序列,直到找到或者序列的和大于s了,就把前面的指针往后挪一个,继续找。直到指针指向的值大于s的一半了,说明再往后不会有和为s的了,就停止找下去。 220 | 221 | 这样的思路比较简单,但可能会有一些多余的计算。 222 | 223 | 仍然延续前面的思路,用两个指针,一个small,一个big。开始时,small指向1,big指向2。我们以s=9为例,一开始{1,2},和为3,小于s,则让big往后移动,{1,2,3},再继续{1,2,3,4},此时和为10,大于s,则让small往后移动,{2,3,4},等于9,找到一个。然后继续增加big,{2,3,4,5}大于s,让small往后,{3,4,5}大于s,让small往后,{4,5}等于9,找到一个。再让big往后,{4,5,6}大于s,让small往后,{5,6},此时small已经大于9的一半了,不再继续寻找。 224 | 225 | 这样不会遗漏吗?比如,当和大于s时,为什么不让big往前走?是因为big之所以到这里,是由于它之前的和小于s了,再让big往前,和还是小于s;当小于s时为什么不让small往前走?是因为small之所以到这里,是由于它之前的和大于s了,再让small往前,和还是大于s。 226 | 227 | > 更多可了解 https://blog.csdn.net/qq_41822235/article/details/82109081 (p.s.拿python刷题真的是写代码一时爽,内存和耗时伤不起啊,给耗时几毫秒的c++跪了) 228 | 229 | ### 代码 230 | ```python 231 | # -*- coding:utf-8 -*- 232 | class Solution: 233 | def FindContinuousSequence(self, tsum): 234 | if not tsum: 235 | return [] 236 | result = [] 237 | small = 1 238 | big = 2 239 | current_sum = small + big 240 | while small < (tsum + 1) / 2: 241 | while current_sum < tsum: 242 | big += 1 243 | current_sum += big 244 | while sum(range(small, big + 1)) > tsum: 245 | current_sum -= small 246 | small += 1 247 | if current_sum == tsum and small != big: 248 | result.append(list(range(small, big + 1))) 249 | big += 1 250 | current_sum += big 251 | return result 252 | ``` 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | ------ 261 | 262 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ 263 | 264 | 265 | 266 | -------------------------------------------------------------------------------- /notes/12.md: -------------------------------------------------------------------------------- 1 | # 第 12 篇 2 | 3 | ## 翻转单词顺序 4 | ### 问题 5 | 输入一个英文句子,翻转句子中单词的顺序,而单词中的字母顺序不变。例如,“student. a am I” 应该是“I am a student.”(标点也当作字母来处理,和它前面的单词作为一个整体) 6 | 7 | ### 思路 8 | 写一个字符串数组翻转函数,第一遍先把整个句子按字母翻转一遍,得到 I ma a .tneduts,第二遍再以空格为分隔,对每个单词运行一遍翻转,得到 I am a student. ,还是很基础的感觉。 9 | 10 | ### 代码 11 | 先来个python偷懒的写法,一行搞定: 12 | ```python 13 | # -*- coding:utf-8 -*- 14 | class Solution: 15 | def ReverseSentence(self, s): 16 | return ' '.join(s.split(' ')[::-1]) if s.strip() else s 17 | # strip的目的是防止输入全是空格导致输出为空 18 | ``` 19 | 20 | 回归老实点的写法: 21 | ``` 22 | # -*- coding:utf-8 -*- 23 | class Solution: 24 | def ReverseSentence(self, s): 25 | if not s: 26 | return "" 27 | s = list(s) 28 | # 第一步,翻转整个数组 29 | s = self.reverse_array(s, 0, len(s)-1) 30 | end = 0 31 | # 第二步,翻转各个单词 32 | while end < len(s): 33 | if s[end] == ' ': 34 | end += 1 35 | else: 36 | start = end 37 | while end < len(s) and s[end] != ' ': 38 | end += 1 39 | s = self.reverse_array(s, start, end-1) 40 | return ''.join(s) 41 | 42 | def reverse_array(self, array, start, end): 43 | print(start, end) 44 | while start < end: 45 | array[start], array[end] = array[end], array[start] 46 | start += 1 47 | end -= 1 48 | return array 49 | ``` 50 | 51 | ## 左旋转字符串 52 | 53 | ### 问题 54 | 字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。 55 | 56 | ### 思路 57 | 可以把字符串切分成几块,重新排序一下输出就行;另一种做法是,这个问题类比成单词翻转,abc是一个单词,xyzdef是一个单词,先分别翻转这两块,再整个串翻转(主要体现了迁移能力) 58 | 59 | ### 代码 60 | 先来python的蒂花之秀系列: 61 | ```python 62 | class Solution: 63 | def LeftRotateString(self, s, n): 64 | return s[n:] + s[:n] 65 | ``` 66 | 67 | 用多次翻转的方式来写: 68 | ```python 69 | # -*- coding:utf-8 -*- 70 | class Solution: 71 | def LeftRotateString(self, s, n): 72 | if not s: 73 | return '' 74 | s = list(s) 75 | s = self.reverse_array(s, 0, n-1) 76 | s = self.reverse_array(s, n, len(s)-1) 77 | s = self.reverse_array(s, 0, len(s)-1) 78 | return ''.join(s) 79 | 80 | def reverse_array(self, array, start, end): 81 | print(start, end) 82 | while start < end: 83 | array[start], array[end] = array[end], array[start] 84 | start += 1 85 | end -= 1 86 | return array 87 | ``` 88 | 89 | ## n个骰子的点数 90 | ### 问题 91 | n个骰子扔地上,所有骰子朝上一面的点数之和为s,输入n,打印出s的所有可能值出现的概率。 92 | 93 | ### 思路 94 | 穷举的话,共有 6^n 种可能的结果,对每一种算出和s(s的范围肯定在n到6n之间),统计每个s值出现的次数除以 6^n 即可,效率很低,因为固定外层的点数1-6每次内层都要循环一遍,感觉有蛮多重复的。 95 | 96 | 另一思路是,假设有一个数组A,第n位置记录了当前和为n出现的次数,假设A目前只记录了第一个骰子的结果,也就是1-6位置的值各为1,那么现在加入下一个骰子,下一个骰子的值取1-6,那么对数组的修改就是,新的数组B的第n个位置的出现次数,应当是数组A的n-1,n-2,n-3,n-4,n-5,n-6位置的次数的和(对应于当前骰子掷出1-6点的情况),所以按此规则根据A来得到数组B。再加入一个骰子,依然是可以根据B得出新的C,直到加满全部六个骰子。(实际工程中可以复用AB两个数组轮番交替) 97 | 98 | ### 代码 99 | 穷举实现的话,可以用递归的方式,第一层先固定第一个骰子的点数分别为1-6,然后传入更深一层的去计算剩余5个的情况,依此类推。用一个全局的数组(长为6n-n+1)记录各个和出现的次数,最后去除以 6^n 来得到概率。 100 | 101 | 针对上面的思路2,写代码: 102 | ```python 103 | def prob(n): 104 | if n <= 0: 105 | return 0 106 | # 没有把骰子最大点数写死 107 | max_num = 6 108 | prob = [[0, ] * (max_num * n + 1)] * 2 109 | # flag 标记当前使用哪个累计数组 110 | flag = 0 111 | # 先把第一个骰子的情况写入 112 | for i in range(1, max_num + 1): 113 | prob[flag][i] = 1 114 | # 后面每个骰子 115 | for i in range(2, n+1): 116 | # 清空数组 117 | prob[1-flag] = [0, ] * (max_num * n + 1) 118 | # 对第i个骰子,其和的范围必定是从 i 到 max_num * i,只需计算这个范围即可 119 | for j in range(i, max_num * i + 1): 120 | if j - max_num > 0: 121 | prob[1-flag][j] = sum(prob[flag][j-max_num:j]) 122 | else: 123 | prob[1-flag][j] = sum(prob[flag][:j]) 124 | flag = 1 - flag 125 | print(prob[flag]) 126 | 127 | # 例如,prob(3)输出 128 | # [0, 0, 0, 1, 3, 6, 10, 15, 21, 25, 27, 27, 25, 21, 15, 10, 6, 3, 1] 129 | ``` 130 | 131 | ## 扑克牌顺子 132 | ### 问题 133 | 扑克牌中,大\小王可以看成任何数字,并且A看作1,J为11,Q为12,K为13,抽五张牌判断是否为顺子。 例如抽到 [大王, 1,3,4,5] 就是顺子。 134 | 135 | ### 思路 136 | 思路就是,大小王可以视作0,然后把数组排序。从头开始读数组,统计0的个数,然后读后面的数字,相邻两个数字之间如有间隔差值,则用0的个数来填补,如果后面的数字本来就是连续的则肯定ok,如果中间有不连续且刚好能被0填补,则也是顺子。 137 | 138 | 另外注意一点,相邻两个数字之间差值如果为0,也是不连续的,这种情况不能用0来填补,直接返回不连续。 139 | 140 | ### 代码 141 | ```python 142 | # -*- coding:utf-8 -*- 143 | class Solution: 144 | def IsContinuous(self, numbers): 145 | if not numbers: 146 | return False 147 | numbers.sort() 148 | num_of_0 = 0 149 | for i in range(0, len(numbers)-1): 150 | if numbers[i] == 0: 151 | num_of_0 += 1 152 | else: 153 | delta = numbers[i+1] - numbers[i] 154 | if delta < 1: 155 | return False 156 | elif delta > 1: 157 | num_of_0 -= (delta - 1) 158 | if num_of_0 < 0: 159 | return False 160 | return True 161 | ``` 162 | 163 | ## 圆圈中最后剩下的数字 164 | ### 问题 165 | 0,1,2.....,n-1 这n个数字排成一圈,从0开始每次删除第m个数字,求剩下的最后一个数字。 166 | 167 | 例如,0,1,2,3,4这5个数字,如果m=3,则依此删除的是 2, 0, 4, 1, 3,最后剩下的是3. 168 | 169 | ### 思路 170 | 本题是有名的约瑟夫(Josephuse)环问题。 171 | 172 | 解法一,模拟环的样子。使用环形链表,或者单链表加重置到头部的方式,来模拟这个圆圈,然后就按照题中所述操作删除元素,直到最后剩下一个。缺点是,要多开辟链表的空间O(n),还要把链表走很多圈,时间复杂度O(mn)。这种思路容易想到,但是代码实现上稍微要多写一点,而且效率略微有点低。 173 | 174 | 另一种解法,寻找规律,找到一个递推公式,从而快速完成计算。 175 | ![](./images/1c5df960-c932-11e8-832e-fa163e7a698c.png) 176 | 177 | ### 代码 178 | 这个代码写得还不错(至少时间只要几十ms,比我运行了600ms的要好很多……) 179 | ```python 180 | # -*- coding:utf-8 -*- 181 | class Solution: 182 | def LastRemaining_Solution(self, n, m): 183 | # write code here 184 | if n < 1: 185 | return -1 186 | con = range(n) 187 | final = -1 188 | start = 0 189 | while con: 190 | k = (start + m - 1) % n 191 | final = con.pop(k) 192 | n -= 1 193 | start = k 194 | return final 195 | ``` 196 | 197 | ## 求 1 + 2 + 3 + ... + n 198 | ### 问题 199 | 求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。 200 | 201 | ### 思路 202 | 严苛的限制条件导致立马想到的思路都不能用了:直接公式求,必须用到乘法;循环要用到while或for;递归需要用到if来判断何时停止。 203 | 204 | **短路原理+递归** 205 | 递归,但利用短路原理来作为递归终止的条件。当&&先左半边判断ans为0后,右边就不执行了,实现了递归的终止 206 | ```c++ 207 | class Solution { 208 | public: 209 | int Sum_Solution(int n) { 210 | int ans = n; 211 | ans && (ans += Sum_Solution(n - 1)); 212 | return ans; 213 | } 214 | }; 215 | ``` 216 | 注意 python 的写法。当and和or等短路运算符,用作普通值而不是布尔值时,短路运算符的返回值是最后一次评估的参数。也就是说下面的句子中,如果ans为0,则temp为0, 如果ans不为0, 则temp就是递归了之后的值。 217 | 218 | ```python 219 | # -*- coding:utf-8 -*- 220 | class Solution: 221 | def Sum_Solution(self, n): 222 | ans = n 223 | temp = ans and self.Sum_Solution(n-1) 224 | ans = ans + temp 225 | return ans 226 | ``` 227 | 228 | 剑指offer书里面写的几种方法,大多和c++的语言特性本身有关了,不太很通用就不看了吧。 229 | 230 | 231 | ## 不用加减乘除做加法 232 | ### 问题 233 | 写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/ 四则运算符号。 234 | 235 | ### 思路 236 | 通过位运算的方法: 237 | 238 | 首先看十进制是如何做的: 5+7=12,三步走 239 | 第一步:相加各位的值,不算进位,得到2。 240 | 第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。 241 | 第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。 242 | 243 | 同样我们可以用三步走的方式计算二进制值相加: 5-101,7-111 244 | 245 | 第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。 246 | 247 | 第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111) << 1。 248 | 249 | 第三步重复上述两步, 各位相加 010^1010=1000,进位值为100=(010&1010) << 1。 继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。 250 | 251 | ### 代码 252 | 253 | ```java 254 | public class Solution { 255 | public int Add(int num1,int num2) { 256 | while (num2!=0) { 257 | int temp = num1^num2; 258 | num2 = (num1&num2)<<1; 259 | num1 = temp; 260 | } 261 | return num1; 262 | } 263 | } 264 | ``` 265 | 拿python刷题的遇到位运算基本感觉要哭晕在厕所…… 266 | ```python 267 | # -*- coding:utf-8 -*- 268 | class Solution: 269 | def Add(self, a, b): 270 | while(b): 271 | a, b = (a^b) & 0xFFFFFFFF,((a&b)<<1) & 0xFFFFFFFF 272 | return a if a<=0x7FFFFFFF else ~(a^0xFFFFFFFF) 273 | ``` 274 | 补充一下python位运算: 275 | ```python 276 | x >> y # 返回 x 向右移 y 位得到的结果 277 | x << y # 返回 x 向左移 y 位得到的结果 278 | x & y # 与操作,xy对应的每一位,只有都为1时才为1 279 | x | y # 或操作,xy对应的每一位,只有都为0时才为0 280 | ~x # 按位取反操作,x的每一位如果为0则变为1,如果为1则变为0,从十进制来看,结果是 -x - 1(例如,~8 = -9) 281 | x ^ y # 异或运算,xy对应的每一位,数字不同则为1,数字相同则为0 282 | ``` 283 | 284 | 285 | 286 | 287 | 288 | ------ 289 | 290 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/13.md: -------------------------------------------------------------------------------- 1 | # 第 13 篇 2 | 3 | ## 把字符串转换成整数 4 | 5 | ### 问题 6 | 将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。 7 | 8 | ### 思路 9 | 逐个位来判断和累加计算就行,但需要注意各种各样的特殊情况要考虑周全。 10 | 比如,以+ - 开头的、字符串不能出现字母等其他符号、+-不能出现在中间,以及空字符串、只有+-字符的字符串等。 11 | 12 | ### 代码 13 | ```python 14 | # -*- coding:utf-8 -*- 15 | class Solution: 16 | def StrToInt(self, s): 17 | # write code here 18 | minus = 1 19 | number = 0 20 | if not s: 21 | return number 22 | if s[0] == '+': 23 | if len(s) > 1: 24 | s = s[1:] 25 | else: 26 | return 0 27 | elif s[0] == '-': 28 | minus = -1 29 | if len(s) > 1: 30 | s = s[1:] 31 | else: 32 | return 0 33 | for i in range(0, len(s)): 34 | if '0' <= s[i] <= '9': 35 | number = number * 10 + ord(s[i]) - ord('0') 36 | else: 37 | return 0 38 | return number * minus 39 | ``` 40 | 41 | ## 数组中重复的数字 42 | 43 | ### 问题 44 | 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。 45 | 46 | ### 思路 47 | 基本的思路就是建一个hash表,读数先判断是否重复,不重复就放进去读下一个,直到找到第一个重复的。 48 | 49 | 另外可以进行排序,然后判断是否有相邻两个数重复。 50 | 51 | 但注意题目中条件,所有数字都在0到n-1范围内。因此无需开辟额外空间,可以直接在数组上做标记。读到数字时,将数组中该数字作为下标的位置的数字,减去n,作为标记。下次读到相同数字,去其下标位置,发现数字值小于0,说明已经被标记过,那么这个就是重复数字了。 52 | 53 | 一些细节:之所以不用加上n的方法来标记,是因为可能会导致数字过大溢出(减去n就不会,因为只要n不溢出, -n-1 就不溢出, -n-1+一个正数 就也不会溢出)。也不能乘以-1来做标记,因为这种方法对0就没法做标记了。 54 | 55 | ### 代码 56 | ```python 57 | # -*- coding:utf-8 -*- 58 | class Solution: 59 | # 返回值格式:函数返回True/False,找到任意重复的一个值并赋值到duplication[0] 60 | def duplicate(self, numbers, duplication): 61 | length = len(numbers) 62 | for num in numbers: 63 | if num >= 0: 64 | # 直接按num为下标去找 65 | index = num 66 | else: 67 | # 如果此处已被标记,则下标要按此处的值 num+length 去找 68 | index = num + length 69 | if numbers[index] < 0: 70 | # 找到重复 71 | duplication[0] = numbers[index] + length 72 | return True 73 | else: 74 | # 不重复,则做下标记 75 | numbers[index] -= length 76 | return False 77 | ``` 78 | 79 | ## 构建乘积数组 80 | ### 问题 81 | 给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。 82 | 83 | ### 思路 84 | 如果可以使用除法,则先求出A所有元素的积,再依此对每个位置做除法就行(注意考虑到0的情况!) 85 | 86 | 让我们以一种清楚点的方式来看这个题,其实对B的每一个元素,都可以以下标i,把整个乘积分为前后两个部分: 87 | 88 | ![](./images/084855b0-c946-11e8-8843-fa163e7a698c.jpg) 89 | 90 | 具体到实现上,可以先做左下部分,即做一个循环,从上往下,B中当前元素的值,是A中对应的一个值和前一个B的值相乘得到。然后再对右上部分做一个循环,此时就要单独开辟一个temp来存储累乘值,从下往上把右上部分的值补全。 91 | 92 | ### 代码 93 | ``` 94 | # -*- coding:utf-8 -*- 95 | class Solution: 96 | def multiply(self, A): 97 | if not A: 98 | return [] 99 | B = [1] * len(A) 100 | # 先从上往下累乘左下半 101 | for i in range(1, len(A)): 102 | # 无需额外开辟temp存储累乘值,因为累乘值就是B[i-1] 103 | B[i] = B[i-1] * A[i-1] 104 | # 再从下往上累乘右上半 105 | temp = 1 # 需要额外开辟temp来存储累乘值了 106 | for i in range(len(A)-2, -1, -1): 107 | temp *= A[i+1] 108 | B[i] *= temp 109 | return B 110 | ``` 111 | 112 | ## 正则表达式匹配 113 | ### 问题 114 | 请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配 115 | 116 | ### 思路 117 | 重点是要思路清晰吧……只能说很容易就忽略各种各样的情况,导致无法通过所有用例。 118 | 119 | 对初始输入的判断: 120 | 如果s和pattern都为空,则True;如果s不空,而pattern为空,则直接False. 121 | 122 | 然后来读取pattern中的每个字符: 123 | - 如果当前pattern字符的后面不是\* ,则说明只可匹配1个 124 | - 如果当前pattern字符是\.的话,可匹配1个任意字符 125 | - 如果不是点,则只能匹配给出的字符,若不匹配则直接返回 126 | - 完成后两者均往后+1,继续判断 127 | - 如果当前pattern字符的后面是\* ,说明当前pattern字符可匹配0-任意多个。 128 | - 如果当前pattern字符与串的字符不一致,而且当前字符不是点,则当作是 * 的不匹配情况,pattern往后+2而字符串不变 129 | - 否则其他情况: 130 | - 要么共匹配0个,pattern往后+2,字符串不变 131 | - 要么共匹配1个,字符串往后+1, pattern 往后+2 132 | - 要么先匹配1个,留待继续匹配,此时字符串+1, pattern不变 133 | - 然后继续匹配后面的是否match; 134 | 135 | ### 代码 136 | ```python 137 | # -*- coding:utf-8 -*- 138 | class Solution: 139 | # s, pattern都是字符串 140 | def match(self, s, pattern): 141 | # 如果s与pattern都为空,则True 142 | if len(s) == 0 and len(pattern) == 0: 143 | return True 144 | # 如果s不为空,而pattern为空,则False 145 | elif len(s) != 0 and len(pattern) == 0: 146 | return False 147 | # 如果s为空,而pattern不为空,则需要判断 148 | elif len(s) == 0 and len(pattern) != 0: 149 | # pattern中的第二个字符为*,则pattern后移两位继续比较 150 | if len(pattern) > 1 and pattern[1] == '*': 151 | return self.match(s, pattern[2:]) 152 | else: 153 | return False 154 | # s与pattern都不为空的情况 155 | else: 156 | # pattern的第二个字符为*的情况 157 | if len(pattern) > 1 and pattern[1] == '*': 158 | # s与pattern的第一个元素不同,则s不变,pattern后移两位,相当于pattern前两位当成空 159 | if s[0] != pattern[0] and pattern[0] != '.': 160 | return self.match(s, pattern[2:]) 161 | else: 162 | # 如果s[0]与pattern[0]相同,且pattern[1]为*,这个时候有三种情况 163 | # pattern后移2个,s不变;相当于把pattern前两位当成空,匹配后面的 164 | # pattern后移2个,s后移1个;相当于pattern前两位与s[0]匹配 165 | # pattern不变,s后移1个;相当于pattern前两位,与s中的多位进行匹配,因为*可以匹配多位 166 | return self.match(s, pattern[2:]) or self.match(s[1:], pattern[2:]) or self.match(s[1:], pattern) 167 | # pattern第二个字符不为*的情况 168 | else: 169 | if s[0] == pattern[0] or pattern[0] == '.': 170 | return self.match(s[1:], pattern[1:]) 171 | else: 172 | return False 173 | ``` 174 | 175 | ## 表示数值的字符串 176 | ### 问题 177 | 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。 178 | 179 | ### 思路 180 | 以下为剑指Offer上的思路: 181 | 表示数值的字符串遵循模式 `A[.[B]][e|EC]` 或者 `.B[e|EC]`。其中,A为数值的整数部分,B为小数部分,C为指数部分。要满足以下条件: 182 | - 可以没有A部分,或者没有B部分,但不能AB同时没有 183 | - AC均可能以+-开头的0-9字符 184 | - B也是0-9字符但不能有+-号 185 | 186 | 判断时,从左到右扫描字符串,先尽可能地扫描出整数部分A,碰到小数点再扫描得到B,碰到e或E再扫描得到C。 187 | 188 | ### 代码 189 | 190 | 按剑指 Offer 上的思路,来写代码如下: 191 | ```python 192 | # -*- coding:utf-8 -*- 193 | class Solution: 194 | def __init__(self): 195 | self.cursor = 0 196 | 197 | def isNumeric(self, s): 198 | if not s: 199 | return False 200 | # 先判断是否存在A部分 201 | numeric = self.scan_integer(s) 202 | # 如果出现小数点,则判断小数部分 203 | if (self.cursor < len(s)) and s[self.cursor] == '.': 204 | self.cursor += 1 205 | # 用or的原因是,既可以没有A部分,也可以没有B部分,也可以AB都有,但不能AB都没有 206 | numeric = self.scan_unsigned_integer(s) or numeric 207 | # 如果出现e,则判断指数部分 208 | if (self.cursor < len(s)) and (s[self.cursor] == 'e' or s[self.cursor] == 'E'): 209 | self.cursor += 1 210 | # 用and的原因是,既然出现了E,后面必须要有整数,且E的前面必须是已判断好了的合法数字 211 | numeric = numeric and self.scan_integer(s) 212 | 213 | return numeric and self.cursor == len(s) 214 | 215 | 216 | def scan_integer(self, s): 217 | # 作用:扫描字符串从当前cursor位置往后,是否有整数 218 | # 先处理掉前面的符号位(如果没有符号位就不处理,无影响) 219 | if (self.cursor < len(s)) and (s[self.cursor] == '+' or s[self.cursor] == '-'): 220 | self.cursor += 1 221 | # 再调用判断无符号整数的方法 222 | return self.scan_unsigned_integer(s) 223 | 224 | def scan_unsigned_integer(self, s): 225 | # 作用:扫描字符串从当前cursor位置往后,是否有无符号整数 226 | # 先记下cursor本来的位置 227 | original_cursor = self.cursor 228 | # 遇到 0-9 数字就往后移动cursor 229 | while (self.cursor < len(s)) and ('0' <= s[self.cursor] <= '9'): 230 | self.cursor += 1 231 | # 如果cursor比最初后移了,说明有无符号整数 232 | return self.cursor > original_cursor 233 | ``` 234 | 235 | 另有思路,用有限自动机来实现,这个感觉是挺好的,而且具有扎实的计算机基础,判断过程也会更严谨。 具体见 https://www.nowcoder.com/questionTerminal/6f8c901d091949a5837e24bb82a731f2 236 | 237 | 238 | 此外,再贴牛客上的一个实现思路: 239 | ```python 240 | # -*- coding:utf-8 -*- 241 | class Solution: 242 | # s字符串 243 | def isNumeric(self, s): 244 | # 标记符号、小数点、e是否出现过 245 | sign = False 246 | decimal = False 247 | has_e = False 248 | for i in range(len(s)): 249 | if s[i] == 'e' or s[i] == 'E': 250 | if i == len(s) - 1: 251 | return False # E后面必须跟数字,E不可以在字符串结尾 252 | if has_e: 253 | return False # 不可以出现两个E 254 | has_e = True 255 | elif s[i] == '+' or s[i] == '-': 256 | if sign and (s[i-1] != 'e' and s[i-1] != 'E'): 257 | return False # 如果是第二次出现符号,符号必须跟在E之后 258 | if (not sign) and (i > 0) and (s[i-1] != 'e' and s[i-1] != 'E'): 259 | return False # 如果第一次出现符号,且不是在开头出现,则必须紧跟在E之后 260 | sign = True 261 | elif s[i] == '.': 262 | if has_e or decimal: 263 | return False # E后面不能出现小数点,或小数点不能出现两次 264 | decimal = True 265 | elif s[i] < '0' or s[i] > '9': 266 | return False # 不合法字符 267 | return True 268 | ``` 269 | 270 | ## 第一个只出现一次的字符 271 | 272 | ### 问题 273 | 请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。 274 | 275 | 如果当前字符流没有存在出现一次的字符,返回#字符。 276 | 277 | (注:本题就是求字符串中第一个只出现一次的字符,但是把具体代码实现稍微做了变化,也即模拟了一个字符流,需要通过insert函数每次插入一个字符,然后通过 fisrt_appear_once获取当前状态下的字符串里的第一个只出现一次的字符) 278 | 279 | ### 思路 280 | (刷题后遗症就是这道题明明觉得有好多种方法可以实现,但还是怀疑自己没有想到最优的方法,也是醉了……) 281 | 282 | 最基本的方法就是,从当前串开头开始读,读到每个字符就往后扫一遍看是否有重样的, 直到找到第一个不重样的。时间复杂度n^2。 283 | 284 | 另外方法自然就是建hash表啦,先扫一遍串,记下每个字符出现的次数,再扫一遍串同时查询每个字符出现的次数,遇到出现次数为1的就是第一个只出现一次的字符了。 285 | 286 | ### 代码 287 | ```python 288 | # -*- coding:utf-8 -*- 289 | class Solution: 290 | def __init__(self): 291 | self.stream = [] 292 | self.counter = {} 293 | # 返回对应char 294 | def FirstAppearingOnce(self): 295 | for c in self.stream: 296 | if self.counter[c] == 1: 297 | return c 298 | return '#' 299 | 300 | def Insert(self, char): 301 | if char in self.counter: 302 | self.counter[char] += 1 303 | else: 304 | self.counter[char] = 1 305 | self.stream.append(char) 306 | ``` 307 | 308 | ## 链表中环的入口结点 309 | 310 | ### 问题 311 | 给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。 312 | 313 | ### 思路 314 | ![](./images/60225484-ca1d-11e8-8843-fa163e7a698c.png) 315 | 首先要确定是否包含环。(这个题在第二版书中是位于 链表倒数第k个结点 之后的,所以思路上会有些类似。)确定的方法是,两个指针同时从头出发,一个一次走一步,一个一次走两步,当快指针和慢指针重逢了,说明存在环,如果快指针走到了链表末尾,也没有和慢的重逢过,说明不包含环。 316 | 317 | 第二步是找出环的入口。仍然可以用两个指针,如图的环有4个节点,那么让p1先走4步,然后p1 p2 一起走,当两个指针重逢时,由于两个指针差距应该是环的长度,而他俩重逢了,说明p1已经绕环走了一圈,重逢的位置就是环的入口。 318 | 319 | 如何找到环的长度?在前面判断是否有环时,如果快慢指针相遇则说明有环,此时相遇的节点必定在环中,记住这个节点然后从它出发,等再次回到这个节点时就走了一圈,就得到环的长度了。 320 | 321 | 所以这个题的关键在于,要知道如何判断是否包含环、如何计算环长度、如何通过两个指针差距的方法找到环的入口。 322 | 323 | ### 代码 324 | ```python 325 | # -*- coding:utf-8 -*- 326 | # class ListNode: 327 | # def __init__(self, x): 328 | # self.val = x 329 | # self.next = None 330 | class Solution: 331 | def EntryNodeOfLoop(self, pHead): 332 | if not pHead: 333 | return None 334 | fast = pHead 335 | slow = pHead 336 | has_loop = False 337 | # 先判断是否有环 338 | while fast: 339 | if fast.next: 340 | fast = fast.next.next 341 | else: # fast已经走到最后的节点了 342 | return None 343 | slow = slow.next 344 | if fast == slow: 345 | has_loop = True 346 | break 347 | if not has_loop: 348 | return None 349 | # 统计环中节点个数 350 | count = 1 351 | fast = fast.next 352 | while slow != fast: 353 | fast = fast.next 354 | count += 1 355 | # 寻找环的入口 356 | fast = pHead 357 | slow = pHead 358 | while count > 0: 359 | fast = fast.next 360 | count -= 1 361 | while fast != slow: 362 | fast = fast.next 363 | slow = slow.next 364 | return fast 365 | ``` 366 | 367 | 另有一种很简洁的python写法: 368 | ```python 369 | class Solution: 370 | def EntryNodeOfLoop(self, pHead): 371 | slow, fast = pHead, pHead 372 | while fast and fast.next: 373 | slow = slow.next 374 | fast = fast.next.next 375 | if slow == fast: # 有环 376 | slow2 = pHead # 直接让slow继续走,另从head同步开始走了?? 377 | while slow != slow2: 378 | slow = slow.next 379 | slow2 = slow2.next 380 | return slow 381 | ``` 382 | 383 | 为何判断有环之后,从相遇的点开始,直接让slow和head出发的slow2继续走,等相遇时就是入口了呢?难道不需要先计算一下环长? 384 | 385 | 不妨这样来算一下,当fast和slow相遇时, l_fast = l_slow + ring = 2 x l_slow,换言之,l_slow = ring ! 相遇时slow走过的距离,就已经是环的长度了!所以无需再去费劲计算环长,还要让一个指针先走过环长步,因为费了一大圈兜兜转转,最开始相遇的地方,就已经是要找的地方了…… 386 | 387 | ## 删除链表中重复的结点 388 | ### 问题 389 | 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5 390 | 391 | ### 思路 392 | 首先构造一个空的头节点(以防出现 1->1 这样的链表,应当返回空),然后有两个指针,pre指向当前已经处理好了的链表,而last指向当前正在处理的节点。 393 | 394 | 对于每个节点 395 | - 如果它和它后面的值相同,那么就让它一直往后走,直到它和它后面的值不相同,然后把它后面的节点,接到当前处理完链表的后面; 396 | - 如果它和它后面的值不同,就直接接到链表上,然后继续往后找即可 397 | 398 | ### 代码 399 | ``` 400 | class Solution: 401 | def deleteDuplication(self, pHead): 402 | # 空,或者只有一个节点,直接返回 403 | if (not pHead) or (not pHead.next): 404 | return pHead 405 | # 构造一个新的头节点 406 | head = ListNode(0) 407 | head.next = pHead 408 | pre = head 409 | last = head.next 410 | while last: 411 | # 当前节点有next,且和它的next值相同,说明这一串都要被抛弃 412 | if last.next and last.val == last.next.val: 413 | while last.next and last.val == last.next.val: 414 | last = last.next 415 | pre.next = last.next 416 | last = last.next 417 | # 当前节点没有next,或者和它的next值不同,则将此节点接入链表,并继续往后走 418 | else: 419 | pre = pre.next 420 | last = last.next 421 | return head.next 422 | ``` 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | ------ 433 | 434 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/14.md: -------------------------------------------------------------------------------- 1 | # 第 14 篇 2 | 3 | ## 二叉树的下一个结点 4 | 5 | ### 问题 6 | 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 7 | 8 | ### 思路 9 | ![](./images/dea686ac-ca29-11e8-8843-fa163e7a698c.png) 10 | 以这个图为例来分析一下。 11 | 12 | - 如果当前节点有右子树,那么中序的下一个节点就是右子树的最左节点。如图中的b,中序的下一个是h。只需沿着右子树,一直往左下找就行。 13 | - 如果没有右子树 14 | - 如果它是父节点的左子节点,那么中序的下一个就是它的父节点,如图中的f,下一个是c 15 | - 如果是父节点的右子节点,就需要往上找父节点,直到找到一个节点x,x是它的父节点的左子节点,这样的话x的父节点就是要找的节点。例如图中i,沿着往上找到b是a的左子节点,那么a就是i中序的下一个。如果找不到,例如图中g,a已经没有父节点,所以g没有下一个中序节点。 16 | 17 | ### 代码 18 | 按照上面思路来写就好,注意不要弄混这些父节点和子节点的关系之类 19 | ```python 20 | # -*- coding:utf-8 -*- 21 | # class TreeLinkNode: 22 | # def __init__(self, x): 23 | # self.val = x 24 | # self.left = None 25 | # self.right = None 26 | # self.next = None 27 | class Solution: 28 | def GetNext(self, pNode): 29 | if not pNode: 30 | return None 31 | if pNode.right: 32 | node = pNode.right 33 | while node.left: 34 | node = node.left 35 | return node 36 | else: 37 | if pNode.next and pNode.next.left == pNode: 38 | return pNode.next 39 | else: 40 | node = pNode.next 41 | while node: 42 | if node.next and node.next.left == node: 43 | return node.next 44 | else: 45 | node = node.next 46 | return node 47 | ``` 48 | 49 | ## 对称的二叉树 50 | ### 问题 51 | 请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。 52 | 53 | ### 思路 54 | ![](./images/c68e74da-ca2e-11e8-8843-fa163e7a698c.png) 55 | 想象一下如果人来做比较,应该如何进行。首先判断根节点的左右,都是6,相等;再判断两个6为根的两棵树,先判断左边的左子树为5,右边的右子树为5,相等;左边没有左子树和右子树了,判断左边的右子树为7,右边的左子树为7,相等;右边也没有左子树和右子树了;…… 56 | 57 | 其实更严格来说,是把根节点的左右两棵树,左边以前序遍历来遍历,右边对应地以一种对称的前序遍历方式,找到和左边对称位置的节点,来比较是否对称。可采用一种递归的方法。 58 | 59 | ### 代码 60 | ```python 61 | # -*- coding:utf-8 -*- 62 | # class TreeNode: 63 | # def __init__(self, x): 64 | # self.val = x 65 | # self.left = None 66 | # self.right = None 67 | class Solution: 68 | def isSymmetrical(self, pRoot): 69 | if not pRoot: 70 | return True 71 | else: 72 | return self.check(pRoot.left, pRoot.right) 73 | 74 | def check(self, left, right): 75 | if (not left) and (not right): 76 | return True 77 | elif not(left and right): 78 | return False 79 | if left.val != right.val: 80 | return False 81 | return self.check(left.left, right.right) and self.check(right.left, left.right) 82 | ``` 83 | 84 | ## 按之字形打印二叉树 85 | ### 问题 86 | 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。 87 | 88 | ### 思路 89 | ![](./images/dea686ac-ca29-11e8-8843-fa163e7a698c.png) 90 | 类似[层级遍历二叉树](https://codingcat.cn/article/26) 的做法,只不过现在需要根据层的奇偶来改变加入的顺序,不能只简简单单用一个队列来解决了。 91 | 92 | 先来分析一下如图,第0层,先a,然后将a的右c-左b加入下一层;第1层,依次输出c,b,但注意的是却需要先将b的左d-右e加入下一层,才能将c的左f右g加入下一层。也就是说,在这一层时,打印的顺序 和 遍历节点并将节点孩子加入下一层 的顺序,是相反着的。这点特别要注意。 93 | 94 | 然后就可以循环着来做了,这里使用了两个list交替存储奇数层和偶数层。 95 | 96 | ### 代码 97 | ``` 98 | # -*- coding:utf-8 -*- 99 | # class TreeNode: 100 | # def __init__(self, x): 101 | # self.val = x 102 | # self.left = None 103 | # self.right = None 104 | class Solution: 105 | def Print(self, pRoot): 106 | if not pRoot: 107 | return [] 108 | result = [] 109 | stack = [[pRoot], []] 110 | current = 0 111 | while stack[0] or stack[1]: 112 | if current == 0: 113 | # 当前偶数下标层,先按list中顺序打印元素 114 | result.append([p.val for p in stack[0]]) 115 | # 然后逆序遍历,并将每个元素的 右-左 先后加入下一层 116 | for i in range(len(stack[0])-1, -1, -1): 117 | p = stack[0][i] 118 | if p.right: 119 | stack[1].append(p.right) 120 | if p.left: 121 | stack[1].append(p.left) 122 | stack[0] = [] 123 | else: 124 | # 当前奇数下标层,先按list顺序打印元素 125 | result.append([p.val for p in stack[1]]) 126 | # 然后逆序遍历,并将每个元素的 左-右 先后加入下一层 127 | for i in range(len(stack[1])-1, -1, -1): 128 | p = stack[1][i] 129 | if p.left: 130 | stack[0].append(p.left) 131 | if p.right: 132 | stack[0].append(p.right) 133 | stack[1] = [] 134 | current = 1 - current 135 | return result 136 | ``` 137 | 138 | ## 把二叉树打印成多行 139 | ### 问题 140 | 从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。 141 | 142 | ### 思路与代码 143 | 依然是层级遍历,但这次要注意区分每一层,因为需要换行。 144 | 我写的思路(插入特殊的节点,作为换行标记): 145 | ```python 146 | # -*- coding:utf-8 -*- 147 | # class TreeNode: 148 | # def __init__(self, x): 149 | # self.val = x 150 | # self.left = None 151 | # self.right = None 152 | class Solution: 153 | # 返回二维列表[[1,2],[4,5]] 154 | def Print(self, pRoot): 155 | if not pRoot: 156 | return [] 157 | sep = TreeNode(-1) 158 | result = [] 159 | queue = [pRoot, sep] 160 | temp = [] 161 | while queue: 162 | current = queue.pop(0) 163 | if current != sep: 164 | temp.append(current.val) 165 | if current.left: 166 | queue.append(current.left) 167 | if current.right: 168 | queue.append(current.right) 169 | else: 170 | result.append(temp) 171 | temp = [] 172 | if queue: 173 | queue.append(sep) 174 | return result 175 | ``` 176 | 177 | 如果不插入特殊标记,其实很简单只需要在每一行开始时计算一下queue的长度,就是这一行的节点数目了。 178 | 179 | ```python 180 | class Solution: 181 | # 返回二维列表[[1,2],[4,5]] 182 | def Print(self, pRoot): 183 | if not pRoot: 184 | return [] 185 | result = [] 186 | queue = [pRoot] 187 | while queue: 188 | temp = [] 189 | length = len(queue) 190 | for i in range(length): 191 | current = queue.pop(0) 192 | temp.append(current.val) 193 | if current.left: 194 | queue.append(current.left) 195 | if current.right: 196 | queue.append(current.right) 197 | result.append(temp) 198 | return result 199 | ``` 200 | 201 | ## 序列化二叉树 202 | ### 问题 203 | 请实现两个函数,分别用来序列化和反序列化二叉树 204 | 205 | ### 思路 206 | 递归前序遍历二叉树,对每个节点的孩子如果遇到空就以#来代替,最终得到一个字符串。 207 | 读取字符串,当前字符作为根,下一个字符作为当前的左孩子,依此递归进行前序遍历,恢复树结构。 208 | 209 | ### 代码 210 | ```python 211 | # -*- coding:utf-8 -*- 212 | # class TreeNode: 213 | # def __init__(self, x): 214 | # self.val = x 215 | # self.left = None 216 | # self.right = None 217 | class Solution: 218 | def __init__(self): 219 | self.flag = -1 220 | 221 | def Serialize(self, root): 222 | # write code here 223 | if not root: 224 | return '#,' 225 | return str(root.val)+','+self.Serialize(root.left)+self.Serialize(root.right) 226 | 227 | def Deserialize(self, s): 228 | # write code here 229 | self.flag += 1 230 | l = s.split(',') 231 | 232 | if self.flag >= len(s): 233 | return None 234 | root = None 235 | 236 | if l[self.flag] != '#': 237 | root = TreeNode(int(l[self.flag])) 238 | root.left = self.Deserialize(s) 239 | root.right = self.Deserialize(s) 240 | return root 241 | ``` 242 | 243 | ## 二叉搜索树的第k个节点 244 | 245 | ### 问题 246 | 给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。 247 | 248 | ### 思路 249 | 二叉搜索树,左子树节点均小于根,右子树节点值均大于根,按中序遍历,找到中序的第k个节点,就是第k小的节点了。 250 | 251 | ### 代码 252 | ```python 253 | # class TreeNode: 254 | # def __init__(self, x): 255 | # self.val = x 256 | # self.left = None 257 | # self.right = None 258 | class Solution: 259 | def __init__(self): 260 | self.current = 0 261 | 262 | # 返回对应节点TreeNode 263 | def KthNode(self, pRoot, k): 264 | if not pRoot: 265 | return None 266 | left_val = self.KthNode(pRoot.left, k) 267 | if left_val: 268 | return left_val 269 | self.current += 1 270 | if self.current == k: 271 | return pRoot 272 | right_val = self.KthNode(pRoot.right, k) 273 | if right_val: 274 | return right_val 275 | return None 276 | ``` 277 | 278 | ## 数据流中的中位数 279 | ### 问题 280 | 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。 281 | 282 | ### 思路 283 | 最直接的就是存着所有数字,加入一个新的之后重新排序取中间;或者加入新数字时就直接加入到指定位置上,比如用链表之类的存储。 284 | 285 | 另外可采用堆的思路: 286 | ``` 287 | 1.维护一个大顶堆,一个小顶堆,且保证两点: 288 | 1)小顶堆里的全大于 大顶堆里的; 289 | 2)2个堆个数的差值小于等于1 290 | 2.当insert的数字个数为奇数时:使小顶堆个数比大顶堆多1; 291 | 当insert的数字个数为偶数时,使大顶堆个数跟小顶堆个数一样。 292 | 3.那么当总数字个数为奇数时,中位数就是小顶堆堆头; 293 | 当总数字个数为偶数时,中位数就是 2个堆堆头平均数 294 | ``` 295 | 296 | ### 代码 297 | 在python heapq的基础上又稍微封装了一下堆的相关判断,这个题还是蛮繁杂的,后面有机会再补一下堆的相关知识吧。 298 | 299 | ```python 300 | # -*- coding:utf-8 -*- 301 | import heapq 302 | 303 | class Heap: 304 | # tp是一个标记,默认1为小根堆,type为-1则为大根堆,大根堆仍然用小根堆存储,但对数值*-1 305 | def __init__(self, tp=1): 306 | self.data = [] 307 | self.tp = tp 308 | 309 | def __len__(self): 310 | return len(self.data) 311 | 312 | def insert(self, num): 313 | heapq.heappush(self.data, num * self.tp) 314 | 315 | def pop(self): 316 | return heapq.heappop(self.data) * self.tp 317 | 318 | def get_top(self): 319 | return heapq.nsmallest(1, self.data)[0] * self.tp 320 | 321 | class Solution: 322 | def __init__(self): 323 | self.total = 0 324 | self.min_heap = Heap() 325 | self.max_heap = Heap(tp=-1) 326 | 327 | def Insert(self, num): 328 | self.total += 1 329 | if (self.total == 1) or (num >= self.min_heap.get_top()): 330 | self.min_heap.insert(num) 331 | else: 332 | self.max_heap.insert(num) 333 | while len(self.max_heap) > len(self.min_heap): 334 | self.min_heap.insert(self.max_heap.pop()) 335 | while (len(self.min_heap) - len(self.max_heap)) > (self.total % 2): 336 | self.max_heap.insert(self.min_heap.pop()) 337 | 338 | def GetMedian(self, wtf): 339 | if self.total == 0: 340 | return None 341 | if self.total % 2 == 0: 342 | return (self.min_heap.get_top() + self.max_heap.get_top()) / 2.0 343 | else: 344 | return self.min_heap.get_top() 345 | ``` 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | ------ 354 | 355 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/15.md: -------------------------------------------------------------------------------- 1 | # 第 15 篇 2 | 3 | ## 滑动窗口的最大值 4 | 5 | ### 问题 6 | 给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。 7 | 8 | ### 思路 9 | 详细思路可参照 https://blog.csdn.net/qq_41822235/article/details/82891762 10 | 11 | 基本思路,从左到右滑过每个窗口,在每个窗口里寻找最大值并记录下来。一点改进,诸如记住当前最大值的下标,到新窗口里,如果根据下标判断该最大值仍然在新窗口,则只比较新窗口比上一个窗口新加入的那1个元素和最大值的大小,如果不在的话则要找遍新窗口才能找出最大,可以减少一些比较次数。 12 | 13 | 上面方法需要比较的次数是 size x n,可以进一步减少比较次数。 14 | 15 | 滑动窗口可以用一个队列来记录,新值进入窗口则加入队列,旧值滑出窗口则移出队列。但这里采用可以从两端删除元素的队列,来减少比较次数。目的是,队列头部始终存储队列中最大的元素。 16 | 17 | 当新的元素到来时,首先判断队尾元素是否比它小,若小,则剔除当前队尾元素,一直重复下去,直到队尾元素比新元素大,或者队列空了。然后新值加到队尾。然后判断当前队头是否下标超出窗口,如果超出,则队头移出。完成上述操作后,当前队头就是当前窗口的最大值。 18 | 19 | ### 代码 20 | 按基本思路(size x n) 21 | ```python 22 | # -*- coding:utf-8 -*- 23 | class Solution: 24 | def maxInWindows(self, num, size): 25 | # write code here 26 | length = len(num) 27 | if size <= 0 or size > length: 28 | return [] 29 | i = 0 30 | ans =[max(num[:size])] 31 | while size + i < length: 32 | if num[i] < ans[-1]: 33 | ans.append(max([ans[-1], num[i+size]])) 34 | else: 35 | ans.append(max(num[i+1:i+size+1])) 36 | i += 1 37 | return ans 38 | ``` 39 | 40 | 双向队列写法: 41 | ```python 42 | # -*- coding:utf-8 -*- 43 | class Solution: 44 | def maxInWindows(self, num, size): 45 | # write code here 46 | ans = [] 47 | length = len(num) 48 | if size <= 0 or size > length or not num: 49 | return [] 50 | deque = [] # 队列里存的是下标 51 | for i in range(0, len(num)): 52 | while(deque and num[deque[-1]] < num[i]): 53 | deque.pop() 54 | deque.append(i) 55 | if i - deque[0] + 1 > size: 56 | deque.pop(0) 57 | if i >= k - 1: 58 | ans.append(num[deque[0]]) 59 | return ans 60 | ``` 61 | 62 | ## 矩阵中的路径 63 | 64 | ### 问题 65 | 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。 66 | 67 | ### 思路 68 | 使用回溯法。首先遍历全部数据,找到和字符串第一个字符相同的位置,以此为起点,上下左右前进寻找和下一个字符相同的位置行。如果某处上下左右都找不到,则回退到上一步。非常适合使用递归的思路,而且代码也比较容易懂,详见下面代码(这段没有自己写,直接搬运了一份)。 69 | 70 | ### 代码 71 | ```python 72 | # -*- coding:utf-8 -*- 73 | class Solution: 74 | def hasPath(self, matrix, rows, cols, path): 75 | # write code here 76 | if not matrix and rows <= 0 and cols <= 0 and path == None: 77 | return False 78 | # 模拟的字符矩阵 79 | markmatrix = [0] * (rows * cols) 80 | pathlength = 0 81 | # 从第一个开始递归,当然第一二个字符可能并不位于字符串之上,所以有这样一个双层循环找起点用的,一旦找到第一个符合的字符串,就开始进入递归, 82 | # 返回的第一个return Ture就直接跳出循环了。 83 | for row in range(rows): 84 | for col in range(cols): 85 | if self.hasPathCore(matrix, rows, cols, row, col, path, pathlength, markmatrix): 86 | return True 87 | return False 88 | 89 | def hasPathCore(self, matrix, rows, cols, row, col, path, pathlength, markmatrix): 90 | # 说明已经找到该路径,可以返回True 91 | if len(path) == pathlength: 92 | return True 93 | 94 | hasPath = False 95 | if row >= 0 and row < rows and col >= 0 and col < cols and matrix[row * cols + col] == path[pathlength] and not \ 96 | markmatrix[row * cols + col]: 97 | pathlength += 1 98 | markmatrix[row * cols + col] = True 99 | # 进行该值上下左右的递归 100 | hasPath = self.hasPathCore(matrix, rows, cols, row - 1, col, path, pathlength, markmatrix) \ 101 | or self.hasPathCore(matrix, rows, cols, row, col - 1, path, pathlength, markmatrix) \ 102 | or self.hasPathCore(matrix, rows, cols, row + 1, col, path, pathlength, markmatrix) \ 103 | or self.hasPathCore(matrix, rows, cols, row, col + 1, path, pathlength, markmatrix) 104 | 105 | # 对标记矩阵进行布尔值标记 106 | if not hasPath: 107 | pathlength -= 1 108 | markmatrix[row * cols + col] = False 109 | return hasPath 110 | ``` 111 | 112 | ## 机器人的运动范围 113 | 114 | ### 问题 115 | 地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子? 116 | 117 | ### 思路 118 | 思路仍然是回溯法,但判断条件有了变化。 119 | 120 | ### 代码 121 | ```python 122 | # -*- coding:utf-8 -*- 123 | class Solution: 124 | def movingCount(self, threshold, rows, cols): 125 | # write code here 126 | markmatrix = [False] * (rows * cols) 127 | count = self.GetNum(threshold, rows, cols, 0, 0, markmatrix) 128 | return count 129 | 130 | def GetNum(self, threshold, rows, cols, row, col, markmatrix): 131 | count = 0 132 | 133 | if self.GetSum(threshold, rows, cols, row, col, markmatrix): 134 | markmatrix[row * cols + col] = True 135 | count = 1 + self.GetNum(threshold, rows, cols, row - 1, col, markmatrix) + \ 136 | self.GetNum(threshold, rows, cols, row, col - 1, markmatrix) + \ 137 | self.GetNum(threshold, rows, cols, row + 1, col, markmatrix) + \ 138 | self.GetNum(threshold, rows, cols, row, col + 1, markmatrix) 139 | return count 140 | 141 | def GetSum(self, threshold, rows, cols, row, col, markmatrix): 142 | if row >= 0 and row < rows and col >= 0 and col < cols and self.getDigit(row) + self.getDigit( 143 | col) <= threshold and not markmatrix[row * cols + col]: 144 | return True 145 | return False 146 | 147 | def getDigit(self, number): 148 | sumNum = 0 149 | while number > 0: 150 | sumNum += number % 10 151 | number = number // 10 152 | return sumNum 153 | ``` 154 | 155 | 156 | 157 | 158 | 159 | ------ 160 | 161 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/2.md: -------------------------------------------------------------------------------- 1 | # 第2篇 2 | 3 | 字符串——替换空格 4 | 链表——逆序输出链表 5 | 6 | ## 替换空格 7 | 8 | **要求** 9 | 请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 10 | 11 | **思路** 12 | 如果用Python或者java等高级点的语言,直接调replace是最简单的写法了,或者开一个新字符串,从前往后读原字符串,读到其他字符就直接追加,读到空格就追加%20。 13 | 14 | 如果用C语言,字符串以char[]来存,就要考虑更多的东西,比如数组的容量,挪动各个char,最后的\0等。 15 | 16 | 一种思路是从前往后读,碰到空格,就先把后面的全部内容往后挪两格,加入%20。这样会导致很多内容被多次挪动,O(n^2)效率。 17 | 另一种思路是,预先扫一遍看有多少个空格,从而知道最终结果的长度,然后从后往前进行替换,预留出恰好的空间。这样每个字符只需要挪动一次,O(n)效率即可。 18 | 19 | 需要特别注意\0、空指针、容量不足以及数组边界的问题。 20 | 21 | ```c 22 | class Solution { 23 | public: 24 | void replaceSpace(char *str,int length) { 25 | //length是字符数组的总容量大小,而不是字符串的长度。 26 | //遍历一遍字符串找出空格的数量 27 | if(str==NULL||length<0) 28 | return ; 29 | int i=0; 30 | int oldnumber=0;//记录以前的长度 31 | int replacenumber=0;//记录空格的数量 32 | while(str[i]!='\0') 33 | { 34 | oldnumber++; 35 | if(str[i]==' ') 36 | { 37 | replacenumber++; 38 | } 39 | i++; 40 | } 41 | int newlength=oldnumber+replacenumber*2;//插入后的长度 42 | if(newlength>length)//如果计算后的长度大于总长度就无法插入 43 | return ; 44 | int pOldlength=oldnumber; //注意不要减一因为隐藏个‘\0’也要算里 45 | int pNewlength=newlength; 46 | while(pOldlength>=0&&pNewlength>pOldlength)//放字符 47 | { 48 | if(str[pOldlength]==' ') //碰到空格就替换 49 | { 50 | str[pNewlength--]='0'; 51 | str[pNewlength--]='2'; 52 | str[pNewlength--]='%'; 53 | } 54 | else //不是空格就把pOldlength指向的字符装入pNewlength指向的位置 55 | { 56 | str[pNewlength--]=str[pOldlength]; 57 | } 58 | pOldlength--; //不管是if还是elsr都要把pOldlength前移 59 | } 60 | } 61 | }; 62 | ``` 63 | 64 | 这里感觉值得一提的是书中特别写出了测试用例: 65 | - 输入的字符串中包含空格:空格在开头、最末、中间、连续多个空格 66 | - 输入的字符串中没有空格 67 | - 输入空指针、空串、只有一个空格、全为空格等特殊情况 68 | 69 | 在写代码时感觉 应该先想好这些各种情况,保证对各种特殊情况都考虑到了,才能使得写出的代码尽可能正确。 70 | 71 | 72 | 73 | ## 倒序输出链表 74 | 75 | **题目描述** 76 | 输入一个链表,从尾到头打印链表每个节点的值。 77 | 78 | **思路** 79 | 1)倒置指针——会改变原始输入信息,需注意是否允许改变原数据 80 | 2)基于递归实现——先输出内层的,注意当链表很长时会导致调用栈溢出 81 | 3)基于栈,用循环实现 82 | 83 | 84 | **code** 85 | 86 | ```python 87 | class Solution: 88 | # 返回从尾部到头部的列表值序列,例如[1,2,3] 89 | def printListFromTailToHead(self, listNode): 90 | # write code here 91 | if not listNode: 92 | return [] 93 | stack = [] 94 | current = listNode 95 | while(current is not None): 96 | stack.append(current.val) 97 | current = current.next 98 | result = [] 99 | 100 | # 注意range的使用 101 | for i in range(len(stack)-1, -1, -1): 102 | result.append(stack[i]) 103 | return result 104 | ``` 105 | 106 | 107 | 108 | 109 | 110 | ------ 111 | 112 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/3.md: -------------------------------------------------------------------------------- 1 | # 第3篇 2 | 3 | 知识点:二叉树、栈和队列、查找 4 | 5 | 6 | # 二叉树 7 | 8 | ## 知识点 9 | 10 | - 树的概念:分支节点、叶节点、度、深度…… 11 | - 二叉树的定义,满二叉树,完全二叉树 12 | - 二叉树的五条性质 13 | - 二叉树的存储方式(二叉链表广泛采用) 14 | - 二叉树的遍历(前序、中序、后序)(递归、非递归),以及层次遍历(采用队列) 15 | 16 | ## 题目:重建二叉树 17 | 18 | ### 要求 19 | 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。 20 | 21 | ### 思路 22 | 前序序列能确定根节点,用根节点,把中序划分为左右子树 23 | 基于递归的思路,重复上述步骤 24 | 25 | ### 代码 26 | ```python 27 | # -*- coding:utf-8 -*- 28 | # class TreeNode: 29 | # def __init__(self, x): 30 | # self.val = x 31 | # self.left = None 32 | # self.right = None 33 | class Solution: 34 | # 返回构造的TreeNode根节点 35 | def reConstructBinaryTree(self, pre, tin): 36 | # 给定前序和中序,重建二叉树 37 | # 思路:前序结果可以确定树根,通过树根可以将中序切成左右两半 38 | if(len(pre)==0): 39 | return None 40 | root = TreeNode(pre[0]) 41 | tin_root_index = tin.index(root.val) 42 | if tin_root_index > 0: # 有左子树 43 | new_tin = tin[0: tin_root_index] 44 | new_pre = pre[1: len(new_tin)+1] 45 | root.left = self.reConstructBinaryTree(new_pre, new_tin) 46 | if tin_root_index != len(tin) - 1: # 有右子树 47 | new_tin = tin[tin_root_index+1:] 48 | new_pre = pre[len(pre)-len(new_tin):] 49 | root.right = self.reConstructBinaryTree(new_pre, new_tin) 50 | return root 51 | ``` 52 | 53 | 这个题主要是清楚前序和中序的特点,理清思路以后,算准确切分的下标,递归实现就行。 54 | 下标可以写的更简洁一点: 55 | 56 | ```python 57 | index = tin.index(root.val) 58 | if index > 0: # 有左子树 59 | root.left = self.reConstructBinaryTree(pre[1: index+1], tin[0: index]) 60 | if index != len(tin) - 1: # 有右子树 61 | root.right = self.reConstructBinaryTree(pre[index+1: ], tin[index+1: ]) 62 | return root 63 | ``` 64 | 65 | 66 | 67 | # 栈和队列 68 | 69 | ## 题目:用两个栈模拟队列 70 | ### 要求 71 | 用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。 72 | 73 | ### 思路 74 | 一个栈处理push,新节点放入该栈,一个栈处理pop,初始为空 75 | 当需要pop时,如果pop栈不空,则直接出栈,如果空,则从push栈弹出全部内容放到pop栈,pop栈出栈一个 76 | 77 | ### 代码 78 | ```python 79 | # -*- coding:utf-8 -*- 80 | class Solution: 81 | # 思路: 82 | # 一个栈处理push,新节点放入该栈,一个栈处理pop,初始为空 83 | # 当需要pop时,如果pop栈不空,则直接出栈,如果空,则从push栈弹出全部内容放到pop栈,pop栈出栈一个 84 | def __init__(self): 85 | self.push_stack = [] 86 | self.pop_stack = [] 87 | 88 | def push(self, node): 89 | self.push_stack.append(node) 90 | 91 | def pop(self): 92 | if len(self.pop_stack) == 0: 93 | if len(self.push_stack) == 0: 94 | print('Nothing to pop!') 95 | return None 96 | else: 97 | for i in range(len(self.push_stack)-1,-1,-1): 98 | self.pop_stack.append(self.push_stack[i]) 99 | self.push_stack = [] 100 | value = self.pop_stack[-1] 101 | self.pop_stack = self.pop_stack[0:-1] 102 | return value 103 | ``` 104 | 105 | 思路很简单,主要是实现的时候,下标不要写越界。 106 | 107 | ### 扩展 108 | 如果用两个队列来模拟栈呢? 109 | 初始两个队列都为空 110 | 元素a,b,c入栈,则将a,b,c先加入到q1中吧 111 | 此时要弹出c,则将ab依次出队,并加入到q2中,将c删除(弹出) 112 | 要弹出b,则将a出队,加入到q1中,将b删除 113 | 要入栈d,由于q1不空,则追加到q1后面 114 | 以此类推。 115 | 116 | 117 | # 查找 118 | 119 | ## 题目:旋转数组的最小数字 120 | 121 | ### 要求 122 | 123 | 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。 124 | 125 | ### 思路 126 | 很简单,从前往后扫,找到第一个比上一个元素小的元素,就说明是最小元素了。 127 | 【注意特例】整个数组未旋转 128 | 129 | ### 代码 130 | ```python 131 | class Solution: 132 | def minNumberInRotateArray(self, rotateArray): 133 | # 思路 134 | # 数组应该为非递减,找到第一个比上一个元素小的位置,就是最小值 135 | if len(rotateArray) == 0: 136 | return 0 137 | for i in range(len(rotateArray)-1): 138 | if rotateArray[i+1] < rotateArray[i]: 139 | return rotateArray[i+1] 140 | # 都没找到 141 | return rotateArray[0] 142 | ``` 143 | 144 | 感觉写完一遍跑过美滋滋,然而这种方法的时间复杂度为O(n)。 145 | 鉴于旋转数组前后两半都是有序的,可以采用类似二分查找的方法,减少比较次数。 146 | 147 | ### 思路2 148 | 149 | 整个数组由两个递增有序子数组构成。 150 | 151 | ![](./images/2cb28482-a305-11e8-8f8e-fa163e7a698c.png) 152 | 153 | 两个指针分别指向首尾,按照旋转的规则,第一个元素应当大于或者等于最后一个元素(如果小于,说明数组未做旋转,第一个元素就是最小的)。 154 | 155 | 找mid中间元素,如果mid属于前一半,则它应该大于或等于head,那么此时可以将head移动到mid处,head仍属于前一个数组,目标位于首尾之间;如果mid属于后一半,则它应该小于或等于rear,此时可将rear移动到mid,rear仍属于后一半,目标位于首尾之间。 156 | 157 | 这种移动方式,head始终属于前一个数组,rear始终属于后一个数组,当hear和rear相邻的时候,说明到达了交界处,rear指向目标。 158 | 159 | ### 代码2 160 | ```python 161 | def minNumberInRotateArray(self, arr): 162 | 163 | if len(arr) == 0: 164 | return 0 165 | head = 0 166 | rear = len(arr) - 1 167 | if arr[head] < arr[rear]: # 说明未旋转 168 | return arr[head] 169 | while(head != rear - 1): # 首尾未相遇,没找到目标 170 | mid = (head + rear) // 2 171 | # 【1】 172 | if arr[mid] >= arr[head]: 173 | head = mid 174 | continue 175 | if arr[mid] <= arr[rear]: 176 | rear = mid 177 | continue 178 | # 由于走到这一步,head值一定是大于等于rear的,因此除非三个值相等,不会出现mid同时满足大于等于head且小于等于rear的情况。 179 | return arr[rear] 180 | 181 | ``` 182 | 183 | ### 补充 184 | 185 | 前面代码中,如果头比尾小,说明未旋转,如果头大于等于尾,则进行mid的判断,此时head值一定是大于等于rear的,因此除非三个值相等,不会出现mid同时满足大于等于head且小于等于rear的情况,while中的两个if只能满足一个,写的没问题。 186 | 187 | 但有一个特例没有考虑到。当head、mid、rear的值都相同时,按照代码,认为mid处仍然属于前一半,先把head移动到了mid处。但这样是对的吗? 188 | 189 | ![](./images/34efc1aa-a305-11e8-8eb6-fa163e7a698c.png) 190 | 191 | 上图两种情况,三个值相同,但可能是mid属于前一半,也可能mid属于后一半……上述方法就失效了,只能采用顺序查找的方法。 192 | 193 | 所以在代码中【1】处,应当加一个判断,如果三者相等,则转到顺序扫描方法进行操作。 194 | 195 | **虽然这个题的顺序查找方法很简单,但要想多换种方法,提高一下效率,还是挺不容易的hhh** 196 | 197 | 198 | 199 | 200 | 201 | ------ 202 | 203 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/4.md: -------------------------------------------------------------------------------- 1 | # 第4篇 2 | 3 | 知识点:递归和循环 4 | 5 | ## 斐波那契数列 6 | 7 | ### 要求 8 | 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。 9 | n<=39 10 | 11 | 斐波那契数列的定义: F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*) 12 | 13 | ### 代码 14 | 版本1: 15 | ```python 16 | class Solution: 17 | def Fibonacci(self, n): 18 | # 定义: F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*) 19 | if n == 0: 20 | return 0 21 | elif n == 1: 22 | return 1 23 | else: 24 | return self.Fibonacci(n-1) + self.Fibonacci(n-2) 25 | ``` 26 | 问题:效率太低,满足不了oj的效率要求。且有很多重复计算! 27 | 28 | ![](./images/5abe55a4-a305-11e8-b04d-fa163e7a698c.png) 29 | 30 | 改进:可以从下往上计算,从0,1一直叠加到n,就像人工做计算那样,从而避免重复 31 | 32 | ```python 33 | class Solution: 34 | def Fibonacci(self, n): 35 | # 定义: F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*) 36 | if n == 0: 37 | return 0 38 | elif n == 1: 39 | return 1 40 | else: 41 | fib0 = 0 42 | fib1 = 1 43 | for _ in range(2, n+1): 44 | temp = fib1 45 | fib1 = fib0 + fib1 46 | fib0 = temp 47 | return fib1 48 | ``` 49 | 妙用python语言的性质,for里面可以写成这样: 50 | ```python 51 | for _ in range(2, n+1): 52 | fib0, fib1 = fib1, fib0 + fib1 53 | ``` 54 | 55 | 56 | ## 跳台阶 57 | 58 | ### 要求 59 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 60 | 61 | ### 思路 62 | 青蛙某次可以是在之前跳了(n-1)级的基础上,再跳了1级到达第n级,也可以是在之前跳了(n-2)级的基础上,再跳了2级到达第n级……依次类推,发现其实是一个斐波那契数列。 63 | 64 | 65 | ### 代码 66 | ```python 67 | class Solution: 68 | def jumpFloor(self, number): 69 | # 思路:斐波那契数列问题,从后往前看,f(n) = f(n-1) + f(n-2) 70 | # f(1) = 1, f(2) = 2 71 | if number <= 2: 72 | return number 73 | else: 74 | fib1 = 1 75 | fib2 = 2 76 | for _ in range(3, number+1): 77 | fib1, fib2 = fib2, fib1+fib2 78 | return fib2 79 | ``` 80 | 81 | 82 | ## 变态跳台阶 83 | ### 要求 84 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 85 | 86 | ### 思路 87 | 思路与上一题类似,只不过在第n次时,要考虑到前面的各种情况,也就是说,到第n个台阶之前,可以是从(0)一步登天过来的,也可以是从(1)跳过来的,也可是从(2)跳过来的,把各种情况加起来。 88 | 89 | 思考时是从n往前思考,但实现时,要从前往后累加,避免重复计算。 90 | 91 | ### 代码 92 | ```python 93 | class Solution: 94 | def jumpFloorII(self, number): 95 | # f(n) = once + f(1) + f(2) + ... + f(n-3) + f(n-2) + f(n-1) 96 | # f(n-1) = once + f(1) + f(2) + ... + f(n-3) + f(n-2) 97 | # f(n-2) = once + (1) + f(2) + ... + f(n-3) 98 | # ... 99 | # f(4) = once + f(1) + f(2) + f(3) 100 | # f(3) = once + f(1) + f(2) 101 | # f(2) = once + f(1) 102 | # f(1) = once 103 | # once = 1 104 | # 前面加上once表示考虑到之前未跳过,一次就跳到这一级的情况,once应该为1 105 | return 2 ** (number-1) 106 | ``` 107 | 108 | 其实观察一下,就能发现f(n) = 2 ^ (n-1),这还写个毛线代码,直接return就ok了…… 109 | 110 | 111 | ## 矩形覆盖 112 | ### 要求 113 | 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? 114 | 115 | ### 思路 116 | 宽度为n,可以用一个小矩形竖着放,占据1宽度,也可以用两个小矩形横着放,占据2宽度。这不还是跳台阶问题嘛 117 | 118 | ### 代码 119 | 不重复了,就是上面的跳台阶。 120 | 121 | 122 | 123 | 124 | 125 | ------ 126 | 127 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/5.md: -------------------------------------------------------------------------------- 1 | # 第5篇 2 | 3 | ## 二进制中1的个数 4 | 5 | ### 问题 6 | 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。 7 | 8 | ### 思路 9 | 详见offer书。 10 | 较好的解法是,以1100为例,1100 减去 1 得到 1011,发现其实减去1就是将最右的1变为0,将右边的0变为1, 所以 1100 和 1011 做按位与运算,得到 1000 就相当于抹掉了最右边的1。 11 | 按照这种思路,只要一直减去1,就逐个从右边抹掉了1, 当整个数 = 0 时说明抹完了,循环次数就是1的个数。 12 | 13 | 14 | ### 代码 15 | ```python 16 | class Solution: 17 | def NumberOf1(self, n): 18 | count = 0 19 | # 很神奇,python要加上对n<0的处理,但是用c语言就不用? 20 | if n < 0: 21 | n = n & 0xffffffff 22 | while n: 23 | n = (n-1) & n 24 | count += 1 25 | return count 26 | ``` 27 | 28 | ## 数值的整数次方 29 | 30 | ### 问题 31 | 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。 32 | 33 | ### 代码 34 | ```python 35 | # 一行代码…… 36 | class Solution: 37 | def Power(self, base, exponent): 38 | return base ** exponent 39 | 40 | ``` 41 | 当然本题原意是不要调用库,自己来实现,关键是考虑到很多细节。 42 | 要点如下: 43 | - 考虑到base为0,且要注意 base是double型,判断double型=0时要小心浮点数不精确 44 | - 考虑到exp为0,则返回-1 45 | - exp大于0,可通过循环累成来实现 46 | - exp小于0,需将乘方结果取倒数 47 | - 优化:例如32次方,可以通过先求16次方再将16次方结果平方得到,可用递归 48 | 49 | 50 | ## 打印1到最大的n位数 51 | ### 问题 52 | 给定n,要按顺序打印从1到最大的n位数,例如,n=3,则打印1,2,3.....999. 53 | 54 | ### 思路 55 | (1)先求出最大的n位数,例如n=3,最大的是 10^3 - 1 = 999,然后循环从1开始打印 56 | 问题:如果n稍微大一点点,例如20,一般的整数类型就溢出了! 57 | 58 | (2) 用字符串的形式,模拟手工加法进位的操作 59 | 60 | (3)转换思路,其实就是输出n个0-9的全排列,按递归的方法,从高位逐个递归深入下去,就能实现按照从小到大的顺序逐个输出了! 61 | 62 | > 好吧,这个题主要就是要提醒注意n的范围,也就是说,要仔细思考给定的输入值是不是0?正数?负数?会不会过大?采取的数据类型有没有问题? 63 | 64 | 65 | ## O(1)的时间删除链表某节点 66 | ### 问题 67 | 单向链表,给定头指针和指向某个节点的指针,要求O(1)时间删除该节点 68 | 69 | ### 思路 70 | 常规的做法是,从头开始沿着链表向后遍历,直到找到目标节点,然后修改目标的前一个节点的指针,让next指向目标的后一个节点,释放空间,完成删除。 71 | 72 | 这种复杂度是O(n),主要就是因为必须遍历才能找到目标节点的**前一个节点**! 73 | 74 | 另辟蹊径,可以O(1)找到目标的后一个节点,将后一个节点的内容复制到目标上,这样问题就转换成删除目标的后一个节点了!无需从头遍历! 75 | 76 | 需要特别注意!处理一些特殊情况,例如单节点链表、目标是尾节点等。 77 | 78 | ### 代码 79 | 简述一下结构吧 80 | - 判断链表头和目标是否指针为空,不空才能继续 81 | - 目标不是尾节点,可以操作,将目标的下一节点的内容和next指针复制过来,删掉下一节点 82 | - 如果链表只有一个节点,则直接将头指针next置空,释放目标节点 83 | - 如果链表不是只有一个节点,而且目标是尾节点,则必须从头遍历找到目标的上一节点,然后按正常方式释放。 84 | 85 | 补充:(想这么多还能不能愉快的编程了!简直有点神经质啊!!)以上基于假设是目标一定在链表中,如果目标节点不在链表中,就gg了…… 86 | 87 | 88 | ## 调整数组顺序,使奇数位于偶数前面 89 | ### 问题 90 | 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并**保证奇数和奇数,偶数和偶数之间的相对位置不变。** 91 | 92 | ### 思路 93 | 需要注意到保证奇数和奇数、偶数和偶数的相对位置不变。 94 | 95 | 如果是允许做数组的delete和append操作,或者说允许建新数组的话: 96 | 1)另开两个数组,扫一遍分别存偶数和奇数,然后将偶数连接到奇数后面 97 | 2)扫一遍统计奇数的个数,新建一个等长的数组,奇数从头往后加,偶数从奇数长度下标处往后加(当允许另开空间时,此方法较好) 98 | 3)扫一遍,遇到偶数就从列表中删除,然后追加到列表末端。需注意结束扫描的条件 99 | 100 | 如果只允许在原数组上做交换操作: 101 | 4)利用冒泡排序的思想,从前往后扫,遇到奇数就往前冒泡(或者从后往前扫,遇到偶数往后冒泡),不开辟额外空间 102 | 103 | ### 代码 104 | 上面(1)用python的简洁写法: 105 | ```python 106 | def reOrderArray(self, array): 107 |         # write code here 108 |         odd,even=[],[] 109 |         for i in array: 110 |             odd.append(i) if i%2==1 else even.append(i) 111 |         return odd+even 112 | ``` 113 | 114 | 再写一下上面(4)的代码: 115 | ```python 116 | class Solution: 117 | def reOrderArray(self, array): 118 | # 冒泡思想。从前往后扫,遇到奇数就往前冒泡。 119 | last_odd = -1 120 | for i in range(0, len(array)): 121 | if array[i] % 2 == 1: 122 | for j in range(i, last_odd+1, -1): 123 | array[j], array[j-1] = array[j-1], array[j] 124 | print(array) 125 | last_odd += 1 126 | return array 127 | ``` 128 | 129 | ### 扩展 130 | 如果不要求奇数和偶数的相对位置不变,只要能奇数在前,偶数在后。 131 | 1)从前往后扫,遇到偶数,就记下,把它之后的内容往前挪一位,把偶数放在最后的空位 132 | 2)维护两个指针,一个从头部往后,一个从尾部往前,如果第一个指向偶数,第二个指向奇数,就交换,当第二个位于第一个前面时说明交换完成。 133 | 134 | 135 | 136 | ------ 137 | 138 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/6.md: -------------------------------------------------------------------------------- 1 | # 第 6 篇 2 | 3 | 4 | ## 链表中倒数第k个节点 5 | 6 | ### 问题 7 | 输入一个链表,输出该链表中倒数第k个结点。 8 | 9 | ### 思路 10 | 1)先扫一遍链表,获得总个数n,然后从头走n-k+1次,就得到倒数第k个了。缺点是要扫两遍 11 | 2)两个指针从开头出发,第二个指针先往后走k-1步,然后两个指针同时往后走,当第二个到达末尾时,第一个指向的就是倒数第k个 12 | 13 | **需要注意** 14 | head为空、k小于或等于0时,k大于链表长度时的特殊情况 15 | 需留心不要写错边界条件 16 | 17 | ### 代码 18 | ```python 19 | class Solution: 20 | def FindKthToTail(self, head, k): 21 | if (not head) or k <= 0: 22 | return None 23 | count = 0 24 | pointer = head 25 | while(count < k and pointer): 26 | count += 1 27 | pointer = pointer.next 28 | if count < k: 29 | return None 30 | else: 31 | target = head 32 | while(pointer): 33 | target = target.next 34 | pointer = pointer.next 35 | return target 36 | ``` 37 | 38 | ## 反转链表 39 | ### 问题 40 | 输入一个链表,反转链表后,输出链表的所有元素(函数返回头节点即可)。 41 | 42 | 43 | ### 分析 44 | 如下面所示,有三个指针,此时,让2的next指向1进行反转,然后指针2移动到3的位置,指针1移动到2的位置,指针3后移,继续进行反转,直到指针3指向为空时再进行最后一次反转。 45 | ![](./images/b575ae8e-a305-11e8-b04d-fa163e7a698c.png) 46 | 47 | 更简单来说只要两个指针,交换之前,先临时开辟第3个指针指向2的下一个,然后2的next指向1,2指向3,1指向2,完成一次循环。 48 | 49 | 需注意代码鲁棒性,考虑到头节点指针为空、只有1个节点即头节点的两种特殊情况。 50 | 51 | ### 代码 52 | 53 | ```python 54 | # -*- coding:utf-8 -*- 55 | # class ListNode: 56 | # def __init__(self, x): 57 | # self.val = x 58 | # self.next = None 59 | class Solution: 60 | # 返回ListNode 61 | def ReverseList(self, pHead): 62 | # 判断头节点为None或者只有头节点的特殊情况 63 | if not (pHead and pHead.next): 64 | return pHead 65 | a = pHead 66 | b = pHead.next 67 | # 需注意处理原来的头节点 68 | pHead.next = None 69 | while(b): 70 | c = b.next 71 | b.next = a 72 | a = b 73 | b = c 74 | return a 75 | ``` 76 | 77 | 78 | ## 合并两个排序的链表 79 | ### 问题 80 | 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 81 | 82 | ### 思路 83 | 两个指针指向两个链表,比较当前两个数,把较小的连到结果链上,指针后移,直到其中某个链表走完,把另一链表余下部分连到结果链末尾即可。 84 | 85 | 需要注意处理特殊输入(空)的情况。 86 | 87 | ### 代码 88 | 递归版本: 89 | ```java 90 | //java 91 | public ListNode Merge(ListNode list1,ListNode list2) { 92 |        if(list1 == null){ 93 |            return list2; 94 |        } 95 |        if(list2 == null){ 96 |            return list1; 97 |        } 98 |        if(list1.val <= list2.val){ 99 |            list1.next = Merge(list1.next, list2); 100 |            return list1; 101 |        }else{ 102 |            list2.next = Merge(list1, list2.next); 103 |            return list2; 104 |        }        105 |    } 106 | ``` 107 | 108 | 非递归版本 109 | ```java 110 | //java 111 | if(list1 == null){ 112 |             return list2; 113 |         } 114 |         if(list2 == null){ 115 |             return list1; 116 |         } 117 |         ListNode mergeHead = null; 118 |         ListNode current = null;      119 |         while(list1!=null && list2!=null){ 120 |             if(list1.val <= list2.val){ 121 |                 if(mergeHead == null){ 122 |                    mergeHead = current = list1; 123 |                 }else{ 124 |                    current.next = list1; 125 |                    current = current.next; 126 |                 } 127 |                 list1 = list1.next; 128 |             }else{ 129 |                 if(mergeHead == null){ 130 |                    mergeHead = current = list2; 131 |                 }else{ 132 |                    current.next = list2; 133 |                    current = current.next; 134 |                 } 135 |                 list2 = list2.next; 136 |             } 137 |         } 138 |         if(list1 == null){ 139 |             current.next = list2; 140 |         }else{ 141 |             current.next = list1; 142 |         } 143 |         return mergeHead; 144 | 145 | ``` 146 | python版本是自己写的,可以看到上一版本中,由于链表头需要单独处理,因此while中对每种情况,都单独区分了当前是链表头还是链表中间,而python版本里,新建了一个空的链表头(多开辟了一丢丢空间),使得链表头时的操作与链表中间的操作相同。 147 | 148 | ```python 149 | # python版本 150 | # class ListNode: 151 | # def __init__(self, x): 152 | # self.val = x 153 | # self.next = None 154 | class Solution: 155 | # 返回合并后列表 156 | def Merge(self, pHead1, pHead2): 157 | # 判空 158 | if not (pHead1 or pHead2): 159 | return None 160 | elif not pHead1: 161 | return pHead2 162 | elif not pHead2: 163 | return pHead1 164 | c1 = pHead1 165 | c2 = pHead2 166 | head = ListNode(0) 167 | c3 = head 168 | while c1 and c2: 169 | if c1.val <= c2.val: 170 | c3.next = c1 171 | c3 = c1 172 | c1 = c1.next 173 | else: 174 | c3.next = c2 175 | c3 = c2 176 | c2 = c2.next 177 | if c1: 178 | c3.next = c1 179 | if c2: 180 | c3.next = c2 181 | return head.next 182 | ``` 183 | 注意由于是链表,所以最后将余下部分直接整个链上就行,一开始很傻的写成了把剩下部分每一个节点遍历链接一遍 = = 184 | 185 | ## 树的子结构 186 | ### 题目 187 | 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构) 188 | 189 | ### 思路 190 | 191 | ![](./images/c2ceddb2-a305-11e8-a6f3-fa163e7a698c.png) 192 | 193 | 首先**遍历树1**,找到树1中与树2根节点值相等的节点,说明可能就是子结构的根。 194 | 然后检查以此节点为根的树3,是否包含树2中的结构。特别注意是3包含2,因此应当**遍历2**,看2中的结构在3中是否都有,即使3比2多,若2的结构3都有的话,也是包含。 195 | 196 | 由于树的操作比较复杂,因此很多地方需要判空,代码要写的足够完善。 197 | 198 | ### 代码 199 | 采用递归方法遍历树1,找到相同根节点后,对1的子树3,采用递归方法遍历树2并同步遍历检查子树3 200 | ```python 201 | # -*- coding:utf-8 -*- 202 | # class TreeNode: 203 | # def __init__(self, x): 204 | # self.val = x 205 | # self.left = None 206 | # self.right = None 207 | class Solution: 208 | def __init__(self): 209 | self.isSub = False 210 | 211 | def HasSubtree(self, pRoot1, pRoot2): 212 | # 外层遍历整个树1,找到和2根节点相等的 213 | # 找到则进入内层,遍历2,按相同方法遍历1,判断是否结构相同 214 | if not pRoot2: 215 | return False 216 | self.visit_tree(pRoot1, pRoot2) 217 | return self.isSub 218 | 219 | def visit_tree(self, root, root2): 220 | if not root: 221 | return 222 | else: 223 | if root.val == root2.val: 224 | if self.check_structure(root, root2): 225 | self.isSub = True 226 | self.visit_tree(root.left, root2) 227 | self.visit_tree(root.right, root2) 228 | 229 | def check_structure(self, root1, root2): 230 | if not root2: 231 | return True 232 | if not root1: 233 | return False 234 | else: 235 | if root1.val != root2.val: 236 | return False 237 | left_check = self.check_structure(root1.left, root2.left) 238 | right_check = self.check_structure(root1.right, root2.right) 239 | return left_check and right_check 240 | ``` 241 | 242 | 上面是自己手写的,比较啰嗦,而且找到子树以后还有一些冗余的遍历,甚至连init都搬出来了= = ,可参照下面较简洁的代码: 243 | 244 | ```python 245 | class Solution: 246 | def HasSubtree(self, pRoot1, pRoot2): 247 | result = False 248 | if pRoot1 and pRoot2: 249 | if pRoot1.val == pRoot2.val: 250 | result = self.check_structure(pRoot1, pRoot2) 251 | if not result: 252 | result = self.HasSubtree(pRoot1.left, pRoot2) 253 | if not result: 254 | result = self.HasSubtree(pRoot1.right, pRoot2) 255 | return result 256 | 257 | def check_structure(self, root1, root2): 258 | if not root2: 259 | return True 260 | if not root1: 261 | return False 262 | if root1.val != root2.val: 263 | return False 264 | left_check = self.check_structure(root1.left, root2.left) 265 | right_check = self.check_structure(root1.right, root2.right) 266 | return left_check and right_check 267 | ``` 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | ------ 276 | 277 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/7.md: -------------------------------------------------------------------------------- 1 | # 第 7 篇 2 | 3 | 4 | ## 二叉树的镜像 5 | ### 问题 6 | 操作给定的二叉树,将其变换为源二叉树的镜像。 7 | ``` 8 | 二叉树的镜像定义: 9 | 源二叉树 10 | 8 11 | / \ 12 | 6 10 13 | / \ / \ 14 | 5 7 9 11 15 | 16 | 镜像二叉树 17 | 8 18 | / \ 19 | 10 6 20 | / \ / \ 21 | 11 9 7 5 22 | ``` 23 | ### 思路 24 | 遍历树的每个节点,交换其左右子树就行。先序遍历或者后序遍历都可以,中序其实也行== 25 | ![](./images/e76b8242-a305-11e8-b04d-fa163e7a698c.png) 26 | 27 | ### 代码 28 | ```python 29 | # -*- coding:utf-8 -*- 30 | # class TreeNode: 31 | # def __init__(self, x): 32 | # self.val = x 33 | # self.left = None 34 | # self.right = None 35 | class Solution: 36 | # 返回镜像树的根节点 37 | def Mirror(self, root): 38 | # 思路 先序/后序遍历二叉树 当某节点被访问时 就交换其左右子树指针、 39 | if not root: 40 | return 41 | self.Mirror(root.left) 42 | self.Mirror(root.right) 43 | root.left, root.right = root.right, root.left 44 | return root 45 | ``` 46 | 47 | ## 顺时针打印矩阵 48 | ### 问题 49 | 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10. 50 | 51 | ### 思路 52 | 一圈一圈打印,如果理不清过程和边界条件,这个题就gg了。 53 | 54 | ![](./images/ec89f6d2-a305-11e8-bcef-fa163e7a698c.png) 55 | 56 | 注意到每一圈开头第一个格子在左上角,坐标为(0,0),(1,1),(2,2),依此类推,如果是5x5矩阵,最后一圈只有一个格子,起始坐标是(2,2);如果是6x6矩阵,最后一圈有4个格子,起始坐标也是(2,2)。仔细分析,可以得出条件:如果起始startx2 start * 2 and rows > start * 2: 76 | # 走一圈的代码 77 | end_x = cols - 1 - start 78 | end_y = rows - 1 - start 79 | # 打印第一行 80 | for i in range(start, end_x+1): 81 | result.append(matrix[start][i]) 82 | # 打印最后一列 83 | if start < end_y: 84 | for i in range(start+1, end_y+1): 85 | result.append(matrix[i][end_x]) 86 | # 打印最后一行 87 | if start < end_x and start < end_y: 88 | for i in range(end_x-1, start-1, -1): 89 | result.append(matrix[end_y][i]) 90 | # 打印第一列 91 | if start < end_x and start < end_y - 1: 92 | for i in range(end_y-1, start, -1): 93 | result.append(matrix[i][start]) 94 | start += 1 95 | return result 96 | ``` 97 | 98 | ### 更多 99 | 另一种很骚气的操作是,先取出第一行,打印,删掉,然后把矩阵按照逆时针旋转90°,仍然是取出第一行,打印,删掉……重复操作,直到删光。 100 | 101 | 矩阵的旋转操作可以这样进行: 102 | 【1,2,3】 103 | 【4,5,6】 104 | 如果是顺时针旋转90°,则先上下翻转矩阵,然后做转置 105 | 翻转得到 106 | 【4,5,6】 107 | 【1,2,3】 108 | 转置得到 109 | 【4,1】 110 | 【5,2】 111 | 【6,3】 112 | 如果是逆时针旋转90°,则先做转置,然后上下翻转矩阵 113 | 转置得到 114 | 【1,4】 115 | 【2,5】 116 | 【3,6】 117 | 翻转得到 118 | 【3,6】 119 | 【2,5】 120 | 【1,4】 121 | 122 | ```python 123 | # 在python中实现矩阵旋转 124 | # 原矩阵 125 | matrix = [ 126 | [1, 2, 3, 4, 5], 127 | [6, 7, 8, 9, 10], 128 | [11, 12, 13, 14, 15] 129 | ] 130 | # 顺时针旋转90° 131 | mt = [[]] 132 | mt[:] = map(list,zip(*matrix[::-1])) 133 | 134 | # 逆时针旋转90° 135 | mt2 = [[]] 136 | mt2[:] = map(list,zip(*matrix)) 137 | mt2 = mt2[::-1] 138 | 139 | # 说明 140 | # [::-1]的操作相当于翻转,意思是从后往前逐个读取列表中的元素 141 | # zip(*matrix)的意思相当于转置 142 | # 结果为 143 | # (1, 6, 11) 144 | # (2, 7, 12) 145 | # (3, 8, 13) 146 | # (4, 9, 14) 147 | # (5, 10, 15) 148 | ``` 149 | 150 | 那么这个题目用上面旋转的骚操作的代码: 151 | ```python 152 | def printMatrix(self, matrix): 153 | if not matrix: 154 | return matrix 155 | result = [] 156 | while len(matrix) > 0: 157 | result.extend(matrix[0]) 158 | matrix = zip(*matrix[1:])[::-1] 159 | return result 160 | ``` 161 | 162 | ## 包含min函数的栈 163 | ### 问题 164 | 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。 165 | 准确来说,就是为栈增加一个函数,调用此函数时,可以获取栈中的最小值。 166 | 167 | ### 思路 168 | 原始思路是,保存一个最小值,每当新值入栈时,就比较一下,更新此最小值。 169 | 但问题是,如果最小值出栈,或者说有多个最小值,其中某一个最小值出栈/全部最小值都已出栈,如何更新?如何判断?会带来比较复杂的问题。 170 | 171 | 较好的思路:开一个新栈,此栈与数值栈一一对应,在元素入栈时,就记录处于每一个位置时的当前最小值,这样不论什么情况,不论之前如何出栈入栈,都能立即获取到最小值。 172 | 173 | 174 | ![](./images/fa1374f4-a305-11e8-bcef-fa163e7a698c.png) 175 | ![](./images/ffc2c7ec-a305-11e8-bff9-fa163e7a698c.png) 176 | 177 | ### 代码 178 | ```python 179 | # -*- coding:utf-8 -*- 180 | class Solution: 181 | 182 | def __init__(self): 183 | self.cursor = -1 184 | self.main_stack = [] 185 | self.min_stack = [] 186 | 187 | def push(self, node): 188 | if self.cursor == -1: 189 | self.main_stack.append(node) 190 | self.min_stack.append(node) 191 | else: 192 | if self.min_stack[self.cursor] <= node: 193 | self.min_stack.append(self.min_stack[self.cursor]) 194 | else: 195 | self.min_stack.append(node) 196 | self.main_stack.append(node) 197 | self.cursor += 1 198 | 199 | def pop(self): 200 | if self.cursor == -1: 201 | return None 202 | else: 203 | del self.min_stack[self.cursor] 204 | result = self.main_stack[self.cursor] 205 | del self.main_stack[self.cursor] 206 | self.cursor -= 1 207 | return result 208 | 209 | def top(self): 210 | if self.cursor == -1: 211 | return None 212 | else: 213 | return self.main_stack[self.cursor] 214 | 215 | def min(self): 216 | if self.cursor == -1: 217 | return None 218 | else: 219 | return self.min_stack[self.cursor] 220 | ``` 221 | 222 | ## 栈的压入、弹出序列 223 | ### 问题 224 | 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的) 225 | 226 | ### 思路 227 | 模拟栈的弹出过程 228 | 读取弹出序列,如果读到的元素在栈顶,则弹出; 229 | 如果不在栈顶,则按照压入顺序压入元素,直到压到当前读到的弹出元素为止,然后继续弹出此元素。 230 | 如果压入序列已经读完,仍然没有读到当前弹出元素,说明这个元素可能已经是早就被压入,但是却早于比它后被压入的元素弹出,因此弹出序列有问题。 231 | 232 | 例如对于压入顺序 12345和弹出顺序45312,首先读到4,则从压入序列里依次压入1234,然后弹出4;读到5,则压入5,弹出5;读到3,栈顶恰好为3,弹出3;读到1,栈顶为2,应当继续压入直到压入1,但压入序列已经读完,没找到1,所以错误。 233 | 234 | 235 | ### 代码 236 | ```python 237 | def IsPopOrder(self, pushV, popV): 238 | # 逐个读取pop序列 239 | # 如果是位于栈顶,则pop 240 | # 如果不是,则读取push序列,依次入栈,直到压入要弹出的元素,然后弹出 241 | # 如果push读完但仍没找到,则错误 242 | 243 | stack = [] 244 | push_cursor = 0 245 | for pop in popV: 246 | if stack and pop == stack: 247 | stack.pop() 248 | else: 249 | while not(stack and pop == stack[-1]) and push_cursor < len(pushV): 250 | stack.append(pushV[push_cursor]) 251 | push_cursor += 1 252 | if push_cursor == len(pushV) and stack[-1] != pop: 253 | return False 254 | else: 255 | stack.pop() 256 | return True 257 | ``` 258 | 259 | ## 从上往下打印二叉树(二叉树层级遍历) 260 | ### 问题 261 | 从上往下打印出二叉树的每个节点,同层节点从左至右打印。 262 | 263 | ### 思路 264 | 本质上是图的广度优先遍历思想。 265 | 借助队列来辅助实现,先把根节点加入队列。每次从队列头取出一个节点,访问,然后把它的左右孩子节点加入队列尾部。重复过程,直到队列为空,则按照层级,访问了所有节点。 266 | 267 | ### 代码 268 | ```python 269 | class Solution: 270 | # 返回从上到下每个节点值列表,例:[1,2,3] 271 | def PrintFromTopToBottom(self, root): 272 | # 使用队列来辅助进行广度优先 273 | if not root: 274 | return [] 275 | queue = [root, ] 276 | output = [] 277 | while(queue): 278 | output.append(queue[0].val) 279 | if queue[0].left: 280 | queue.append(queue[0].left) 281 | if queue[0].right: 282 | queue.append(queue[0].right) 283 | queue.pop(0) 284 | return 285 | ``` 286 | 287 | ## 二叉搜索树的后序遍历序列 288 | 289 | ### 二叉搜索树 290 | 二叉搜索树(又称二叉排序树、二叉查找树) 的一些知识 见 : 291 | https://blog.csdn.net/yanxiaolx/article/details/51986428 292 | https://blog.csdn.net/yixianfeng41/article/details/52802855 293 | 294 | ### 问题 295 | 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。 296 | 297 | 298 | ### 思路 299 | 举例子来分析一下规律。 300 | 以{5,7,6,9,11,10,8}为例,最后一个节点就是根节点,因此以8为界,前面576是左子树,后面9,11,10是右子树。递归下去,根为6,5是左子树,7是右子树。10是根,9是左,11是右,ok。 301 | 以{7,4,6,5}为例,5为根,第一个元素就是7,说明没有左子树,全都右子树。但是右子树中出现了4比5小,所以不对。 302 | 303 | 需整理一下判断过程: 304 | 1)利用根节点,划分左右子树 305 | 2)检查,右子树的值必须都大于根节点 306 | 3)递归下去,判断左子树和右子树,且两个子树的结果均ok,才算ok 307 | 308 | 309 | ### 代码 310 | ```python 311 | # -*- coding:utf-8 -*- 312 | class Solution: 313 | def VerifySquenceOfBST(self, sequence): 314 | # 递归的过程,每一个子树最后一个元素是根节点,且前一段都比它小,后一段都比它大 315 | if not sequence: 316 | return False 317 | # 找到根节点 318 | root = sequence[-1] 319 | split = len(sequence) - 1 320 | # 找到划分点 321 | for i in range(0, len(sequence)-1): 322 | if sequence[i] >= root: 323 | split = i 324 | break 325 | # 确认后半段都比root大 326 | for i in range(split, len(sequence)-1): 327 | if sequence[i] <= root: 328 | return False 329 | # 递归检查前半段和后半段 330 | left = True 331 | if split > 0: 332 | # 前半段至少有两个元素,才递归 333 | left = self.VerifySquenceOfBST(sequence[0:split]) 334 | right = True 335 | if split < len(sequence) - 1: 336 | # 后半段至少有两个元素,才递归 337 | right = self.VerifySquenceOfBST(sequence[split:-1]) 338 | return left and right 339 | ``` 340 | 341 | ## 二叉树中和为某一值的路径 342 | ### 问题 343 | 输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 344 | 345 | 346 | ### 思路 347 | 采用递归,先序遍历。保留一个序列,存放当前路径,访问到某节点时,将其加入路径,并递归访问其孩子。如果遇到叶节点,加入路径,计算路径之和,如果和满足要求,则输出路径。继续后退到上一级时,从路径中删除下级节点。 348 | 349 | 例如,树 350 | ``` 351 | 10 352 | 5 12 353 | 4 7 354 | ``` 355 | 整个过程如下 356 | ![](./images/0fe0d024-a306-11e8-8f8e-fa163e7a698c.png) 357 | 358 | 359 | 分析路径的加入和删除,发现符合栈的结构。 360 | 361 | ### 代码 362 | ``` 363 | # -*- coding:utf-8 -*- 364 | # class TreeNode: 365 | # def __init__(self, x): 366 | # self.val = x 367 | # self.left = None 368 | # self.right = None 369 | import copy 370 | 371 | class Solution: 372 | def __init__(self): 373 | self.result = [] 374 | self.stack = [] 375 | self.sum = 0 376 | 377 | # 返回二维列表,内部每个列表表示找到的路径 378 | def FindPath(self, root, expectNumber): 379 | if not root: 380 | return [] 381 | self.Find(root, expectNumber) 382 | return self.result 383 | 384 | def Find(self, root, exp): 385 | self.stack.append(root.val) 386 | self.sum += root.val 387 | if root.left: 388 | self.Find(root.left, exp) 389 | if root.right: 390 | self.Find(root.right, exp) 391 | if not(root.left or root.right): 392 | if self.sum == exp: 393 | self.result.append(copy.deepcopy(self.stack)) 394 | self.sum -= root.val 395 | ``` 396 | 注意几点: 397 | 1)只当叶节点时才判断sum,因为这是根据定义的要求,路径必须结束在叶节点 398 | 2)注意python中的深浅拷贝,在append时如果直接append了stack,后面stack都pop光了,最后只剩下一堆空列表。可以用深拷贝,或者用`[n for n in self.stack]`这种列表生成式的方式。 399 | 400 | 401 | 402 | 403 | 404 | ------ 405 | 406 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/8.md: -------------------------------------------------------------------------------- 1 | # 第 8 篇 2 | 3 | ## 复杂链表的复制 4 | 5 | ### 问题 6 | 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点)。要把这个复杂链表完全复制独立的一份新链表。返回结果为复制后复杂链表的head。 7 | 8 | ![](./images/313e5700-a306-11e8-bcef-fa163e7a698c.png) 9 | 10 | 11 | ### 思路 12 | **基本思路** 13 | 两趟,第一趟按照next指针,把链表复制一遍;第二趟再处理random指针,比如说从A开始,A的random到C,数一数C在原链表中的位置,那么就在新链表里把新A位置的random指向对应的新C位置。 14 | 15 | 缺点:时间复杂度高,第二趟时每个节点都要从头找一遍,复杂度O(n^2) 16 | 17 | **改进思路:空间换时间** 18 | 既然上面第二趟要挨个找,那不如直接第一趟的时候就记录下新旧节点的地址(引用)的对应关系。 19 | 第二趟时,比如说从A开始,A的random指向C的引用,查表,就能立马得到新C的引用,将新C的引用直接赋给新A的random就行。 20 | 21 | 时间复杂度仍为O(n),而且代码简单,易于操作。缺点是多加了n的空间消耗。 22 | 23 | **变态思路:不用额外空间,时间复杂度仍O(n)** 24 | 在新建链表时,做成这种形式,第一步,将新链表的节点接到对应的旧节点的后面 25 | ![](./images/36acd5f4-a306-11e8-8eb6-fa163e7a698c.png) 26 | 27 | 第二步,复制指针。比如说A指向C,那么A的next是A',就指向C的next是C'。 28 | 29 | 30 | ![](./images/3c4c2578-a306-11e8-a6f3-fa163e7a698c.png) 31 | 32 | 第三步,拆分链表,恢复到新旧两个表。奇数位置的连起来就是原链表,偶数位置的连起来就是新链表。 33 | 34 | ![](./images/40ca72bc-a306-11e8-8f8e-fa163e7a698c.png) 35 | 36 | 这种方法虽然遍历了三趟,时间复杂度计算仍是O(n),而且未占用额外空间。缺点就是思路不是很容易想到,而且写的代码要复杂一些。 37 | 38 | ### 代码 39 | 这一段是按照思路2来写的。 40 | ```python 41 | # -*- coding:utf-8 -*- 42 | # class RandomListNode: 43 | # def __init__(self, x): 44 | # self.label = x 45 | # self.next = None 46 | # self.random = None 47 | class Solution: 48 | # 返回 RandomListNode 49 | def Clone(self, pHead): 50 | # 按照思路2来写的 51 | if not pHead: 52 | return None 53 | nHead = RandomListNode(pHead.label) 54 | pCurrent = pHead.next 55 | nCurrent = nHead 56 | # 初始化映射 57 | mapper = {} 58 | # 需要注意,要特别添加一个None到None的映射!! 59 | mapper[id(pHead)] = nHead 60 | mapper[id(None)] = None 61 | # 原样复制链表 62 | while(pCurrent): 63 | node = RandomListNode(pCurrent.label) 64 | nCurrent.next = node 65 | nCurrent = node 66 | mapper[id(pCurrent)] = node 67 | pCurrent = pCurrent.next 68 | # 添加random指针 69 | pCurrent = pHead 70 | nCurrent = nHead 71 | while(pCurrent): 72 | nCurrent.random = mapper[id(pCurrent.random)] 73 | pCurrent = pCurrent.next 74 | nCurrent = nCurrent.next 75 | return nHead 76 | ``` 77 | 78 | 79 | ## 二叉搜索树与双向链表 80 | 81 | ### 问题 82 | 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 83 | 84 | ### 思路 85 | 分析此问题,建议方法是画出一个二叉搜索树,然后手推一遍转换,从手推的过程中,发现如何程序化地做这道题。 86 | 87 | ![](./images/473e0550-a306-11e8-a6f3-fa163e7a698c.png) 88 | 89 | 直观来看,对二叉搜索树,做中序遍历即可按照从小到大的顺序来输出,可以用递归的方式来简便地写出中序遍历,这个过程,相当于把大树做了分解,分成根节点、左子树和右子树来处理。在遍历过程中,需要适时修改指针,完成向链表的转换。 90 | 91 | ![](./images/4c13f1d4-a306-11e8-8f8e-fa163e7a698c.png) 92 | 93 | 手推一遍转换,大概可以总结出如下步骤: 94 | 95 | - 拿到一棵子树 96 | - 如果它有左子树,则递归左子树。同时,需考虑到连接指针的问题,根节点要和左子树的最大节点进行连接,所以递归需要返回左子树**最大节点**,根节点与它双向连接。 97 | - 如果它有右子树,则递归右子树。同时,需考虑到连接指针的问题,根节点要和右子树的最大节点连接,所以递归需要返回右子树**最小节点**,根节点与它双向连接。但这样就会导致左右子树的递归函数不同,额外造成很多麻烦,考虑到递归返回时,子树已经转换成双向链表了,因此右子树也返回**最大节点**,然后一直往左找到其最小,连接到根上即可。这样左右子树递归的函数可以同样的方法来处理。 98 | - return返回值。根据上面的叙述,递归函数返回的是该子树中的最大值,如果有右子树,则返回右子树的最大值即可,如果没有右子树,则根节点最大,返回根节点的值。 99 | 100 | 按照上面步骤,写成代码如下。 101 | 102 | 另外的一种做法是,可以中序遍历,遍历到根节点时将根节点加入一个list里面,然后从头到尾访问一遍list,把前后相邻元素的指针互相修改指一下就可以。缺点是开辟了额外的一个list的一丢丢空间(但是没有创建新的树的节点嘛),优点是思路好想,不容易出错,而且没有多次一直向左寻找最小值的操作。 103 | 104 | ### 代码 105 | ```python 106 | # -*- coding:utf-8 -*- 107 | # class TreeNode: 108 | # def __init__(self, x): 109 | # self.val = x 110 | # self.left = None 111 | # self.right = None 112 | class Solution: 113 | def Convert(self, pRootOfTree): 114 | if not pRootOfTree: 115 | return None 116 | newRoot = self.visit(pRootOfTree) 117 | # 注意这里:递归返回的是整个双向链表的最大值节点,题目要求返回头节点(最小值) 118 | while(newRoot.left): 119 | newRoot = newRoot.left 120 | return newRoot 121 | 122 | # 递归遍历 123 | def visit(self, root): 124 | right_max = None 125 | if not root: 126 | return None 127 | # 先递归左子树 128 | if root.left: 129 | # 递归左子树,返回值是左子树的最大节点 130 | left = self.visit(root.left) 131 | # 修改根节点和left的指向 132 | left.right = root 133 | root.left = left 134 | # 再递归右子树 135 | if root.right: 136 | # 递归右子树,返回值是右子树的最大节点 137 | right = self.visit(root.right) 138 | # 向左寻找最小值之前,先记下最大值,后面return要用 139 | right_max = right 140 | # 修改根节点和right的指向 141 | while(right.left): 142 | right = right.left 143 | right.left = root 144 | root.right = right 145 | # 返回最大值 146 | if right_max: 147 | return right_max 148 | else: 149 | return root 150 | 151 | ``` 152 | 153 | 154 | ## 字符串的排列 155 | 156 | ### 问题 157 | 输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 158 | 159 | 输入描述: 160 | 输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。 161 | 162 | ### 思路 163 | 这相当于一个排列组合的问题。以abc为例,思考一下如果人工做这个题的思路。首先是固定首位为a,那么剩下bc,第二位若排b,第三位则排c,否则二c三b,所以可以排出abc,acb。然后首位为b,剩下ac,可以排出bac,bca。然后首位为c,剩下ab,可以排出cab,cba。 164 | 165 | 整体思路应该是,先固定首位,然后余下的部分做排列;余下部分固定首位,再余下做排列……是一种递归的思路。 166 | 167 | 思路还可以,实现的时候要想清楚细节。 168 | 169 | 在具体实现中,固定首位取余下,代码方面可以这样做:字符串的首位依次与后边交换,比如说 abc,首位a先与它自己a换,即abc,然后递归bc;首位a再与b换,即bac,然后递归ac(递归后,记得再换回去);首位a再与c换,得到cba,然后递归ba…… 170 | 171 | 因为题目要求字典序打印,而且输入中会有重复字符,所以对最终得到的list要做去重,然后字典序排序。 172 | 173 | ### 代码 174 | ```python 175 | class Solution: 176 | def __init__(self): 177 | self.result = [] 178 | 179 | def Permutation(self, ss): 180 | if not ss: 181 | return [] 182 | # python 不可直接修改string,先变成char数组 183 | chars = list(ss) 184 | # 开始递归 185 | self.permute(chars, 0) 186 | # 最终结果去重、排序 187 | res = list(set(self.result)) 188 | res.sort() 189 | return res 190 | 191 | def permute(self, string, begin):、 192 | # 已经递归到底了 193 | if begin == len(string): 194 | self.result.append(''.join(string)) 195 | else: 196 | # 轮番从各个位置的char中选一个当作首位,交换位置 197 | for i in range(begin, len(string)): 198 | string[i], string[begin] = string[begin], string[i] 199 | # 固定首位,递归余下的 200 | self.permute(string, begin+1) 201 | # 记得换回去 202 | string[i], string[begin] = string[begin], string[i] 203 | ``` 204 | 205 | ### 扩展 206 | 207 | (1)如果不是排列,而是组合问题呢?例如abc的组合有a,b,c,ab,ac,bc,abc 208 | 是否仍然可用递归的方法? 209 | 210 | (2)给定8个数字,判断有没有可能把8个数字放到正方体8个顶点上,使得正方体三组相对的面上的4个顶点的和相等。 211 | 212 | 思路:得到8个数字的各种排列,逐个判断是否符合条件 213 | 214 | (3)**八皇后问题** 215 | 8x8棋盘上摆放8个棋子,使其不能互相攻击(任意两个棋子不得放在同一行、同一列、同一对角线) 216 | 217 | 218 | ![](./images/5b7cd91a-a306-11e8-89f2-fa163e7a698c.png) 219 | 220 | 【一种思路】 221 | 222 | 首先8个皇后不能有任意两个在同一行,肯定每个皇后一行,所以我们定义一个数组arr[8],数组的下标为每个皇后所在行,值为该皇后所在列。所以这个数组就能唯一确定一个棋局了。 223 | 224 | 由于8个皇后也不能有任意两个在同一列,肯定每个 一列,所以数组arr[8]中的元素可以用0-7来填充,相当于对0-7数字进行全排列。也就是说,问题到这里就变成了求0-7这8个数字的全排列。 225 | 226 | 上述已满足行列的限制,对于全排列中的每种方式,再判断是否对角线即可,也就是说对每种棋局,要判断任意两个下标i和j,不得有 i-j=arr[i]-arr[j] 或者 j-i=arr[i]-arr[j]。 227 | 228 | 【其他思路】 229 | 230 | 模拟8x8棋盘,在初始棋盘的任何位置,都可以先放一个棋子,然后标记它的控制范围,再在余下的可行位置放置一个棋子,标记控制范围,依此递归下去。 231 | 缺点是要判断的次数太多了。不如第一种思路好。 232 | 233 | 234 | 【关键词】 235 | 八皇后、回溯法、排列组合、递归 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | ------ 244 | 245 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/9.md: -------------------------------------------------------------------------------- 1 | # 第 9 篇 2 | 3 | ## 数组中出现次数超过一半的数字 4 | 5 | ### 题目 6 | 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。 7 | 8 | ### 思路 9 | (1)基本思路 10 | 直观的思路就是拿空间换时间,用一个字典记录下出现的字符和次数,最后找出最大;另外思路是先把数组排序,那么中间的数字应当是要找的,检查一下它的出现次数是否真的超过长度的一半就行。第一种方法占了很多额外空间,第二种方法时间复杂度是O(nlogn)-由排序决定。 11 | 12 | (2)较好思路 13 | 有没有时间复杂度为O(n)的方法呢?“多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(N)。”。关于这种算法的思想,介绍如下。(ref: https://www.jianshu.com/p/dfd676b71ef0) 14 | 15 | 直观理解,该方法的思路就是,让数组中不同的元素两两抵消,最后只剩下同一种元素。如果是两个非主要元素抵消了,那么主要元素的地位得到了巩固;就算是一个主要元素和一个非主要元素抵消了,由于目标出现的次数超过数组长度的一半,所以就算是主要元素挨个与其他元素抵消,最后剩下的也肯定是主要元素。 16 | 17 | 但是这种抵消的方法,人工做起来很快,写成代码从数组里增增减减就比较麻烦。此时可以采用一种计数的方法。规则是,记下一个数字target和一个count。遍历数组,当遍历到下一个元素next时,如果和当前记下的数字target相同,那么count+1,如果不同,count-1(相当于两个抵消了)。如果在遍历到下一个元素next之前,count已经被减为0了,则说明之前的抵消干净了,此时应当将target设置为next,count记为1。仔细想一下,这种方法就是模拟了不同的元素两两抵消。所以最后一个使count设为1的元素,战胜了其他所有元素,它就是出现次数多于一半的目标。 18 | 19 | 需要注意:上面的前提是目标一定存在。如果目标不存在,比如[1,2,3,3,4],使count最后一次被设置为1时的元素是3,但3出现的次数小于一半。所以最终还要再遍历一遍,判断一下找到的元素是否个数真的多于一半。 20 | 21 | ### 代码 22 | ```python 23 | # -*- coding:utf-8 -*- 24 | class Solution: 25 | def MoreThanHalfNum_Solution(self, numbers): 26 | if not numbers: 27 | return 0 28 | target = numbers[0] 29 | count = 1 30 | for i in range(1, len(numbers)): 31 | if count == 0: 32 | target = numbers[i] 33 | count = 1 34 | elif target == numbers[i]: 35 | count += 1 36 | elif target != numbers[i]: 37 | count -= 1 38 | # count==0说明刚好消干净了,没有个数多于一半的,就不用再数了直接返回0 39 | if count == 0: 40 | return 0 41 | count = 0 42 | for i in range(0, len(numbers)): 43 | if numbers[i] == target: 44 | count += 1 45 | if count > len(numbers)/2: 46 | return target 47 | else: 48 | return 0 49 | ``` 50 | 51 | ### 另外思路:Partition的递归方法 52 | 复杂度也是O(n)。之前的分析提到,如果数组已经排好序,那么位于n/2位置的数字,就是要找到的数字。(如果数组不一定有解的话,再检查一遍n/2位置数字出现的次数是否满足多于一半的要求即可)。 53 | 54 | 但其实,不一定非要排好序,也能找到位于中间的数字。借鉴了快速排序的思路,在随机快速排序中,先随机取一个数,比它小的移到它左边,比它大的移到它右边。如果移动之后该数字的下标是n/2说明它就是中间的,如果大于n/2说明中位数在它左边,如果小于n/2说明中位数在它右边,递归继续找就行。最后找到了应当位于n/2位置的数,再数一遍检查是否满足要求即可。 55 | 56 | 值得一提的是,这种思路修改了原来的数组内容。 57 | 58 | 59 | 60 | 61 | ## 最小的K个数 62 | 63 | ### 问题 64 | 输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。 65 | 66 | ### 思路 67 | (1)基本思路 68 | 最直接的还是排序,排完直接就能取到。时间复杂度依赖于排序的时间复杂度。 69 | 70 | (2)Partition的思路【推荐】 71 | 此外,还可使用之前Partition的思想,不过这次找的是下标为k-1的(也就是第k个数)。先随便找一个,比它小的移到左边,比它大的移到右边,然后看它的下标是否为k-1,如果是就找到了,它左边的就是最小的4个,如果小于k-1则把它 右边的递归,如果大于k-1则把它左边的递归去找k-1。这种思路修改了原来的数组内容。 72 | 73 | (3)冒泡思路 74 | 类似冒泡排序的方法,只不过由于要找最小的4个,从右往左冒泡,只要冒四次就可以了,不用全部冒完。正常冒泡排序的复杂度是O(n^2)。 75 | 76 | (4)堆的思路【推荐】 77 | 另一种比较容易想的思路是,找个小本本记下当前遇到的最小的四个数,把所有数找一遍,遇到新的数时就和小本本上的做比较,更新小本本,把整个数据走一遍,就找到最小的四个数了。 78 | 79 | 因此整个原数组只需过一遍,但这时重点转移在了如何构建这个小本本,实现快速查找和更新(判断当前数应不应该插入,插入在哪里)。这时就可以引入堆树(一种完全二叉树,见 https://blog.csdn.net/guoweimelon/article/details/50904346)。 80 | 81 | 由于我们要找最小的k个,因此可以采用最大堆,根节点的值总是大于任何一个子节点,这样当遇到新的数时,只需看一下它和根节点的大小,O(1)就能判断是否需要更新这个堆,如果需要更新的话,则需要O(logk)来找到要插入的位置。每个数字都需要经过这一步骤,因此时间复杂度为O(nlogk)(这里的log都以2为底)。 82 | 83 | 堆的解法虽然时间复杂度大,但好处在于没有修改原输入数据,而且适合海量数据的场景。只要k不是很大,只需在内存中维护这个小本本就行,哪怕原始输入非常大无法放入内存,也只需从磁盘中读一遍,完全能处理得过来。 84 | 85 | ### 代码 86 | 偷懒一下,这里写了一个冒泡的代码。 87 | ```python 88 | class Solution: 89 | def GetLeastNumbers_Solution(self, tinput, k): 90 | if not tinput: 91 | return [] 92 | # 注意:要处理当k值比输入的数组长度还长时的报错 93 | if len(tinput) < k: 94 | return [] 95 | for i in range(0, k): 96 | for j in range(len(tinput)-1, i, -1): 97 | if tinput[j] < tinput[i]: 98 | tinput[j], tinput[i] = tinput[i], tinput[j] 99 | return tinput[:k] 100 | ``` 101 | 102 | 堆和各种排序的,后面抽出时间专门整理一下排序相关的算法吧。 103 | 104 | 105 | ## 连续子数组的最大和 106 | 107 | ### 问题 108 | 一个整型数组(有正数也有负数),数组中一个或连续多个的元素组成子数组,找出子数组的最大和。要求时间复杂度O(n)。 109 | 110 | 例如,[1,-2,3,10,-4,7,2,-5]其中和最大的子数组是[3,10,-4,7,2],因此输出18。 111 | 112 | ### 思路 113 | (1)最笨的方法,枚举所有子数组然后找最大,这种方法相当于一个组合问题,复杂度O(n^2)肯定不行。 114 | 115 | (2)举例来模拟判断过程,寻找判断规律。 116 | ![](./images/8615a224-a306-11e8-89f2-fa163e7a698c.png) 117 | 记下两个数字:当前的累加和A,以及当前出现过的最大的和B。 118 | A=0,B=0. 119 | 从1开始,先加1,A=B=1。 120 | 再加-2,A=-1,B=1. 121 | 再加3, A=2,而如果抛弃历史加和,A=3,比带历史包袱的情况下要高,所以果断抛弃历史,A=B=3. 122 | 再加10, A=B=13 123 | 再加-4,A=9, B=13 124 | 再加7, A=16,B=16 125 | 再加2,A=18, B=18 126 | 再加-5,A=13, B=18 127 | 所以最后得到最大和为18. 128 | 129 | 整理一套判断思路是,获得一个数,看A如果小于0,说明此时历史带来的是负面影响,直接抛弃历史,将A更新为当前数;如果A大于0,则A加上当前数。更新A之后,如果A大于B则更新B,否则继续读下一个数。(注意:A<0抛弃历史,A可以更新为当前数,但B不可以直接更新,因为B记录的是历史最大值) 130 | 131 | 132 | ### 代码 133 | ```python 134 | # -*- coding:utf-8 -*- 135 | import sys 136 | 137 | class Solution: 138 | def FindGreatestSumOfSubArray(self, array): 139 | # write code here 140 | if not array: 141 | return 0 142 | current_sum = sys.maxsize * -1 143 | max_sum = current_sum 144 | for i in array: 145 | if current_sum < 0: 146 | current_sum = i 147 | else: 148 | current_sum += i 149 | if current_sum > max_sum: 150 | max_sum = current_sum 151 | return max_sum 152 | ``` 153 | 154 | 注意的地方:1,判断输入是否有效;2,一开始的时候将current_sum和max_sum设为0,但当整个数组的和最大值是负数的时候,上述设置按照代码的逻辑max_sum一直得不到更新就出现了问题。 155 | 156 | ## 从1到n整数中1出现的次数 157 | 158 | ### 问题 159 | 给定n,求1到n的整数中1这个字符出现的次数。例如n=13,1~13中包含1的数字有1、10、11、12、13,因此字符1共出现6次. 160 | 161 | ### 思路 162 | 最笨的每个数字去判断的方法肯定不行,太复杂。可以从分析数字的规律入手。 163 | 164 | 先以个位数字为例,每0-9中,个位上就会出现1个1。 165 | 以十位数字为例,每0-99中,十位上就会出现10个1。 166 | 以百位数字为例,每0-999中,百位上就会出现100个1。 167 | 以千位数字为例,每0-9999中,千位上就会出现1000个1。 168 | 按照如上规律,可以从个位开始,逐个分析每一位上能出现的1的个数,加总起来就可以了。 169 | 170 | 有很需要注意的细节问题。 171 | 172 | 例如1133中,1133/10是113,但实际上由于个位是3,0-3相当于也能凑够一个0-9,所以应当是114个个位的1。 173 | 174 | 同理,1133中,要考虑十位上的1的个数,1133/100是11,但由于十位是3,0-3相当于0-9,所以有12x10个十位的1。 175 | 176 | 如果数字是1113,要考虑十位上1的个数呢?情况变得更加复杂,因为1133能凑够12个100,但1113只能凑够11个整的100,余下的13,其十位上也有一个1,所以就是11个整的一百x每一百就有10个十位上的一,外加单独13中的4个十位上的1。 177 | 178 | 如果数字是1103呢?那就只有11个整的100,余下的03十位上没有1。 179 | 180 | 综合以上的叙述,大概形成下面的代码。虽然代码看起来只有几行,但道理比较难想通。举个例子按代码跑一遍就能好理解一些。以n=113为例。 181 | 182 | m=1, a=113, b=0, a+8=121, (a+8)/10 = 12, 12x1=12. a%10=3,所以由于这个3中包含的1已经在前面a+8里考虑进去了,所以不再管了。 183 | 184 | m=10, a=11, b=3, a+8=19, (a+8)/10=1(此时113中只考虑了0-99,而100-113中的十位的1没有考虑进去),1x10=10. a%10=1,所以单独拎出来考虑100-113,这当中的十位上有多少个1呢,从110-113,有4个十位上的1,所以b+1=4。 185 | 186 | m=100, a=1, b=13, a+8=9, (a+8)/10=0 (所以此时并没有凑够0-999),而由于 a%10=1,所以单独考虑100-113的百位上的1,数量为13+1=14。 187 | 188 | (真复杂啊呵呵) 189 | 190 | ### 代码 191 | ```python 192 | class Solution: 193 | def NumberOf1Between1AndN_Solution(self, n): 194 | ones = 0 195 | m = 1 196 | while m <= n: 197 | a = n // m 198 | b = n % m 199 | ones += (a + 8) / 10 * m + (a % 10 == 1) * (b + 1) 200 | m *= 10 201 | return ones 202 | ``` 203 | 204 | ## 把数组排成最小的数 205 | 206 | ### 问题 207 | 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。 208 | 209 | ### 思路 210 | 最笨的方法:对数组的元素进行全排列,比较各个结果的大小。 211 | 212 | 这里需要注意一些问题:将多个数字拼接起来,可能会导致超出某些语言里面int的最大范围;全排列的话非常慢,而且将两个整数用数学的方法拼起来也需要一些处理过程。 213 | 214 | 既然是数字拼接,可以先将数字转成字符串,拼接之后再转回数字,就能很快实现拼接了。进而思考,像题中的例子,3,32,321的拼接规则是:先看高位,高位选最小的;如果高位一样,再看第二位,选最小的,依此类推。也就是说,似乎将数字转为字符串,然后字符串排序一下,拼出来的就是满足要求的结果了。 215 | 216 | 但是需注意:默认字符串字典排序中,3和32,应当是32更大,但显然这里应当将32排在前面,因为323和332相比显然323更小。所以需要自定义一个排序的方法。Java中可以重写Comparator,Python中也可以自定义排序的方法。下面代码中的这种自定义的排序方法很骚气,完全面向要实现的目标,而且简洁易懂,没有复杂的各种规则。 217 | 218 | 剑指Offer书中还对这种排序的规则和这种字符串排序拼接方法的正确性进行了论证,详可参考书籍。 219 | 220 | ### 代码 221 | ```python 222 | # -*- coding:utf-8 -*- 223 | class Solution: 224 | def PrintMinNumber(self, numbers): 225 | if not numbers: 226 | return "" 227 | str_list = [str(n) for n in numbers] 228 | # python中,sort可以传入自定义的排序方法 229 | str_list.sort(self.Sort) 230 | return int(''.join(str_list)) 231 | 232 | def Sort(self, x, y): 233 | # 如何判断3和32谁应该排在前面呢? 234 | # 只要将他俩正反拼接一下,就知道了!这个排序的比较方法很骚气,完全不需要定义什么复杂的字典序规则! 235 | left = x + y 236 | right = y + x 237 | if left > right: 238 | return 1 239 | else: 240 | return -1 241 | ``` 242 | 243 | 244 | 245 | 246 | 247 | ------ 248 | 249 | 本文稿来自 https://github.com/dox1994/offer-coding-interviews-python,欢迎前来给个star🌟~ 如有错误或遗漏欢迎issue~ -------------------------------------------------------------------------------- /notes/images/084855b0-c946-11e8-8843-fa163e7a698c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/084855b0-c946-11e8-8843-fa163e7a698c.jpg -------------------------------------------------------------------------------- /notes/images/0fe0d024-a306-11e8-8f8e-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/0fe0d024-a306-11e8-8f8e-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/1c5df960-c932-11e8-832e-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/1c5df960-c932-11e8-832e-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/2cb28482-a305-11e8-8f8e-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/2cb28482-a305-11e8-8f8e-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/313e5700-a306-11e8-bcef-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/313e5700-a306-11e8-bcef-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/34efc1aa-a305-11e8-8eb6-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/34efc1aa-a305-11e8-8eb6-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/36acd5f4-a306-11e8-8eb6-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/36acd5f4-a306-11e8-8eb6-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/3c4c2578-a306-11e8-a6f3-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/3c4c2578-a306-11e8-a6f3-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/40ca72bc-a306-11e8-8f8e-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/40ca72bc-a306-11e8-8f8e-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/473e0550-a306-11e8-a6f3-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/473e0550-a306-11e8-a6f3-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/4c13f1d4-a306-11e8-8f8e-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/4c13f1d4-a306-11e8-8f8e-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/5abe55a4-a305-11e8-b04d-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/5abe55a4-a305-11e8-b04d-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/5b7cd91a-a306-11e8-89f2-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/5b7cd91a-a306-11e8-89f2-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/60225484-ca1d-11e8-8843-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/60225484-ca1d-11e8-8843-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/8615a224-a306-11e8-89f2-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/8615a224-a306-11e8-89f2-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/b575ae8e-a305-11e8-b04d-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/b575ae8e-a305-11e8-b04d-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/bb21c362-a306-11e8-b04d-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/bb21c362-a306-11e8-b04d-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/be838be4-a306-11e8-8f8e-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/be838be4-a306-11e8-8f8e-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/c2ceddb2-a305-11e8-a6f3-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/c2ceddb2-a305-11e8-a6f3-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/c3beec5c-a306-11e8-89f2-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/c3beec5c-a306-11e8-89f2-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/c68e74da-ca2e-11e8-8843-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/c68e74da-ca2e-11e8-8843-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/dea686ac-ca29-11e8-8843-fa163e7a698c (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/dea686ac-ca29-11e8-8843-fa163e7a698c (1).png -------------------------------------------------------------------------------- /notes/images/dea686ac-ca29-11e8-8843-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/dea686ac-ca29-11e8-8843-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/e76b8242-a305-11e8-b04d-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/e76b8242-a305-11e8-b04d-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/ec89f6d2-a305-11e8-bcef-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/ec89f6d2-a305-11e8-bcef-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/f26efe8a-a305-11e8-89f2-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/f26efe8a-a305-11e8-89f2-fa163e7a698c.png -------------------------------------------------------------------------------- /notes/images/fa1374f4-a305-11e8-bcef-fa163e7a698c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/notes/images/fa1374f4-a305-11e8-bcef-fa163e7a698c.png -------------------------------------------------------------------------------- /pdf/1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/1.pdf -------------------------------------------------------------------------------- /pdf/10.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/10.pdf -------------------------------------------------------------------------------- /pdf/11.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/11.pdf -------------------------------------------------------------------------------- /pdf/12.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/12.pdf -------------------------------------------------------------------------------- /pdf/13.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/13.pdf -------------------------------------------------------------------------------- /pdf/14.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/14.pdf -------------------------------------------------------------------------------- /pdf/15.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/15.pdf -------------------------------------------------------------------------------- /pdf/2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/2.pdf -------------------------------------------------------------------------------- /pdf/3.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/3.pdf -------------------------------------------------------------------------------- /pdf/4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/4.pdf -------------------------------------------------------------------------------- /pdf/5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/5.pdf -------------------------------------------------------------------------------- /pdf/6.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/6.pdf -------------------------------------------------------------------------------- /pdf/7.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/7.pdf -------------------------------------------------------------------------------- /pdf/8.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/8.pdf -------------------------------------------------------------------------------- /pdf/9.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bytetopia/offer-coding-interviews-python/126bb08209528ed541fddfb2d17f2e412b07897d/pdf/9.pdf --------------------------------------------------------------------------------