diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..89fa887 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,4 @@ +# These are supported funding model platforms + +github: [PegasusWang] +custom: ["https://www.paypal.me/pegasuswang"] diff --git a/.gitignore b/.gitignore index e70d4d9..43c172d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # python __pycache__/ *.py[cod] +*.pyc *$py.class .python-version @@ -30,4 +31,4 @@ site/ .vscode/ # pytest -.pytest_cache/ \ No newline at end of file +.pytest_cache/ diff --git a/163_course.png b/163_course.png new file mode 100644 index 0000000..9fce038 Binary files /dev/null and b/163_course.png differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c4910bd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 PegasusWang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 31a7173..0d2a994 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,26 @@ ## 链接 视频教程已经发布在网易云课堂和 csdn 学院,内容一致,推荐使用网易云课堂。 -[网易云课堂: Python数据结构与算法教程](http://study.163.com/course/introduction.htm?courseId=1005526003) 视频教程 +- [网易云课堂: Python数据结构与算法教程](http://study.163.com/course/introduction.htm?courseId=1005526003) +- [csdn 学院:Python数据结构与算法教程](https://edu.csdn.net/course/detail/8332) -[csdn 学院:Python数据结构与算法教程](https://edu.csdn.net/course/detail/8332) +电子书地址: -[网上阅读《Python 算法与数据结构教程 》](http://pegasuswang.github.io/python_data_structures_and_algorithms/) +- [网上阅读《Python 算法与数据结构教程 》](http://pegasuswang.github.io/python_data_structures_and_algorithms/) +- [github 链接](https://github.com/PegasusWang/python_data_structures_and_algorithms) +- [readthedoc 电子书下载](http://python-data-structures-and-algorithms.readthedocs.io/zh/latest/) +- [《开源一个 Python 算法和数据结构中文教程[视频]》](https://zhuanlan.zhihu.com/p/36038003) 视频讲解示例 -[github 链接](https://github.com/PegasusWang/python_data_structures_and_algorithms) +leetcode 实战图解教程(推荐): -[readthedoc 电子书下载](http://python-data-structures-and-algorithms.readthedocs.io/zh/latest/) +如果您有一定的基础,只是想快速针对面试刷题,也可以直接参考笔者针对《剑指offer》和 leetcode 经典题目的 Python 刷题图解实战。 -[《开源一个 Python 算法和数据结构中文教程[视频]》](https://zhuanlan.zhihu.com/p/36038003) 视频讲解示例 +- [图解Python数据结构与算法-实战篇- leetcode经典题目实战](https://study.163.com/course/courseMain.htm?courseId=1212203808&share=2&shareId=400000000496051) + +笔者的其他课程: + +- [玩转Vim 从放弃到爱不释手](https://www.imooc.com/learn/1129) +- [Python工程师面试宝典](https://coding.imooc.com/class/318.html) ## 痛点 - 讲 Python 数据结构和算法的资料很少,中文资料更少 @@ -27,7 +36,7 @@ - 网上很多视频教程不够循序渐进,不成系统 ## 作者简介 -曾就职于[知乎](https://www.zhihu.com/people/pegasus-wang/activities),任后端工程师,多年 Python 开发经验。 +曾就职于[知乎](https://www.zhihu.com/people/pegasus-wang/activities),现腾讯视频后端工程师,多年 Python/Go 开发经验。 知乎专栏: @@ -100,23 +109,25 @@ Python 抽象程度比较高, 我们能用更少的代码来实现功能,同 - 无需太多数学基础,仅在算法时间复杂度分析的时候会用到一些简单数学知识。对于学习基础算法,逻辑思维可能更重要一些 ## 参考教材和链接 -这里我参考过三本书,均可以网购纸质版或者网络上搜索电子版,建议大家先大致阅读一本教材掌握基本原理: +这里我参考过三本书,均可以网购纸质版或者网络上搜索电子版,建议大家先大致阅读一本教材掌握基本原理,本教程重点在于 Pythonic 代码实现: [《算法图解》](https://book.douban.com/subject/26979890/): 图解的形式很适合新手,示例使用的是 python。推荐基础较少的同学看这本书入门 [《Data Structures and Algorithms in Python》]( https://book.douban.com/subject/10607365/): 适合对 Python 和算法比较熟悉的同学,或者是有其他语言编程经验的同学。本书是英文版,缺点是书中错误真的很多,代码有些无法运行而且不够 Pythonic。该书 [勘误](http://bcs.wiley.com/he-bcs/Books?action=resource&bcsId=9003&itemId=0470618299&resourceId=35653) -[《算法导论》第三版]( https://book.douban.com/subject/20432061/): 喜欢数学证明和板砖书的同学可以参考,有很多高级主题。使用伪代码 +[《算法导论》第三版]( https://book.douban.com/subject/20432061/): 喜欢数学证明和板砖书的同学可以参考,有很多高级主题。使用伪代码可以很快翻译成 Python ## 算法可视化 -学习算法的过程中有时候会比较抽象,这里给大家推荐一些可视化的网站,方便更直观地理解: - -https://github.com/algorithm-visualizer/algorithm-visualizer - -https://www.cs.usfca.edu/~galles/visualization/Algorithms.html +学习算法的过程中有时候会比较抽象,这里给大家推荐一些可视化的网站,方便更直观地理解各种算法和数据结构的执行步骤: +遇到一个算法或数据结构,你可以 google 搜索 "名称+ visualization" 找到一些可视化网站方便理解,比如学习跳跃表的时候笔者就 +可以通过 goole "skip list visualization" 搜到一些可视化网站帮助你理解它的工作原理。 +- https://github.com/algorithm-visualizer/algorithm-visualizer +- https://www.cs.usfca.edu/~galles/visualization/Algorithms.html +- https://cmps-people.ok.ubc.ca/ylucet/DS/Algorithms.html +- https://runestone.academy/runestone/books/published/pythonds/index.html# ## 讲课形式 @@ -156,7 +167,9 @@ https://www.cs.usfca.edu/~galles/visualization/Algorithms.html ## 如何获取每章代码 注意每一章目录里都有 py 文件,在电子书里看不到。clone 下本代码仓库找到对应目录里的 python 文件即是每章涉及到的代码。 -由于代码实现千差万别,本书代码实现具有一定的个人风格,不代表最佳实现,仅供参考。 +由于代码实现千差万别,本书代码实现具有一定的个人风格,不代表最佳实现,仅供参考,笔者尽量使用 python2/3 兼容代码。 +目前已经新增《剑指offer》大部分经典题目的 Python 解法,每道题目附带leetcode 地址,大家可以自己尝试解决提交。 +本项目遵守 MIT 协议,本项目下的所有代码您可以任意学习修改和使用, 但是直接引用代码请加上本项目 github 地址。 ## 如何学习 @@ -301,9 +314,9 @@ def test(): 如果读者关于代码、视频、讲义有任何疑问,欢迎一起讨论 请注意以下几点: -- 优先在网易云课堂的讨论区提问,方便别的同学浏览。如果未购买视频,也可以直接在 github 里提出 issue,笔者有空会给大家解答和讨论。 -- 描述尽量具体,视频或者代码哪一部分有问题?请尽量把涉及章节和代码贴出来,方便定位问题。 +- 描述尽量具体,视频或者代码哪一部分有问题(可以具体到文档或者代码行数)?请尽量把涉及章节和代码贴出来,方便定位问题。 - 如果涉及到代码,提问时请保持代码的格式 +- 如果直接提了代码bug,最好有相关测试用例展示失败 test case,方便复现问题 ## 本电子书制作和写作方式 @@ -331,4 +344,4 @@ mkdocs gh-deploy # 部署到自己的 github pages 扫码加入课程: -![扫码加入课程返现30%](https://camo.githubusercontent.com/a217604a83d60fdc610ba91e5c771664a4645a79/687474703a2f2f376b747574792e636f6d312e7a302e676c622e636c6f7564646e2e636f6d2f53637265656e25323053686f74253230323031382d30362d3032253230617425323032302e33372e34362e706e67) +![扫码加入课程](./163_course.png) diff --git "a/docs/03_\351\223\276\350\241\250/linked_list.py" "b/docs/03_\351\223\276\350\241\250/linked_list.py" index cb5608b..b64eb1c 100644 --- "a/docs/03_\351\223\276\350\241\250/linked_list.py" +++ "b/docs/03_\351\223\276\350\241\250/linked_list.py" @@ -77,7 +77,10 @@ def remove(self, value): # O(n) if curnode.value == value: prevnode.next = curnode.next if curnode is self.tailnode: # NOTE: 注意更新 tailnode - self.tailnode = prevnode + if prevnode is self.root: + self.tailnode = None + else: + self.tailnode = prevnode del curnode self.length -= 1 return 1 # 表明删除成功 @@ -185,6 +188,13 @@ def test_linked_list_remove(): ll.remove(7) print(list(ll)) +def test_single_node(): + # https://github.com/PegasusWang/python_data_structures_and_algorithms/pull/21 + ll = LinkedList() + ll.append(0) + ll.remove(0) + ll.appendleft(1) + assert list(ll) == [1] def test_linked_list_reverse(): ll = LinkedList() @@ -203,6 +213,7 @@ def test_linked_list_append(): if __name__ == '__main__': + test_single_node() test_linked_list() test_linked_list_append() test_linked_list_reverse() diff --git "a/docs/03_\351\223\276\350\241\250/lru_cache.py" "b/docs/03_\351\223\276\350\241\250/lru_cache.py" index 7e77a03..e1683bb 100644 --- "a/docs/03_\351\223\276\350\241\250/lru_cache.py" +++ "b/docs/03_\351\223\276\350\241\250/lru_cache.py" @@ -62,8 +62,9 @@ def f(n): 只需要让 root 的前一个指向这个被删除节点,然后让之前的最后一个节点也指向它就行了。 使用了循环双端链表之后,我们的操作就都是 O(1) 的了。这也就是使用一个 dict 和一个 循环双端链表 实现LRU 的思路。 -不过一般我们使用内置的 OrderedDict(原理和这个类似)就好了,要实现一个循环双端链表是一个不简单的事情。 +不过一般我们使用内置的 OrderedDict(原理和这个类似)就好了,要实现一个循环双端链表是一个不简单的事情,因为链表操作很容易出错。 +补充:其实 lru 有个缺点就是额外的链表比较占用空间,如果你感兴趣的话可以看看 redis 如何实现的 lru 算法 """ @@ -112,7 +113,7 @@ def _(n): def f_use_lru(n): if n <= 1: # 0 or 1 return n - return f(n - 1) + f(n - 2) + return f_use_lru(n - 1) + f_use_lru(n - 2) def test(): @@ -131,3 +132,83 @@ def test(): if __name__ == '__main__': test() + + +######################################### 使用双链表实现 LRUcache #################################################### +""" +一般面试中不会让我们直接用内置结构,所以这里提供一个自己实现的双链表+map lru 缓存。这也是力扣上的一道真题: +[146] LRU 缓存 https://leetcode-cn.com/problems/lru-cache/description/ +""" + +class ListNode: + def __init__(self, key=None, value=None): + self.key = key + self.value = value + self.prev = self.next = None + + +class List: + def __init__(self): + """循环双链表。注意增加了虚拟头尾结点 head,tail 方便处理""" + self.head = ListNode() + self.tail = ListNode() + self.head.prev = self.head.next = self.tail + self.tail.next = self.tail.prev = self.head + + def delete_node(self, node): # 删除指定节点 + node.prev.next = node.next + node.next.prev = node.prev + + def add_to_head(self, node): # 指定节点添加到 self.head 后 + nextnode = self.head.next + node.next = nextnode + node.prev = self.head + self.head.next = node + nextnode.prev = node + + +class LRUCache(object): + + def __init__(self, capacity): + """ + 思路:循环双链表 + 字典 + :type capacity: int + """ + self.map = dict() + self.ll = List() + self.capacity = capacity + + def get(self, key): + """ + :type key: int + :rtype: int + """ + if key not in self.map: + return -1 + + node = self.map[key] + self.ll.delete_node(node) + self.ll.add_to_head(node) + return node.value + + def put(self, key, value): + """ + :type key: int + :type value: int + :rtype: None + """ + if key in self.map: # 更新不会改变元素个数,这里不用判断是否需要剔除 + node = self.map[key] + node.value = value # 修改结构体会也会修改 map 对应 value 的引用 + self.ll.delete_node(node) + self.ll.add_to_head(node) + else: + if len(self.map) >= self.capacity: # 直接用 len(self.map) ,不需要self.size 字段了 + tailnode = self.ll.tail.prev + self.ll.delete_node(tailnode) + del self.map[tailnode.key] + + node = ListNode(key, value) + self.map[key] = node + self.ll.add_to_head(node) + diff --git "a/docs/04_\351\230\237\345\210\227/queue.md" "b/docs/04_\351\230\237\345\210\227/queue.md" index ffac287..bae29fb 100644 --- "a/docs/04_\351\230\237\345\210\227/queue.md" +++ "b/docs/04_\351\230\237\345\210\227/queue.md" @@ -22,12 +22,13 @@ 我们先来看看 list 可以不?对照这个三个需求,看看能否满足: - 1.我们选择了 list -- 2.看起来队列需要从头删除,向尾部增加元素,也就是 list.insert(0, element) 和 list.append(element) -- 3.嗯,貌似 list.insert(0, element) 会导致所有list元素后移,O(n)复杂度。append 平均倒是O(1),但是如果内存不够还要重新分配内存。 +- 2.看起来队列需要从头删除,向尾部增加元素,也就是 list.pop(0) 和 list.append(element) +- 3.嗯,貌似 list.pop(0) 会导致所有其后所有元素向前移动一个位置,O(n)复杂度。append 平均倒是O(1),但是如果内存不够还要重新分配内存。 -你看,使用了 list 的话频繁 insert(0, element) 和 append 都是非常低效的。 +你看,使用了 list 的话频繁 pop(0) 是非常低效的。(当然list 实现还有另一种方式就是插入用 list.insert(0, item),删除用list.pop()) 脑子再转转, 我们第二章实现了 链表 LinkedList,看看能否满足要求: + - 1.这里选择 LinkedList - 2.删除头元素 LinkedList.popleft(),追加 append(element)。都可以满足 - 3.哇欧,这两个操作都是 O(1) 的,完美。 diff --git "a/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.md" "b/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.md" index 50a0ae7..efccb63 100644 --- "a/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.md" +++ "b/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.md" @@ -178,3 +178,4 @@ class HashTable(object): - 《Data Structures and Algorithms in Python》11 章 Hash Tables - 《算法导论》第三版 11 章散列表,了解几种哈希冲突的解决方式,以及为什么我们选择二次探查而不是线性探查法? - 介绍 c 解释器如何实现的 python dict对象:[Python dictionary implementation](http://www.laurentluce.com/posts/python-dictionary-implementation/) +- [Python hash function implement](https://stackoverflow.com/questions/2070276/where-can-i-find-source-or-algorithm-of-pythons-hash-function) diff --git "a/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.py" "b/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.py" index 2904b06..f1df5e2 100644 --- "a/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.py" +++ "b/docs/07_\345\223\210\345\270\214\350\241\250/hashtable.py" @@ -28,10 +28,12 @@ def __iter__(self): class Slot(object): - """定义一个 hash 表 数组的槽 - 注意,一个槽有三种状态,看你能否想明白。相比链接法解决冲突,二次探查法删除一个 key 的操作稍微复杂。 + """定义一个 hash 表数组的槽(slot 这里指的就是数组的一个位置) + hash table 就是一个 数组,每个数组的元素(也叫slot槽)是一个对象,对象包含两个属性 key 和 value。 + + 注意,一个槽有三种状态,看你能否想明白。相比链接法解决冲突,探查法删除一个 key 的操作稍微复杂。 1.从未使用 HashMap.UNUSED。此槽没有被使用和冲突过,查找时只要找到 UNUSED 就不用再继续探查了 - 2.使用过但是 remove 了,此时是 HashMap.EMPTY,该探查点后边的元素扔可能是有key + 2.使用过但是 remove 了,此时是 HashMap.EMPTY,该探查点后边的元素仍然可能是有key的,需要继续查找 3.槽正在使用 Slot 节点 """ @@ -60,34 +62,44 @@ def _hash(self, key): return abs(hash(key)) % len(self._table) def _find_key(self, key): - index = self._hash(key) + """ + 解释一个 slot 为 UNUSED 和 EMPTY 的区别 + 因为使用的是二次探查的方式,假如有两个元素 A,B 冲突了,首先A hash 得到是 slot 下标5,A 放到了第5个槽,之后插入 B 因为冲突了,所以继续根据二次探查方式放到了 slot8。 + 然后删除 A,槽 5 被置为 EMPTY。然后我去查找 B,第一次 hash 得到的是 槽5,但是这个时候我还是需要第二次计算 hash 才能找到 B。但是如果槽是 UNUSED 我就不用继续找了,我认为 B 就是不存在的元素。这个就是 UNUSED 和 EMPTY 的区别。 + """ + origin_index = index = self._hash(key) # origin_index 判断是否又走到了起点,如果查找一圈了都找不到则无此元素 _len = len(self._table) while self._table[index] is not HashTable.UNUSED: - if self._table[index] is HashTable.EMPTY: - index = (index*5 + 1) % _len + if self._table[index] is HashTable.EMPTY: # 注意如果是 EMPTY,继续寻找下一个槽 + index = (index * 5 + 1) % _len + if index == origin_index: + break continue - elif self._table[index].key == key: + if self._table[index].key == key: # 找到了key return index else: - index = (index*5 + 1) % _len + index = (index * 5 + 1) % _len # 没有找到继续找下一个位置 + if index == origin_index: + break + return None def _find_slot_for_insert(self, key): index = self._hash(key) _len = len(self._table) - while not self._slot_can_insert(index): - index = (index*5 + 1) % _len + while not self._slot_can_insert(index): # 直到找到一个可以用的槽 + index = (index * 5 + 1) % _len return index def _slot_can_insert(self, index): return (self._table[index] is HashTable.EMPTY or self._table[index] is HashTable.UNUSED) - def __contains__(self, key): # in operator + def __contains__(self, key): # in operator,实现之后可以使用 in 操作符判断 index = self._find_key(key) return index is not None def add(self, key, value): - if key in self: + if key in self: # update index = self._find_key(key) self._table[index].value = value return False diff --git "a/docs/15_\345\240\206\344\270\216\345\240\206\346\216\222\345\272\217/min_heap.py" "b/docs/15_\345\240\206\344\270\216\345\240\206\346\216\222\345\272\217/min_heap.py" new file mode 100644 index 0000000..b424331 --- /dev/null +++ "b/docs/15_\345\240\206\344\270\216\345\240\206\346\216\222\345\272\217/min_heap.py" @@ -0,0 +1,66 @@ +# python3 +class MinHeap: + def __init__(self): + """ + 这里提供一个最小堆实现。如果面试不让用内置的堆非让你自己实现的话,考虑用这个简版的最小堆实现。 + 一般只需要实现 heqppop,heappush 两个操作就可以应付面试题了 + parent: (i-1)//2。注意这么写 int((n-1)/2), python3 (n-1)//2当n=0结果是-1而不是0 + left: 2*i+1 + right: 2*i+2 + 参考: + https://favtutor.com/blogs/heap-in-python + https://runestone.academy/ns/books/published/pythonds/Trees/BinaryHeapImplementation.html + https://www.askpython.com/python/examples/min-heap + """ + self.pq = [] + + def min_heapify(self, nums, k): + """递归调用,维持最小堆特性""" + l = 2*k+1 # 左节点位置 + r = 2*k+2 # 右节点 + if l < len(nums) and nums[l] < nums[k]: + smallest = l + else: + smallest = k + if r < len(nums) and nums[r] < nums[smallest]: + smallest = r + if smallest != k: + nums[k], nums[smallest] = nums[smallest], nums[k] + self.min_heapify(nums, smallest) + + def heappush(self, num): + """列表最后就加入一个元素,之后不断循环调用维持堆特性""" + self.pq.append(num) + n = len(self.pq) - 1 + # 注意必须加上n>0。因为 python3 (n-1)//2 当n==0 的时候结果是-1而不是0! + while n > 0 and self.pq[n] < self.pq[(n-1)//2]: # parent 交换 + self.pq[n], self.pq[(n-1)//2] = self.pq[(n-1)//2], self.pq[n] # swap + n = (n-1)//2 + + def heqppop(self): # 取 pq[0],之后和pq最后一个元素pq[-1]交换之后调用 min_heapify(0) + minval = self.pq[0] + last = self.pq[-1] + self.pq[0] = last + self.min_heapify(self.pq, 0) + self.pq.pop() + return minval + + def heapify(self, nums): + n = int((len(nums)//2)-1) + for k in range(n, -1, -1): + self.min_heapify(nums, k) + + +def test_MinHeqp(): + import random + l = list(range(1, 9)) + random.shuffle(l) + pq = MinHeap() + for num in l: + pq.heappush(num) + res = [] + for _ in range(len(l)): + res.append(pq.heqppop()) # 利用 heqppop,heqppush 实现堆排序 + + def issorted(l): return all(l[i] <= l[i+1] for i in range(len(l) - 1)) + assert issorted(res) diff --git a/docs/163_course.png b/docs/163_course.png new file mode 120000 index 0000000..c4f4aa7 --- /dev/null +++ b/docs/163_course.png @@ -0,0 +1 @@ +../163_course.png \ No newline at end of file diff --git "a/docs/16_\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227/priority_queue.py" "b/docs/16_\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227/priority_queue.py" index 039885c..7b6071c 100644 --- "a/docs/16_\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227/priority_queue.py" +++ "b/docs/16_\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227/priority_queue.py" @@ -129,3 +129,63 @@ def test_priority_queue(): while not pq.is_empty(): res.append(pq.pop()) assert res == ['purple', 'orange', 'black', 'white'] + + +def test_buildin_PriorityQueue(): # python3 + """ + 测试内置的 PriorityQueue + https://pythonguides.com/priority-queue-in-python/ + """ + from queue import PriorityQueue + q = PriorityQueue() + q.put((10, 'Red balls')) + q.put((8, 'Pink balls')) + q.put((5, 'White balls')) + q.put((4, 'Green balls')) + while not q.empty(): + item = q.get() + print(item) + + +def test_buildin_heapq_as_PriorityQueue(): + """ + 测试使用 heapq 实现优先级队列,保存一个 tuple 比较元素(tuple第一个元素是优先级) + """ + import heapq + s_roll = [] + heapq.heappush(s_roll, (4, "Tom")) + heapq.heappush(s_roll, (1, "Aruhi")) + heapq.heappush(s_roll, (3, "Dyson")) + heapq.heappush(s_roll, (2, "Bob")) + while s_roll: + deque_r = heapq.heappop(s_roll) + print(deque_r) + + +# python3 没有了 __cmp__ 魔法函数 https://stackoverflow.com/questions/8276983/why-cant-i-use-the-method-cmp-in-python-3-as-for-python-2 +class Item: + def __init__(self, key, weight): + self.key, self.weight = key, weight + + def __lt__(self, other): # 看其来 heapq 实现只用了 小于 比较,这里定义了就可以 push 一个 item 类 + return self.weight < other.weight + + def __eq__(self, other): + return self.weight == other.weight + + def __str__(self): + return '{}:{}'.format(self.key,self.weight) + + +def test_heap_item(): + """ + 测试使用 Item 类实现优先级队列,因为 heapq 内置使用的是小于运算法, + 重写魔术 < 比较方法即可实现 + """ + import heapq + pq = [] + heapq.heappush(pq, Item('c', 3)) + heapq.heappush(pq, Item('a', 1)) + heapq.heappush(pq, Item('b', 2)) + while pq: + print(heapq.heappop(pq)) diff --git "a/docs/19_python\345\206\205\347\275\256\345\270\270\347\224\250\347\256\227\346\263\225\345\222\214\346\225\260\346\215\256\347\273\223\346\236\204/builtins.md" "b/docs/19_python\345\206\205\347\275\256\345\270\270\347\224\250\347\256\227\346\263\225\345\222\214\346\225\260\346\215\256\347\273\223\346\236\204/builtins.md" index 40d838b..a38b5ef 100644 --- "a/docs/19_python\345\206\205\347\275\256\345\270\270\347\224\250\347\256\227\346\263\225\345\222\214\346\225\260\346\215\256\347\273\223\346\236\204/builtins.md" +++ "b/docs/19_python\345\206\205\347\275\256\345\270\270\347\224\250\347\256\227\346\263\225\345\222\214\346\225\260\346\215\256\347\273\223\346\236\204/builtins.md" @@ -1,23 +1,563 @@ -# Python 常用内置算法和数据结构 +# Python 刷题常用内置算法和数据结构 + 相信到这里大家对常用的数据结构和算法及其实现都比较熟悉了。 之前在每章的数据结构和算法中涉及到的章节我都会提到对应的 python 内置模块,一般如果内置的可以满足需求,我们优先使用内置模块, 因为在性能和容错性方面内置模块要好于我们自己实现(比如有些是 c 实现的)。本章我们不会再对每个模块的原理详细说明,仅列举出一些常见模块供大家参考, 如果有需要最好的学习方式就是参考 Python 的官方文档。很多高级的数据结构我们也可以通过 google 搜索现成的库拿来直接用。 - 常用内置数据类型:list, tuple, dict, set, frozenset -- collections -- heapq -- bisect - -下边我列了一个常用的表格,如果有遗漏可以在 issue 中提出。确保你了解这些数据结构和算法的使用以及时间、空间复杂度。 - -| 数据结构/算法 | 语言内置 | 内置库 | -|----------------|---------------------------------|---------------------------------------------------------------| -| 线性结构 | list(列表)/tuple(元祖) | array(数组,不常用)/collections.namedtuple | -| 链式结构 | | collections.deque(双端队列) | -| 字典结构 | dict(字典) | collections.Counter(计数器)/OrderedDict(有序字典)/defaultdict | -| 集合结构 | set(集合)/frozenset(不可变集合) | | -| 排序算法 | sorted | | -| 二分算法 | | bisect模块 | -| 堆算法 | | heapq模块 | -| 缓存算法 | | functools.lru_cache(Least Recent Used, python3) | +- collections 模块:Counter(计数器), deque(双端队列), OrderedDict(有序字典),defaultdict(默认值字典) +- heapq: 堆操作 +- bisect: 二分查找 + +下边我列了一个常用 python 内置数据结构和算法的表格,如果有遗漏可以在 issue 中提出。确保你了解这些数据结构和算法的使用以及时间、空间复杂度。 + +| 数据结构/算法 | 语言内置 | 内置库 | +|---------------|---------------------------------|-------------------------------------------------------------------------| +| 线性结构 | list(列表)/tuple(元组) | array(数组,不常用)/collections.namedtuple | +| 链式结构 | | collections.deque(双端队列) | +| 字典结构 | dict(字典) | collections.Counter(计数器)/OrderedDict(有序字典)/defaultdict(默认字典) | +| 集合结构 | set(集合)/frozenset(不可变集合) | | +| 排序算法 | sorted | | +| 二分算法 | | bisect模块 | +| 堆算法 | | heapq模块 | +| 优先级队列 | | queue.PriorityQueue/heapq | +| 缓存算法 | | functools.lru_cache(Least Recent Used, python3)/cache | + +# 一些坑 + +如果你使用 python2 or python3 刷题(比如力扣leetcode),有一些坑或者技巧需要注意: + +- 字典顺序。python3 和 python2 的 dict 有所用不同,python3.7 之后的 dict 会保持插入顺序(不是字典序), python2 不要依赖 dict 迭代顺序,请使用 OrderedDict +- 矩阵。正确初始化一个不可变对象的二维数组:`dp = [ [0]*col for _ in range(row) ]`,不要用 `dp = [[0] * n] * m`, 否则里边都 +引用的同一个 list,修改一个都会变。`[[0 for _ in range(col)] for _ in range(row)]` 也可以(稍慢),因为数字 0 是不可变对象 +- 深浅拷贝。经常在回溯题中需要`res,path=[],[]`,path 是用来回溯的路径。找到一个结果的时候需要用 `res.append(path[:])` 而 +不是`res.append(path)#错!` ,因为这里append的path的引用,之后修改了 path 结果就是错的!(或者用copy模块,不过不如[:]语法简洁) +- int范围。python在数值范围建议用:`MAXINT = 2**63-1; MININT = -2**63` 。因为 python2 sys.maxint 和 python3 sys.maxsize 不统一 +- 优先级队列:使用内置queue.PriorityQueue or heapq ,定义一个 Item 类实现"小于" 魔术方法就可以实现,下边有代码演示 +- 缓存。python3 的 functools 模块自带了 cache(等价于lru_cache(maxsize=None)) 和 lru_cache 装饰器,在一些需要递归记忆化搜索的时候会很方便 +- 除法变更:python2和 python3 除法做了变更要注意。还有负数除法。 python2 `int(6/-123)==-1, int(-3/2)==-2`,但是 python3 `int(6/-123)==0, int(-3/2)==-1`。 +正数的整数除法统一用"//"。比如二分求中间值 `mid=(l+r)//2` 或者 `mid=l+(r-l)//2`,因为python天生支持大数不会溢出两种写法都行。负数整数除法统一写 int(a/b)。 +凡是遇到除法运算的题目建议统一使用 python3 提交。 +- 自定义排序函数。python2 可以用 `nums.sort(cmp=lambda a, b: a - b)`,但是python3移除了cmp参数。 +python3如果想要用自定义排序函数可以使用 functools.cmp_to_key 函数改成 `nums.sort(key=cmp_to_key(lambda a, b: a - b))` + +# python 递归暴栈(栈溢出) + +python 递归函数默认递归深度比较小,你可以通过 `sys.getrecursionlimit()` 函数打印出来。 +我在 mac 机器上测试的时候,以下结果 python2 输出 1000。这就导致一些递归函数测试用例稍微多一些就会报错。 +(一个用例超过上千个数据就会报错了) + +```py +import sys +print(sys.getrecursionlimit()) # 我的 mac 机器上输出 1000 +``` + +可以把以下代码设置最大栈深度,放到文件开头,在牛客上提交代码的时候可以避免一些递归代码报错。 +(leetcode 似乎给设置了,类似的题目发现力扣上提交不会栈溢出但是在牛客就会) + +```py +import sys +sys.setrecursionlimit(100000) # 设置函数栈深度足够大,避免栈溢出错误 +``` + +# python int 值范围 + +``` +# 乘方 (比较推荐⭐️,py2/3 都兼容不容易出错) +MAXINT = 2**63-1 +MININT = -2**63 + +# py3 +import sys +MAXINT = sys.maxsize +MININT = -sys.maxsize - 1 + +# py2 +sys.maxint + +# 位运算 +MAXINT = (1<<63) - 1 +MININT = ~MAXINT +``` + +# python 负数位运算的坑 +1. Python3 中的整型是补码形式存储的 +2. Python3 中 bin 一个负数(十进制表示),输出的是它的原码的二进制表示加上个负号 +3. 为了获得负数(十进制表示)的补码,需要手动将其和十六进制数 0xffffffff 进行按位与操作,得到结果是个十六进制数,再交给 bin() 进行输出, +得到的才是你想要的补码表示。 + +```py +# 整数转换 https://leetcode-cn.com/problems/convert-integer-lcci/ +class Solution: + def convertInteger(self, A: int, B: int) -> int: + return bin((A & 0xffffffff) ^ (B & 0xffffffff)).count('1') +``` + +参考: +- https://www.runoob.com/w3cnote/python-negative-storage.html +- https://leetcode-cn.com/problems/convert-integer-lcci/solution/python3-zhu-yi-qi-dui-yu-fu-shu-de-cun-chu-fang-sh/ + +# python list 技巧 + +```py +# 排序嵌套 list,比如元素值是一个 tuple 或者 list +l = [('a', 1), ('c', 2), ('b',3)] +sorted(l, key=lambda p:p[0]) # 根据第1个值排序,[('a', 1), ('b', 3), ('c', 2)] +sorted(l, key=lambda p:p[1]) # 根据第2个值排序,[('a', 1), ('c', 2), ('b', 3)] +sorted(l, key=lambda p:(-p[0], p[1])) # 先根据第一个倒排,如果相等再根据第二个正排序 + +# 同时获取最大值的下标和值 +l = [1,2,5,4,3] +maxi, maxval = max(enumerate(l), key=lambda iv: iv[1]) # 2, 5 + +# python3 排序list自定义函数(python2 直接用 cmp 参数, python3 需要用 cmp_to_key 转成 key 参数) +from functools import cmp_to_key +nums = [3,2,1,4,5] +sorted(nums, key=cmp_to_key(lambda a,b: a-b) ) # [1 ,2 ,3, 4, 5] +sorted(nums, key=cmp_to_key(lambda a,b: b-a) ) # [5, 4, 3, 2, 1] + +# 一行代码判断列表是否有序 +issorted = all(l[i] <= l[i+1] for i in range(len(l) - 1)) + +# python3 一行代码求前缀和 +from itertools import accumulate +presums = list(accumulate([1,2,3])) # [1, 3, 6] + +# 一行代码求矩阵元素总和 https://stackoverflow.com/questions/10713150/how-to-sum-a-2d-array-in-python +allsum = sum(map(sum, matrix)) # 或者 allsum = sum((sum(row) for row in matrix)) +# 一行代码判断一个元素是否在矩阵中,比如判断 1 是否在矩阵matrix中 +any(1 in row for row in matrix) +# 一行代码获取矩阵最大、最小值 +maxval = max(map(max, matrix)) +``` + +# python dict 技巧 + +```py +# python 根据 key,value 排序字典 +d = {'d': 4, 'a': 1, 'b': 2, 'c':3} +# dict sort by **key** and reverse +dict(sorted(d.items())) # {'a': 1, 'b': 2, 'c': 3, 'd': 4} +dict(sorted(d.items(), reverse=True)) # {'d': 4, 'c': 3, 'b': 2, 'a': 1} + +# dict sort by **value** and reverse +dict(sorted(d.items(), key = lambda kv:kv[1])) # {'a': 1, 'b': 2, 'c': 3, 'd': 4} +dict(sorted(d.items(), key = lambda kv:kv[1], reverse=True)) # {'d': 4, 'c': 3, 'b': 2, 'a': 1} + +# 获取字典对应的最大值对应的 key,value +mydict = {'A':4,'B':10,'C':0,'D':87} +maximum = max(mydict, key=mydict.get) # Just use 'min' instead of 'max' for minimum. +maxk, maxv = maximum, mydict[maximum] +# 或者 +maxk, maxv = max(mydict.items(), key=lambda k: k[1]) + +# 支持默认值的有序字典 (OrderedDict and defaultdict) (注意是 key 插入顺序不是字典序) +# https://stackoverflow.com/questions/6190331/how-to-implement-an-ordered-default-dict +od = OrderedDict() # collections.OrderedDict() +od[i] = od.get(i, 0) + 1 # 间接实现了 defaultdict(int) ,同时保持了插入字典的 key 顺序 +``` + +# 链表题目调试函数 + +```py +# 编写链表题目经常用到的一些通用函数和调试函数,定义等,方便代码调试 + +class ListNode(object): + def __init__(self, val=0, next=None): + self.val = val + self.next = next + + def __str__(self): + return 'Node({})'.format(self.val) + + # 用来输出调试 + __repr__ = __str__ + + +# 缩写,单测方便写,比如构建链表 1->2->3 N(1, N(2, N(3))) +N = Node = ListNode + + +def to_list(head): + """linked list to python []""" + res = [] + curnode = head + while curnode: + res.append(curnode.val) + curnode = curnode.next + return res + + +def gen_list(nums): + """用数组生成一个链表方便测试 [1,2,3] 1->2->3 + """ + if not nums: + return None + head = ListNode(nums[0]) + pre = head + for i in range(1, len(nums)): + node = ListNode(nums[i]) + pre.next = node + pre = node + return head + + +def print_list(head): + """打印链表""" + cur = head + res = "" + while cur: + res += "{}->".format(cur.val) + cur = cur.next + res += "nil" + print(res) +``` + + +# 内置库实现优先级队列的三种方式 + +```py +def test_buildin_PriorityQueue(): # python3 + """ + 测试内置的 PriorityQueue + https://pythonguides.com/priority-queue-in-python/ + """ + from queue import PriorityQueue + q = PriorityQueue() + q.put((10, 'Red balls')) + q.put((8, 'Pink balls')) + q.put((5, 'White balls')) + q.put((4, 'Green balls')) + while not q.empty(): + item = q.get() + print(item) + + +def test_buildin_heapq_as_PriorityQueue(): + """ + 测试使用 heapq 实现优先级队列,保存一个 tuple 比较元素(tuple第一个元素是优先级) + 实际上是利用了元组tuple比较从第一个开始比较的性质 + """ + import heapq + s_roll = [] + heapq.heappush(s_roll, (4, "Tom")) + heapq.heappush(s_roll, (1, "Aruhi")) + heapq.heappush(s_roll, (3, "Dyson")) + heapq.heappush(s_roll, (2, "Bob")) + while s_roll: + deque_r = heapq.heappop(s_roll) + print(deque_r) + + +# python3 没有了 __cmp__ 魔法函数 https://stackoverflow.com/questions/8276983/why-cant-i-use-the-method-cmp-in-python-3-as-for-python-2 +class Item: + def __init__(self, key, weight): + self.key, self.weight = key, weight + + def __lt__(self, other): # heapq 源码实现只用了 小于 比较,这里定义了就可以 push 一个 item 类 + return self.weight < other.weight + +# def __eq__(self, other): # 这个可以省略,只要定义了 __lt__ 魔法函数就可以了 +# return self.weight == other.weight +# +# def __str__(self): +# return '{}:{}'.format(self.key,self.weight) + +# Item.__lt__ = lambda self, other: self.weight < other.weight # 对于已有的类,直接加一句就可以实现作为 heap item 了 + +def test_heap_item(): + """ + 测试使用 Item 类实现优先级队列,因为 heapq 内置使用的是小于运算法, + 重写魔术 < 比较方法即可实现 + """ + import heapq + pq = [] + heapq.heappush(pq, Item('c', 3)) + heapq.heappush(pq, Item('a', 1)) + heapq.heappush(pq, Item('b', 2)) + while pq: + print(heapq.heappop(pq)) +``` + +# python 如何实现最大堆 +python自带了heapq 模块实现了最小堆(min-heaq),但是如果想要实现最大堆(max-heap),有几种实现方式: + +1. 对放入的数字取反。比如 10 放入 -10 ,然后取出来的时候再取反。个人倾向于这种,可以自己封装一个类防止来回取反搞晕 +2. 直接根据 heapq 模块的函数封装几个最大堆的函数,也是通过取反实现 +3. 新建一个对象重写 `__lt__` 魔术方法。这种方式也可以,但是重写魔术方法修改了语义不太好(个人不推荐) + +```py +# 方法1:封装一个 max heap 类 +import heapq +class MaxHeap: + """ + https://stackoverflow.com/questions/2501457/what-do-i-use-for-a-max-heap-implementation-in-python + """ + def __init__(self, capacity): + self.capacity = capacity + self.minheap = [] + + def push(self, val): + heapq.heappush(self.minheap, -val) # push取反后的数字, 1 -> -1 + + def pop(self): + val = heapq.heappop(self.minheap) + return -val # 拿出来的数字再取反 + + def max(self): + return -self.minheap[0] # min-heap 的数组最小值是 m[0],最大值取反 + +# 方法2: 重新定几个新的 max-heap 方法 +import heapq +def maxheappush(h, item): + return heapq.heappush(h, -item) + +def maxheappop(h): + return -heapq.heappop(h) + +def maxheapval(h): + return -h[0] +``` + +# lru_cache/cache 优化记忆化搜索 + +python3 functools 模块的 cache 功能和 lru_cache(maxsize=None) 一样,不过更加轻量更快。在记忆化递归搜索的时候很方便。 +注意这里的参数 `maxsize=None` 一定要设置为 None,否则默认的 maxsize=128。 +举一个力扣上的例子,如果不加 cache 递归函数因为会大量重复计算直接超时,但是加一个装饰器就可以通过。 +当然了如果你用 python2 没有这个装饰器,你可以直接用 python 的 dict 来实现。(存在就返回,否则计算结果保存到 dict 里) + +```py +""" +[337] 打家劫舍 III +https://leetcode-cn.com/problems/house-robber-iii/description/ +""" +# cache 等价于 functools.lru_cache(maxsize=None), 不过python3版本低可能没有 cache 只有 lru_cache +from functools import cache, lru_cache + + +class Solution(object): + def rob(self, root): + """ + 思路 1:递归求解(注意不加 cache 会超时!!) + :type root: TreeNode + :rtype: int + """ + # @lru_cache(maxsize=None) # 注意如果 python3 版本不是很新的话,只能用 lru_cache(maxsize=None) + @cache # NOTE: 不加 cache 会直接超时,就只能用动态规划了 + def dfs(root): + if root is None: + return 0 + + if root.left is None and root.right is None: # 左右孩子都是空 + return root.val + # 不偷父节点,考虑偷 root 的左右孩子 + val1 = dfs(root.left) + dfs(root.right) + # 偷父节点 + val2 = root.val + if root.left: + val2 += dfs(root.left.left) + dfs(root.left.right) + if root.right: + val2 += dfs(root.right.left) + dfs(root.right.right) + return max(val1, val2) + + return dfs(root) + +``` + + +# leetcode 二叉树调试函数 + +```py +""" +二叉树树相关问题调试函数 +""" + + +class TreeNode(object): # leetcode tree 节点定义 + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right + + def __str__(self): + return "TreeNode:{} left:{} right:{}".format(self.val, self.left, self.right) + __repr__ = __str__ + + +def gen_tree_from_lc_input(vals_str): # [1,2,3] -> root TreeNode + """ 根据 输入生成一个 tree,返回 root 节点,注意输入字符串 + # [450] 删除二叉搜索树中的节点 + # https://leetcode-cn.com/problems/delete-node-in-a-bst/description/ + # 比如 450 题目单测代码可以这么写 + def test(): + s = Solution() + root = gen_tree_from_lc_input("[2,1]") + key = 1 + res = "[2]" + assert to_lc_tree_str(s.deleteNode(root, key)) == res + """ + import ast + valids = vals_str.replace("null", "None") + vals = ast.literal_eval(valids) + # 以下就是 gen_tree 函数的内容,为了方便单独使用不调用函数了 + if not vals: + return None + nodemap = {} + for i in range(len(vals)): + if vals[i] is not None: # 一开始写的 if vals[i],但是 0 节点就错了! 应该显示判断是否为 None(空节点) + nodemap[i] = TreeNode(vals[i]) + else: + nodemap[i] = None + + root = nodemap[0] + for i in range(len(vals)): + l = 2*i + 1 + r = 2*i + 2 + cur = nodemap.get(i, None) + left = nodemap.get(l, None) + right = nodemap.get(r, None) + if cur: + cur.left = left + cur.right = right + return root + + +def to_lc_tree_str(root): # root TreeNode -> [1,2,3,null] + """返回层序序列化后的树字符串,可以和 leetcode 输出结果比对字符串""" + import json + if not root: + return '[]' + curnodes = [root] + res = [root.val] + while curnodes: + nextnodes = [] + for node in curnodes: + if node: + if node.left: + nextnodes.append(node.left) + res.append(node.left.val) + else: + nextnodes.append(None) + res.append(None) + if node.right: + nextnodes.append(node.right) + res.append(node.right.val) + else: + nextnodes.append(None) + res.append(None) + curnodes = nextnodes + + while res[-1] is None: # 最后空节点去掉 + res.pop() + s = json.dumps(res) + s = s.replace(" ", "") + return s + + +def gen_tree(vals): + """ + 根据层序遍历结果生成二叉树并且返回 root。 + 把题目中输入 null 换成 None + vals = [1,2,3,None,5] + """ + if not vals: + return None + nodemap = {} + for i in range(len(vals)): + if vals[i]: + nodemap[i] = TreeNode(vals[i]) + else: + nodemap[i] = None + + root = nodemap[0] + for i in range(len(vals)): + l = 2*i + 1 + r = 2*i + 2 + cur = nodemap.get(i, None) + left = nodemap.get(l, None) + right = nodemap.get(r, None) + if cur: + cur.left = left + cur.right = right + return root +``` + +# python 交换列表元素的坑(交换副作用) + +``` +# 41. 缺失的第一个正数 https://leetcode-cn.com/problems/first-missing-positive/ +class Solution(object): + def firstMissingPositive(self, nums): + """ + 平常习惯了 python 里边交换元素 a,b=b,a 这里你可能这么写,那就中招了! + nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i] # 这么写死循环! + 这个等价于 + x, y = nums[nums[i]-1], nums[i] + nums[i] = x # 这一步 nums[i] 已经修改了,下边一句赋值不是期望的 nums[i]了 + nums[nums[i]-1] = y + + :type nums: List[int] + :rtype: int + """ + n = len(nums) + for i in range(n): + while 1 <= nums[i] <= n and nums[nums[i]-1] != nums[i]: + # NOTE: 注意这一句交换右边有副作用的,不能颠倒!!! + # nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i] # 这么写死循环! + nums[nums[i]-1], nums[i] = nums[i], nums[nums[i]-1] # 有副作用的放前边 + for i in range(n): + if nums[i] != i+1: + return i+1 + + return n+1 +``` + +# 兼容代码ACM/核心提交格式 + +注意牛客网有两种模式,一种是和 leetcode 一样的提交(无需处理输入),只需要提交核心代码。 +一种是 ACM 模式,还需要自己处理输入和输出。 +建议使用这种兼容写法,同样的题目可以同时提交到 牛客、leetcode 和 acwing(python3)。 +这道题目为例子 [679] 奖品分配 https://www.acwing.com/problem/content/681/ + +```py +# 这段代码可以直接以OJ输入模式提交,如果题目一样,直接复制 Solution 类就可以同时提交到leetcode +class Solution: + def solve(self, scores): + """ + 思路:记忆化搜索。时间O(N) + 对于旁边都比自己大的点,它肯定是1 + 对于旁边有比自己小的点,先算出比自己小的点的值再+1就好了。 + 每个点如果计算过了就记忆化,下次再计算他的时候不用重复递归直接返回。 + 参考:https://www.acwing.com/solution/acwing/content/1520/ + """ + from functools import lru_cache + n = len(scores) + + @lru_cache(maxsize=None) + def dfs(x): + left = (x-1+n) % n + right = (x+1) % n + + if scores[x] <= scores[left] and scores[x] <= scores[right]: # 注意是 <= ,下边是 < + return 1 + + l, r = 0, 0 + if scores[left] < scores[x]: + l = dfs(left) + if scores[right] < scores[x]: + r = dfs(right) + + return max(l, r) + 1 + + return sum([dfs(i) for i in range(n)]) + + +if __name__ == "__main__": # python3 提交,python3 input 都当做 str 输入 + so = Solution() # 构造 Solution 实例后续调用 + n = int(input()) + for i in range(n): + arrlen = input() + arr = list(map(int, input().split())) + print(so.solve(arr)) +``` diff --git "a/docs/20_\351\235\242\350\257\225\346\214\207\345\215\227/interview.md" "b/docs/20_\351\235\242\350\257\225\346\214\207\345\215\227/interview.md" index 5958458..57d2492 100644 --- "a/docs/20_\351\235\242\350\257\225\346\214\207\345\215\227/interview.md" +++ "b/docs/20_\351\235\242\350\257\225\346\214\207\345\215\227/interview.md" @@ -41,3 +41,9 @@ leetcode 上的基础题目练练手感。 - [《程序员面试金典(第5版)》](https://book.douban.com/subject/25753386/) - [《剑指Offer》](https://book.douban.com/subject/25910559/) - [python leetCode](https://github.com/HuberTRoy/leetCodek) + +# 刷题网站 +leetcode 和牛客网是国内常用的两个刷题网站,笔者建议刷一下高频的 200 道题左右,基本可以应付大部分公司的算法面试了。 + +- https://leetcode-cn.com/ +- https://www.nowcoder.com/exam/oj diff --git "a/\345\211\221\346\214\207offer/03_FindInPartiallySortedMatrix(\344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276).py" "b/\345\211\221\346\214\207offer/03_FindInPartiallySortedMatrix(\344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276).py" new file mode 100644 index 0000000..2023573 --- /dev/null +++ "b/\345\211\221\346\214\207offer/03_FindInPartiallySortedMatrix(\344\272\214\347\273\264\346\225\260\347\273\204\344\270\255\347\232\204\346\237\245\346\211\276).py" @@ -0,0 +1,48 @@ +""" +剑指offer 第三题。从左到右升序从上到下升序数组,判断是否能找到一个值 +思路: +从右上角开始找,大于 target 排除当前列。小于 target 排除当前行 +""" + + +class Solution: + def solve(self, matrix, target): + if not matrix or not matrix[0]: + return False + leny = len(matrix) + + x = 0 + y = leny - 1 + + while x >= 0 and y >= 0: + if matrix[x][y] == target: + return True + elif matrix[x][y] > target: + y -= 1 + else: + x += 1 + return False + + +def test(): + s = Solution() + matrix = [ + [1, 2, 8, 9], + [2, 4, 9, 12], + [4, 7, 10, 13], + [6, 8, 11, 15], + ] + assert s.solve(matrix, 0) is False + assert s.solve(matrix, 1) is True + assert s.solve(matrix, 7) is True + assert s.solve(matrix, 5) is False + + # empty + matrix = [ + [], + ] + assert s.solve(matrix, 0) is False + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/04_ReplaceBlank(\346\233\277\346\215\242\347\251\272\346\240\274).py" "b/\345\211\221\346\214\207offer/04_ReplaceBlank(\346\233\277\346\215\242\347\251\272\346\240\274).py" new file mode 100644 index 0000000..f809497 --- /dev/null +++ "b/\345\211\221\346\214\207offer/04_ReplaceBlank(\346\233\277\346\215\242\347\251\272\346\240\274).py" @@ -0,0 +1,36 @@ +""" +替换字符串中的空格 +题目:请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We are happy.”,则输出“We%20are%20happy.” +""" + + +class Solution: + def solve(self, string): + """因为 python string 不可变对象,和其他的语言用字符串数组能直接修改有点区别""" + res = [] + for char in string: + if char == ' ': + res.append('%20') + else: + res.append(char) + return ''.join(res) + + def solve2(self, string): + """ + 思路: + 先遍历一次计算替换后的总长度 + 从后往前替换,防止从前往后需要 + """ + pass + + +def test(): + s = Solution() + ss = 'We are happy.' + assert s.solve(ss) == 'We%20are%20happy.' + + assert s.solve('') == '' + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/05_PrintListInReversedOrder(\344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250).py" "b/\345\211\221\346\214\207offer/05_PrintListInReversedOrder(\344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250).py" new file mode 100644 index 0000000..4a0f74f --- /dev/null +++ "b/\345\211\221\346\214\207offer/05_PrintListInReversedOrder(\344\273\216\345\260\276\345\210\260\345\244\264\346\211\223\345\215\260\351\223\276\350\241\250).py" @@ -0,0 +1,59 @@ +""" +面试题5:从尾到头打印链表 +题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。链表结点定义如下: +""" +from collections import deque + + +class Stack: + def __init__(self): + self.items = deque() + + def push(self, val): + return self.items.append(val) + + def pop(self): + return self.items.pop() + + def empty(self): + return len(self.items) == 0 + + +class Node: + def __init__(self, val, next=None): + self.val, self.next = val, next + + +class Solution: + def solve(self, headnode): + """ + 思路:用一个栈保存所有节点,之后一个一个 pop + """ + val_s = Stack() + cur_node = headnode + while cur_node: + val_s.push(cur_node.val) + cur_node = cur_node.next + while not val_s.empty(): + print(val_s.pop()) + + def solve2(self, headnode): + """ + 能用栈就可以使用递归。这一点需要能联想到 + """ + curnode = headnode + if curnode: + self.solve2(curnode.next) + print(curnode.val) # 注意 print 放到 递归之后才是倒序 + + +def test(): + s = Solution() + linklist = Node(0, Node(1)) + s.solve2(linklist) + + # linklist = Node(0) + # s.solve2(linklist) + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/06_ConstructBinaryTree(\351\207\215\345\273\272\344\272\214\345\217\211\346\240\221).py" "b/\345\211\221\346\214\207offer/06_ConstructBinaryTree(\351\207\215\345\273\272\344\272\214\345\217\211\346\240\221).py" new file mode 100644 index 0000000..c328ed4 --- /dev/null +++ "b/\345\211\221\346\214\207offer/06_ConstructBinaryTree(\351\207\215\345\273\272\344\272\214\345\217\211\346\240\221).py" @@ -0,0 +1,63 @@ +""" +面试题6:重建二叉树 +题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 +例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出图2.6所示的二叉树并输出它的头结点。二叉树结点的定义如下: +""" + + +class Node: + def __init__(self, val, left=None, right=None): + self.val, self.left, self.right = val, left, right + + +class Solution: + def __init__(self): + self.pres = [] + self.inorders = [] + + def solve(self, prevals, invals): + """ + 思路:先序找到根,然后可以找到中序遍历根的位置确定左子树和右子树,递归处理 + """ + if not prevals or not invals: + return None + root_val = prevals[0] + root = Node(root_val) + inorder_root_idx = invals.index(root_val) + left_length = inorder_root_idx + right_length = len(invals) - inorder_root_idx - 1 + + if left_length: + root.left = self.solve(prevals[1:1 + left_length], invals[:inorder_root_idx]) + + if right_length: + root.right = self.solve(prevals[left_length + 1:], invals[inorder_root_idx + 1:]) + return root + + def inorder(self, subtree): + if subtree: + self.inorder(subtree.left) + self.inorders.append(subtree.val) + self.inorder(subtree.right) + + def preorder(self, subtree): + if subtree: + self.pres.append(subtree.val) + self.preorder(subtree.left) + self.preorder(subtree.right) + + +def test(): + s = Solution() + prevals = [1, 2, 4, 7, 3, 5, 6, 8] + invals = [4, 7, 2, 1, 5, 3, 8, 6] + root = s.solve(prevals, invals) + s.inorder(root) + assert s.inorders == invals + + s.preorder(root) + assert s.pres == prevals + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/07_QueueWithTwoStacks(\347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227).py" "b/\345\211\221\346\214\207offer/07_QueueWithTwoStacks(\347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227).py" new file mode 100644 index 0000000..6deb686 --- /dev/null +++ "b/\345\211\221\346\214\207offer/07_QueueWithTwoStacks(\347\224\250\344\270\244\344\270\252\346\240\210\345\256\236\347\216\260\351\230\237\345\210\227).py" @@ -0,0 +1,56 @@ +""" +面试题7:用两个栈实现队列 +题目:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。 +""" + +from collections import deque + + +class Stack: + def __init__(self): + self.items = deque() + + def push(self, val): + return self.items.append(val) + + def pop(self): + return self.items.pop() + + def empty(self): + return len(self.items) == 0 + + def __len__(self): + return len(self.items) + + +class Queue: + def __init__(self): + self.s1 = Stack() + self.s2 = Stack() + + def append(self, val): + self.s1.push(val) + + def pop(self): + if len(self.s2): + return self.s2.pop() + while len(self.s1): + val = self.s1.pop() + self.s2.push(val) + return self.s2.pop() + + +def test(): + q = Queue() + q.append(1) + q.append(2) + q.append(3) + assert q.pop() == 1 + q.append(4) + assert q.pop() == 2 + assert q.pop() == 3 + assert q.pop() == 4 + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/08_MinNumberInRotatedArray(\346\227\213\350\275\254\346\225\260\347\273\204\346\234\200\345\260\217\346\225\260\345\255\227).py" "b/\345\211\221\346\214\207offer/08_MinNumberInRotatedArray(\346\227\213\350\275\254\346\225\260\347\273\204\346\234\200\345\260\217\346\225\260\345\255\227).py" new file mode 100644 index 0000000..72373ea --- /dev/null +++ "b/\345\211\221\346\214\207offer/08_MinNumberInRotatedArray(\346\227\213\350\275\254\346\225\260\347\273\204\346\234\200\345\260\217\346\225\260\345\255\227).py" @@ -0,0 +1,41 @@ +""" +面试题8:旋转数组的最小数字 +题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。 +例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 +""" + + +class Solution: + def findMin(self, array): + """ + 思路:二分 + 关键点:旋转数组的第一个数字是前半部分最小的,也是后半部分最大的 + """ + if len(array) == 1: + return array[0] + first = array[0] + size = len(array) + beg = 1 + end = size + + while beg < end: + mid = (beg + end) // 2 + if array[mid] > first: + beg = mid + 1 + else: + end = mid + if beg == size: + return first + else: + return array[beg] + + +def test(): + s = Solution() + assert s.findMin([0]) == 0 + assert s.findMin([1, 2]) == 1 # 注意这个特殊case + assert s.findMin([3, 4, 5, 1, 2]) == 1 + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/10_NumberOf1InBinary(\344\272\214\350\277\233\345\210\266\344\270\2551\347\232\204\344\270\252\346\225\260).py" "b/\345\211\221\346\214\207offer/10_NumberOf1InBinary(\344\272\214\350\277\233\345\210\266\344\270\2551\347\232\204\344\270\252\346\225\260).py" new file mode 100644 index 0000000..0c8cfff --- /dev/null +++ "b/\345\211\221\346\214\207offer/10_NumberOf1InBinary(\344\272\214\350\277\233\345\210\266\344\270\2551\347\232\204\344\270\252\346\225\260).py" @@ -0,0 +1,48 @@ +""" +面试题10:二进制中1的个数 +题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。"" +""" + + +class Solution: + def solve_wrong(self, num): + """ + 不断右移,每次和1做与运算,结果为1就加1. + NOTE:但是输入负数会死循环。 + """ + cnt = 0 + while num: + if num & 1: + cnt += 1 + num >>= 1 + return cnt + + def solve(self, num): + """ + 不太能想出来:把一个数字不断和它的 num-1 与运算,能做几次就有几个 1 + 用一条语句判断一个整数是不是2的整数次方。一个整数如果是2的整数次方,那么它的二进制表示中有且只有一位是1, + 而其他所有位都是0。根据前面的分析,把这个整数减去1之后再和它自己做与运算,这个整数中唯一的1就会变成0。 + """ + cnt = 0 + while num: + num = (num - 1) & num + cnt += 1 + return cnt + + def solve_py(self, num): + """python有一种比较 tricky 的方式来做 + """ + s = format(num, 'b') + return s.counts('1') + + +def test(): + s = Solution() + assert s.solve(9) == 2 + assert s.solve(1) == 1 + assert s.solve(8) == 1 + assert s.solve(0) == 0 + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/12_Print1ToMaxOfNDigits(\346\211\223\345\215\2601\345\210\260\346\234\200\345\244\247\347\232\204n\344\275\215\346\225\260).py" "b/\345\211\221\346\214\207offer/12_Print1ToMaxOfNDigits(\346\211\223\345\215\2601\345\210\260\346\234\200\345\244\247\347\232\204n\344\275\215\346\225\260).py" new file mode 100644 index 0000000..f9214cd --- /dev/null +++ "b/\345\211\221\346\214\207offer/12_Print1ToMaxOfNDigits(\346\211\223\345\215\2601\345\210\260\346\234\200\345\244\247\347\232\204n\344\275\215\346\225\260).py" @@ -0,0 +1,31 @@ +""" +面试题12:打印1到最大的n位数 +题目:输入数字n,按顺序打印出从1最大的n位十进制数。比如输入3,则打印出1、2、3一直到最大的3位数即999。 + +思路:不能上来直接模拟。时间复杂度太大。 +因为 python 其实支持大数字运算,不需要像其他语言一样使用数组模拟大数。 +""" + + +class Solution: + def solve(self, n): + """递归的思路输出所有排列""" + nums = [0] * n + self._solve(nums, 0, n) + + def _solve(self, nums, beg, end): + if beg == end: + print(nums) + return + for i in range(10): + nums[beg] = i + self._solve(nums, beg + 1, end) + + +def test(): + s = Solution() + s.solve(9) + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/13_DeleteNodeInList(O1\346\227\266\351\227\264\345\210\240\351\231\244\351\223\276\350\241\250\350\212\202\347\202\271).py" "b/\345\211\221\346\214\207offer/13_DeleteNodeInList(O1\346\227\266\351\227\264\345\210\240\351\231\244\351\223\276\350\241\250\350\212\202\347\202\271).py" new file mode 100644 index 0000000..8c58c90 --- /dev/null +++ "b/\345\211\221\346\214\207offer/13_DeleteNodeInList(O1\346\227\266\351\227\264\345\210\240\351\231\244\351\223\276\350\241\250\350\212\202\347\202\271).py" @@ -0,0 +1,43 @@ +""" +面试题13:在O(1)时间删除链表结点 +题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。链表结点与函数的定义如下: + +https://leetcode.com/problems/delete-node-in-a-linked-list/ +""" + + +class Node: + def __init__(self, val, next=None): + self.val, self.next = val, next + + +class Solution: + def solve(self, headnode, target_node): + """ + 传统方法是从头遍历到要删除的节点,然后让前一个节点指向下一个节点。 + 思路:把下一个节点节点复制到当前节点就好了。但是要注意只有一个节点的情况 + - 链表只有一个节点 + - 链表有多个节点并且不是尾节点 + - 链表有多个节点并且是尾节点(此时不存在下一个节点了,需要从头遍历) + """ + if not headnode or not target_node: + return + + if target_node.next: + next_node = target_node.next + target_node.next = next_node.next + target_node.val = next_node.val + del next_node + + elif target_node == headnode: + headnode.next = None + del target_node + + else: # O(n) 删除 + cur_node = headnode + while cur_node: + if cur_node.next == target_node: + cur_node.next = target_node.next + del target_node + break + cur_node = cur_node.next diff --git "a/\345\211\221\346\214\207offer/14_ReorderArray(\350\260\203\346\225\264\345\245\207\345\201\266\351\241\272\345\272\217).py" "b/\345\211\221\346\214\207offer/14_ReorderArray(\350\260\203\346\225\264\345\245\207\345\201\266\351\241\272\345\272\217).py" new file mode 100644 index 0000000..9487332 --- /dev/null +++ "b/\345\211\221\346\214\207offer/14_ReorderArray(\350\260\203\346\225\264\345\245\207\345\201\266\351\241\272\345\272\217).py" @@ -0,0 +1,43 @@ +""" +面试题14:调整数组顺序使奇数位于偶数前面 +题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分 + +https://www.lintcode.com/problem/partition-array-by-odd-and-even/description +""" + + +class Solution: + """ + @param: nums: an array of integers + @return: nothing + """ + + def partitionArray(self, nums): + """ + 和快排的 partition 比较类似,只不过一个是根据数据的大小,一个是根据是否是 奇数和偶数 + """ + beg, end = 0, len(nums)-1 + while True: + while beg < end and nums[beg] % 2 == 1: + beg += 1 + while beg < end and nums[end] % 2 == 0: + end -= 1 + if beg >= end: + break + else: + nums[beg], nums[end] = nums[end], nums[beg] + + +def test(): + s = Solution() + ll = [1, 2, 3, 4] + s.partitionArray(ll) + assert ll == [1, 3, 2, 4] + + ll = [] + s.partitionArray(ll) + assert ll == [] + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/15_KthNodeFromEnd(\351\223\276\350\241\250\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271).py" "b/\345\211\221\346\214\207offer/15_KthNodeFromEnd(\351\223\276\350\241\250\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271).py" new file mode 100644 index 0000000..8ea8b4d --- /dev/null +++ "b/\345\211\221\346\214\207offer/15_KthNodeFromEnd(\351\223\276\350\241\250\345\200\222\346\225\260\347\254\254k\344\270\252\350\212\202\347\202\271).py" @@ -0,0 +1,87 @@ +""" +面试题15:链表中倒数第k个结点 +题目:输入一个链表,输出该链表中倒数第k个结点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾结点是倒数第1个结点。 +例如一个链表有6个结点,从头结点开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是值为4的结点。 + +https://leetcode.com/problems/remove-nth-node-from-end-of-list/ +""" + +# Definition for singly-linked list. + + +class Node: + def __init__(self, x=None, next=None): + self.val = x + self.next = next + + def __str__(self): + return '{}'.format(self.val) + __repr__ = __str__ + + +class Solution: + def removeNthFromEnd(self, head, n): + """ + 思路:这一题的约束是只需要遍历一次。可以两个指针,相差 k 步 + + + :type head: ListNode + :type n: int + :rtype: ListNode + """ + # 做麻烦了,具体看 leetcode 这一题题解。加上root指向head 更容易做 + curnode = head + prenode = behind_node = head + for i in range(n-1): + curnode = curnode.next + while curnode.next: + curnode = curnode.next + prenode = behind_node + behind_node = behind_node.next + if prenode == behind_node == head: + newhead = head.next + del head + return newhead + elif prenode != behind_node: + prenode.next = behind_node.next + del behind_node + return head + + def to_list(self, head): + res = [] + cur_node = head + while cur_node: + res.append(cur_node.val) + cur_node = cur_node.next + return res + + +def test(): + s = Solution() + linklist = Node(1) + head = s.removeNthFromEnd(linklist, 1) + assert s.to_list(head) == [] + + linklist = Node(1, Node(2)) + head = s.removeNthFromEnd(linklist, 1) + assert s.to_list(head) == [1] + + linklist = Node(1, Node(2)) + head = s.removeNthFromEnd(linklist, 2) + assert s.to_list(head) == [2] + + linklist = Node(1, Node(2, Node(3, Node(4, Node(5))))) + head = s.removeNthFromEnd(linklist, 2) + assert s.to_list(head) == [1, 2, 3, 5] + + linklist = Node(1, Node(2, Node(3, Node(4, Node(5))))) + head = s.removeNthFromEnd(linklist, 1) + assert s.to_list(head) == [1, 2, 3, 4] + + linklist = Node(1, Node(2, Node(3, Node(4, Node(5))))) + head = s.removeNthFromEnd(linklist, 5) + assert s.to_list(head) == [2, 3, 4, 5] + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/16_ReverseList(\347\277\273\350\275\254\351\223\276\350\241\250).py" "b/\345\211\221\346\214\207offer/16_ReverseList(\347\277\273\350\275\254\351\223\276\350\241\250).py" new file mode 100644 index 0000000..a488e8b --- /dev/null +++ "b/\345\211\221\346\214\207offer/16_ReverseList(\347\277\273\350\275\254\351\223\276\350\241\250).py" @@ -0,0 +1,73 @@ +""" +定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。链表结点定义如下: +leetcode: +https://leetcode.com/problems/reverse-linked-list/ +""" + +# Definition for singly-linked list. + + +class Node: + def __init__(self, x, next=None): + self.val = x + self.next = next + + +class Solution: + + def reverseList(self, head): + """ + :type head: ListNode + :rtype: ListNode + """ + if not head or not head.next: + return head + newhead = self.reverseList(head.next) + nextnode = head.next # head -> nextnode + nextnode.next = head # head <- nextnode + head.next = None # None -< head <= nextnode + return newhead + + def reverseList_iter(self, head): + """ + :type head: ListNode + :rtype: ListNode + """ + prenode = None + curnode = head + while curnode: + nextnode = curnode.next + curnode.next = prenode + prenode = curnode + curnode = nextnode + return prenode # 注意返回的是 prenode + + def to_list(self, head): + res = [] + curnode = head + while curnode: + res.append(curnode.val) + curnode = curnode.next + return res + + +def test(): + s = Solution() + ll = Node(1, Node(2, Node(3, Node(4)))) + head = s.reverseList(ll) + assert s.to_list(head) == [4, 3, 2, 1] + + ll = Node(1) + head = s.reverseList(ll) + assert s.to_list(head) == [1] + + +def test_rec(): + s = Solution() + ll = Node(1, Node(2, Node(3, Node(4)))) + head = s.reverseList(ll) + assert s.to_list(head) == [4, 3, 2, 1] + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/17_MergeSortedLists(\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250).py" "b/\345\211\221\346\214\207offer/17_MergeSortedLists(\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250).py" new file mode 100644 index 0000000..7acc42e --- /dev/null +++ "b/\345\211\221\346\214\207offer/17_MergeSortedLists(\345\220\210\345\271\266\344\270\244\344\270\252\346\234\211\345\272\217\351\223\276\350\241\250).py" @@ -0,0 +1,98 @@ +""" +面试题17:合并两个排序的链表 +题目:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的 + +https://leetcode.com/problems/merge-two-sorted-lists/ + +Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists. + +Example: + +Input: 1->2->4, 1->3->4 +Output: 1->1->2->3->4->4 +""" + + +# Definition for singly-linked list. +class ListNode: + def __init__(self, x, next=None): + self.val = x + self.next = next + + def __str__(self): + return '{}'.format(self.val) + __repr__ = __str__ + + +Node = ListNode + + +class Solution: + def mergeTwoLists(self, l1, l2): + """ + :type l1: ListNode + :type l2: ListNode + :rtype: ListNode + """ + if not l1: + return l2 + if not l2: + return l1 + + root = cur = ListNode(None) + while l1 and l2: + if l1.val < l2.val: + node = ListNode(l1.val) + l1 = l1.next + else: + node = ListNode(l2.val) + l2 = l2.next + cur.next = node + cur = node + cur.next = l1 or l2 # 链接剩余元素 + return root.next + + +class Solution2: + + def mergeTwoLists(self, l1, l2): + """ + 思路:使用递归简化问题,一开始想用循环来写的,比较麻烦 + + :type l1: ListNode + :type l2: ListNode + :rtype: ListNode + """ + if not l1: + return l2 + if not l2: + return l1 + node1, node2 = l1, l2 + head = None + if node1.val < node2.val: + head = node1 + head.next = self.mergeTwoLists(node1.next, node2) + else: + head = node2 + head.next = self.mergeTwoLists(node1, node2.next) + return head + + def to_list(self, head): + res = [] + curnode = head + while curnode: + res.append(curnode.val) + curnode = curnode.next + return res + + +def test(): + ll1 = Node(1, Node(2, Node(4))) + ll2 = Node(1, Node(3, Node(4))) + s = Solution() + head = s.mergeTwoLists(ll1, ll2) + assert s.to_list(head) == [1, 1, 2, 3, 4, 4] + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/18_SubstructureInTree(\346\240\221\347\232\204\345\255\220\347\273\223\346\236\204).py" "b/\345\211\221\346\214\207offer/18_SubstructureInTree(\346\240\221\347\232\204\345\255\220\347\273\223\346\236\204).py" new file mode 100644 index 0000000..896d0ce --- /dev/null +++ "b/\345\211\221\346\214\207offer/18_SubstructureInTree(\346\240\221\347\232\204\345\255\220\347\273\223\346\236\204).py" @@ -0,0 +1,55 @@ +""" +面试题18:树的子结构 +题目:输入两棵二叉树A和B,判断B是不是A的子结构。 + +https://leetcode.com/problems/subtree-of-another-tree/ 这一题和 leetcode 还不太一样。剑指offer 上要求是子树, +leetcode 这一题要求是严格的子树 +""" + + +class Node: + def __init__(self, val, left=None, right=None): + self.val, self.left, self.right = val, left, right + + +class Solution: + def isSubtree(self, s, t): + """ TODO 思路 + :type s: TreeNode + :type t: TreeNode + :rtype: bool + """ + res = False + if s and t: + if s.val == t.val: + res = self.is_sametree(s, t) + if not res: + res = self.isSubtree(s.left, t) + if not res: + res = self.isSubtree(s.right, t) + return res + + def is_sametree(self, t1, t2): + """递归判断两个树是否相同""" + if t1 is None and t2 is None: + return True + if t1 is None or t2 is None: + return False + if t1.val != t2.val: + return False + return self.is_sametree(t1.left, t2.left) and self.is_sametree(t1.right, t2.right) + + +def test(): + s = Node(3, Node(4, Node(1), Node(2)), Node(5)) + t = Node(4, Node(1), Node(2)) + so = Solution() + assert so.isSubtree(s, t) is True + + s = Node(3, Node(4, Node(1), Node(2, Node(0))), Node(5)) + t = Node(4, Node(1), Node(2)) + so = Solution() + assert so.isSubtree(s, t) is False + + +test() diff --git "a/\345\211\221\346\214\207offer/19_MirrorOfBinaryTree(\344\272\214\345\217\211\346\240\221\351\225\234\345\203\217).py" "b/\345\211\221\346\214\207offer/19_MirrorOfBinaryTree(\344\272\214\345\217\211\346\240\221\351\225\234\345\203\217).py" new file mode 100644 index 0000000..1fb3dad --- /dev/null +++ "b/\345\211\221\346\214\207offer/19_MirrorOfBinaryTree(\344\272\214\345\217\211\346\240\221\351\225\234\345\203\217).py" @@ -0,0 +1,132 @@ +""" +请完成一个函数,输入一个二叉树,该函数输出它的镜像。 +https://leetcode.com/problems/invert-binary-tree/ +""" + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + + +class _Solution: + def invertTree(self, root): + """ + :type root: TreeNode + :rtype: TreeNode + """ + if root: + root.left, root.right = root.right, root.left + self.invertTree(root.left) + self.invertTree(root.right) + return root + + +# Definition for a binary tree node. +class Node: + def __init__(self, x, left=None, right=None): + self.val = x + self.left = left + self.right = right + + def __str__(self): + return '{}'.format(self.val) + + __repr__ = __str__ + + +from collections import deque + + +class Stack: + def __init__(self): + self.items = deque() + + def push(self, val): + return self.items.append(val) + + def pop(self): + return self.items.pop() + + def empty(self): + return len(self.items) == 0 + + +class Solution: # https://leetcode.com/problems/symmetric-tree/ + + def isSymmetric(self, root): + """ use stack """ + if not root: + return True + s = Stack() + s.push((root.left, root.right)) # push a tuple + while not s.empty(): + top_vals = s.pop() + left_node, right_node = top_vals[0], top_vals[1] + if left_node and right_node: + if left_node.val == right_node.val: + s.push((left_node.left, right_node.right)) + s.push((left_node.right, right_node.left)) + else: + return False + else: + if left_node != right_node: + return False + return True + + def isSymmetric_recursive(self, root): + """ 判断是否是镜像,使用递归的方式 + :type root: TreeNode + :rtype: bool + """ + def _check(left, right): + if left and right: + if left.val == right.val: + flag1 = _check(left.left, right.right) + flag2 = _check(left.right, right.left) + return flag1 and flag2 + else: + return False + else: + return left == right # 这种情况下 left 和 right 要么一个为 None,或者都是 None + + if root: + return _check(root.left, root.right) + return True + + def isSymmetric_layer(self, root): + """ 判断是否是镜像,使用层序遍历 + :type root: TreeNode + :rtype: bool + """ + if not root: + return True + curnodes = [root] + next_nodes = [] + while curnodes or next_nodes: + lefts = [] + rights = [] + for node in curnodes: + lefts.append(node.left.val if node.left else None) # NOTE: append val not node + rights.append(node.right.val if node.right else None) + if node.left: + next_nodes.append(node.left) + if node.right: + next_nodes.append(node.right) + if lefts != rights[::-1]: + return False + + curnodes = next_nodes + next_nodes = [] + return True + + +def test(): + t = Node(1, Node(2, Node(3), Node(4)), Node(2, Node(4), Node(3))) + s = Solution() + assert s.isSymmetric(t) is True + + +test() diff --git "a/\345\211\221\346\214\207offer/20_PrintMatrix(\350\236\272\346\227\213\347\237\251\351\230\265).py" "b/\345\211\221\346\214\207offer/20_PrintMatrix(\350\236\272\346\227\213\347\237\251\351\230\265).py" new file mode 100644 index 0000000..60b30b8 --- /dev/null +++ "b/\345\211\221\346\214\207offer/20_PrintMatrix(\350\236\272\346\227\213\347\237\251\351\230\265).py" @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +""" +https://leetcode.com/problems/spiral-matrix/ + +输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。 + +Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral order. + +Example 1: + +Input: +[ + [ 1, 2, 3 ], + [ 4, 5, 6 ], + [ 7, 8, 9 ] +] +Output: [1,2,3,6,9,8,7,4,5] +Example 2: + +Input: +[ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9,10,11,12] +] +Output: [1,2,3,4,8,12,11,10,9,5,6,7] + + +# https://leetcode.com/problems/spiral-matrix/discuss/20571/1-liner-in-Python-%2B-Ruby +看到了这个天才的解法,只需要一行 + +spiral_order([[1, 2, 3], + [4, 5, 6], + [7, 8, 9]]) + += [1, 2, 3] + spiral_order([[6, 9], + [5, 8], + [4, 7]]) + += [1, 2, 3] + [6, 9] + spiral_order([[8, 7], + [5, 4]]) + += [1, 2, 3] + [6, 9] + [8, 7] + spiral_order([[4], + [5]]) + += [1, 2, 3] + [6, 9] + [8, 7] + [4] + spiral_order([[5]]) + += [1, 2, 3] + [6, 9] + [8, 7] + [4] + [5] + spiral_order([]) + += [1, 2, 3] + [6, 9] + [8, 7] + [4] + [5] + [] + += [1, 2, 3, 6, 9, 8, 7, 4, 5] +""" + + +class Solution: + def spiralOrder(self, matrix): + """ + :type matrix: List[List[int]] + :rtype: List[int] + """ + if not matrix: # 注意递归出口,凡是涉及递归的都要注意 + return [] + first_row = matrix.pop(0) # 弹出第一行元素 + rotate_matrix = list(zip(*matrix))[::-1] + return list(first_row) + self.spiralOrder(rotate_matrix) # 注意 list(first_row)强转 + + +def test(): + matrix = [ + [1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ] + s = Solution() + assert s.spiralOrder(matrix) == [1, 2, 3, 6, 9, 8, 7, 4, 5] + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/21_MinInStack(\345\214\205\345\220\253min \345\207\275\346\225\260\347\232\204\346\240\210).py" "b/\345\211\221\346\214\207offer/21_MinInStack(\345\214\205\345\220\253min \345\207\275\346\225\260\347\232\204\346\240\210).py" new file mode 100644 index 0000000..67a25a9 --- /dev/null +++ "b/\345\211\221\346\214\207offer/21_MinInStack(\345\214\205\345\220\253min \345\207\275\346\225\260\347\232\204\346\240\210).py" @@ -0,0 +1,101 @@ +""" +面试题21:包含min函数的栈 +题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min、push及pop的时间复杂度都是O(1)。 + +https://leetcode.com/problems/min-stack/ + +Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. + +push(x) -- Push element x onto stack. +pop() -- Removes the element on top of the stack. +top() -- Get the top element. +getMin() -- Retrieve the minimum element in the stack. + +Example: +MinStack minStack = new MinStack(); +minStack.push(-2); +minStack.push(0); +minStack.push(-3); +minStack.getMin(); --> Returns -3. +minStack.pop(); +minStack.top(); --> Returns 0. +minStack.getMin(); --> Returns -2. +""" + +from collections import deque + + +class Stack: + def __init__(self): + self.items = deque() + + def push(self, val): + return self.items.append(val) + + def pop(self): + return self.items.pop() + + def empty(self): + return len(self.items) == 0 + + def top(self): + return self.items[-1] + + +class MinStack: + + def __init__(self): + """ + initialize your data structure here. + """ + self.s = Stack() + self.mins = Stack() + + def push(self, x): + """ + :type x: int + :rtype: void + """ + self.s.push(x) + if self.mins.empty(): + self.mins.push(x) + else: + min_val = self.mins.top() + if x < min_val: + self.mins.push(x) + else: + self.mins.push(min_val) + + def pop(self): + """ + :rtype: void + """ + self.mins.pop() + return self.s.pop() + + def top(self): + """ + :rtype: int + """ + return self.s.top() + + def getMin(self): + """Retrieve the minimum element in the stack. + :rtype: int + """ + return self.mins.top() + + +def test(): + minStack = MinStack() + minStack.push(-2) + minStack.push(0) + minStack.push(-3) + assert minStack.getMin() == -3 # --> Returns -3. + minStack.pop() + assert minStack.top() == 0 # --> Returns 0. + assert minStack.getMin() == -2 # --> Returns -2. + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/22_StackPushPopOrder(\346\240\210\347\232\204\345\216\213\345\205\245\345\274\271\345\207\272\345\272\217\345\210\227).py" "b/\345\211\221\346\214\207offer/22_StackPushPopOrder(\346\240\210\347\232\204\345\216\213\345\205\245\345\274\271\345\207\272\345\272\217\345\210\227).py" new file mode 100644 index 0000000..e1b4555 --- /dev/null +++ "b/\345\211\221\346\214\207offer/22_StackPushPopOrder(\346\240\210\347\232\204\345\216\213\345\205\245\345\274\271\345\207\272\345\272\217\345\210\227).py" @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + +""" +面试题22:栈的压入、弹出序列 +题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。 +假设压入栈的所有数字均不相等。 +例如序列1、2、3、4、5是某栈的压栈序列,序列4、5、3、2、1是该压栈序列对应的一个弹出序列, +但4、3、5、1、2就不可能是该压栈序列的弹出序列。 + +比如: +push 1 +push 2 +push 3 +push 4 +pop 4 # 4 +push 5 +pop 5 # 4,5 +pop 3 # 4,5,3 +pop 2 # 4,5,3,2 +pop 1 # 4,5,3,2,1 +""" + + +from collections import deque + + +class Stack: + def __init__(self): + self.items = deque() + + def push(self, val): + return self.items.append(val) + + def pop(self): + return self.items.pop() + + def empty(self): + return len(self.items) == 0 + + def top(self): + return self.items[-1] + + def __str__(self): + return str(self.items) + + __repr__ = __str__ + + +class Solution: + def solve(self, nums1, nums2): + """ + 思路:借助一个辅助stack + 如果下一个弹出数字刚好是栈顶数字,直接弹出。 + 如果下一个弹出数字不在栈顶,把压栈序列中还没有入栈的数字push 进辅助栈, + 直到把下一个需要弹出的数字压入栈顶为止。 + 如果所有数字压入栈了仍然没找到下一个弹出的数字,说明不可能是一个弹出序列。 + """ + idx1, idx2 = 0, 0 + s = Stack() + + while True: + if s.empty() or s.top() != nums2[idx2]: + s.push(nums1[idx1]) + idx1 += 1 + if s.top() == nums2[idx2]: + s.pop() + idx2 += 1 + print(s, idx1, idx2) + if idx1 == len(nums1) and s.empty(): + return True + # 注意最后一个判断条件,这个时候如果已经push 完了,结果栈顶还是找不到 nums2中元素,返回False + if idx1 == len(nums1) and not s.empty() and s.top() != nums2[idx2]: + return False + + +def test(): + s = Solution() + assert s.solve([1, 2, 3, 4, 5], [4, 5, 3, 2, 1]) is True + assert s.solve([1, 2, 3, 4, 5], [4, 3, 5, 1, 2]) is False + assert s.solve([1], [1]) is True + assert s.solve([1], [2]) is False + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/23_BfsTree(\345\261\202\345\272\217\344\273\216\344\270\212\345\276\200\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221).py" "b/\345\211\221\346\214\207offer/23_BfsTree(\345\261\202\345\272\217\344\273\216\344\270\212\345\276\200\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221).py" new file mode 100644 index 0000000..c897408 --- /dev/null +++ "b/\345\211\221\346\214\207offer/23_BfsTree(\345\261\202\345\272\217\344\273\216\344\270\212\345\276\200\344\270\213\346\211\223\345\215\260\344\272\214\345\217\211\346\240\221).py" @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +""" +层序遍历二叉树 +""" + +from collections import deque + + +class Queue: + def __init__(self): + self.items = deque() + + def append(self, val): + return self.items.append(val) + + def pop(self): + return self.items.popleft() + + def empty(self): + return len(self.items) == 0 + + +class Node: + def __init__(self, val, left=None, right=None): + self.val, self.left, self.right = val, left, right + + +class Solution: + def solve(self, root): + if not root: + return [] + curnodes = [root] + nextnodes = [] + res = [root.val] # 别忘记第一个元素的值放进去 + while curnodes: + for node in curnodes: + if node.left: + nextnodes.append(node.left) + if node.right: + nextnodes.append(node.right) + curnodes = nextnodes + res.extend([node.val for node in nextnodes]) + nextnodes = [] # 更新nextnodes + return res + + def solve_use_queue(self, root): + """use queue bfs""" + if not root: + return [] + q = Queue() + res = [root.val] + q.append(root) + while not q.empty(): + curnode = q.pop() + if curnode.left: + q.append(curnode.left) + res.append(curnode.left.val) + if curnode.right: + q.append(curnode.right) + res.append(curnode.right.val) + return res + + +def test(): + tree = Node(1, Node(2), Node(3)) + s = Solution() + assert s.solve(tree) == [1, 2, 3] + assert s.solve_use_queue(tree) == [1, 2, 3] + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/24_SquenceOfBST(\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227).py" "b/\345\211\221\346\214\207offer/24_SquenceOfBST(\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227).py" new file mode 100644 index 0000000..798f785 --- /dev/null +++ "b/\345\211\221\346\214\207offer/24_SquenceOfBST(\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\347\232\204\345\220\216\345\272\217\351\201\215\345\216\206\345\272\217\345\210\227).py" @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +""" +面试题24:二叉搜索树的后序遍历序列 +题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。 +例如输入数组{5,7,6,9,11,10,8},则返回true,因为这个整数序列是图4.6二叉搜索树的后序遍历结果。如果输入的数组是{7,4,6,5},由于没有哪棵二叉搜索树的后序遍历的结果是这个序列,因此返回false。 + +类似练习:https://leetcode.com/problems/validate-binary-search-tree/ +""" + + +class Solution: + def solve(self, nums): + """考点:二叉搜索树,性质:左子树都小于根节点值,右子树都大于根节点值 + + 思路:递归求解 + - 注意递归出口 + - 找到左右子树 + - 判断是否右子树值都是大于根节点的,如果不是,直接 return False + - 递归判断左子树和右子树 + """ + if len(nums) == 1: # 递归出口,只有一个元素怎么遍历都是自己 + return True + + root_val = nums[-1] # 后根序,根节点的值 + left_end = 0 + while nums[left_end] < root_val: + left_end += 1 + left_part = nums[0:left_end] + right_part = nums[left_end: -1] + + for val in right_part: + if val < root_val: # 右子树必须都要大于根节点的值 + return False + + return self.solve(left_part) and self.solve(right_part) + + +def test(): + s = Solution() + nums = [5, 7, 6, 9, 11, 10, 8] + assert s.solve(nums) is True + + nums = [5] + assert s.solve(nums) is True + + nums = [7, 4, 6, 5] + assert s.solve(nums) is False + + +if __name__ == '__main__': + test() diff --git "a/\345\211\221\346\214\207offer/25_PathInTree(\344\272\214\345\217\211\346\240\221\345\222\214\344\270\272\346\237\220\344\270\200\344\270\252\345\200\274).py" "b/\345\211\221\346\214\207offer/25_PathInTree(\344\272\214\345\217\211\346\240\221\345\222\214\344\270\272\346\237\220\344\270\200\344\270\252\345\200\274).py" new file mode 100644 index 0000000..830a791 --- /dev/null +++ "b/\345\211\221\346\214\207offer/25_PathInTree(\344\272\214\345\217\211\346\240\221\345\222\214\344\270\272\346\237\220\344\270\200\344\270\252\345\200\274).py" @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +""" +面试题25:二叉树中和为某一值的路径 +题目:输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。 +从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 + +https://leetcode.com/problems/path-sum/ +""" + + +# Definition for a binary tree node. +class Node: + def __init__(self, x, left=None, right=None): + self.val = x + self.left = left + self.right = right + + +from collections import deque + + +class Stack: + def __init__(self): + self.items = deque() + + def push(self, val): + return self.items.append(val) + + def pop(self): + return self.items.pop() + + def empty(self): + return len(self.items) == 0 + + +class Solution: + """ + https://leetcode.com/problems/path-sum/ + """ + + def hasPathSum(self, root, sum): + if not root: + return False + s = Stack() + s.push((root, root.val)) + while not s.empty(): + topnode, val = s.pop() + if topnode.left is None and topnode.right is None: + if val == sum: + return True + if topnode.left: + s.push((topnode.left, val+topnode.left.val)) + if topnode.right: + s.push((topnode.right, val+topnode.right.val)) + return False + + def _hasPathSum(self, root, sum, cursum=0): + """思路:递归判断,如果到了叶节点(left=right=None)当前 cursum 等于sum,返回 True + :type root: TreeNode + :type sum: int + :rtype: bool + """ + if not root: + return False + cursum += root.val + if root.left is None and root.right is None: + if cursum == sum: + return True + else: + cursum -= root.val + return self._hasPathSum(root.left, sum, cursum) or self._hasPathSum(root.right, sum, cursum) + + +def test(): + tree = Node(1, Node(2), Node(3)) + s = Solution() + # assert s.hasPathSum(tree, 4) + + N = Node + tree = N(5, N(4, N(11, N(7), N(2))), N(8, N(13), N(4, None, N(1)))) + assert s.hasPathSum(tree, 22) + + +test() diff --git "a/\345\211\221\346\214\207offer/27_ConvertBinarySearchTree(\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\210\220\345\217\214\345\220\221\351\223\276\350\241\250).py" "b/\345\211\221\346\214\207offer/27_ConvertBinarySearchTree(\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\210\220\345\217\214\345\220\221\351\223\276\350\241\250).py" new file mode 100644 index 0000000..9e32701 --- /dev/null +++ "b/\345\211\221\346\214\207offer/27_ConvertBinarySearchTree(\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\210\220\345\217\214\345\220\221\351\223\276\350\241\250).py" @@ -0,0 +1,65 @@ +""" +面试题27:二叉搜索树与双向链表 +题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 +比如输入图4.12中左边的二叉搜索树,则输出转换之后的排序双向链表。 + +https://www.lintcode.com/problem/convert-binary-search-tree-to-doubly-linked-list/description + +Description +Convert a binary search tree to doubly linked list with in-order traversal. + +Have you met this question in a real interview? +Example +Given a binary search tree: + + 4 + / \ + 2 5 + / \ +1 3 +return 1<->2<->3<->4<->5 + +""" + + +# Definition of Doubly-ListNode +class DoublyListNode(object): + def __init__(self, val, next=None): + self.val = val + self.next = self.prev = None # nextDefinition of TreeNode: + + +class TreeNode: + def __init__(self, val): + self.val = val + self.left, self.right = None, None + + +class Solution: + """ 用了一种有点 tricky 的方式,中序遍历输出所有节点以后拼成链表 + @param root: The root of tree + @return: the head of doubly list node + """ + + def bstToDoublyList(self, root): + if not root: + return None + vals = [] + self.inorder(root, vals) + prevnode = head = DoublyListNode(vals[0]) + for idx in range(1, len(vals)): + node = DoublyListNode(vals[idx]) + node.prev = prevnode + prevnode.next = node + prevnode = node + return head + + def inorder(self, subtree, vals): + if subtree: + self.inorder(subtree.left, vals) + vals.append(subtree.val) + self.inorder(subtree.right, vals) + + +def test(): + pass diff --git "a/\345\211\221\346\214\207offer/28_StringPermutation(\345\255\227\347\254\246\344\270\262\345\205\250\346\216\222\345\210\227).py" "b/\345\211\221\346\214\207offer/28_StringPermutation(\345\255\227\347\254\246\344\270\262\345\205\250\346\216\222\345\210\227).py" new file mode 100644 index 0000000..d3d27c1 --- /dev/null +++ "b/\345\211\221\346\214\207offer/28_StringPermutation(\345\255\227\347\254\246\344\270\262\345\205\250\346\216\222\345\210\227).py" @@ -0,0 +1,125 @@ +""" +面试题28:字符串的排列 +题目:输入一个字符串,打印出该字符串中字符的所有排列。 +例如输入字符串abc,则打印出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。 + +https://leetcode.com/problems/permutations/ + +Given a collection of distinct integers, return all possible permutations. + +Example: + +Input: [1,2,3] +Output: +[ + [1,2,3], + [1,3,2], + [2,1,3], + [2,3,1], + [3,1,2], + [3,2,1] + ] + + +https://leetcode.com/problems/permutations-ii/ + +Given a collection of numbers that might contain duplicates, return all possible unique permutations. + +Example: + +Input: [1,1,2] +Output: +[ + [1,1,2], + [1,2,1], + [2,1,1] +] +""" + + +class Solution1: + def permute(self, nums): + """ + 思路:使用递归。 + 可以把问题拆解为更小的问题,分成两步走:(固定第一个字符并求解其后边数组的全排列) + 1. 首先求解可能出现在第一个位置的所有字符(即把第一个字符和后边的所有字符交换) + 2. 求该字符后的所有字符的全排列 + + :type nums: List[int] + :rtype: List[List[int]] + """ + def _per(nums, beg, end, res): + print(nums) + if beg == end - 1: + res.append(nums[:]) + else: + for i in range(beg, end): + nums[i], nums[beg] = nums[beg], nums[i] + _per(nums, beg + 1, end, res) + nums[i], nums[beg] = nums[beg], nums[i] + res = [] + _per(nums, 0, len(nums), res) + return res + + +class Solution2: + def permuteUnique(self, nums): + """ + :type nums: List[int] + :rtype: List[List[int]] + """ + if not nums: + return [] + + nums.sort() + + def _per(nums, beg, end, res): + if beg == end - 1: + res.append(nums[:]) # appen copy + + for i in range(beg, end): + if nums[i] not in nums[beg:i]: + nums[i], nums[beg] = nums[beg], nums[i] + _per(nums, beg + 1, end, res) + nums[i], nums[beg] = nums[beg], nums[i] + + res = [] + _per(nums, 0, len(nums), res) + return res + + +def test_per1(): + s = Solution1() + nums = [1, 2, 3] + for i in s.permute(nums): + print(i, '=') + + +def test_per2(): + s = Solution2() + nums = [1, 1, 2] + for i in s.permuteUnique(nums): + print(i, '=') + print('\n') + nums = [1, 2, 3] + for i in s.permuteUnique(nums): + print(i, '=') + + +def test(): + # test_per1() + test_per2() + + +def perms(nums, beg, end): + if beg == end - 1: + print(nums) + for i in range(beg, end): + nums[i], nums[beg] = nums[beg], nums[i] + perms(nums, beg + 1, end) + nums[i], nums[beg] = nums[beg], nums[i] + + +if __name__ == '__main__': + nums = [1, 2, 3] + perms(nums, 0, len(nums)) diff --git "a/\345\211\221\346\214\207offer/29_MoreThanHalfNumber(\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227).py" "b/\345\211\221\346\214\207offer/29_MoreThanHalfNumber(\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227).py" new file mode 100644 index 0000000..20a4187 --- /dev/null +++ "b/\345\211\221\346\214\207offer/29_MoreThanHalfNumber(\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\346\254\241\346\225\260\350\266\205\350\277\207\344\270\200\345\215\212\347\232\204\346\225\260\345\255\227).py" @@ -0,0 +1,121 @@ +""" +面试题29:数组中出现次数超过一半的数字 +题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2} 。 +由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。 + +https://leetcode.com/problems/majority-element/ + +Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋ times. + +You may assume that the array is non-empty and the majority element always exist in the array. + +Example 1: + +Input: [3,2,3] +Output: 3 +Example 2: + +Input: [2,2,1,1,1,2,2] +Output: 2 + + + +思路: +1. nlogn 直接排序输出中位数 +2. 使用快排的 partion 求中位数 +3. 遍历数组, +因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字的时候, +如果下一个数字和我们之前保存的数字相同,则次数加1;如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零, +我们需要保存下 一个数字,并把次数设为1。 +由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字。 +""" + + +class Solution3: + def majorityElement(self, nums): + """ + :type nums: List[int] + :rtype: int + """ + mid = len(nums)//2 + return sorted(nums)[mid] + + +def partition(array, beg, end): + """对给定数组执行 partition 操作,返回新的 pivot 位置""" + pivot_index = beg + pivot = array[pivot_index] + left = pivot_index + 1 + right = end - 1 # 开区间,最后一个元素位置是 end-1 [0, end-1] or [0: end),括号表示开区间 + + while True: + # 从左边找到比 pivot 大的 + while left <= right and array[left] < pivot: + left += 1 + + while right >= left and array[right] >= pivot: + right -= 1 + + if left > right: + break + else: + array[left], array[right] = array[right], array[left] + left += 1 + right -= 1 + + array[pivot_index], array[right] = array[right], array[pivot_index] + return right # 新的 pivot 位置 + + +def nth_element(array, beg, end, nth): + """查找一个数组第 n 大元素""" + if beg < end: + pivot_idx = partition(array, beg, end) + if pivot_idx == nth - 1: # 数组小标从 0 开始 + return array[pivot_idx] + elif pivot_idx > nth - 1: + return nth_element(array, beg, pivot_idx, nth) + else: + return nth_element(array, pivot_idx + 1, end, nth) + + +class Solution2: + def majorityElement(self, nums): + """ + :type nums: List[int] + :rtype: int + """ + mid = len(nums)//2+1 + return nth_element(nums, 0, len(nums), mid) # 自己写的 partition 超时了 + + +class Solution: + def majorityElement(self, nums): + """有一个次数过半,说明他出现的次数比其他所有次数和还多。 + 思路: + 设置两个变量一个记录次数times,一个记录当前数字cur。 + 如果下一个数字等于当前数字,times+1,否则times-1。 + 如果times为0,把times重新置位1,然后赋值为当前 cur。最后返回cur 就是需要找的值 + :type nums: List[int] + :rtype: int + """ + cur = nums[0] + times = 1 + for i in range(1, len(nums)): + if nums[i] == cur: + times += 1 + else: + times -= 1 + if times == 0: + times = 1 + cur = nums[i] + return cur + + +def test(): + s = Solution() + assert s.majorityElement([3, 2, 3]) == 3 + assert s.majorityElement([2, 2, 1, 1, 1, 2, 2]) == 2 + + +test() diff --git "a/\345\211\221\346\214\207offer/30_KLeastNumbers(\346\234\200\345\260\217\347\232\204 k \344\270\252\346\225\260).py" "b/\345\211\221\346\214\207offer/30_KLeastNumbers(\346\234\200\345\260\217\347\232\204 k \344\270\252\346\225\260).py" new file mode 100644 index 0000000..fc7e0c9 --- /dev/null +++ "b/\345\211\221\346\214\207offer/30_KLeastNumbers(\346\234\200\345\260\217\347\232\204 k \344\270\252\346\225\260).py" @@ -0,0 +1,86 @@ +""" +面试题30:最小的k个数 +题目:输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。 +这道题最简单的思路莫过于把输入的n个整数排序,排序之后位于最前面的k个数就是最小的k个数。这种思路的时间复杂度是O(nlogn),面试官会提示我们还有更快的算法。 + + +思路1:O(n) +如果能修改数组,我们可以用 parittion 函数求前 k 个(前k 个最小的,不一定是排序的) + +思路2:nlogk +适合大数据处理,用一个最大堆 +""" + +import heapq + + +class MaxHeap: + """ + https://stackoverflow.com/questions/2501457/what-do-i-use-for-a-max-heap-implementation-in-python + py 的 heapq 模块提供了方便的最小堆,但是最大堆需要我们自己实现。 + 有两种方式实现: + 1. 对放入的数字取反。比如 10 放入 -10 ,然后取出来的时候再取反。个人喜欢这种方式 + 2. 新建一个对象重写 __lt__ 方法。这种方式也可以,但是重写魔术方法修改了语义不太好 + + import heapq + + class MaxHeapObj(object): + def __init__(self,val): self.val = val + def __lt__(self,other): return self.val > other.val + def __eq__(self,other): return self.val == other.val + def __str__(self): return str(self.val) + """ + + def __init__(self, capacity): + self.capacity = capacity + self.minheap = [] + + def push(self, val): + heapq.heappush(self.minheap, -val) # 取反后的数字 + + def pop(self): + val = heapq.heappop(self.minheap) + return -val + + def max(self): + return -self.minheap[0] + + def __iter__(self): + for val in self.minheap: + yield -val + + +def test_maxheap(): + mh = MaxHeap(3) + mh.push(2) + mh.push(1) + mh.push(3) + assert mh.max() == 3 + assert mh.pop() == 3 + assert mh.pop() == 2 + assert mh.pop() == 1 + + +class Solution: + + def min_k(self, nums, k): + """最小的 k 个数字 + 分析时间复杂度 + """ + maxheap = MaxHeap(k) + for idx, val in enumerate(nums): + if idx < k: + maxheap.push(val) + else: + maxval = maxheap.max() + if val < maxval: + maxheap.pop() + maxheap.push(val) + return [i for i in maxheap] + + +def test(): + s = Solution() + nums = [4, 5, 1, 6, 2, 7, 3, 8] + res = s.min_k(nums, 4) + assert sorted(res) == [1, 2, 3, 4] diff --git "a/\345\211\221\346\214\207offer/31_GreatestSumOfSubarrays(\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\345\222\214).py" "b/\345\211\221\346\214\207offer/31_GreatestSumOfSubarrays(\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\345\222\214).py" new file mode 100644 index 0000000..b90c57e --- /dev/null +++ "b/\345\211\221\346\214\207offer/31_GreatestSumOfSubarrays(\350\277\236\347\273\255\345\255\220\346\225\260\347\273\204\346\234\200\345\244\247\345\222\214).py" @@ -0,0 +1,46 @@ +""" +面试题31:连续子数组的最大和 +题目:输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。 +求所有子数组的和的最大值。 要求时间复杂度为O(n)。 + +Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum. + +Example: + +Input: [-2,1,-3,4,-1,2,1,-5,4], +Output: 6 +Explanation: [4,-1,2,1] has the largest sum = 6. +Follow up: + +If you have figured out the O(n) solution, try coding another solution using the divide and conquer approach, which is more subtle. + +https://leetcode.com/problems/maximum-subarray/ +""" + + +class Solution: + def maxSubArray(self, nums): + """ 动态规划问题。写出来状态转移方程。 + dp[i] = max(dp[i-1]+nums[i], dp[i]) + :type nums: List[int] + :rtype: int + """ + if not nums: + return 0 + size = len(nums) + max_list = [0] * size + for i in range(size): + if i == 0: + max_list[i] = nums[i] + else: + max_list[i] = max(nums[i] + max_list[i-1], nums[i]) + return max(max_list) + + +def test(): + s = Solution() + nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] + assert s.maxSubArray(nums) == 6 + + +test() diff --git "a/\345\211\221\346\214\207offer/32_NumberOf1(\344\273\2161\345\210\260n\346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260).py" "b/\345\211\221\346\214\207offer/32_NumberOf1(\344\273\2161\345\210\260n\346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260).py" new file mode 100644 index 0000000..26889f4 --- /dev/null +++ "b/\345\211\221\346\214\207offer/32_NumberOf1(\344\273\2161\345\210\260n\346\225\264\346\225\260\344\270\2551\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260).py" @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +""" +面试题32:从1到n整数中1出现的次数 +题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。 +例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次。 + +思路:比较容易想出来的一种方法是从1到 n 统计每个数字包含的1。时间复杂度n*logn +""" + + +def numberof1(n): + strn = str(n) + length = len(strn) + first = int(strn[0]) + if len(strn) == 1 and first == 0: + return 0 + if len(strn) == 1 and first > 0: + return 1 + # 假设 strn = "21345" + # num_first_digit是数字 10000-19999 第一个位中的数目 + num_first_digit = 0 + if first > 1: # 第一位不是1 + num_first_digit = power_base10(length - 1) + elif first == 1: # 如果第一位置是1,是后边数字+1, 12345有 2346个 + num_first_digit = int(strn[1:]) + 1 + + # num_other_digits 是 1346-21345 除了第一位之外的数位中的数目 + num_other_digits = first * (length - 1) * power_base10(length - 2) + + # num_recursive 是 1-1345中的数目 + num_recursive = numberof1(strn[1:]) + return num_first_digit + num_other_digits + num_recursive + + +def power_base10(n): + res = 1 + i = 0 + while i < n: + res *= 10 + i += 1 + return res + + +def test(): + assert numberof1(10) == 1 + print(numberof1(21345)) + + +test() diff --git "a/\345\211\221\346\214\207offer/33_SortArrayForMinNumber(\346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260).py" "b/\345\211\221\346\214\207offer/33_SortArrayForMinNumber(\346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260).py" new file mode 100644 index 0000000..b488a2f --- /dev/null +++ "b/\345\211\221\346\214\207offer/33_SortArrayForMinNumber(\346\212\212\346\225\260\347\273\204\346\216\222\346\210\220\346\234\200\345\260\217\347\232\204\346\225\260).py" @@ -0,0 +1,75 @@ +""" +面试题33:把数组排成最小的数 +题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 +例如输入数组{3,32,321},则打印出这3个数字能排成的最小数字321323。 + +类似的 leetcode 题目如下,不过是排成的最大的数字: + +https://leetcode.com/problems/largest-number/ + +Given a list of non negative integers, arrange them such that they form the largest number. + +Example 1: + +Input: [10,2] +Output: "210" +Example 2: + +Input: [3,30,34,5,9] +Output: "9534330" +Note: The result may be very large, so you need to return a string instead of an integer. +""" + +from functools import cmp_to_key + + +class Solution: + def largestNumber(self, nums): + """思路:直接定义比较规则就好""" + def str_cmp(a, b): + return int(a+b) - int(b+a) + strs = [str(i) for i in nums] + strs.sort(key=cmp_to_key(str_cmp), reverse=True) + return str(int(''.join(strs))) + + def largestNumber_2(self, nums): + """ 思路:一开始的解法,比较 ugly,把所有的数字扩充成一样长度,之后再比较 + :type nums: List[int] + :rtype: str + """ + if len(nums) == 1: + return str(nums[0]) + num_strs = [str(i) for i in nums] + maxlen = len(max(num_strs, key=lambda _s: len(_s))) + pairs = [] + for num_str in num_strs: + if len(num_str) < maxlen: + difflen = maxlen - len(num_str) + max_char = max(num_str[0], num_str[-1]) + append_num_str = num_str + ''.join([max_char] * difflen) + pairs.append((append_num_str, num_str)) + else: + pairs.append((num_str, num_str)) + res = [] + for pair in sorted(pairs, reverse=True): + res.append(pair[1]) + idx, size = 0, len(res) + while res[idx] == '0' and idx != size-1: + idx += 1 + return ''.join(res[idx:]) + + +def test(): + s = Solution() + assert s.largestNumber([824, 938, 1399, 5607, 6973, 5703, 9609, 4398, 8247]) == '9609938824824769735703560743981399' + assert s.largestNumber([824, 8247]) == '8248247' + assert s.largestNumber([0, 0]) == '0' + assert s.largestNumber([1, 1, 1]) == '111' + assert s.largestNumber([3, 30, 34, 5, 9]) == "9534330" + assert s.largestNumber([10, 2]) == '210' + assert s.largestNumber([0, 2]) == '20' + assert s.largestNumber([1]) == '1' + assert s.largestNumber([0]) == '0' + + +test() diff --git "a/\345\211\221\346\214\207offer/34_UglyNumber(\344\270\221\346\225\260).py" "b/\345\211\221\346\214\207offer/34_UglyNumber(\344\270\221\346\225\260).py" new file mode 100644 index 0000000..b7c26d6 --- /dev/null +++ "b/\345\211\221\346\214\207offer/34_UglyNumber(\344\270\221\346\225\260).py" @@ -0,0 +1,113 @@ +""" +面试题34:丑数 +题目:我们把只包含因子2、3和5的数称作丑数(Ugly Number)。求按从小到大的顺序的第1500个丑数。例如6、8都是丑数,但14不是, +因为它包含因子7。习惯上我们把1当做第一个丑数。 + + +https://leetcode.com/problems/ugly-number/ +Write a program to check whether a given number is an ugly number. + +Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. + +Example 1: + +Input: 6 +Output: true +Explanation: 6 = 2 × 3 +Example 2: + +Input: 8 +Output: true +Explanation: 8 = 2 × 2 × 2 +Example 3: + +Input: 14 +Output: false +Explanation: 14 is not ugly since it includes another prime factor 7. +Note: + +1 is typically treated as an ugly number. +Input is within the 32-bit signed integer range: [−231, 231 − 1]. + + + +https://leetcode.com/problems/ugly-number-ii/ + +Write a program to find the n-th ugly number. + +Ugly numbers are positive numbers whose prime factors only include 2, 3, 5. + +Example: + +Input: n = 10 +Output: 12 +Explanation: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers. +Note: + +1 is typically treated as an ugly number. +n does not exceed 1690. +""" + + +class Solution1: + def isUgly(self, num): + """ + :type num: int + :rtype: bool + """ + if num == 0: + return False + for p in (2, 3, 5): + while num % p == 0: + num /= p + return num == 1 + + +class Solution: + def nthUglyNumber(self, n): + """ + 思路1:自增数字并且判断是否是丑数,直到找到第 n 个。效率低 + + 思路2:创建一个数组保存排好序的丑数。 + 根据其定义,丑数应该是另一个丑数乘以2,3,5的结果(1除外)。 + 可以创建一个数组,里边的数字是*排好序*的丑数,每一个丑数都是前边的丑数乘以2,3或5得到的。 + 关键在于如何保证数组里的丑数是排好序的。 + + :type n: int + :rtype: int + """ + idx2, idx3, idx5 = 0, 0, 0 + uglys = [1] + beg = 1 + while beg < n: + u2, u3, u5 = uglys[idx2] * 2, uglys[idx3]*3, uglys[idx5]*5 + minu = min(u2, u3, u5) + uglys.append(minu) + if u2 == minu: + idx2 += 1 + if u3 == minu: + idx3 += 1 + if u5 == minu: + idx5 += 1 + beg += 1 + return uglys[-1] + + +def test_isugly(): + s = Solution1() + assert s.isUgly(6) + assert s.isUgly(8) + assert s.isUgly(14) is False + assert s.isUgly(0) is False + assert s.isUgly(1) + + +def test_nth_ugly(): + s = Solution() + assert s.nthUglyNumber(11) == 15 + assert s.nthUglyNumber(1) == 1 + assert s.nthUglyNumber(10) == 12 + + +if __name__ == '__main__': + test_nth_ugly() diff --git "a/\345\211\221\346\214\207offer/35_FirstNotRepeatingChar(\347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246).py" "b/\345\211\221\346\214\207offer/35_FirstNotRepeatingChar(\347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246).py" new file mode 100644 index 0000000..5e2f979 --- /dev/null +++ "b/\345\211\221\346\214\207offer/35_FirstNotRepeatingChar(\347\254\254\344\270\200\344\270\252\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\345\255\227\347\254\246).py" @@ -0,0 +1,52 @@ +""" +面试题35:第一个只出现一次的字符 +题目:在字符串中找出第一个只出现一次的字符。如输入"abaccdeff",则输出'b'。 + + +https://leetcode.com/problems/first-unique-character-in-a-string/ + +Given a string, find the first non-repeating character in it and return it's index. If it doesn't exist, return -1. + +Examples: + +s = "leetcode" +return 0. + +s = "loveleetcode", +return 2. +Note: You may assume the string contain only lowercase letters. +""" + + +class Solution: + def firstUniqChar(self, s): + """ + 思路:哈希表。 + 对于范围大小确定的有限集合,字母可以看成是数字0-25。可以直接用数组来保存也可以 + + 变形题目: + 1. 定义一个函数,输入俩字符串,从第一个字符串中删除第二个字符串中出现过的所有字符 + 2. 写一个函数删除字符串中所有重复出现的字符。 + 3. 判断是否是变位词。两个单词字母和每个字母出现次数均相同 + :type s: str + :rtype: int + """ + chars = [0] * 26 + for char in s: + chars[ord(char) - ord('a')] += 1 + for idx, char in enumerate(s): + _idx = ord(char) - ord('a') + if chars[_idx] == 1: + return idx + return -1 + + +def test(): + s = Solution() + assert s.firstUniqChar('ll') == -1 + assert s.firstUniqChar('l') == 0 + assert s.firstUniqChar('leetcode') == 0 + assert s.firstUniqChar('loveleetcode') == 2 + + +test() diff --git "a/\345\211\221\346\214\207offer/36_InversePairs(\346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271).py" "b/\345\211\221\346\214\207offer/36_InversePairs(\346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271).py" new file mode 100644 index 0000000..c24455e --- /dev/null +++ "b/\345\211\221\346\214\207offer/36_InversePairs(\346\225\260\347\273\204\344\270\255\347\232\204\351\200\206\345\272\217\345\257\271).py" @@ -0,0 +1,65 @@ +""" +面试题36:数组中的逆序对 +题目:在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。 +输入一个数组,求出这个数组中的逆序对的总数。 +“例如在数组{7,5,6,4}中,一共存在5个逆序对,分别是(7,6)、(7,5)、(7,4)、(6,4)和(5,4)。” + +https://www.lintcode.com/problem/reverse-pairs/ + +思路1:扫描整个数组,寻找每个数字后边小于它的。O(n^2) +思路2:类似归并排序的思路。 +""" + + +class Solution: + """ + @param A: an array + @return: total of reverse pairs + """ + + def reversePairs(self, A): + """做归并排序的同时计数 + """ + count = 0 + + def merge(A, beg, end): + nonlocal count + if beg == end - 1: + return [A[beg]] + mid = (beg + end) // 2 + left = merge(A, beg, mid) + right = merge(A, mid, end) + copy = [None] * (len(left) + len(right)) + i = len(left) - 1 + j = len(right) - 1 + k = len(left) + len(right) - 1 + while i >= 0 and j >= 0: + if left[i] > right[j]: + count += j + 1 + copy[k] = left[i] + i -= 1 + k -= 1 + else: + copy[k] = right[j] + k -= 1 + j -= 1 + if i >= 0: + copy[0:i + 1] = left[0:i + 1] + if j >= 0: + copy[0:j + 1] = right[0:j + 1] + return copy + + res = merge(A, 0, len(A)) + print(res, '|||||') + return count + + +def test(): + s = Solution() + assert s.reversePairs([2]) == 0 + assert s.reversePairs([2, 4, 1, 3, 5]) == 3 + assert s.reversePairs([2, 1]) == 1 + assert s.reversePairs([2]) == 0 + + +test() diff --git "a/\345\211\221\346\214\207offer/37_FirstCommonNodesInLists(\344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271).py" "b/\345\211\221\346\214\207offer/37_FirstCommonNodesInLists(\344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271).py" new file mode 100644 index 0000000..dfb2e7c --- /dev/null +++ "b/\345\211\221\346\214\207offer/37_FirstCommonNodesInLists(\344\270\244\344\270\252\351\223\276\350\241\250\347\232\204\347\254\254\344\270\200\344\270\252\345\205\254\345\205\261\347\273\223\347\202\271).py" @@ -0,0 +1,98 @@ +""" +面试题37:两个链表的第一个公共结点 +题目:输入两个链表,找出它们的第一个公共结点。链表结点定义如下: + + +https://leetcode.com/problems/intersection-of-two-linked-lists/ + +思路: +两个链表连接以后,之后的节点都是一样的了。 + +1. 使用两个栈push 所有节点,然后比较栈顶元素,如果一样就 都 pop继续比较。如果栈顶不一样,结果就是上一次 pop 的值。 + +2. 先分别遍历两个链表,找到各自长度,然后让一个链表先走 diff(len1-len2)步骤,之后一起往前走,找到的第一个就是。 + +""" + +# Definition for singly-linked list. + + +class Node(object): + def __init__(self, x, next=None): + self.val = x + self.next = next + + +class _Solution(object): + def getIntersectionNode(self, headA, headB): + """ + :type head1, head1: ListNode + :rtype: ListNode + """ + if headA is None or headB is None or (headA is None and headB is None): + return None + len1 = 0 + cura = headA + while cura: + len1 += 1 + cura = cura.next + + len2 = 0 + curb = headB + while curb: + len2 += 1 + curb = curb.next + + difflen = abs(len1 - len2) + if len1 > len2: + for i in range(difflen): + headA = headA.next + else: + for i in range(difflen): + headB = headB.next + + while headA and headB: + if headA == headB: # headA.val == headB.val and headA.next == headB.next + return headA + headA = headA.next + headB = headB.next + + return None + + +class Solution(object): + def getIntersectionNode(self, headA, headB): + """ + :type head1, head1: ListNode + :rtype: ListNode + """ + if headA is None or headB is None: + return None + + len1 = 0 + cura = headA + while cura: + len1 += 1 + cura = cura.next + + len2 = 0 + curb = headB + while curb: + len2 += 1 + curb = curb.next + + difflen = abs(len1 - len2) + if len1 > len2: + for i in range(difflen): + headA = headA.next + else: + for i in range(difflen): + headB = headB.next + + while headA and headB: + if headA == headB: # headA.val == headB.val and headA.next == headB.next + return headA + headA = headA.next + headB = headB.next + + return None diff --git "a/\345\211\221\346\214\207offer/38_NumberOfK(\346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260).py" "b/\345\211\221\346\214\207offer/38_NumberOfK(\346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260).py" new file mode 100644 index 0000000..2cb5002 --- /dev/null +++ "b/\345\211\221\346\214\207offer/38_NumberOfK(\346\225\260\345\255\227\345\234\250\346\216\222\345\272\217\346\225\260\347\273\204\344\270\255\345\207\272\347\216\260\347\232\204\346\254\241\346\225\260).py" @@ -0,0 +1,88 @@ +""" +面试题38:数字在排序数组中出现的次数 +题目:统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2,3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4 + + +https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/ + +Given an array of integers nums sorted in ascending order, find the starting and ending position of a given target value. + +Your algorithm's runtime complexity must be in the order of O(log n). + +If the target is not found in the array, return [-1, -1]. + +Example 1: + +Input: nums = [5,7,7,8,8,10], target = 8 +Output: [3,4] +Example 2: + +Input: nums = [5,7,7,8,8,10], target = 6 +Output: [-1,-1] +""" + + +def find_left(array, beg, end, target): + while beg < end: + mid = (beg + end) // 2 + if target > array[mid]: + beg = mid + 1 + else: + end = mid + if beg != len(array) and array[beg] == target: + return beg + return -1 + + +def find_right(nums, beg, end, target): + while beg < end: + mid = (beg + end) >> 1 + if target >= nums[mid]: # 条件是>=,找到之后beg会在 target 后一个位置 + beg = mid + 1 + else: + end = mid + before = beg - 1 + if before != len(nums) and nums[before] == target: + return before + return -1 + + +class Solution(object): + def searchRange(self, nums, target): + """ + :type nums: List[int] + :type target: int + :rtype: List[int] + """ + if not nums: + return [-1, -1] + l = find_left(nums, 0, len(nums), target) + r = find_right(nums, 0, len(nums), target) + return [l, r] + + +def test_left(): + nums = [5, 7, 7, 8, 8, 10] + assert find_left(nums, 0, len(nums), 5) == 0 + assert find_left(nums, 0, len(nums), 10) == 5 + assert find_left(nums, 0, len(nums), 8) == 3 + assert find_left(nums, 0, len(nums), 9) == -1 + assert find_left(nums, 0, len(nums), 4) == -1 + + +def test_right(): + nums = [5, 7, 7, 8, 8, 10] + assert find_right(nums, 0, len(nums), 5) == 0 + assert find_right(nums, 0, len(nums), 10) == 5 + assert find_right(nums, 0, len(nums), 11) == -1 + assert find_right(nums, 0, len(nums), 8) == 4 + + +def test(): + s = Solution() + assert s.searchRange([5, 7, 7, 8, 8, 10], 8) == [3, 4] + assert s.searchRange([5, 7, 7, 8, 8, 10], 6) == [-1, -1] + + +test_left() +test_right() diff --git "a/\345\211\221\346\214\207offer/39_1_TreeDepth(\344\272\214\345\217\211\346\240\221\346\267\261\345\272\246).py" "b/\345\211\221\346\214\207offer/39_1_TreeDepth(\344\272\214\345\217\211\346\240\221\346\267\261\345\272\246).py" new file mode 100644 index 0000000..6c548e9 --- /dev/null +++ "b/\345\211\221\346\214\207offer/39_1_TreeDepth(\344\272\214\345\217\211\346\240\221\346\267\261\345\272\246).py" @@ -0,0 +1,101 @@ +""" +面试题39:二叉树的深度 +题目一:输入一棵二叉树的根结点,求该树的深度。从根结点到叶结点依次经过的 +结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 + + + +https://leetcode.com/problems/maximum-depth-of-binary-tree/ + + + +Given a binary tree, determine if it is height-balanced. + +For this problem, a height-balanced binary tree is defined as: + +a binary tree in which the depth of the two subtrees of every node never differ by more than 1. + +Example 1: + +Given the following tree [3,9,20,null,null,15,7]: + + 3 + / \ + 9 20 + / \ + 15 7 +Return true. + +Example 2: + +Given the following tree [1,2,2,3,3,null,null,4,4]: + + 1 + / \ + 2 2 + / \ + 3 3 + / \ + 4 4 +Return false. +""" + + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +class Solution1: + def maxDepth(self, root): + """ + :type root: TreeNode + :rtype: int + """ + if not root: + return 0 + left = self.maxDepth(root.left) + right = self.maxDepth(root.right) + if left > right: + return left + 1 + else: + return right + 1 + + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None + +def maxDepth(root): + """ + :type root: TreeNode + :rtype: int + """ + if not root: + return 0 + left = maxDepth(root.left) + right = maxDepth(root.right) + if left > right: + return left + 1 + else: + return right + 1 + + +class Solution: + def isBalanced(self, root): + """ + :type root: TreeNode + :rtype: bool + """ + if not root: + return True + leftd = maxDepth(root.left) + rightd = maxDepth(root.right) + if abs(leftd-rightd) > 1: + return False + return self.isBalanced(root.left) and self.isBalanced(root.right) diff --git "a/\345\211\221\346\214\207offer/40_NumbersAppearOnce(\346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227).py" "b/\345\211\221\346\214\207offer/40_NumbersAppearOnce(\346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227).py" new file mode 100644 index 0000000..fa6f8fc --- /dev/null +++ "b/\345\211\221\346\214\207offer/40_NumbersAppearOnce(\346\225\260\347\273\204\344\270\255\345\217\252\345\207\272\347\216\260\344\270\200\346\254\241\347\232\204\346\225\260\345\255\227).py" @@ -0,0 +1,106 @@ +""" + +题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n), +空间复杂度是O(1)。 + +我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其他数字都出现了两次, +在异或中全部抵消了。由于这两个数字肯定不一样,那么异或的结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1。 +我们在结果数字中找到第一个为1的位的位置,记为第n位。现在我们以第n位是不是1为标准把原数组中的数字分成两个子数组 +,第一个子数组中每个数字的第n位都是1,而第二个子数组中每个数字的第n位都是0。由于我们分组的标准是数字中的某一位是1还是0, +那么出现了两次的数字肯定被分配到同一个子数组。因为两个相同的数字的任意一位都是相同的,我们不可能把两个相同的数字分配到两个子数组中去, +于是我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。 +我们已经知道如何在数组中找出唯一一个只出现一次数字,因此到此为止所有的问题都已经解决了。 + + + +https://leetcode.com/problems/single-number-iii/ + +Given an array of numbers nums, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once. + +Example: + +Input: [1,2,1,3,2,5] +Output: [3,5] +Note: + +The order of the result is not important. So in the above example, [5, 3] is also correct. +Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity? +""" + + +class Solution1: + def singleNumber(self, nums): + """ + 类似题:求只出现一次的数字,其他都出现两次。 + :type nums: List[int] + :rtype: int + """ + first = 0 + for num in nums: + first ^= num + return first + + +class Solution: + def singleNumber(self, nums): + """ + :type nums: List[int] + :rtype: List[int] + """ + def get_single_num(nums): + first = 0 + for num in nums: + first ^= num + return first + + single = get_single_num(nums) + print(single) + mask = 1 + while single & mask == 0: + mask = mask << 1 + + print(mask, '||||||||||||||||||') + left = [i for i in nums if i & mask] + right = [i for i in nums if not(i & mask)] + return [get_single_num(left), get_single_num(right)] + + +class Solution(object): + def singleNumber(self, nums): + """ + 思路:异或。 + + 136 题做过只出现一次的一个数字,本题有两个只出现一次的数字。 + 核心在于把数组分成两个。怎么分组呢? + + 假设只出现一次的数字式 x1,x2,所有元素异或结果是 x。一定有 x=x1^x2。 + x不能是0,否则x1==x2,就不是只出现一次的数字了。 + *可以用位运算 x&-x 取出x 二进制位最低位的 1,设其实是第 L 位置。* + 可以据此分类数组,一组是二进制第 L位置为 0 的数字,一组L 位置为 1的数字。 + x1,x2会分别出现在两个组中。这样第一组全部异或得到x1, 第二组全部异或得到 x2 + + :type nums: List[int] + :rtype: List[int] + """ + x = 0 + for num in nums: + x ^= num + + low1pos = x & -x # 这里也可以用移位运算,x&-x 不太好想,类似上边那个解法 + x1, x2 = 0, 0 + + for num in nums: + if num & low1pos: + x1 ^= num + else: + x2 ^= num + + return x1, x2 + + +def test(): + s = Solution() + assert s.singleNumber([1, 2, 1, 3, 2, 5]) == [3, 5] + + +test() diff --git "a/\345\211\221\346\214\207offer/41_1_TwoNumbersWithSum(\345\222\214\344\270\272s\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227VS\345\222\214\344\270\272s\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227).py" "b/\345\211\221\346\214\207offer/41_1_TwoNumbersWithSum(\345\222\214\344\270\272s\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227VS\345\222\214\344\270\272s\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227).py" new file mode 100644 index 0000000..5b2149f --- /dev/null +++ "b/\345\211\221\346\214\207offer/41_1_TwoNumbersWithSum(\345\222\214\344\270\272s\347\232\204\344\270\244\344\270\252\346\225\260\345\255\227VS\345\222\214\344\270\272s\347\232\204\350\277\236\347\273\255\346\255\243\346\225\260\345\272\217\345\210\227).py" @@ -0,0 +1,51 @@ +""" +2-sum问题 + +面试题41:和为s的两个数字VS和为s的连续正数序列 +题目一:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s, +输出任意一对即可。 +""" + + +class Solution: + def twoSum(self, nums, target): + """ 注意这题目 letcode 不是有序的,剑指offer 上的是有序的。一种方式是用 hash 来做 + :type nums: List[int] + :type target: int + :rtype: List[int] + """ + num_idx_map = {} + for idx, num in enumerate(nums): + diff = target - num + if diff in num_idx_map: + return [num_idx_map[diff], idx] + else: + num_idx_map[num] = idx + + def twoSum1(self, nums, target): + """ 注意这题目 letcode 不是有序的,剑指offer 上的是有序的。可以先排序来做。之后首位指针向中间归并 + :type nums: List[int] + :type target: int + :rtype: List[int] + """ + pairs = [(num, i) for i, num in enumerate(nums)] + nums = sorted(pairs) + print(nums) + beg, end = 0, len(nums) - 1 + while beg < end: + sum2 = nums[beg][0] + nums[end][0] + if sum2 == target: + break + elif sum2 > target: + end -= 1 + else: + beg += 1 + return [nums[beg][1], nums[end][1]] + + +def test(): + s = Solution() + assert s.twoSum([3, 2, 4], 6) == [1, 2] + assert s.twoSum([2, 7], 9) == [0, 1] + assert s.twoSum([2, 7, 11, 15], 9) == [0, 1] + assert s.twoSum([1, 2, 4, 7, 11, 15], 15) == [2, 4] diff --git "a/\345\211\221\346\214\207offer/README.md" "b/\345\211\221\346\214\207offer/README.md" new file mode 100644 index 0000000..04011b5 --- /dev/null +++ "b/\345\211\221\346\214\207offer/README.md" @@ -0,0 +1,16 @@ +# 《剑指offer》题解 + +《剑指offer》是一本经典的面试算法书籍,包含了大量常见笔试和面试题,笔者这里编写和练习了大部分题目的题解提供大家参考。 +如果可以搜到对应的 leetcode 题目,笔者会在代码注释里添加链接,方便读者直接找到对应的题目进行练习。 + +你也可以根据关键词搜索到 《剑指offer》上对应的 leetcode 题目,尝试自己思考并且提交一下,看看能否跑通所有测试用例。 + +# 勘误 + +如果您发现缺失的题目或者忘记附上 leetcode 链接,可以直接提交 MR 笔者合并进去。如果有错误也欢迎批评指正,及时修复。 + +# leetcode + +目前 leetcode 中文官方网站已经得到授权,不用辛苦找题目,可以直接在这个题库链接找到大部分题目练习: + +https://leetcode-cn.com/problem-list/xb9nqhhg/