├── .gitignore ├── Application └── 螺丝和螺母问题.py ├── Array & String ├── L_ 旋转数组中查找最小值.py ├── L_ 旋转数组中查找给定元素.py ├── L_Two sum - 有序数组.py ├── L_两个排序数组的中位数.py ├── L_众数_三分之一.py ├── L_众数_二分之一.py ├── L_岛屿的个数.py ├── L_归并两个有序数组.py ├── L_找出n个数里最小的k个.py ├── L_最小移动次数使数组元素相等.py ├── L_求局部最大值.py ├── L_第一个缺失的整数.py ├── L_第一个缺失的整数_题解_1.png ├── L_第一个缺失的整数_题解_2.png ├── L_第一个缺失的整数_题解_3.png ├── L_荷兰国旗问题.py ├── L_除自身以外数组的乘积.py ├── L_零子数组.py ├── S_KMP.py ├── S_划分字母区间.py ├── S_反转字符串中的元音字符.py ├── S_循环左移.py ├── S_循环左移_题目.png └── S_按频率对字符串进行排序.py ├── Bit operation ├── a.知识点介绍.py ├── 不用额外变量交换两个整数.py ├── 判断一个数是不是 2 的 n 次方.py ├── 找出数组中缺失的那个数.py ├── 数组中唯一一个不重复的元素.py ├── 数组中唯一两个不重复的元素.py └── 统计两个数的二进制表示有多少位不同.py ├── Dynamic Programming ├── 0-1背包.py ├── 1.操作最少次数.py ├── 1.操作最少次数_思路.png ├── 1.操作最少次数_题目.png ├── 2.任务安排.py ├── 2.任务安排_题目.png ├── 3.股票交易.py ├── 4.走棋盘、格子取数.py ├── 4.走棋盘、格子取数_题目.png ├── LCS-最长公共子序列.py ├── LIS-最长递增子序列.py ├── 数字连续的子数组.py ├── 最大连续子数组.py ├── 最小路径和.py └── 最长回文子串.py ├── Linklist ├── __pycache__ │ └── utils.cpython-35.pyc ├── utils.py ├── 两两交换链表中的节点.py ├── 从有序链表中删除重复节点.py ├── 删除链表的倒数第 n 个节点.py ├── 回文链表.py ├── 奇偶链表.py ├── 找出两个链表的交点.py ├── 把链表分隔成 k 部分.py └── 逆序链表.py ├── Math ├── pow(x,n).py ├── 判断一个数是否为两个数的平方和.py ├── 加法_二进制.py ├── 加法_字符串.py ├── 加法_逆序链表.py ├── 加法_顺序链表.py └── 计算n以内的所有素数.py ├── Nowcoder ├── 1.n个数里最小的K个.py ├── 1.n个数里最小的K个_题目.png ├── 2.字符串中找出连续最长的数字串.py ├── 2.字符串中找出连续最长的数字串_题目.png ├── 3.204-计算质数.py ├── 4.黑暗字符串.py ├── 4.黑暗字符串_题目.png ├── 5.回文序列.py └── 5.回文序列_题目.png ├── Permutation & Combination ├── 全排列.py └── 特征组合.py ├── README.md ├── Search & Sort ├── 1-冒泡.py ├── 1-堆排序.py ├── 1-外排序.py ├── 1-希尔.py ├── 1-归并.py ├── 1-快速.py ├── 1-插入.py ├── 1-选择.py ├── 2-折半查找.py ├── 3-逆序数-图解.png ├── 3-逆序数.py └── 4-查找和为定值的两个数.py ├── The beauty of programming ├── README.md ├── chapter-2 │ └── 1-求二进制数中1的个数.py ├── chapter-3 │ └── 3-计算字符串的相似度.py └── chapter-4 │ ├── 4-点是否在三角形内.py │ ├── 6-桶中取黑白球的问题.py │ ├── 7 蚂蚁爬树_题目.png │ └── 7-蚂蚁爬树.py └── Tree ├── tree.py ├── 二叉查找树-两数之和.py ├── 二叉查找树-中第K小的元素.py ├── 二叉查找树-从有序数组中构造二叉查找树.py ├── 二叉查找树-从有序链表构造平衡的二叉查找树.py ├── 二叉树-的最大深度.py ├── 分行打印一棵树.py ├── 字典树-前缀树.py ├── 完全二叉树-寻找完全二叉树最后一个节点.py ├── 完全二叉树-计算完全二叉树的节点个数.py ├── 最近公共祖先.py ├── 遍历-中序遍历-非递归.py ├── 遍历-前序遍历-非递归.py ├── 遍历-后序遍历-非递归.py └── 遍历-层次遍历.py /.gitignore: -------------------------------------------------------------------------------- 1 | test.py 2 | /other 3 | /.idea 4 | -------------------------------------------------------------------------------- /Application/螺丝和螺母问题.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给你一堆螺母和螺帽,每个螺母都有一个相对应的螺帽,但是他们之间的对应关系已经打乱。 4 | 你可以比较螺母和螺帽的大小关系,但是你无法比较螺母和螺母的大小关系, 5 | 你也无法比较螺帽和螺帽的大小关系。设计一个算法,找出螺母和螺帽的对应关系。 6 | 7 | 算法题: 8 | 给两个数组nut = [A,C,E,F,B,D,...] 和bolt = [1,2,3,6,5,4,...]。其中字母A和数字1对应,B和2对应,依次内推。。。 9 | 每个数组内部无法比较大小,数组之间可以比较大小,比如 A == 1, 1 < B, 2 > A. 10 | 11 | 思路: 快速排序,利用数组中数对另一个数组做快排 12 | 在nut数组拿一个,可以把bolt数组分为比那个小的,比那个大的,还有一个匹配的3个部分。 13 | 在bolt中小的那堆那一个可以把nut分成比那个小的,比那个大的,还有一个匹配的3个部分。 14 | 这样可以发现,现在数组产生两对比配的螺母和螺钉和一队小的螺钉和螺母,一队大的螺钉和螺母。 15 | """ 16 | 17 | 18 | def fix(nut, bolt, left, right): 19 | if left >= right: 20 | return 21 | 22 | temp = nut[left] 23 | i = left 24 | j = right 25 | while i < j: 26 | while i < j and bolt[i] < temp: 27 | i += 1 28 | while i < j and bolt[j] > temp: 29 | j -= 1 30 | if i < j: 31 | bolt[i], bolt[j] = bolt[j], bolt[i] 32 | bolt[i] = temp 33 | bolt[left], bolt[i] = bolt[i], bolt[left] 34 | 35 | temp = bolt[left + 1] 36 | i = left + 1 37 | j = right 38 | while i < j: 39 | while i < j and nut[i] < temp: 40 | i += 1 41 | while i < j and nut[j] > temp: 42 | j -= 1 43 | if i < j: 44 | nut[i], nut[j] = nut[j], nut[i] 45 | nut[i] = temp 46 | nut[left + 1], nut[i] = nut[i], nut[left + 1] 47 | 48 | fix(nut, bolt, left + 2, i) 49 | fix(nut, bolt, i + 1, right) 50 | 51 | 52 | if __name__ == '__main__': 53 | nut1 = [4, 9, 5, 1, 7, 8, 2, 3, 6] 54 | bolt1 = [7, 4, 1, 2, 5, 6, 9, 8, 3] 55 | fix(nut1, bolt1, 0, len(nut1) - 1) 56 | print(nut1) 57 | print(bolt1) 58 | -------------------------------------------------------------------------------- /Array & String/L_ 旋转数组中查找最小值.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | 3 | """ 4 | 一、题目 5 | 0124567 --旋转--> 4567012 , 最小值 是 0 6 | 7 | 二、思路 8 | 用索引start, end 分别指向首尾元素,元素不重复。 9 | 1、若子数组是普通升序数组,则 Array[start] < Array[end]; eg:Array = [4,5,6] 10 | 2、若子数组是循环升序数组,前半段子数组的元素全都大于后半子数组中的元素:Array[start] > Array[end]. eg: Array = [7,0,1,2] 11 | 12 | 三、步骤: 13 | 计算中间位置 mid = (start + end) >> 1 14 | 1、显然,Array[start...mid] 和 Array[mid+1....end]必有一个是循环升序数组,有一个是普通升序数组;而最小元素一定在循环升序数组中 15 | 2、若,Array[mid] > Array[end],说明子数组Array[mid+1,mid+2,...,end]是循环升序,更新 start = mid + 1 16 | 3、若,Array[mid]> 1 26 | if nums[mid] < nums[high]: 27 | high = mid 28 | elif nums[mid] > nums[high]: 29 | low = mid + 1 30 | return nums[low] 31 | 32 | 33 | if __name__ == '__main__': 34 | array = [4, 5, 6, 7, 0, 1, 2, 3] 35 | print(find_min(array)) 36 | -------------------------------------------------------------------------------- /Array & String/L_ 旋转数组中查找给定元素.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 4 | 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 5 | 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。 6 | 你可以假设数组中不存在重复的元素。 7 | 你的算法时间复杂度必须是 O(logN) 级别。 8 | 9 | 示例: 10 | 输入: nums = [4,5,6,7,0,1,2], target = 0 11 | 输出: 4 12 | 13 | 输入: nums = [4,5,6,7,0,1,2], target = 3 14 | 输出: -1 15 | 16 | 思路: 17 | 用索引start, end 分别指向首尾元素,元素不重复。 18 | 1、若子数组是普通升序数组,则 Array[start] < Array[end]; eg:Array = [4,5,6] 19 | 2、若子数组是循环升序数组,前半段子数组的元素全都大于后半子数组中的元素:Array[start] > Array[end]. eg: Array = [7,0,1,2] 20 | 21 | 步骤: 22 | 计算中间位置 mid = (start + end) >> 1 23 | 1、显然,Array[start...mid] 和 Array[mid+1....end]必有一个是循环升序数组,有一个是普通升序数组 24 | 2、若,Array[mid] > Array[end],说明子数组Array[mid+1,mid+2,...,end]是循环升序,Array[start,...,mid]是普通升序数组: 25 | 先判断target是否在普通升序中,否则在循环升序数组中 26 | 3、若,Array[mid]> 1 36 | if nums[mid] == target: 37 | return mid 38 | elif nums[mid] < nums[high]: 39 | if nums[mid] < target <= nums[high]: 40 | low = mid + 1 41 | else: 42 | high = mid - 1 43 | elif nums[mid] > nums[high]: 44 | if nums[low] <= target < nums[mid]: 45 | high = mid - 1 46 | else: 47 | low = mid + 1 48 | else: 49 | break 50 | return -1 51 | 52 | 53 | if __name__ == '__main__': 54 | print(search([4, 5, 6, 7, 0, 1, 2], 100)) 55 | -------------------------------------------------------------------------------- /Array & String/L_Two sum - 有序数组.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 在有序数组中找出两个数,使它们的和为 target。 4 | 5 | 思路: 双指针 6 | 使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。 7 | 指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。 8 | 遍历过程: 9 | * 如果两个指针指向元素的和 sum == target,那么得到要求的结果; 10 | * 如果 sum > target,移动较大的元素,使 sum 变小一些; 11 | * 如果 sum < target,移动较小的元素,使 sum 变大一些。 12 | """ 13 | 14 | 15 | def two_sum(nums, target): 16 | size = len(nums) 17 | start = 0 18 | end = size - 1 19 | while start < end: 20 | temp = nums[start] + nums[end] 21 | if temp == target: 22 | return nums[start], nums[end] 23 | elif temp < target: 24 | start += 1 25 | else: 26 | end -= 1 27 | return None 28 | 29 | 30 | if __name__ == '__main__': 31 | nums = [2, 7, 11, 15] 32 | print(two_sum(nums=nums,target=9)) -------------------------------------------------------------------------------- /Array & String/L_两个排序数组的中位数.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定两个大小为 m 和 n 的有序数组 nums1 和 nums2 。 4 | 请找出这两个有序数组的中位数。要求算法的时间复杂度为 O(log (m+n)) 。 5 | 你可以假设 nums1 和 nums2 均不为空。 6 | """ 7 | 8 | 9 | def find_median_sorted_arrays(nums1, nums2): 10 | size = len(nums1) + len(nums2) 11 | mid = size >> 1 12 | if size % 2 == 1: 13 | return find_k_th(nums1, nums2, mid + 1) 14 | else: 15 | smaller = find_k_th(nums1, nums2, mid) 16 | bigger = find_k_th(nums1, nums2, mid + 1) 17 | return (smaller + bigger) / 2.0 18 | 19 | 20 | def find_k_th(nums1, nums2, k): 21 | if len(nums1) == 0: 22 | return nums2[k - 1] 23 | if len(nums2) == 0: 24 | return nums1[k - 1] 25 | if k == 1: 26 | return min(nums1[0], nums2[0]) 27 | mid = k >> 1 28 | a = nums1[mid - 1] if len(nums1) >= mid else None 29 | b = nums2[mid - 1] if len(nums2) >= mid else None 30 | if b is None or (a is not None and a < b): 31 | return find_k_th(nums1[mid:], nums2, k - mid) 32 | return find_k_th(nums1, nums2[mid:], k - mid) 33 | 34 | 35 | if __name__ == '__main__': 36 | print(find_median_sorted_arrays([1, 2, 3, 10], [4, 5, 6, 7])) 37 | print(find_k_th([1, 2, 3, 10], [4, 5, 6, 7], 8)) 38 | -------------------------------------------------------------------------------- /Array & String/L_众数_三分之一.py: -------------------------------------------------------------------------------- 1 | """ 2 | 给定N个整数,查找出现次数超过N/3次的所有可能的数 3 | 注:最多只能有2个 4 | """ 5 | 6 | 7 | def find_mode(nums): 8 | size = len(nums) 9 | m, n = 0, 0 10 | cm, cn = 0, 0 11 | for a in nums: 12 | if cm == 0: 13 | m, cm = a, 1 14 | elif a == m: 15 | cm += 1 16 | elif cn == 0: 17 | n, cn = a, 1 18 | elif a == n: 19 | cn += 1 20 | else: 21 | cm -= 1 22 | cn -= 1 23 | if cm == 0 and cn > 0: 24 | m = n 25 | cm = cn 26 | cn = 0 27 | cm, cn = 0, 0 28 | for a in nums: 29 | if a == m: 30 | cm += 1 31 | elif a == n: 32 | cn += 1 33 | result = [] 34 | if cm > size / 3: 35 | result.append(m) 36 | if cn > size / 3: 37 | result.append(n) 38 | return result 39 | 40 | 41 | nums1 = [1, 2, 2, 3, 2, 1, 1, 3] 42 | print(find_mode(nums1)) 43 | -------------------------------------------------------------------------------- /Array & String/L_众数_二分之一.py: -------------------------------------------------------------------------------- 1 | """ 2 | 给定N个数,称出现次数最多的数为众数;若某众数出现的次数大于N/2,称改众数为绝对众数 3 | A = [1,2,1,3,1] , 1是绝对众数 4 | """ 5 | 6 | 7 | # 已知给定的数组存在绝对众数,求 8 | def mode(nums): 9 | count = 0 10 | m = nums[0] 11 | for a in nums: 12 | if count == 0: 13 | m = a 14 | count = 1 15 | elif m != a: 16 | count -= 1 17 | else: 18 | count += 1 19 | return m 20 | 21 | 22 | array = [8, 8, 1, 1, 1, 8, 1, 1, 6, 1, 8, 8, 8, 8] 23 | print(mode(array)) 24 | -------------------------------------------------------------------------------- /Array & String/L_岛屿的个数.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目:岛屿的个数(200) 3 | 给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围, 4 | 并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 5 | 6 | 示例 1: 7 | 输入: 输出: 8 | 11110 1 9 | 11010 10 | 11000 11 | 00000 12 | 13 | 示例 2: 14 | 输入: 输出: 15 | 11000 3 16 | 11000 17 | 00100 18 | 00011 19 | 20 | 思路: 21 | 本题是要统计岛屿的个数。首先一个岛屿的定义是由相连的1组成的。 22 | 解题思路是,对整个二维数组进行遍历,如果遇到grid[i][j]是“1”的话,则将所有与“1”直接相连和间接相连的“1”全部 23 | 赋值为“#”(此步骤采用递归的思路,看grid[i][j]上、下、左、右的位置是否为“1”),这样便识别出了一个完整的岛屿。 24 | 继续遍历grid,寻找下一个岛屿。这样便可以求出岛屿的个数。 25 | 26 | 题目升级: 27 | 不仅要求出岛屿的个数,还要求出每个岛屿中“1”的个数。 28 | 问题很简单,在上面解题思路的基础上,在每次发现岛屿的时候,统计"#"的个数,即可求出。 29 | """ 30 | from pprint import pprint 31 | 32 | 33 | class Solution: 34 | def num_of_islands(self, grid): 35 | if not grid: 36 | return 0 37 | count = 0 38 | pprint(grid) 39 | for i in range(len(grid)): 40 | for j in range(len(grid[0])): 41 | if grid[i][j] == '1': 42 | self.dfs(grid, i, j) 43 | count += 1 44 | print("\n发现第{}个岛屿.".format(count)) 45 | pprint(grid) 46 | return count 47 | 48 | def dfs(self, grid, i, j): 49 | if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]) or grid[i][j] != '1': 50 | return 51 | grid[i][j] = '#' 52 | self.dfs(grid, i + 1, j) 53 | self.dfs(grid, i - 1, j) 54 | self.dfs(grid, i, j + 1) 55 | self.dfs(grid, i, j - 1) 56 | 57 | 58 | if __name__ == '__main__': 59 | grid1 = [["1", "1", "1", "0"], 60 | ["1", "0", "1", "0"], 61 | ["0", "0", "0", "0"], 62 | ["1", "0", "1", "1"], 63 | ["1", "0", "0", "1"]] 64 | so = Solution() 65 | print("岛屿数目是 {}".format(so.num_of_islands(grid1))) 66 | -------------------------------------------------------------------------------- /Array & String/L_归并两个有序数组.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 归并两个有序数组nums1,nums2,并要求将nums2归并到nums1上 4 | 5 | 示例: 6 | nums1 = [1,2,3], nums2=[2,5,6] ,得 nums1 = [1,2,2,3,5,6] 7 | 8 | 思路: 9 | 如果不做任何要求,归并两个有序数组可以直接申请一个新的list,将nums1和nums2中的数组逐个移动过去即可; 10 | 而本题要求必须归并到nums1中,这样在操作过程中需要不断移动nums1的数据。要想移动不出错,则需要记录从nums2中插入的数据个数。 11 | 12 | 从前往后的归并,会出现nums1中的数被多次后移,比较复杂。 13 | 可以逆向思考,先在nums1后面接上长度为nums2的空间,在从后往前归并,这样每个数只会被移动一次 14 | """ 15 | 16 | 17 | def merge(nums1, nums2): 18 | end1 = len(nums1) - 1 # 原nums1的尾指针 19 | end2 = len(nums2) - 1 # 原nums2的尾指针 20 | 21 | nums1 = nums1 + [0] * (end2 + 1) 22 | index = len(nums1) - 1 # 新nums1的尾指针 23 | # 从后往前,将nums2归并到nums1中 24 | while index >= 0: 25 | if end2 < 0: # 表示 nums2 上的数已经全部归并到 nums1 上 26 | break 27 | if end1 < 0: # 表示 nums1 上的数比较小,已经全部被后移 28 | nums1[index] = nums2[end2] 29 | end2 -= 1 30 | index -= 1 31 | continue 32 | # 取较大的值放在nums1[index]上 33 | if nums1[end1] >= nums2[end2]: 34 | nums1[index] = nums1[end1] 35 | end1 -= 1 36 | else: 37 | nums1[index] = nums2[end2] 38 | end2 -= 1 39 | index -= 1 40 | del nums2 41 | return nums1 42 | 43 | 44 | if __name__ == '__main__': 45 | nums1 = [1, 2, 3] 46 | nums2 = [1, 1, 1] 47 | print(merge(nums1, nums2)) 48 | -------------------------------------------------------------------------------- /Array & String/L_找出n个数里最小的k个.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 找出n个数中最小的k个数 4 | 二、思路 5 | 题目联想--“找出n个数中第k小的数” 6 | 解法是:利用快速排序,每次调整后,会有一个基准值在正确的位置上,将基准值的index和k进行比较, 7 | 找到第k小的值。 8 | 对于本题,如果能找到第k小的值,则 数组的左边k个数 便是最小的k个数 9 | """ 10 | 11 | 12 | def find_min_top_k(nums, k): 13 | # 快速排序 14 | def quick_sort(nums, start, end, k): 15 | if start >= end: 16 | return 17 | pivot = nums[start] # 基准值 18 | low = start 19 | high = end 20 | while low < high: 21 | while low < high and nums[high] >= pivot: 22 | high -= 1 23 | nums[low] = nums[high] 24 | while low < high and nums[low] < pivot: 25 | low += 1 26 | nums[high] = nums[low] 27 | nums[low] = pivot 28 | if low == k - 1: 29 | return 30 | elif low < k - 1: 31 | quick_sort(nums, low + 1, end, k) 32 | else: 33 | quick_sort(nums, start, low - 1, k) 34 | 35 | quick_sort(nums, 0, len(nums) - 1, k) 36 | result = [i for i in sorted(nums[0:k])] 37 | return result 38 | 39 | 40 | if __name__ == '__main__': 41 | array = [1, 2, 3, -1, 2, 199, 100] 42 | print(find_min_top_k(array, 6)) 43 | -------------------------------------------------------------------------------- /Array & String/L_最小移动次数使数组元素相等.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定一个非空整数数组,找到使所有数组元素相等所需的最小移动数, 4 | 其中每次移动可将选定的一个元素加1或减1。 您可以假设数组的长度最多为10000。 5 | 6 | 例如: 7 | 输入: 8 | [1,2,3] 9 | 输出: 10 | 2 11 | 说明: 12 | 只有两个动作是必要的(记得每一步仅可使其中一个元素加1或减1): 13 | [1,2,3] => [2,2,3] => [2,2,2] 14 | 15 | 思路: 16 | 要想将数组中所有元素都移动为相同的,而且总移动步数还得最小。必须将所有的数往数组的"中位数"移动. 17 | [将所有数往“平均数”移动的想法是错误,平均值容易受到异常点的影响,例如,实操一下数组[1,1,1,1,1,1000],即可明白] 18 | 本题的难点就是如何快速找到数组的中位数了,可以联想到“L_找出n个数里最小的k个”一题。中位数就是第(len(nums) >> 1)+1个数了。 19 | 在逐个统计每个数与中位数的距离了,累加起来即可 20 | 21 | 注意: 22 | 此代码Python版在LeetCode上超时,Java版的不超时 23 | leetcode-462题 24 | """ 25 | 26 | 27 | def min_moves(nums): 28 | k = (len(nums) >> 1) + 1 29 | median = find_min_k_th(nums, k) 30 | count = 0 31 | for num in nums: 32 | count += abs(num - median) 33 | return count 34 | 35 | 36 | def find_min_k_th(nums, k): 37 | # 快速排序 38 | def quick_sort(nums, start, end, k): 39 | if start >= end: 40 | return 41 | pivot = nums[start] # 基准值 42 | low = start 43 | high = end 44 | while low < high: 45 | while low < high and nums[high] >= pivot: 46 | high -= 1 47 | nums[low] = nums[high] 48 | while low < high and nums[low] < pivot: 49 | low += 1 50 | nums[high] = nums[low] 51 | nums[low] = pivot 52 | if low == k - 1: 53 | return 54 | elif low < k - 1: 55 | quick_sort(nums, low + 1, end, k) 56 | else: 57 | quick_sort(nums, start, low - 1, k) 58 | 59 | quick_sort(nums, 0, len(nums) - 1, k) 60 | return nums[k - 1] 61 | 62 | 63 | if __name__ == '__main__': 64 | print(min_moves([1, 2, 3])) 65 | -------------------------------------------------------------------------------- /Array & String/L_求局部最大值.py: -------------------------------------------------------------------------------- 1 | """ 2 | 求局部最大值 3 | 给定一个无重复元素的数组A,求找到一个该数组的局部最大值 4 | 满足条件:a[i]>a[i-1] and a[i]>a[i+1] , a[i]是局部最大值 5 | """ 6 | 7 | 8 | def local_maximum(nums): 9 | start = 0 10 | end = len(nums) - 1 11 | while start < end: 12 | mid = (start + end) >> 1 13 | if nums[mid] > nums[mid + 1]: 14 | end = mid 15 | else: 16 | start = mid + 1 17 | return nums[start] 18 | 19 | 20 | array = [1, 2, 1, 3, 1, 1, 1, 1, 9] 21 | print(local_maximum(array)) 22 | -------------------------------------------------------------------------------- /Array & String/L_第一个缺失的整数.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定一个数组A[0....N-1], 找到从1开始,第一个不再数组中的正整数 4 | 如 [3,5, 1, 2, -3, 7, 14, 8] 输出4 5 | 6 | 思路: 7 | 对数组中的元素进行不断交换,使得元素都满足A[i] == i。交换完后,第一个不满足该等式的便是缺失值! 【下标从 1 开始】 8 | 有一个思想--假如数组A的长度是size,缺失值只有两种可能: 9 | 一、在[1,size]闭区间内 10 | 二、等于size+1。出现这可能的情况是数组A是形如[1,2,3,4,5,...]从1开头的连续数组 11 | 接下来的思想: 12 | * 不断将数字放在正确的位置(A[i] == i)上 13 | * 将不在有效区间[1,size]的数字舍弃,更新size的大小 14 | * 将重复出现的数字也舍弃,更新size的大小 15 | """ 16 | 17 | 18 | def first_miss_number(nums): 19 | size = len(nums) 20 | nums.insert(0, -1) # 在数组前面添加一个元素,这样原来数字的下标从 1 开始,便于计算 21 | i = 1 22 | while i <= size: 23 | if nums[i] == i: 24 | i += 1 25 | # 数字小于下标 数字大于理论的最大值 数字存在重复 --> 都舍弃,理论最大值减一 26 | elif nums[i] < i or nums[i] > size or nums[i] == nums[nums[i]]: 27 | nums[i] = nums[size] 28 | size -= 1 29 | elif nums[i] > i: # 当前数字比下标要大,则交换 30 | nums[nums[i]], nums[i] = nums[i], nums[nums[i]] 31 | else: 32 | pass 33 | return i 34 | 35 | 36 | if __name__ == '__main__': 37 | print(first_miss_number([3, 5, 1, 2, -3, 7, 4, 8])) 38 | print(first_miss_number([2, 3, 4, 5, 6, 7])) 39 | print(first_miss_number([1, 2, 3, 4])) 40 | -------------------------------------------------------------------------------- /Array & String/L_第一个缺失的整数_题解_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Array & String/L_第一个缺失的整数_题解_1.png -------------------------------------------------------------------------------- /Array & String/L_第一个缺失的整数_题解_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Array & String/L_第一个缺失的整数_题解_2.png -------------------------------------------------------------------------------- /Array & String/L_第一个缺失的整数_题解_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Array & String/L_第一个缺失的整数_题解_3.png -------------------------------------------------------------------------------- /Array & String/L_荷兰国旗问题.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 现有红、白、蓝三个不同颜色的小球,乱序排序在一起, 4 | 请重新排列这些小球,使得红白蓝三色的同颜色的球在一起。 5 | 6 | 二、问题转换: 7 | 给定数组array[0,...n-1],元素只能取0,1,2三个值,设计算法, 8 | 使得数组排列成“0000...00001111....11112222...2222” 9 | 10 | 三、解题思路: 11 | * 借鉴快速排序中partition的过程 12 | * 初始化: begin = 0,cur = 0,end = N-1 13 | * 打算[0,begin)全是0, [begin,cur)全是1, 14 | [cur,end)是未遍历的数, [end,size-1)全是2 15 | 四、解题步骤: 16 | * if array[cur] = 2, then swap(cur, end), end-- 17 | * if array[cur] =1, then cur++ 18 | * if array[cur] = 0, then : 19 | * if begin == cur, then begin++, cur++ 20 | * if begin != cur, then swap(cur,begin), begin++,cur++ 21 | """ 22 | 23 | 24 | def hollandr(nums): 25 | size = len(nums) 26 | begin = 0 27 | cur = 0 28 | end = size - 1 29 | while cur <= end: 30 | if nums[cur] == 2: 31 | nums[cur], nums[end] = nums[end], nums[cur] 32 | end -= 1 33 | elif nums[cur] == 1: 34 | cur += 1 35 | else: 36 | if cur != begin: 37 | nums[cur], nums[begin] = nums[begin], nums[cur] 38 | begin += 1 39 | cur += 1 40 | 41 | 42 | if __name__ == '__main__': 43 | nums = [1, 2, 0, 0, 0, 1, 0, 2, 0, 1, 0, 1, 1, 1, 1, 0, 1] 44 | hollandr(nums) 45 | print(nums) 46 | -------------------------------------------------------------------------------- /Array & String/L_除自身以外数组的乘积.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output , 4 | 其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。 5 | 6 | 示例: 7 | 输入: [1,2,3,4] 8 | 输出: [24,12,8,6] 9 | 说明: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。 10 | 11 | 思路: 12 | 本题难点在于 不许使用除法。 13 | 其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积, 14 | 即 output[i] = nums[0] * nums[1] * ... * nums[i-1] * nums[i+1] * ... * nums[size-1], 15 | 要想实现连乘,只能采用构造的方法:将数组从左往右遍历,从右往左遍历,不断累加相乘,对每一个output[i]构成出满足条件的乘法因子。 16 | """ 17 | 18 | 19 | def product_except_self(nums): 20 | size = len(nums) 21 | output = [1] * size 22 | left = 1 23 | for i in range(1, size): 24 | left *= nums[i - 1] 25 | output[i] *= left 26 | right = 1 27 | for j in range(size - 2, -1, -1): 28 | right *= nums[j + 1] 29 | output[j] *= right 30 | return output 31 | 32 | 33 | if __name__ == '__main__': 34 | print(product_except_self([1, 2, 3, 4])) 35 | -------------------------------------------------------------------------------- /Array & String/L_零子数组.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 求长度为N的数组array,求连续子数组的和 最接近0的值 4 | 5 | 二、算法思想 6 | 申请比array长1的空间 sum[-1,0,1,2,....,n-1], sum[i]是array的前i项和 7 | 定义sum[-1] = 0 8 | 显然有 array[i] + array[i+1]+....+array[j] = sum[j] - sum[i-1] 9 | 10 | 三、步骤 11 | 对于sum[-1,0,...,n-1]排序,然后计算sum相邻元素的差的绝对值,最小值即为所求 12 | 13 | """ 14 | 15 | 16 | # 子数组的和最接近0,求这个数 17 | def min_sub_array_num(nums): 18 | size = len(nums) 19 | sum_ = [0] * (size + 1) 20 | i = 1 21 | for a in nums: 22 | sum_[i] = sum_[i - 1] + a 23 | i += 1 24 | sum_ = sorted(sum_) 25 | difference = abs(sum_[1] - sum_[0]) # 差值 26 | result = difference 27 | for i in range(1, size): 28 | difference = abs(sum_[i + 1] - sum_[i]) 29 | result = min(difference, result) 30 | del sum_ 31 | return result 32 | 33 | 34 | # 子数组的和最接近0,求这个子数组 35 | # 利用字典存储一下每一个sum对应的index, 这样在排序后,可以利用index找出该子数组 36 | def min_sub_array(nums): 37 | size = len(nums) 38 | sum_ = dict() 39 | for i in range(-1, size): 40 | sum_[i] = 0 41 | i = 0 42 | for a in nums: 43 | sum_[i] = sum_[i - 1] + a 44 | i += 1 45 | sum_ = sorted(sum_.items(), key=lambda d: d[1]) 46 | difference = abs(sum_[1][1] - sum_[0][1]) # 差值 47 | result = difference 48 | start, end = 0, 0 49 | for i in range(1, size): 50 | difference = abs(sum_[i + 1][1] - sum_[i][1]) 51 | result = min(difference, result) 52 | if result == difference: 53 | start = min(sum_[i + 1][0], sum_[i][0]) 54 | end = max(sum_[i + 1][0], sum_[i][0]) 55 | return result, nums[start + 1: end + 1] 56 | 57 | 58 | if __name__ == '__main__': 59 | array = [1, -2, 3, 10, -4, 7, 2, -5, 7, -4] 60 | print(min_sub_array(array)) 61 | -------------------------------------------------------------------------------- /Array & String/S_KMP.py: -------------------------------------------------------------------------------- 1 | def compute_temporary_array(pattern): 2 | size_p = len(pattern) 3 | lsp = [0] * size_p 4 | index = 0 5 | i = 1 6 | while i < size_p: 7 | if pattern[i] == pattern[index]: 8 | lsp[i] = index + 1 9 | index += 1 10 | i += 1 11 | else: 12 | if index != 0: 13 | index = lsp[index - 1] 14 | else: 15 | lsp[i] = 0 16 | i += 1 17 | return lsp 18 | 19 | 20 | def kmp(text, pattern): 21 | lsp = compute_temporary_array(pattern) 22 | i, j = 0, 0 23 | size_t = len(text) 24 | size_p = len(pattern) 25 | count = 0 26 | while i < size_t and j < size_p: 27 | count += 1 28 | print(count) 29 | if text[i] == pattern[j]: 30 | i += 1 31 | j += 1 32 | else: 33 | if j != 0: 34 | j = lsp[j - 1] 35 | else: 36 | i += 1 37 | if j == size_p: 38 | return i - size_p 39 | else: 40 | return -1 41 | 42 | 43 | if __name__ == '__main__': 44 | src = 'ababcabcabd' 45 | sub_string = 'abcabd' 46 | result = kmp(src, sub_string) 47 | print(result) 48 | -------------------------------------------------------------------------------- /Array & String/S_划分字母区间.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段, 4 | 同一个字母只会出现在其中的一个片段。返回一个表示每个字符串片段的长度的列表。 5 | 6 | 示例: 7 | 输入: S = "ababcbacadefegdehijhklij" 8 | 输出形式1: [9,7,8] 9 | 输出形式2: ["ababcbaca", "defegde", "hijhklij"] 10 | 11 | 解释: 12 | 划分结果为 "ababcbaca", "defegde", "hijhklij"。 13 | 每个字母最多出现在一个片段中。 14 | 像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。 15 | 16 | 思路: 17 | 本题是想将字符串分割成若干连续的子串,使得各连续子串的字符都不相同。 18 | 可以想到,某一具体的字符只可能出现在一个子串中。形如"a***********a"的字符串就只能划分为其本身一个子串 19 | 启发得到,字符在字符串中的起始位置是很重要的信息,可以想象成一个闭区间。 20 | * 同一个子串内,不同字符的区间存在交集 21 | * 不同子串之间,字符的区间不存在交集 22 | 23 | 示例讲解: 24 | index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 25 | char a b a b c b a c a d e f e g d e h i j h k l i j 26 | ****先统计每个字符的起始位置,然后按字符出现的顺序排列,如下: 27 | *以下区间若有交集,才并在一起;否则,不并在一起 28 | a -- [0, 8] 29 | b -- [1, 5] ^^^^ a 并 b, get[0,8] 30 | c -- [4, 7] ^^^^ [0,8] 并 c, get[0,8] 31 | **[0,8]与d不存在交集,说明[0,8]便是一个符合要求的子串区间 32 | d -- [9,14] 33 | e -- [10.15] ^^^^ d 并 e, get [9,15] 34 | f -- [11,11] ^^^^ [9,15] 并 f,get [9,15] 35 | g -- [13,13] ^^^^ [9,15] 并 g, get [9,15] 36 | **[9,15]与h不存在交集,说明[9,15]便是一个符合要求的子串区间 37 | 同理,继续操作,即可以得到剩余的子串区间 38 | h -- [16,19] 39 | i -- [17,22] 40 | j -- [18,23] 41 | k -- [20,20] 42 | l -- [21,21] 43 | 44 | """ 45 | 46 | 47 | # 返回具体的子串 48 | def partition_labels_return_sub_str(string): 49 | order = [] 50 | start_end = dict() # 统计每个字符的起始位置 51 | for i, c in enumerate(string): 52 | if c not in order: 53 | order.append(c) 54 | start_end[c] = [i, i] 55 | else: 56 | start_end[c][-1] = i 57 | result = list() 58 | temp = start_end[order[0]] 59 | for k in order[1:]: 60 | aft_se = start_end[k] 61 | if aft_se[0] > temp[1]: 62 | result.append(string[temp[0]:temp[1] + 1]) 63 | temp = aft_se 64 | else: 65 | temp[1] = max(temp[1], aft_se[1]) 66 | result.append(string[temp[0]:temp[1] + 1]) 67 | return result 68 | 69 | 70 | # 返回子串的长度 71 | def partition_labels_return_len(string): 72 | order = [] 73 | start_end = dict() # 统计每个字符的起始位置 74 | for i, c in enumerate(string): 75 | if c not in order: 76 | order.append(c) 77 | start_end[c] = [i, i] 78 | else: 79 | start_end[c][-1] = i 80 | result = list() 81 | temp = start_end[order[0]] 82 | for k in order[1:]: 83 | aft_se = start_end[k] 84 | if aft_se[0] > temp[1]: 85 | result.append(temp[1] - temp[0] + 1) 86 | temp = aft_se 87 | else: 88 | temp[1] = max(temp[1], aft_se[1]) 89 | result.append(temp[1] - temp[0] + 1) 90 | return result 91 | 92 | 93 | if __name__ == '__main__': 94 | print(partition_labels_return_sub_str("ababcbacadefegdehijhklij")) 95 | print(partition_labels_return_len("ababcbacadefegdehijhklij")) 96 | 97 | -------------------------------------------------------------------------------- /Array & String/S_反转字符串中的元音字符.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 反转字符串中的元音(vowel)字符 4 | 5 | 示例: 6 | Given s = "leetcode", return "leotcede". 7 | 8 | 思路:双指针 9 | 题目很简单,直接用双指针分别从头和尾遍历字符串即可。 10 | 当头和尾指针都遇到元音的时候,需要交换。 11 | 注意:由于Python中字符串不允许修改,所以额外申请了一个list来存储 12 | """ 13 | 14 | 15 | def reverse_vowels(s): 16 | size = len(s) 17 | if size < 1: 18 | return s 19 | start = 0 20 | end = size - 1 21 | vowels = ["a", "e", "i", "o", "u", "A", "E", "I", "O", "U"] 22 | result = [""] * size 23 | while start < end: 24 | if s[start] not in vowels: # start不是元音,直接拷贝到result 25 | result[start] = s[start] 26 | start += 1 27 | elif s[end] not in vowels: # end不是元音,直接拷贝到result 28 | result[end] = s[end] 29 | end -= 1 30 | else: # start和end都是元音,先交换,再拷贝到result中 31 | result[start] = s[end] 32 | result[end] = s[start] 33 | start += 1 34 | end -= 1 35 | return "".join(result) 36 | 37 | 38 | if __name__ == '__main__': 39 | print(reverse_vowels("ai")) 40 | -------------------------------------------------------------------------------- /Array & String/S_循环左移.py: -------------------------------------------------------------------------------- 1 | """ 2 | *例如将abcdef循环左移2位得到 cdefab 3 | 4 | *思路:转置 (X’Y')' = YX 5 | * "abcd"[::-1] = "dcba" 6 | """ 7 | 8 | 9 | def left_roatet_str(str1, m): 10 | return ((str1[0:m])[::-1] + (str1[m:])[::-1])[::-1] 11 | 12 | 13 | if __name__ == '__main__': 14 | str1 = "abcdef" 15 | print(left_roatet_str(str1, 2)) 16 | -------------------------------------------------------------------------------- /Array & String/S_循环左移_题目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Array & String/S_循环左移_题目.png -------------------------------------------------------------------------------- /Array & String/S_按频率对字符串进行排序.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定一个字符串,请将字符串里的字符按照出现的频率降序排列。 4 | 5 | 示例1: 6 | 输入: 7 | "tree" 8 | 9 | 输出: 10 | "eert" 11 | 12 | 解释: 13 | 'e'出现两次,'r'和't'都只出现一次。 14 | 因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。 15 | 16 | 示例2: 17 | 输入: 18 | "Aabb" 19 | 20 | 输出: 21 | "bbAa" 22 | 23 | 解释: 24 | 此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。 25 | 注意'A'和'a'被认为是两种不同的字符。 26 | 27 | 思路: 28 | 本题是按字符的词频对字符串进行重新排序,词频越大排在越前面。 29 | * 先用dict存储每个字符的频数 30 | * 再对dict的value项进行从大到小的顺序排序 31 | * 再按词频,重新组合字符串,即为结果。 32 | """ 33 | 34 | 35 | def frequency_sort(string): 36 | times = dict() 37 | for c in string: 38 | times[c] = 1 if c not in times else times[c] + 1 39 | times_list = sorted(times.items(), key=lambda d: d[1], reverse=True) 40 | result = [] 41 | for c, t in times_list: 42 | result += [c] * t 43 | return "".join(result) 44 | 45 | 46 | if __name__ == '__main__': 47 | string1 = "apple" 48 | print(frequency_sort(string1)) 49 | -------------------------------------------------------------------------------- /Bit operation/a.知识点介绍.py: -------------------------------------------------------------------------------- 1 | """ 位运算知识点介绍 2 | 3 | 1. 基本原理 4 | 0s 表示一串 0,1s 表示一串 1。 5 | 6 | 按位异或运算符 按位与运算符 按位或运算符 7 | x ^ 0s = x x & 0s = 0 x | 0s = x 8 | x ^ 1s = ~x x & 1s = x x | 1s = 1s 9 | x ^ x = 0 x & x = x x | x = x 10 | 11 | * 利用 x ^ 1s = ~x 的特点,可以将位级表示翻转; 12 | 利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数 13 | * 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。 14 | 一个数 num 与 mask :00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位 15 | * 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。 16 | 一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1 17 | 18 | 2. 位与运算技巧 19 | * n&(n-1) 去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110 100 ,减去 1 得到 10110011,这两个数相与得到 10110000 20 | * n&(-n) 得到 n 的位级表示中最低的那一位。-n 得到 n 的反码加 1,对于n二进制表示 10110 100 ,-n 得到 01001100,相与&得到 00000100 21 | * n-n&(~n+1) 去除 n 的位级表示中最高的那一位 22 | 23 | 3. 移位运算 24 | * >> n 为算术右移,相当于除以 pow(2,n) 25 | * << n 为算术左移,相当于乘以 pow(2,n) 26 | * >>> n 为无符号右移,左边会补上 0 27 | 28 | 4. mask 计算 29 | * 要获取 111111111,将 0 取反即可,~0 30 | * 要得到只有第 i 位为 1 的 mask,将 1 向左移动 i-1 位即可,1<<(i-1) 。例如 1<<4 得到只有第 5 位为 1 的 mask :00010000 31 | * 要得到 1 到 i 位为 1 的 mask,1<<(i+1)-1 即可,例如将 1<<(4+1)-1 = 00010000-1 = 00001111 32 | * 要得到 1 到 i 位为 0 的 mask,只需将 1 到 i 位为 1 的 mask 取反,即 ~(1<<(i+1)-1) 33 | 34 | copyright by the great project of GitHub, CyC2018/Interview-Notebook 35 | """ 36 | -------------------------------------------------------------------------------- /Bit operation/不用额外变量交换两个整数.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 交换x 和 y,但是不许申请额外的空间 4 | 5 | 二、思路 6 | 对于这种要求奇葩的题目,当然得用最神奇的操作--位运算 7 | 利用的是 x ^ x = 0 x ^ 0 = x 这两个重要的异或运算性质 8 | """ 9 | 10 | 11 | def swap(x, y): 12 | x = x ^ y # 第一步 13 | y = x ^ y # 这一步中的 x 其实是 x ^ y (第一步计算的结果),so y = x ^ y = (x ^ y) ^ y = x 14 | x = x ^ y # 这一步中 x 其实是 x ^ y (第一步计算的结果), y 是第二步计算的结果 x, so x = x ^ y = (x ^ y) ^ x = y 15 | return x, y 16 | 17 | 18 | if __name__ == '__main__': 19 | print(swap(4, 5)) 20 | -------------------------------------------------------------------------------- /Bit operation/判断一个数是不是 2 的 n 次方.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 判断一个数是不是 2 的n 次方 4 | 5 | 二、思路 6 | 满足条件的数的二进制中有且只有最左边的一位是1,其余都是0 7 | """ 8 | 9 | 10 | def isPowerOfTwo(num): 11 | # num & (num - 1)的意思是:8(1000)& 7(0111) = 0(0000),所有满足条件的数都满足这一性质 12 | return num > 0 and num & (num - 1) == 0 13 | 14 | 15 | if __name__ == '__main__': 16 | print(isPowerOfTwo(7)) 17 | -------------------------------------------------------------------------------- /Bit operation/找出数组中缺失的那个数.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 给定一个包含 0, 1, 2, ..., n 中 n 个数的序列nums, 4 | 找出 0 .. n 中没有出现在序列中的那个数。 5 | 6 | 二、题意理解 7 | 序列 0,1,2,...,n 一共有n+1个数,任意选择其中n个数生成一个序列,求未被选择的数字 8 | 9 | 三、思路 10 | * 排序后,挨个判断哪个数没有,即为所求。 时间复杂度O(NlogN) 11 | * 利用本题的数组是从0,1,2,..,n 中选择的n个数,数组nums的index是 0,1,2,3,...,n-1,加上数组的长度n. 12 | index 和 长度 构成了 0,1,2,3,...,n 一共 n+1 个数, 再加上nums中的数正好构成了2*n+1个数 13 | 其中n个数出现了2次,1个数出现了1次,只出现一次的数就是缺少的数。 有木有很神奇!!! 14 | 解法同“数组中唯一一个不重复的元素” 15 | """ 16 | 17 | 18 | def missing_number(nums): 19 | ret = 0 20 | size = len(nums) 21 | for i in range(size): 22 | ret = ret ^ i ^ nums[i] 23 | ret = ret ^ size 24 | return ret 25 | 26 | 27 | if __name__ == '__main__': 28 | nums = [1, 2, 0, 4, 5] 29 | print(missing_number(nums)) # output:3 30 | -------------------------------------------------------------------------------- /Bit operation/数组中唯一一个不重复的元素.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 给定的任意数组只有一个数字只出现了一次,其他数字出现了偶数次, 4 | 求这个只出现一次的数? 5 | 6 | 二、思路 7 | * 暴力求解 8 | 使用hash统计每个数字出现的次数。时间复杂度O(N),空间复杂度是O(N) 9 | * 位运算快速就求解 10 | 利用异或运算的性质可以快速求解,见知识点介绍 11 | “利用 x ^ x = 0 和 x ^ 0 = x 的特点,可以将三个数中重复的两个数去除,只留下另一个数” 12 | 三、例子 13 | 2 2 3 3 4 14 | 2^2=0 15 | 0^3=3 16 | 3^3=0 17 | 0^4=4 18 | 连续异或操作后即可得到只出现一次的数字 19 | """ 20 | 21 | 22 | def single_number(nums): 23 | ret = 0 24 | for n in nums: 25 | ret = ret ^ n # 异或运算 26 | return ret 27 | 28 | 29 | if __name__ == '__main__': 30 | nums = [1, 2, 1, 3, 2, 3, 5, 4, 4, 4, 4] 31 | print(single_number(nums)) 32 | -------------------------------------------------------------------------------- /Bit operation/数组中唯一两个不重复的元素.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 给定的任意数组只有两个数字只出现了一次,其他数字出现了偶数次, 4 | 求只出现一次的两个数? 5 | 6 | 二、思路 7 | * 暴力求解 8 | 使用hash统计每个数字出现的次数。时间复杂度O(N),空间复杂度是O(N) 9 | * 位运算快速求解 10 | 利用异或运算的性质可以快速求解,见知识点介绍 11 | 性质1: “利用 x ^ x = 0 和 x ^ 0 = x 的特点,可以将三个数中重复的两个数去除,只留下另一个数” 12 | 性质2: “n&(-n) 得到 n 的位级表示中最低的那一位。-n 得到 n 的反码加 1,对于n二进制表示 10110 100 ,-n 得到 01001100,相与&得到 00000100” 13 | * 具体想法: 14 | 将所有数字进行异或操作得到 diff ,其实 diff 就是两个只出现一次的数字的异或结果。出现次数为偶数的数字,异或的结果都是0。 15 | 接下来 就如何利用 diff 找到这两个数字? 16 | 两个不同的数进行异或,二进制数 diff 中一定包含 1。 17 | 那么我们可以利用上面的性质2 找到diff中最右边的一个1,其余的全是0的数 loc。(假如diff = 10110 100 ,那么 loc = diff &(-diff) = 00000100), 18 | 而且,这 1 一定来自于要求的两个数在二进制中对应位置上异或的结果, 19 | 再次遍历数组,利用将数组里的每一个数和loc求与&运算,结果是否为0,将数组分成两个子数组【子数组的特点是:只有一个数字出现1次,其余的都出现偶数次】 20 | 那么,此时的问题就和“数组中唯一一个不重复的元素”一模一样了。 21 | 三、例子 22 | 2 2 3 3 4 23 | 2^2=0 24 | 0^3=3 25 | 3^3=0 26 | 0^4=4 27 | 连续异或操作后即可得到只出现一次的数字 28 | """ 29 | 30 | 31 | def single_numbers(nums): 32 | diff = 0 33 | res = [0, 0] 34 | for num in nums: 35 | diff = diff ^ num 36 | loc = diff & (-diff) 37 | for num in nums: 38 | if num & loc == 0: 39 | res[0] = res[0] ^ num 40 | else: 41 | res[1] = res[1] ^ num 42 | return res 43 | 44 | 45 | if __name__ == '__main__': 46 | nums = [1, 2, 1, 3, 2, 3, 5, 4, 4, 4, 4, 6] 47 | print(single_numbers(nums)) 48 | -------------------------------------------------------------------------------- /Bit operation/统计两个数的二进制表示有多少位不同.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 统计两个数的二进制表示有多少位不同,即汉明距离 4 | 5 | 二、示例 6 | 输入: x = 1, y = 4 7 | 输出: 2 8 | 解释: 9 | 1 (0 0 0 1) 10 | 4 (0 1 0 0) 11 | ↑ ↑ 12 | 13 | 上面的箭头指出了对应二进制位不同的位置。 14 | 15 | 三、思路 16 | 有上面的解释过程可以想到要统计不同的字符数,进行异或操作即可, 17 | 对于二进制数,只有 1^ 0 = 1, 正好可以统计不同点 18 | 1^4 = (0 1 0 1) count = 2 19 | """ 20 | 21 | 22 | def hamming_distance(x, y): 23 | z = x ^ y 24 | count = 0 25 | # 接下来统计二进制数z中1的个数 26 | while z != 0: 27 | if z & 1 == 1: # 二进制数z的最右边的一位是1 28 | count += 1 29 | z = z >> 1 # 右移一位 30 | return count 31 | 32 | 33 | if __name__ == '__main__': 34 | print(hamming_distance(1, 4)) 35 | -------------------------------------------------------------------------------- /Dynamic Programming/0-1背包.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | """ 3 | 有一个容量为 N 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v。 4 | 5 | 定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示前 i 件物品体积不超过 j 的情况下能达到的最大价值。 6 | 设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论: 7 | 8 | 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。 9 | 第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。 10 | 11 | 第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。 12 | 13 | 综上,0-1 背包的状态转移方程为: 14 | dp[i][j] = max( dp[i-1][j], dp[i-1][j-w] + v ) 15 | """ 16 | # def knapsack(W, N, weights, values): 17 | # dp = [] 18 | # for i in range(N + 1): 19 | # row = [] 20 | # for j in range(W + 1): 21 | # row.append(0) 22 | # dp.append(row) 23 | # for i in range(1, N+1): 24 | # w = weights[i-1] 25 | # v = values[i-1] 26 | # for j in range(1, W+1): 27 | # if j >= w: 28 | # dp[i][j] = max(dp[i-1][j], dp[i-1][j-w] + v) 29 | # else: 30 | # dp[i][j] = dp[i-1][j] 31 | -------------------------------------------------------------------------------- /Dynamic Programming/1.操作最少次数.py: -------------------------------------------------------------------------------- 1 | def CalcCount(num, pCount, pPre): 2 | if num == 1: 3 | return 0 4 | if num % 2 == 1: # 奇数 5 | if pCount[num - 1] == 0: 6 | pCount[num - 1] = CalcCount(num - 1, pCount, pPre) 7 | pPre[num] = num - 1 8 | pCount[num] = pCount[num - 1] + 1 9 | else: # 偶数 10 | n2 = num >> 1 11 | if pCount[n2] == 0: 12 | pCount[n2] = CalcCount(n2, pCount, pPre) 13 | pCount[num] = pCount[n2] + 1 14 | pPre[num] = n2 15 | return pCount[num] 16 | 17 | 18 | def CalcCount1(num): 19 | pCount = [0 for i in range(num + 1)] 20 | pPre = [0 for i in range(num + 1)] 21 | print(CalcCount(num, pCount, pPre)) 22 | n = num 23 | while n != 1: 24 | print(pPre[n]) 25 | n = pPre[n] 26 | 27 | 28 | def CalcCount2(num): 29 | path = [] 30 | while num != 1: 31 | path.append(num) 32 | if num % 2 == 1: 33 | num -= 1 34 | else: 35 | num = num >> 1 36 | print(path[::-1]) 37 | 38 | 39 | CalcCount1(2015) 40 | -------------------------------------------------------------------------------- /Dynamic Programming/1.操作最少次数_思路.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Dynamic Programming/1.操作最少次数_思路.png -------------------------------------------------------------------------------- /Dynamic Programming/1.操作最少次数_题目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Dynamic Programming/1.操作最少次数_题目.png -------------------------------------------------------------------------------- /Dynamic Programming/2.任务安排.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | """ 4 | 5 | 6 | def isTaskable(N, M, R, O): 7 | RO = dict() 8 | for i in range(N): 9 | RO[i] = R[i] - O[i] 10 | sort = sorted(RO.items(), key=lambda d: d[1], reverse=True) 11 | occupy = 0 # 已经占用了多少空间 12 | bOK = True 13 | for i, _ in sort: 14 | if occupy + R[i] > M: 15 | bOK = False 16 | break 17 | occupy += O[i] 18 | return bOK 19 | 20 | 21 | N = 2 22 | M = 14 23 | R = [10, 8] 24 | O = [5, 6] 25 | print(isTaskable(N, M, R, O)) 26 | -------------------------------------------------------------------------------- /Dynamic Programming/2.任务安排_题目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Dynamic Programming/2.任务安排_题目.png -------------------------------------------------------------------------------- /Dynamic Programming/3.股票交易.py: -------------------------------------------------------------------------------- 1 | """题目1----交易1次 2 | 给定数组A[0....N-1],其中A[i]表示某股票第i天的价格。如果允许最多只进行一次交易 3 | (先买一次,再卖一次),请计算何时买卖达到最大收益,返回最大收益值。 4 | 结题思路:遍历prices数组,如果在第i次(1<=i> 1 6 | # for kk in range(int(k)): 7 | # left = brand[0:middle] 8 | # right = brand[middle:] 9 | # index = len(brand) 10 | # while index > 1: 11 | # if index % 2 == 0: 12 | # brand[index - 1] = right.pop() 13 | # index -= 1 14 | # else: 15 | # brand[index - 1] = left.pop() 16 | # index -= 1 17 | # print(" ".join(brand)) 18 | # # 19 | # 20 | # data = input().split() 21 | # rounds = int(data[0]) 22 | # index = 1 23 | # for r in range(rounds): 24 | # n = int(data[index]) 25 | # k = int(data[index+1]) 26 | # index += 2 27 | # brand = data[index:index+2*n] 28 | # index += 2*n 29 | # middle = 2*n >> 1 30 | # for kk in range(k): 31 | # left = brand[0:middle] 32 | # right = brand[middle:] 33 | # i = 2*n 34 | # while i>1: 35 | # if i % 2 == 0: 36 | # brand[i - 1] = right.pop() 37 | # i -= 1 38 | # else: 39 | # brand[i - 1] = left.pop() 40 | # i -= 1 41 | # print(" ".join(brand)) 42 | 43 | # rounds = int(input()) 44 | # data = input().split() 45 | # index = 0 46 | # for r in range(rounds): 47 | # n = int(data[index]) 48 | # k = int(data[index+1]) 49 | # index += 2 50 | # brand = data[index:index+2*n] 51 | # index += 2*n 52 | # middle = 2*n >> 1 53 | # for kk in range(k): 54 | # left = brand[0:middle] 55 | # right = brand[middle:] 56 | # i = 2*n 57 | # while i>1: 58 | # if i % 2 == 0: 59 | # brand[i - 1] = right.pop() 60 | # i -= 1 61 | # else: 62 | # brand[i - 1] = left.pop() 63 | # i -= 1 64 | # print(" ".join(brand)) 65 | 66 | try: 67 | rounds = int(input()) 68 | for r in range(rounds): 69 | param = input().split() 70 | n = param[0] 71 | k = param[1] 72 | brand = input().split() 73 | middle = len(brand) >> 1 74 | for kk in range(int(k)): 75 | left = brand[0:middle] 76 | right = brand[middle:] 77 | index = len(brand) 78 | while index >= 1: 79 | if index % 2 == 0: 80 | brand[index - 1] = right.pop() 81 | index -= 1 82 | else: 83 | brand[index - 1] = left.pop() 84 | index -= 1 85 | print(" ".join(brand)) 86 | except EOFError: 87 | pass 88 | -------------------------------------------------------------------------------- /Dynamic Programming/4.走棋盘、格子取数_题目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Dynamic Programming/4.走棋盘、格子取数_题目.png -------------------------------------------------------------------------------- /Dynamic Programming/LCS-最长公共子序列.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | """ 3 | 对于两个子序列 S1 和 S2,找出它们最长的公共子序列。 4 | 5 | 定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。 6 | 考虑 S1i 与 S2j 值是否相等,分为两种情况: 7 | 8 | 当 S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值, 9 | 最长公共子序列长度加 1 ,即 dp[i][j] = dp[i-1][j-1] + 1。 10 | 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列, 11 | 与 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。 12 | 综上,最长公共子序列的状态转移方程为: 13 | 14 | 15 | 对于长度为 N 的序列 S1 和 长度为 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最长公共子序列长度。 16 | 17 | 与最长递增子序列相比,最长公共子序列有以下不同点: 18 | 19 | 针对的是两个序列,求它们的最长公共子序列。 20 | 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ; 21 | 在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j 。 22 | 在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解, 23 | 因为以 SN 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。 24 | 25 | https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3.md#%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92 26 | """ 27 | 28 | 29 | def lengthofLCS(nums1, nums2): 30 | dp = [] 31 | len1, len2 = len(nums1), len(nums2) 32 | for i in range(len1 + 1): 33 | row = [] 34 | for j in range(len2 + 1): 35 | row.append(0) 36 | dp.append(row) 37 | for i in range(1, len1 + 1): 38 | for j in range(1, len2 + 1): 39 | if nums1[i - 1] == nums2[j - 1]: 40 | dp[i][j] = dp[i - 1][j - 1] + 1 41 | else: 42 | dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) 43 | i = len1 44 | j = len2 45 | sub = [] 46 | while i != 0 and j != 0: 47 | if nums1[i - 1] == nums2[j - 1]: 48 | sub.append(nums1[i - 1]) 49 | i -= 1 50 | j -= 1 51 | else: 52 | if dp[i][j - 1] > dp[i - 1][j]: 53 | j -= 1 54 | else: 55 | i -= 1 56 | return dp[len1][len2], sub[::-1] 57 | 58 | 59 | nums1 = ['a', 'b', 'c', 'b', 'd', 'a', 'b'] 60 | nums2 = ['b', 'd', 'c', 'a', 'b', 'a'] 61 | print(lengthofLCS(nums1, nums2)) 62 | -------------------------------------------------------------------------------- /Dynamic Programming/LIS-最长递增子序列.py: -------------------------------------------------------------------------------- 1 | class Solution(object): 2 | # 求最长递增子序列的长度, DP , O(N^2) 3 | def LIS1(self, nums): 4 | lenn = len(nums) 5 | longest = [1 for i in range(lenn)] 6 | for i in range(1, lenn): 7 | for j in range(0, i): 8 | if nums[j] <= nums[i]: 9 | longest[i] = max(longest[i], longest[j] + 1) 10 | return max(longest) 11 | 12 | # 利用换冲池, 贪心算法, O(N*log(N)) 13 | def LIS2(self, nums): 14 | def binary_search(nums, k): 15 | start = 0 16 | end = len(nums) - 1 17 | while start <= end: 18 | mid = (start + end) >> 1 19 | if nums[mid] == k: 20 | break 21 | elif nums[mid] < k: 22 | start = mid + 1 23 | else: 24 | end = mid - 1 25 | nums[start] = k 26 | 27 | cache = [] 28 | for a in nums: 29 | if len(cache) == 0: 30 | cache.append(a) 31 | else: 32 | if a > cache[-1]: 33 | cache.append(a) 34 | else: 35 | binary_search(cache, a) 36 | return len(cache) 37 | 38 | 39 | if __name__ == '__main__': 40 | arr = [1, 4, 6, 2, 8, 9, 7, 100, 111] 41 | s = Solution() 42 | print(s.LIS2(arr)) 43 | -------------------------------------------------------------------------------- /Dynamic Programming/数字连续的子数组.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 给定长度是N的数组array[0,....,n-1],求递增且连续数字最长的子数组。 4 | 例如: 数组array=[1,2,3,34,56,57,58,59,60,61,99,121]的连续数字 5 | 最长的一段为56 57 58 59 60 61 6 | 7 | 二、思路:动态规划 8 | 定义:dp[i]存的是以array[i]结尾的最长连续递增子数组的长度 9 | """ 10 | 11 | 12 | def max_sequence_len(array): 13 | size = len(array) 14 | dp = [1] * size 15 | m = 1 # 记录最大的长度 16 | for i in range(1, size): 17 | if array[i] - array[i - 1] == 1: 18 | dp[i] += dp[i - 1] 19 | m = max(dp[i], m) 20 | return m 21 | 22 | 23 | def max_sequence(array): 24 | size = len(array) 25 | dp = [1] * size # dp[i]全部初始化为1 26 | m = 1 # 记录最大的长度 27 | index = 0 28 | for i in range(1, size): 29 | if array[i] - array[i - 1] == 1: 30 | dp[i] += dp[i - 1] 31 | m = max(dp[i], m) 32 | if m == dp[i]: 33 | index = i 34 | return m, array[index - m + 1:index + 1] 35 | 36 | 37 | if __name__ == '__main__': 38 | array = [1, 2, 3, 54, 55, 56, 57, 58, 59, 60, 99, 121] 39 | print(max_sequence(array)) 40 | -------------------------------------------------------------------------------- /Dynamic Programming/最大连续子数组.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 给定一个数组array[0,1,...,n-1],求array的连续子数组,使得该子数组的和最大 4 | 5 | 二、思路:动态规划 6 | dp[i]为以array[i]结尾的数组中和最大的子数组 7 | """ 8 | 9 | 10 | # 求得最大值 11 | def max_sub_array_sum(array): 12 | if array == None: 13 | return 0 14 | size = len(array) 15 | if size == 0: 16 | return 0 17 | sum_ = array[0] # 当前子串的和 18 | result = sum_ # 当前找到的最优解 19 | for a in array[1:]: 20 | if sum_ > 0: 21 | sum_ += a 22 | else: 23 | sum_ = a 24 | result = max(sum_, result) 25 | return result 26 | 27 | 28 | # 求得最大连续子数组本身 29 | def max_sub_array(array): 30 | if array == None: 31 | return 0 32 | size = len(array) 33 | if size == 0: 34 | return 0 35 | start, end = 0, 0 36 | sum_ = array[0] 37 | result = sum_ # 最大和 38 | for i, a in enumerate(array[1:]): 39 | if sum_ > 0: 40 | sum_ += a 41 | else: 42 | sum_ = a 43 | startNew = i + 1 44 | if result < sum_: 45 | result = sum_ 46 | start = startNew 47 | end = i + 1 48 | return array[start:end + 1], result 49 | 50 | 51 | if __name__ == '__main__': 52 | array = [1, -2, 3, 10, -4, 7, 2, -5, 6] 53 | print(max_sub_array(array)) 54 | -------------------------------------------------------------------------------- /Dynamic Programming/最小路径和.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径, 4 | 使得路径上的数字总和为最小。 5 | 说明:每次只能向下或者向右移动一步。 6 | 7 | 示例: 8 | 输入: 9 | [ 10 | [1,3,1], 11 | [1,5,1], 12 | [4,2,1] 13 | ] 14 | 输出: 7 15 | 解释: 因为路径 1→3→1→1→1 的总和最小。 16 | 17 | 思路: 18 | 最优解问题一般首选思路是动态规划!!! 19 | dp[i][j]是走到坐标(i,j)位置的最短路径。可以从上到下,从左到右挨个计算dp[i][j]. 20 | 由于只能向下或向右移动,那么动态转移方程是: 21 | dp[i][j] = max(dp[i-1][j] + grid[i][j], dp[i][j-1] + grid[i][j]) 22 | 23 | 注意:本题,边界条件是最上面的一行和最左边的一列,单独处理即可! 24 | 本题是直接在原始数组grid上修改的,没有显示的dp数组! 25 | """ 26 | 27 | 28 | def min_path_sum(grid): 29 | m = len(grid) # m 行数 30 | n = len(grid[0]) # 列数 31 | for i in range(1, m): 32 | grid[i][0] += grid[i - 1][0] 33 | for j in range(1, n): 34 | grid[0][j] += grid[0][j - 1] 35 | 36 | for i in range(1, m): 37 | for j in range(1, n): 38 | grid[i][j] = min(grid[i][j - 1], grid[i - 1][j]) + grid[i][j] 39 | return grid[m - 1][n - 1] 40 | 41 | 42 | if __name__ == '__main__': 43 | grid1 = [ 44 | [1, 3, 1], 45 | [1, 5, 1], 46 | [4, 2, 1] 47 | ] 48 | print(min_path_sum(grid=grid1)) 49 | -------------------------------------------------------------------------------- /Dynamic Programming/最长回文子串.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 求字符串中的最长回文子串。 形如 abba、aba都是 4 | 二、思路。 动态规划 5 | dp[i][j]存的是string[i,...,j]是否是回文子串。是则为True, 不是则为False 6 | 三、步骤: 7 | * dp初始化为False 8 | * if j == i, 包含一个字符的串是回文串, dp[i][j] = True 9 | * if j == i + 1,then: 10 | * if string[i] == string[j], 长度为2的两个 相同 的字符构成的子串是回文串(例如,aa,bb,cc),True 11 | * if string[i] != string[j], 长度为2的两个 不同 的字符构成的子串不是回文串(例如,ac,ba,cf),False 12 | * if j > i + 1, 表示子串的长度至少为3,then: (思想,如果 *** 是回文串,则 a***a 也是回文串,a***b 不是回文串) 13 | * if string[i] == string[j], 若dp[i+1][j-1]是回文,则dp[i][j]才是回文,否则不是 14 | * if string[i] != string[j], then 不是回文 15 | """ 16 | 17 | 18 | def longestPalindrome(string): 19 | size = len(string) 20 | dp = [] 21 | # 初始化一个N * N的矩阵 22 | for i in range(size): 23 | row = [] 24 | for j in range(size): 25 | row.append(False) 26 | dp.append(row) 27 | max_seq_len = 0 28 | max_seq = "" 29 | for i in range(size - 1, -1, -1): 30 | for j in range(i, size): 31 | if j == i: 32 | dp[i][j] = True 33 | elif j == i + 1: 34 | if string[i] == string[j]: 35 | dp[i][j] = True 36 | else: 37 | dp[i][j] = False 38 | else: 39 | if string[i] == string[j] and dp[i + 1][j - 1]: 40 | dp[i][j] = True 41 | else: 42 | dp[i][j] = False 43 | if j - i + 1 >= max_seq_len and dp[i][j]: 44 | max_seq_len = j - i + 1 45 | max_seq = string[i:j + 1] 46 | return max_seq 47 | 48 | 49 | if __name__ == '__main__': 50 | string = "yyyyabcdcbawe" 51 | print(longestPalindrome(string)) 52 | -------------------------------------------------------------------------------- /Linklist/__pycache__/utils.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Linklist/__pycache__/utils.cpython-35.pyc -------------------------------------------------------------------------------- /Linklist/utils.py: -------------------------------------------------------------------------------- 1 | """ 链表tools 2 | 本模块只是想实现若干个链表的基本函数,方便其他的代码实现和测试 3 | """ 4 | 5 | 6 | class ListNode(object): 7 | def __init__(self, val): 8 | self.val = val 9 | self.next = None 10 | 11 | 12 | def build_l(nums): 13 | head = ListNode(nums[0]) 14 | cur = head 15 | for i in nums[1:]: 16 | cur.next = ListNode(i) 17 | cur = cur.next 18 | return head 19 | 20 | 21 | def print_l(head, p_type="STR"): 22 | p = head 23 | result = [] 24 | while p: 25 | if p_type in ["STR", "SINGLE"]: 26 | result.append(str(p.val)) 27 | else: 28 | result.append(p.val) 29 | p = p.next 30 | if p_type == "STR": 31 | print(" ".join(result)) 32 | elif p_type == "LIST": 33 | print(result) 34 | elif p_type == "SINGLE": 35 | print(" -> ".join(result)) 36 | else: 37 | print("error dType!!!") 38 | 39 | 40 | if __name__ == '__main__': 41 | nums1 = [1, 2, 3, 4, 5, 8, 1, 2] 42 | head1 = build_l(nums1) 43 | print_l(head1, "SINGLE") 44 | -------------------------------------------------------------------------------- /Linklist/两两交换链表中的节点.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表 4 | 5 | 示例: 6 | 给定 1->2->3->4, 你应该返回 2->1->4->3. 7 | """ 8 | 9 | from Linklist.utils import * 10 | 11 | 12 | def swap_pairs(head): 13 | node = ListNode(-1) # 交换前,在头节点前面加一个值为“-1”的节点 14 | node.next = head 15 | pre = node 16 | while pre.next is not None and pre.next.next is not None: 17 | l1 = pre.next 18 | l2 = pre.next.next 19 | l2_next = l2.next 20 | l1.next = l2_next 21 | l2.next = l1 22 | pre.next = l2 23 | pre = l1 24 | return node.next 25 | 26 | 27 | if __name__ == '__main__': 28 | head1 = build_l([1, 2, 3, 4, 5]) 29 | print_l(swap_pairs(head1)) 30 | -------------------------------------------------------------------------------- /Linklist/从有序链表中删除重复节点.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 4 | 5 | 示例: 6 | 输入: 1->1->2->3->3 7 | 输出: 1->2->3 8 | 9 | 思路: 10 | 递归大法 11 | 先对后n-1个节点进行去重,得头节点temp,再比较第一个节点和temp, 12 | 若值相同,直接返回temp; 13 | 若值不同,则把temp接在第一个节点后面,返回第一个节点 14 | """ 15 | from Linklist.utils import * 16 | 17 | 18 | def delete_duplicates(head): 19 | if head is None or head.next is None: 20 | return head 21 | head.next = delete_duplicates(head.next) 22 | if head.val == head.next.val: 23 | return head.next 24 | else: 25 | return head 26 | 27 | 28 | if __name__ == '__main__': 29 | head1 = build_l([1, 1, 1, 2, 2, 3, 10, 12]) 30 | print_l(delete_duplicates(head1)) 31 | -------------------------------------------------------------------------------- /Linklist/删除链表的倒数第 n 个节点.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定一个链表,删除链表的倒数第 k 个节点,并且返回链表的头结点。 4 | 5 | 示例: 6 | 给定一个链表: 1->2->3->4->5, 和 k = 2. 7 | 当删除了倒数第二个节点后,链表变为 1->2->3->5. 8 | 9 | 思路: 10 | 第一种:遍历一次,获得链表长度N,则可知 倒数第k个节点 == 顺数第N-k+1个节点,再次遍历,删除即可。 (一共需要两次遍历) 11 | 第二种:使用快慢指针遍历,让快指针先走k步,然后快慢指针一起移动,当快指针遍历到末尾,慢指针正好在倒数第k个节点上,删除即可 12 | """ 13 | 14 | from Linklist.utils import * 15 | 16 | 17 | def remove_k_th_from_end(head, k): 18 | fast = head 19 | while k > 0: 20 | fast = fast.next 21 | k -= 1 22 | if fast is None: 23 | return head.next 24 | slow = head 25 | while fast.next is not None: 26 | fast = fast.next 27 | slow = slow.next 28 | slow.next = slow.next.next 29 | return head 30 | 31 | 32 | if __name__ == '__main__': 33 | head1 = build_l([1, 2, 3, 4, 5]) 34 | print_l(remove_k_th_from_end(head1, 2)) 35 | -------------------------------------------------------------------------------- /Linklist/回文链表.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 判断一个链表是否为回文链表 4 | 5 | 示例: 6 | 1->2 False 7 | 1->2->2->1 True 8 | 9 | 思路: 10 | step1:通过快慢指针找到链表中间节点 11 | step2:将链表切分成两个子链表 12 | step3:将第二个链表逆序 13 | step4:逐个判断链表元素是否相同 14 | 15 | 注意:要考虑链表节点个数的奇偶性 16 | """ 17 | from Linklist.utils import * 18 | 19 | 20 | def is_palindrome(head): 21 | if head is None or head.next is None: 22 | return True 23 | slow = head 24 | fast = slow.next 25 | while fast is not None and fast.next is not None: 26 | slow = slow.next 27 | fast = fast.next.next 28 | if fast is not None: # 链表共偶数个节点,让 slow 指向下一个节点 29 | slow = slow.next 30 | cut(head, slow) 31 | return is_equal(head, reverse_list(slow)) 32 | 33 | 34 | def reverse_list(head): 35 | new_head = ListNode(-1) # 头结点 36 | while head: 37 | next = head.next 38 | head.next = new_head.next 39 | new_head.next = head 40 | head = next 41 | return new_head.next 42 | 43 | 44 | def cut(head, cut_node): 45 | while head.next != cut_node: 46 | head = head.next 47 | head.next = None 48 | 49 | 50 | def is_equal(head1, head2): 51 | while head1 is not None and head2 is not None: 52 | if head1.val != head2.val: 53 | return False 54 | head1 = head1.next 55 | head2 = head2.next 56 | return True 57 | 58 | 59 | if __name__ == '__main__': 60 | print("----------test 1-------------") 61 | l1 = build_l([1, 2, 3, 2, 1]) 62 | print_l(l1, p_type="SINGLE") 63 | print(is_palindrome(l1)) 64 | print("----------test 2-------------") 65 | l2 = build_l([1, 2, 3]) 66 | print_l(l2, p_type="SINGLE") 67 | print(is_palindrome(l2)) 68 | -------------------------------------------------------------------------------- /Linklist/奇偶链表.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。 4 | 请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。 5 | 请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。 6 | 7 | 示例: 8 | 输入: 2->1->3->5->6->4->7->NULL 9 | 输出: 2->3->6->7->1->5->4->NULL 10 | 11 | 思路: 12 | 将奇数位置的节点连接起来,将偶数位置的节点连接起来,再拼接起来即可 13 | """ 14 | from Linklist.utils import * 15 | 16 | 17 | def odd_even_list(head): 18 | if head is None: 19 | return head 20 | odd = head 21 | even = head.next 22 | even_head = even 23 | while even is not None and even.next is not None: 24 | odd.next = odd.next.next 25 | odd = odd.next 26 | even.next = even.next.next 27 | even = even.next 28 | odd.next = even_head 29 | return head 30 | 31 | 32 | if __name__ == '__main__': 33 | head1 = build_l([2, 1, 3, 5, 6, 4, 7]) 34 | print_l(odd_even_list(head1)) 35 | -------------------------------------------------------------------------------- /Linklist/找出两个链表的交点.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 编写一个程序,找到两个单链表相交的起始节点。 4 | 5 | 示例: 6 | A: a1 → a2 7 | ↘ 8 | c1 → c2 → c3 9 | ↗ 10 | B: b1 → b2 → b3 11 | A和B相较于c1节点 12 | 13 | 思路: 14 | 设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 15 | 当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B; 16 | 同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。 17 | 这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。 18 | 19 | 图解: 20 | a1 -> a2 -> c1 -> c2 -> c3 -> b1 -> b2 -> b3 -> c1 -> c2 -> c3 访问A的指针,先访问A,再访问B 21 | b1 -> b2 -> b3 -> c1 -> c2 -> c3 -> a1 -> a2 -> c1 -> c2 -> c3 访问B的指针,先访问B,再访问A 22 | 同时到达交点c1 23 | 24 | """ 25 | 26 | 27 | def get_intersection_node(head1, head2): 28 | cur1, cur2 = head1, head2 29 | while cur1 != cur2: 30 | cur1 = head2 if cur1 is None else cur1.next 31 | cur2 = head1 if cur2 is None else cur2.next 32 | return cur1 33 | -------------------------------------------------------------------------------- /Linklist/把链表分隔成 k 部分.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 把链表分隔成 k 部分,每部分的长度都应该尽可能相同,排在前面的长度应该大于等于后面的。 4 | 5 | 示例: 6 | Input: 7 | root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3 8 | Output: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] 9 | """ 10 | 11 | from Linklist.utils import * 12 | 13 | 14 | def split_list_to_parts(root, k): 15 | cur = root 16 | count = 0 # 统计节点个数 17 | while cur is not None: 18 | count += 1 19 | cur = cur.next 20 | mod = count % k 21 | size = int(count / k) 22 | cut_way = [size] * k # 划分成k个部分,cut_way中记录每部分的节点个数 23 | for i in range(mod): 24 | cut_way[i] += 1 25 | 26 | result = [None] * k 27 | cur = root 28 | i = 0 29 | # 按cut_way中每部分的节点个数将链表分成k断,存于result中 30 | while cur is not None and i < k: 31 | result[i] = cur 32 | for j in range(cut_way[i] - 1): 33 | cur = cur.next 34 | next1 = cur.next 35 | cur.next = None 36 | cur = next1 37 | i += 1 38 | return result 39 | 40 | 41 | if __name__ == '__main__': 42 | head = build_l([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 43 | for l in split_list_to_parts(head, 3): 44 | print_l(l) 45 | -------------------------------------------------------------------------------- /Linklist/逆序链表.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 将一个链表进行逆序 4 | 如: 1->2->3 逆序得到 3->2->1 5 | 6 | 二、思路 7 | * 递归大法 8 | 可以将链表任意一个链表看成由 一个节点和一串节点 组成的,将其进行交换即可。 9 | 如: 1 - > 2 - > 3 -> 4 2 - > 3 -> 4 -> 1 10 | --- -------------- 交换 ------------ --- 11 | 一个 一串 一串 一个 12 | 对上面的一串再进行同样的递归操作,最后得到的便是逆序的 13 | 14 | * 头插法 15 | 新建一个头结点,遍历单链表,将每个结点插入到该头结点后面,即可实现逆序 16 | """ 17 | from Linklist.utils import * 18 | 19 | 20 | # 递归实现 21 | def reverse_list(head): 22 | if head is None or head.next is None: 23 | return head 24 | right = head.next 25 | new_head = reverse_list(right) 26 | right.next = head 27 | head.next = None 28 | return new_head 29 | 30 | 31 | # 头插法逆序 32 | def reverse_list(head): 33 | new_head = ListNode(-1) # 头结点 34 | while head: 35 | next = head.next 36 | head.next = new_head.next 37 | new_head.next = head 38 | head = next 39 | return new_head.next 40 | 41 | 42 | if __name__ == '__main__': 43 | head1 = build_l([1, 2, 3, 4]) 44 | print_l(reverse_list(head1)) 45 | -------------------------------------------------------------------------------- /Math/pow(x,n).py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 实现 pow(x, n) ,即计算 x 的 n 次幂函数。 4 | 5 | 示例 1: 6 | 输入: 2.00000, 10 7 | 输出: 1024.00000 8 | 9 | 示例 2: 10 | 输入: 2.10000, 3 11 | 输出: 9.26100 12 | 13 | 示例 3: 14 | 输入: 2.00000, -2 15 | 输出: 0.25000 16 | 17 | 思路: 18 | 当n是负数的时候, 可以计算 1 / pow(x,abs(n)). 19 | 所以只需要计算x的正整数次方。 20 | 计算方法有3 种: 21 | 第一种:暴力解决, 将 x 连乘 n 次; 22 | 第二种:递归解决, 23 | pow(x,n) = pow(x, n>>1) * pow(x,n>>1) n为偶数 24 | pow(x,n) = pow(x, n>>1) * pow(x,n>>1) * a n为奇数 25 | 这样可以减少计算复杂度 26 | 第三种:利用二进制计算,将n表示成二进制的数,再展开。 27 | 例如 计算pow(x = 3,n = 5). 28 | 5 的 二进制是 101, 则计算结果 = pow(3,1) * pow(3.4). 29 | 总结来说,将 pow(x,n) 拆分成 pow(x,pow(2, i))的连乘结果, i = 0,1,2,3,... 30 | 31 | """ 32 | 33 | 34 | class Solution: 35 | def my_pow(self, x, n): 36 | """ 37 | :type x: float 38 | :type n: int 39 | :rtype: float 40 | """ 41 | if n == 0: 42 | return 1 43 | elif n > 0: 44 | return self.pow_pos(x, n) 45 | else: 46 | return 1.0 / self.pow_pos(x, abs(n)) 47 | 48 | def pow_pos(self, x, n): 49 | """ 50 | n: 是正整数 51 | """ 52 | num = x 53 | result = 1 54 | while n > 0: 55 | if n & 1 == 1: 56 | result *= num 57 | num = num * num # 由 pow(x, k) 直接计算 pow(x,2k) 58 | n = n >> 1 59 | return result 60 | 61 | 62 | if __name__ == '__main__': 63 | so = Solution() 64 | print(so.my_pow(2, 5)) 65 | -------------------------------------------------------------------------------- /Math/判断一个数是否为两个数的平方和.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 判断一个数是否是两个数的平方和 4 | 5 | 示例: 6 | Input: 5 7 | Output: True 8 | Explanation: 1 * 1 + 2 * 2 = 5 9 | 10 | 思路: 双指针 11 | 本题是要判断一个数是否等于两个数的平方和 <=等价=> 在自然数数组中,是否存在两个数的平方和是否等于一个数。 12 | 所以本题的思路与 “/数组/Two sum - 有序数组”基本一致。 13 | 14 | """ 15 | 16 | 17 | def judge_square_sum(c): 18 | start = 0 19 | end = int(c ** 0.5) 20 | while start <= end: 21 | temp = start ** 2 + end ** 2 22 | if temp == c: 23 | return True 24 | elif temp < c: 25 | start += 1 26 | else: 27 | end -= 1 28 | return False 29 | 30 | 31 | if __name__ == '__main__': 32 | print(judge_square_sum(3)) 33 | -------------------------------------------------------------------------------- /Math/加法_二进制.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 给定两个二进制字符串,返回他们的和(用二进制表示) 4 | 5 | 示例 1: 6 | 输入: a = "11", b = "1" 7 | 输出: "100" 8 | 9 | 示例 2: 10 | 输入: a = "1010", b = "1011" 11 | 输出: "10101" 12 | 13 | 思路:同其他几种"加法" 14 | 直接按照二进制数的加法运算规则计算即可。从右到左,逐位相加,注意进位。 15 | 下面的循环的结构是任何加法运算(二进制、字符串、链表)的通用结构,值得仔细品味 16 | """ 17 | 18 | 19 | def add_binary(str_a, str_b): 20 | end_a = len(str_a) - 1 21 | end_b = len(str_b) - 1 22 | carry = 0 23 | result = [] 24 | while carry == 1 or end_a >= 0 or end_b >= 0: 25 | if end_a >= 0: 26 | carry += (ord(str_a[end_a]) - ord('0')) 27 | end_a -= 1 28 | if end_b >= 0: 29 | carry += (ord(str_b[end_b]) - ord('0')) 30 | end_b -= 1 31 | # 充分利用二进制数的特点,获得求和后的结果,还有进位 32 | result.append(str(carry % 2)) 33 | carry = carry >> 1 34 | return "".join(result[::-1]) 35 | 36 | 37 | if __name__ == '__main__': 38 | print(add_binary("11111", "100000")) 39 | -------------------------------------------------------------------------------- /Math/加法_字符串.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目:大数相加 3 | 给定两个字符串形式的非负整数 str_a 和 str_b ,计算它们的和。 4 | !!!注意: 5 | str_a 和 str_b 的长度都小于 5100 6 | str_a 和 str_b 都只包含数字 0-9 7 | str_a 和 str_b 都不包含任何前导零 8 | 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式。 9 | 10 | 示例 1: 11 | 输入: a = "979", b = "1211" 12 | 输出: "2190" 13 | 14 | 思路: 15 | 思路 同其他几种"加法" 16 | 将两个数按最右端对齐,逐位相加。 17 | 下面的循环的结构是任何加法运算(二进制、字符串、链表)的通用结构,值得仔细品味 18 | """ 19 | 20 | 21 | def add_string(str_a, str_b): 22 | end_a = len(str_a) - 1 23 | end_b = len(str_b) - 1 24 | carry = 0 25 | result = [] 26 | while carry > 0 or end_a >= 0 or end_b >= 0: 27 | if end_a >= 0: 28 | carry += (ord(str_a[end_a]) - ord('0')) 29 | end_a -= 1 30 | if end_b >= 0: 31 | carry += (ord(str_b[end_b]) - ord('0')) 32 | end_b -= 1 33 | result.append(str(carry % 10)) 34 | carry = int(carry / 10) 35 | return "".join(result[::-1]) 36 | 37 | 38 | if __name__ == '__main__': 39 | print(add_string("979", "1211")) 40 | -------------------------------------------------------------------------------- /Math/加法_逆序链表.py: -------------------------------------------------------------------------------- 1 | """ 2 | 链表相加的题目一共两种: 3 | 1、数字用链表逆序存储,例如 123 用链表表示是 3 -> 2 -> 1 4 | 2、数字用链表顺序存储,例如 123 用链表表示是 1 -> 2 -> 3 5 | 6 | 以链表为基础做加法运算 7 | """ 8 | 9 | """ 10 | 一、题目 11 | 数字用链表逆序存储,计算任意给定的两个非负整数。 12 | 如:输入 2 -> 4 -> 3, 5 -> 6 -> 4, 得到 7 -> 0 -> 8. 13 | 342 + 465 = 807 14 | 15 | 二、思路 同其他几种"加法" 16 | 题目的本质是加法运算,注意加法的进位情况,和链表的一些特性即可 17 | 加法运算都是从个位数开始加的,注意进位 18 | 逆序表示的数据,正好可以从头向后依次相加 19 | 20 | 下面的循环的结构是任何加法运算(二进制、字符串、链表)的通用结构,值得仔细品味 21 | """ 22 | 23 | from Linklist.utils import * 24 | 25 | 26 | def add(head1, head2): 27 | cur1 = head1 28 | cur2 = head2 29 | carry = 0 30 | new_head = ListNode(-1) 31 | new_cur = new_head 32 | while carry > 0 or cur1 is not None or cur2 is not None: 33 | if cur1 is not None: 34 | carry += cur1.val 35 | cur1 = cur1.next 36 | if cur2 is not None: 37 | carry += cur2.val 38 | cur2 = cur2.next 39 | # 将求和结果利用“尾插法”,得到逆序链表 40 | new_cur.next = ListNode(carry % 10) 41 | new_cur = new_cur.next 42 | carry = int(carry / 10) 43 | return new_head.next 44 | 45 | 46 | if __name__ == '__main__': 47 | l1 = build_l([9, 9, 9]) 48 | l2 = build_l([1]) 49 | print_l(head=add(l1, l2), p_type="SINGLE") 50 | -------------------------------------------------------------------------------- /Math/加法_顺序链表.py: -------------------------------------------------------------------------------- 1 | """ 2 | 链表相加的题目一共两种: 3 | 1、数字用链表逆序存储,例如 123 用链表表示是 3 -> 2 -> 1 4 | 2、数字用链表顺序存储,例如 123 用链表表示是 1 -> 2 -> 3 5 | 6 | 以链表为基础做加法运算 7 | """ 8 | # ---------------------- 建议先看链表相加-逆序篇-------------------- 9 | """ 10 | 一、题目 11 | 数字用链表顺序存储,计算任意给定的两个非负整数。 12 | 如:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4) 得到: 7 -> 8 -> 0 -> 7 13 | 14 | 二、思路 同其他几种"加法" 15 | 题目的本质是加法运算,注意加法的进位情况,和链表的一些特性即可 16 | 加法运算都是从个位数开始加的,注意进位 17 | 注意,将顺序链表进行逆序操作,可以得到逆序表示,这样就可以从左到右进行逐位计算 18 | 19 | 下面的循环的结构是任何加法运算(二进制、字符串、链表)的通用结构,值得仔细品味 20 | """ 21 | 22 | from Linklist.utils import * 23 | 24 | 25 | def reverse_list(head): 26 | new_head = ListNode(-1) # 头结点 27 | while head: 28 | next = head.next 29 | head.next = new_head.next 30 | new_head.next = head 31 | head = next 32 | return new_head.next 33 | 34 | 35 | def add(head1, head2): 36 | # 先将链表逆序 37 | head1 = reverse_list(head1) 38 | head2 = reverse_list(head2) 39 | # 以下操作同逆序链表相加 40 | cur1 = head1 41 | cur2 = head2 42 | carry = 0 43 | new_head = ListNode(-1) 44 | while carry > 0 or cur1 is not None or cur2 is not None: 45 | if cur1 is not None: 46 | carry += cur1.val 47 | cur1 = cur1.next 48 | if cur2 is not None: 49 | carry += cur2.val 50 | cur2 = cur2.next 51 | # 将求和结果利用“头插法”,得到顺序链表 52 | cur = ListNode(carry % 10) 53 | cur.next = new_head.next 54 | new_head.next = cur 55 | carry = int(carry / 10) 56 | return new_head.next 57 | 58 | 59 | if __name__ == '__main__': 60 | l1 = build_l([8, 9, 9]) 61 | l2 = build_l([9, 0, 1]) 62 | print_l(add(l1, l2)) 63 | -------------------------------------------------------------------------------- /Math/计算n以内的所有素数.py: -------------------------------------------------------------------------------- 1 | def calPrimes(n): 2 | notPrimes = [False] * (n + 1) 3 | count = 0 4 | for i in range(2, n): 5 | if notPrimes[i]: 6 | continue 7 | count += 1 8 | for j in range(i*i, n, i): 9 | notPrimes[j] = True 10 | return count 11 | 12 | # public int countPrimes(int n) { 13 | # boolean[] notPrimes = new boolean[n + 1]; 14 | # int count = 0; 15 | # for (int i = 2; i < n; i++) { 16 | # if (notPrimes[i]) { 17 | # continue; 18 | # } 19 | # count++; 20 | # // 从 i * i 开始,因为如果 k < i,那么 k * i 在之前就已经被去除过了 21 | # for (long j = (long) (i) * i; j < n; j += i) { 22 | # notPrimes[(int) j] = true; 23 | # } 24 | # } 25 | # return count; 26 | # } 27 | 28 | if __name__ == '__main__': 29 | print(calPrimes(10)) 30 | -------------------------------------------------------------------------------- /Nowcoder/1.n个数里最小的K个.py: -------------------------------------------------------------------------------- 1 | # 快速排序 2 | def quick_sort(alist, start, end, k): 3 | if start >= end: 4 | return 5 | pivot = alist[start] # 基准值 6 | low = start 7 | high = end 8 | while low < high: 9 | while low < high and alist[high] >= pivot: 10 | high -= 1 11 | alist[low] = alist[high] 12 | while low < high and alist[low] < pivot: 13 | low += 1 14 | alist[high] = alist[low] 15 | alist[low] = pivot 16 | if low == k - 1: 17 | return 18 | elif low < k - 1: 19 | quick_sort(alist, low + 1, end, k) 20 | else: 21 | quick_sort(alist, start, low - 1, k) 22 | 23 | 24 | datas = input().split() 25 | k = int(datas[-1]) 26 | nums = [int(i) for i in datas[0:-1]] 27 | quick_sort(nums, 0, len(nums) - 1, k) 28 | result = [str(i) for i in sorted(nums[0:k])] 29 | print(" ".join(result)) 30 | -------------------------------------------------------------------------------- /Nowcoder/1.n个数里最小的K个_题目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Nowcoder/1.n个数里最小的K个_题目.png -------------------------------------------------------------------------------- /Nowcoder/2.字符串中找出连续最长的数字串.py: -------------------------------------------------------------------------------- 1 | try: 2 | while True: 3 | string = input() 4 | nums = [] 5 | num = "" 6 | for s in string: 7 | if s>='0' and s<='9': 8 | num += s 9 | else: 10 | if num not in nums: 11 | nums.append(num) 12 | num = "" 13 | nums.append(num) 14 | mv = 0 15 | ms = "" 16 | for n in nums: 17 | if mv < len(n): 18 | mv = len(n) 19 | ms = n 20 | print(ms) 21 | except EOFError: 22 | pass -------------------------------------------------------------------------------- /Nowcoder/2.字符串中找出连续最长的数字串_题目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Nowcoder/2.字符串中找出连续最长的数字串_题目.png -------------------------------------------------------------------------------- /Nowcoder/3.204-计算质数.py: -------------------------------------------------------------------------------- 1 | """ 2 | 统计所有小于非负整数 n 的质数的数量。" 3 | """ 4 | class Solution(object): 5 | 6 | def countPrimes(self, n): 7 | notPrimes = [] 8 | for k in range(n + 1): 9 | notPrimes.append(False) 10 | count = 0 11 | for i in range(2, n): 12 | if notPrimes[i]: 13 | continue 14 | count += 1 15 | for j in range(i * i, n, i): 16 | notPrimes[j] = True 17 | return count 18 | 19 | 20 | if __name__ == '__main__': 21 | solution = Solution() 22 | print(solution.countPrimes(10000)) -------------------------------------------------------------------------------- /Nowcoder/4.黑暗字符串.py: -------------------------------------------------------------------------------- 1 | # f(n) = 2*f(n-1) + f(n-2) ---> f(n)表示长度为n的字符串的黑暗字符串的个数 2 | # 求f(n)时,可以分类考虑长度为n-1的最后两个字符相同和不相同,再讨论 3 | # https://www.nowcoder.com/practice/7e7ccd30004347e89490fefeb2190ad2?tpId=85&tqId=29853&tPage=2&rp=2&ru=/ta/2017test&qru=/ta/2017test/question-ranking 4 | n = int(input()) 5 | dp = [0 for i in range(31)] 6 | dp[1] = 3 7 | dp[2] = 9 8 | for i in range(3, n+1): 9 | dp[i] = 2*dp[i-1]+dp[i-2] 10 | print(dp[n]) -------------------------------------------------------------------------------- /Nowcoder/4.黑暗字符串_题目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Nowcoder/4.黑暗字符串_题目.png -------------------------------------------------------------------------------- /Nowcoder/5.回文序列.py: -------------------------------------------------------------------------------- 1 | size = int(input()) 2 | datas = input().split() 3 | nums = [int(d) for d in datas] 4 | count = 0 5 | start = 0 6 | end = size - 1 7 | while start < end: 8 | if nums[start] == nums[end]: 9 | start += 1 10 | end -= 1 11 | else: 12 | if nums[start] < nums[end]: 13 | temp = nums[start] + nums[start + 1] 14 | if temp == nums[end]: 15 | nums[start + 1] = temp 16 | count += 1 17 | start += 2 18 | end -= 1 19 | 20 | else: 21 | nums[start + 1] = temp 22 | count += 1 23 | start += 1 24 | else: 25 | temp = nums[end] + nums[end - 1] 26 | if temp == nums[start]: 27 | nums[end - 1] = temp 28 | end -= 2 29 | start += 1 30 | count += 1 31 | else: 32 | nums[end - 1] = temp 33 | count += 1 34 | end -= 1 35 | print(count) -------------------------------------------------------------------------------- /Nowcoder/5.回文序列_题目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Nowcoder/5.回文序列_题目.png -------------------------------------------------------------------------------- /Permutation & Combination/全排列.py: -------------------------------------------------------------------------------- 1 | """ 2 | 无重复 字符串的全排列 递归 3 | array=[1,2,3,4],start是排列的起始位 4 | """ 5 | 6 | 7 | def permutation(nums, start=0): 8 | size = len(nums) 9 | if start == size - 1: 10 | print(nums) 11 | return 12 | for i in range(start, size): 13 | nums[i], nums[start] = nums[start], nums[i] 14 | permutation(nums, start + 1) 15 | nums[i], nums[start] = nums[start], nums[i] 16 | 17 | 18 | # ---------------------- important -------------------------- 19 | """ 20 | 有重复 字符串的全排列 递归 21 | array=[1,3,3,4],start是排列的起始位 22 | """ 23 | 24 | 25 | def permutation(nums, start=0): 26 | size = len(nums) 27 | if start == size - 1: 28 | print(nums) 29 | return 30 | dup = set() 31 | for i in range(start, size): 32 | if nums[i] in dup: 33 | continue 34 | dup.add(nums[i]) 35 | nums[i], nums[start] = nums[start], nums[i] 36 | permutation(nums, start + 1) 37 | nums[i], nums[start] = nums[start], nums[i] 38 | 39 | 40 | """ *******全排列的非递归算法***************** 41 | * 思路: 42 | 起点:字典序最小的排列,例如 12345 43 | 终点:字典序最大的排序,例如 54321 44 | 过程:从当前排列生成字典序刚好比它大的下一个排列 45 | * 如何求字典序中一个排序的下一个排列? 46 | * 后找:字符串中最后一个升序的位置i, 即: 47 | string[k]>string[k+1](k>i),string[i]= 0 and nums[i] >= nums[i + 1]: 60 | i -= 1 61 | if i < 0: 62 | return False 63 | 64 | # 小大 65 | j = size - 1 66 | while nums[j] <= nums[i]: 67 | j -= 1 68 | 69 | # 交换 70 | nums[j], nums[i] = nums[i], nums[j] 71 | 72 | # 翻转 73 | reverse(nums, i + 1, size - 1) 74 | return True 75 | 76 | 77 | def reverse(nums, start, end): 78 | while start < end: 79 | nums[start], nums[end] = nums[end], nums[start] 80 | start += 1 81 | end -= 1 82 | 83 | 84 | def permutation(nums): 85 | print(nums) 86 | while get_next_permutation(nums): 87 | print(nums) 88 | 89 | 90 | # ---------------------- important -------------------------- 91 | 92 | array = list("1234") 93 | permutation(array) 94 | -------------------------------------------------------------------------------- /Permutation & Combination/特征组合.py: -------------------------------------------------------------------------------- 1 | """ 2 | 题目: 3 | 有M个数组,每个数组中若干个不同字符。从每个数组中任意取一个字符,拼接成一个字符串,求所有字符串 4 | 5 | 示例: 6 | input: [['1', '2'], ['a', 'b', 'c'], ['A', "B", "C"]] 7 | output: ['1aA', '1aB', '1aC', '1bA', '1bB', '1bC', '1cA', '1cB', '1cC', 8 | '2aA', '2aB', '2aC', '2bA', '2bB', '2bC', '2cA', '2cB', '2cC'] 9 | 10 | 思路: 11 | 要解决本题,采用递归的思路即可。 12 | 每一步递归就是将两个数组进行做笛卡尔积 13 | 14 | 扩展: 15 | 本题的思想可以用在特征组合上。假如做一个机器学习的项目,现在一共有M个不同的特征,而每个特征又有若干个不同取值, 16 | 求出所有的取值方式 17 | 18 | """ 19 | 20 | 21 | def combine(all_nums): 22 | if len(all_nums) == 0: 23 | return [""] 24 | new_result = [] 25 | result = combine(all_nums[1:]) # 先将后面len(all_nums)-1个数组进行组合 26 | for n in all_nums[0]: # 再将 第一个数组 和 组合后结果 进行组合 27 | for r in result: 28 | new_result.append(n + r) 29 | return new_result 30 | 31 | 32 | if __name__ == '__main__': 33 | all_nums = [['1', '2'], ['a', 'b', 'c'], ['A', "B", "C"]] 34 | result1 = combine(all_nums) 35 | from pprint import pprint 36 | 37 | pprint(result1) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BAT-algorithms 2 | 用Python语言实现数据结构中各类经典的算法,面试笔试宝典 3 | 4 | # 目录结构 5 | * [树](https://github.com/whtlkeep/BAT-algorithms/tree/master/Tree) 6 | * [字典树](https://github.com/whtlkeep/BAT-algorithms/blob/master/Tree/%E5%AD%97%E5%85%B8%E6%A0%91-%E5%89%8D%E7%BC%80%E6%A0%91.py) 7 | * [遍历-层次遍历](https://github.com/whtlkeep/BAT-algorithms/blob/master/Tree/%E9%81%8D%E5%8E%86-%E5%B1%82%E6%AC%A1%E9%81%8D%E5%8E%86.py) 8 | * [遍历-中序遍历-非递归](https://github.com/whtlkeep/BAT-algorithms/blob/master/Tree/%E9%81%8D%E5%8E%86-%E4%B8%AD%E5%BA%8F%E9%81%8D%E5%8E%86-%E9%9D%9E%E9%80%92%E5%BD%92.py) 9 | * [遍历-前序遍历-非递归](https://github.com/whtlkeep/BAT-algorithms/blob/master/Tree/%E9%81%8D%E5%8E%86-%E5%89%8D%E5%BA%8F%E9%81%8D%E5%8E%86-%E9%9D%9E%E9%80%92%E5%BD%92.py) 10 | * [遍历-后序遍历-非递归](https://github.com/whtlkeep/BAT-algorithms/blob/master/Tree/%E9%81%8D%E5%8E%86-%E5%90%8E%E5%BA%8F%E9%81%8D%E5%8E%86-%E9%9D%9E%E9%80%92%E5%BD%92.py) 11 | * [二叉查找树-两数之和](https://github.com/whtlkeep/BAT-algorithms/blob/master/Tree/%E4%BA%8C%E5%8F%89%E6%9F%A5%E6%89%BE%E6%A0%91-%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.py) 12 | * [二叉查找树-中第K小的元素](https://github.com/whtlkeep/BAT-algorithms/blob/master/Tree/%E4%BA%8C%E5%8F%89%E6%9F%A5%E6%89%BE%E6%A0%91-%E4%B8%AD%E7%AC%ACK%E5%B0%8F%E7%9A%84%E5%85%83%E7%B4%A0.py) 13 | * [二叉查找树-从有序数组中构造二叉查找树](https://github.com/whtlkeep/BAT-algorithms/blob/master/Tree/%E4%BA%8C%E5%8F%89%E6%9F%A5%E6%89%BE%E6%A0%91-%E4%BB%8E%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E4%B8%AD%E6%9E%84%E9%80%A0%E4%BA%8C%E5%8F%89%E6%9F%A5%E6%89%BE%E6%A0%91.py) 14 | * [二叉查找树-从有序链表构造平衡的二叉查找树](https://github.com/whtlkeep/BAT-algorithms/blob/master/Tree/%E4%BA%8C%E5%8F%89%E6%9F%A5%E6%89%BE%E6%A0%91-%E4%BB%8E%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8%E6%9E%84%E9%80%A0%E5%B9%B3%E8%A1%A1%E7%9A%84%E4%BA%8C%E5%8F%89%E6%9F%A5%E6%89%BE%E6%A0%91.py) 15 | * [二叉树-的最大深度](https://github.com/whtlkeep/BAT-algorithms/blob/master/Tree/%E4%BA%8C%E5%8F%89%E6%A0%91-%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.py) 16 | * [数组&字符串](https://github.com/whtlkeep/BAT-algorithms/tree/master/Array%20%26%20String) 17 | * [查找排序](https://github.com/whtlkeep/BAT-algorithms/tree/master/Search%20%26%20Sort) 18 | * [排列组合](https://github.com/whtlkeep/BAT-algorithms/tree/master/Permutation%20%26%20Combination) 19 | * [动态规划](https://github.com/whtlkeep/BAT-algorithms/tree/master/Dynamic%20Programming) 20 | * [树](https://github.com/whtlkeep/BAT-algorithms/tree/master/Tree) 21 | * [链表](https://github.com/whtlkeep/BAT-algorithms/tree/master/Linklist) 22 | * [数学](https://github.com/whtlkeep/BAT-algorithms/tree/master/Math) 23 | * [位运算](https://github.com/whtlkeep/BAT-algorithms/tree/master/Bit%20operation) 24 | * [编程之美](https://github.com/whtlkeep/BAT-algorithms/tree/master/The%20beauty%20of%20programming) 25 | 26 | 27 | 数据结构资源链接:https://pan.baidu.com/s/1PNOUUGm4sYCRmasttrm8SA 密码:f8l7 28 | 29 | 本项目主要来源: 小象学院数据结构视频和牛客网真题,本人用Python实现。 30 | -------------------------------------------------------------------------------- /Search & Sort/1-冒泡.py: -------------------------------------------------------------------------------- 1 | """ 2 | ------------------------------- 冒泡排序 -------------------------------------- 3 | 一、介绍 4 | 冒泡排序(英语:Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素, 5 | 如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数 6 | 列已经排序完成。 7 | 8 | 二、步骤 9 | 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。 10 | 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。 11 | 针对所有的元素重复以上的步骤,除了最后一个。 12 | 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 13 | 14 | """ 15 | 16 | 17 | def bubble_sort(nums): 18 | for j in range(len(nums) - 1, 0, -1): # j的取值是[len(alist)-1,len(alist)-2,.....,1] 19 | # j 表示每次遍历需要比较的次数,是逐渐减小的 20 | for i in range(j): 21 | if nums[i] > nums[i + 1]: 22 | nums[i], nums[i + 1] = nums[i + 1], nums[i] 23 | 24 | 25 | if __name__ == '__main__': 26 | nums = [54, 26, 93, 17, 77, 31, 44, 55, 20] 27 | bubble_sort(nums) 28 | print(nums) 29 | -------------------------------------------------------------------------------- /Search & Sort/1-堆排序.py: -------------------------------------------------------------------------------- 1 | """ 2 | ------------------------------- 冒泡排序 -------------------------------------- 3 | 一、介绍 4 | ​堆排序是一种树形选择排序,是对直接选择排序的有效改进。 5 | 6 | 二、思想:初始时把要排序的数的序列看作是一棵顺序存储的二叉树,调整它们的存储序,使之成为一个 堆, 7 | 这时堆的根节点的数最大。然后将根节点与堆的最后一个节点交换。然后对前面(n-1)个数重新调整使之成为堆。 8 | 依此类推,直到只有两个节点的堆,并对 它们作交换,最后得到有n个节点的有序序列。从算法描述来看, 9 | 堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。 10 | 一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。 11 | 12 | 三、堆的知识点: 13 | ​堆的定义下:具有n个元素的序列 (h1,h2,...,hn),当且仅当满足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,...,n/2)时称之为堆。 14 | 在这里只讨论满足前者条件的堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最大项(大顶堆)。 15 | 完全二叉树可以很直观地表示堆的结构。堆顶为根,其它为左子树、右子树。 16 | """ 17 | 18 | 19 | # 调整堆,把最大值调整到堆顶 20 | def adjust_heap(nums, i, size): 21 | lchild = 2 * i + 1 22 | rchild = 2 * i + 2 23 | max = i 24 | if i < size / 2: 25 | if lchild < size and nums[lchild] > nums[max]: 26 | max = lchild 27 | if rchild < size and nums[rchild] > nums[max]: 28 | max = rchild 29 | if max != i: 30 | nums[max], nums[i] = nums[i], nums[max] 31 | adjust_heap(nums, max, size) 32 | 33 | 34 | # 创建堆 35 | def build_heap(lists, size): 36 | for i in range(0, (size >> 1))[::-1]: 37 | adjust_heap(lists, i, size) 38 | 39 | 40 | # 堆排序 41 | def heap_sort(lists): 42 | size = len(lists) 43 | build_heap(lists, size) 44 | for i in range(0, size)[::-1]: 45 | lists[0], lists[i] = lists[i], lists[0] 46 | adjust_heap(lists, 0, i) 47 | return lists 48 | 49 | 50 | if __name__ == '__main__': 51 | nums = [54, 26, 93, 17, 77, 31, 44, 55, 20] 52 | result = heap_sort(nums) 53 | print(result) 54 | -------------------------------------------------------------------------------- /Search & Sort/1-外排序.py: -------------------------------------------------------------------------------- 1 | """------------------------------- 外排序 -------------------------------------- 2 | 一、介绍 3 | 外排序(External sorting)是指处理超过内存限度的数据的排序算法。通常将中间结果 4 | 放在读写比较慢的外存储器(通常是硬盘上) 5 | 6 | 二、算法: 7 | 外排序常采用“排序-归并”策略。 8 | * 排序阶段,读入能放在内存中的数据量,将其排序输出到临时文件,依次进行,将 9 | 待排序数据组织为多个有序的临时文件; 10 | * 归并阶段,将这些临时文件组合为大的有序文件 11 | 12 | 三、举例: 13 | 使用100MB内存的计算机对900MB的数据进行排序: 14 | * 读入100M数据至内存,用常规方式(如堆排序)排序; 15 | * 将排序后的数据写入磁盘; 16 | * 重复前面两个步骤,得到9个100MB的块(临时文件)中; 17 | * 将100M内存划分为10份,前9份中为输入缓冲区,第10份为输出缓冲区 18 | - 如前9份各9M,第10份19M,; 或者10份大小同时为10M; 19 | * 执行九路归并算法,将结果输出到输出缓冲区: 20 | - 若输出缓冲区满,将数据写至目标文件,清空缓冲区; 21 | - 若输入缓冲区空,读入相应文件的下一份数据。" 22 | 23 | """ 24 | -------------------------------------------------------------------------------- /Search & Sort/1-希尔.py: -------------------------------------------------------------------------------- 1 | """ 2 | ------------------------------- 希尔排序 -------------------------------------- 3 | 一、介绍 4 | 希尔排序(Shell Sort)也是插入排序的一种。也称为缩小增量排序,是直接插入排序算法的一种更高效的改进版本。 5 | 希尔排序是非稳定排序算法。该方法因DL.Shell于1959年提出而得名。 6 | 7 | 二、步骤 8 | 将待排序列划分为若干组,在每一组内进行插入排序,以使整个序列基本有序,然后再对整个序列进行插入排。 9 | 10 | """ 11 | 12 | 13 | def shell_sort(nums): 14 | size = len(nums) 15 | gap = size >> 1 16 | while gap > 0: 17 | for i in range(gap, size): 18 | j = i 19 | while j >= gap and nums[j - gap] > nums[j]: 20 | nums[j - gap], nums[j] = nums[j], nums[j - gap] 21 | j -= gap 22 | gap = gap >> 1 23 | 24 | 25 | if __name__ == '__main__': 26 | nums = [54, 26, 93, 17, 77, 31, 44, 55, 20] 27 | shell_sort(nums) 28 | print(nums) 29 | -------------------------------------------------------------------------------- /Search & Sort/1-归并.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、介绍 3 | 基本思想: 4 | 将数组array[0,...,n-1]中的元素分成两个子数组:array1[0,...,n/2] 5 | 和array2[n/2+1,...,n-1]。分别对这两个数组单独排序,然后将已排序的 6 | 两个子数组归并成一个含有n个元素的有序数组 7 | 8 | 二、步骤 9 | 递归实现 10 | 11 | 三、时间复杂度:O(N*logN) 12 | 13 | 四、归并排序的两点改进 14 | * 在数组长度比较短的情况下,不进行递归,而是选择其他排序方案,如插入排序 15 | * 归并过程中,可以用记录数组下标的方式代替申请新内存空间;从而避免array和 16 | 辅助数组间的频繁数据移动 17 | """ 18 | 19 | 20 | def merge(left, right): # 合并两个有序数组 21 | l, r = 0, 0 22 | result = [] 23 | while l < len(left) and r < len(right): 24 | if left[l] <= right[r]: 25 | result.append(left[l]) 26 | l += 1 27 | else: 28 | result.append(right[r]) 29 | r += 1 30 | result += left[l:] 31 | result += right[r:] 32 | return result 33 | 34 | 35 | def merge_sort(nums): 36 | if len(nums) <= 1: 37 | return nums 38 | num = len(nums) >> 1 39 | left = merge_sort(nums[:num]) 40 | right = merge_sort(nums[num:]) 41 | return merge(left, right) 42 | 43 | 44 | # ------------------- 按第二个改进的修改---------------------------- 45 | 46 | temp = [0] * 100 47 | 48 | 49 | def Merge(nums, low, mid, high): 50 | i = low 51 | j = mid + 1 52 | size = 0 53 | while i <= mid and j <= high: 54 | if nums[i] < nums[j]: 55 | temp[size] = nums[i] 56 | i += 1 57 | else: 58 | temp[size] = nums[j] 59 | j += 1 60 | size += 1 61 | while i <= mid: 62 | temp[size] = nums[i] 63 | size += 1 64 | i += 1 65 | while j <= high: 66 | temp[size] = nums[j] 67 | size += 1 68 | j += 1 69 | for i in range(size): 70 | nums[low + i] = temp[i] 71 | 72 | 73 | def Merge_sort(nums, low, high): 74 | if low >= high: 75 | return 76 | mid = (low + high) >> 1 77 | Merge_sort(nums, low, mid) 78 | Merge_sort(nums, mid + 1, high) 79 | Merge(nums, low, mid, high) 80 | 81 | 82 | if __name__ == '__main__': 83 | nums = [54, 26, 93, 17, 77, 31, 44, 55, 20] 84 | Merge_sort(nums, 0, len(nums) - 1) 85 | print(nums) 86 | -------------------------------------------------------------------------------- /Search & Sort/1-快速.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、介绍 3 | 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小, 4 | 然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 5 | 6 | 二、步骤 7 | 递归实现 8 | """ 9 | 10 | 11 | def quick_sort(nums, start, end): 12 | if start >= end: 13 | return 14 | pivot = nums[start] # 基准值 15 | low = start # 左指针 16 | high = end # 右指针 17 | while low < high: 18 | while low < high and nums[high] >= pivot: 19 | high -= 1 20 | nums[low] = nums[high] 21 | 22 | while low < high and nums[low] < pivot: 23 | low += 1 24 | nums[high] = nums[low] 25 | nums[low] = pivot 26 | quick_sort(nums, start, low - 1) 27 | quick_sort(nums, low + 1, end) 28 | 29 | 30 | if __name__ == '__main__': 31 | nums = [54, 26, 93, 17, 77, 31, 44, 55, 20] 32 | quick_sort(nums, 0, len(nums) - 1) 33 | print(nums) 34 | -------------------------------------------------------------------------------- /Search & Sort/1-插入.py: -------------------------------------------------------------------------------- 1 | """ 2 | ------------------------------- 插入排序 -------------------------------------- 3 | 一、介绍 4 | 插入排序(英语:Insertion Sort)是一种简单直观的排序算法。 5 | 6 | 二、步骤 7 | 通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。 8 | 插入排序在实现上,在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 9 | """ 10 | 11 | 12 | def insert_sort(nums): 13 | for i in range(1, len(nums)): 14 | for j in range(i, 0, -1): 15 | if nums[j] < nums[j - 1]: 16 | nums[j], nums[j - 1] = nums[j - 1], nums[j] 17 | 18 | 19 | if __name__ == '__main__': 20 | nums = [54, 26, 93, 17, 77, 31, 44, 55, 20] 21 | insert_sort(alist) 22 | print(alist) 23 | -------------------------------------------------------------------------------- /Search & Sort/1-选择.py: -------------------------------------------------------------------------------- 1 | """ 2 | ------------------------------- 选择排序 -------------------------------------- 3 | 一、介绍 4 | 选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小(大)元素, 5 | 存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。 6 | 以此类推,直到所有元素均排序完毕。 7 | 8 | 二、步骤 9 | 选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素, 10 | 它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换 11 | 去移动元素的排序方法中,选择排序属于非常好的一种。 12 | """ 13 | 14 | 15 | def selection_sort1(nums): 16 | # 思路是将最小值逐一选择到前面 17 | n = len(nums) 18 | for i in range(n - 1): 19 | min_index = i # 记录最小值的位置 20 | for j in range(i + 1, n): 21 | if nums[j] < nums[min_index]: 22 | min_index = j 23 | if min_index != i: 24 | nums[i], nums[min_index] = nums[min_index], nums[i] 25 | 26 | 27 | def selection_sort2(nums): 28 | # 思路是将最大值逐一选择到后面 29 | n = len(nums) 30 | for i in range(n - 1, 0, -1): 31 | max_index = i # 记录最大值的位置 32 | for j in range(i): 33 | if nums[j] > nums[max_index]: 34 | max_index = j 35 | 36 | if max_index != i: 37 | nums[i], nums[max_index] = nums[max_index], nums[i] 38 | 39 | 40 | if __name__ == '__main__': 41 | nums = list(range(31,0,-1)) 42 | selection_sort2(nums) 43 | print(nums) 44 | -------------------------------------------------------------------------------- /Search & Sort/2-折半查找.py: -------------------------------------------------------------------------------- 1 | # 本方法是 a 在nums中,则返回对应的index 2 | def binary_find1(nums, a): 3 | size = len(nums) 4 | start = 0 5 | end = size - 1 6 | bFind = False 7 | middle = 0 8 | while start <= end: 9 | middle = (start + end) >> 1 10 | if nums[middle] == a: 11 | bFind = True 12 | break 13 | if nums[middle] > a: 14 | end = middle - 1 15 | else: 16 | start = middle + 1 17 | if bFind: 18 | return middle 19 | return -1 20 | 21 | 22 | # 本方法是通过二分查找找到k在arr中插入位置,并插入 23 | # 例如 arr = [1,2,3,4,5], k = 3.5, 则将k插入到4的位置,变成 arr = [1,2,3,3.5,5] 24 | def binary_search2(arr, k): 25 | start = 0 26 | end = len(arr) - 1 27 | while start <= end: 28 | mid = (start + end) >> 1 29 | if arr[mid] == k: 30 | arr[mid] = k 31 | return arr 32 | elif arr[mid] < k: 33 | start = mid + 1 34 | else: 35 | end = mid - 1 36 | if arr[mid] < k and arr[mid + 1] > k: 37 | arr[mid + 1] = k 38 | return arr 39 | if arr[mid] > k and arr[mid - 1] < k: 40 | arr[mid] = k 41 | return arr 42 | 43 | 44 | if __name__ == '__main__': 45 | array = [1, 2, 3, 4, 5, 6] 46 | a = 4 47 | print(binary_find1(array, 1)) 48 | -------------------------------------------------------------------------------- /Search & Sort/3-逆序数-图解.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/Search & Sort/3-逆序数-图解.png -------------------------------------------------------------------------------- /Search & Sort/3-逆序数.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、逆序数: 3 | 给定一个数组array[0,...,n-1], 若对于某两个元素array[i],array[j],若iarray[j], 4 | 则认为array[i],array[j]是逆序对。一个数组中包含的逆序对的数目称为该数组的逆序数。设计一个算法求一个数组的逆序数 5 | 二、利用 归并排序 的思想 6 | 在归并排序中,会将两个升序的数组进行合并,利用升序数组的特性,可以快速求得逆序数 7 | 见 图解 8 | """ 9 | 10 | temp = [0] * 100 11 | count = [0] 12 | pairs = [] 13 | 14 | 15 | def Merge(nums, low, mid, high): 16 | i = low 17 | j = mid + 1 18 | size = 0 19 | while i <= mid and j <= high: 20 | if nums[i] < nums[j]: 21 | temp[size] = nums[i] 22 | i += 1 23 | else: 24 | # 除了以下三行代码,其余代码与归并排序一模一样 25 | count[0] += (mid - i + 1) 26 | for h in range(i, mid + 1): 27 | pairs.append((nums[h], nums[j])) 28 | temp[size] = nums[j] 29 | j += 1 30 | size += 1 31 | while i <= mid: 32 | temp[size] = nums[i] 33 | size += 1 34 | i += 1 35 | while j <= high: 36 | temp[size] = nums[j] 37 | size += 1 38 | j += 1 39 | for i in range(size): 40 | nums[low + i] = temp[i] 41 | 42 | 43 | def Merge_sort(nums, low, high): 44 | if low >= high: 45 | return 46 | mid = (low + high) >> 1 47 | Merge_sort(nums, low, mid) 48 | Merge_sort(nums, mid + 1, high) 49 | Merge(nums, low, mid, high) 50 | 51 | 52 | if __name__ == '__main__': 53 | nums = [3, 56, 2, 7, 45, 8, 1] 54 | Merge_sort(nums, 0, len(nums) - 1) 55 | print(pairs) 56 | -------------------------------------------------------------------------------- /Search & Sort/4-查找和为定值的两个数.py: -------------------------------------------------------------------------------- 1 | """ 2 | 寻找和为定值的两个数 3 | 给定N个 不同 的数A[0,...,N-1]以及某定值sum,找到这个N个数中的两个数,使得他们的和为sum 4 | 5 | 方法一:时间复杂度 O(N*logN) 6 | 先将数组排序,然后用两个指针start,end 各自指向数组的首尾两端。 7 | 步骤: 8 | * if a[start] + a[end] > sum, then end-- 9 | * if a[start] + a[end] < sum, then start++ 10 | * if a[start] + a[end] == sum, then 若果只输出一个结果,则break,否则,start++,end-- 11 | 12 | 方法二:时间复杂度是O(N),空间复杂度是O(N) 13 | """ 14 | 15 | 16 | def two_sum1(nums, s): 17 | def quick_sort(nums, start, end): # 快速排序 18 | if start >= end: 19 | return 20 | pivot = nums[start] # 基准值 21 | low = start # 左指针 22 | high = end # 右指针 23 | while low < high: 24 | while low < high and nums[high] >= pivot: 25 | high -= 1 26 | nums[low] = nums[high] 27 | 28 | while low < high and nums[low] < pivot: 29 | low += 1 30 | nums[high] = nums[low] 31 | nums[low] = pivot 32 | quick_sort(nums, start, low - 1) 33 | quick_sort(nums, low + 1, end) 34 | 35 | quick_sort(nums, 0, len(nums) - 1) 36 | size = len(nums) 37 | start = 0 38 | end = size - 1 39 | result = [] 40 | while start < end: 41 | if nums[start] + nums[end] < s: 42 | start += 1 43 | elif nums[start] + nums[end] > s: 44 | end -= 1 45 | else: 46 | result.append([nums[start], nums[end]]) 47 | start += 1 48 | end -= 1 49 | return result 50 | 51 | 52 | def two_sum2(array, s): 53 | hashh = dict() 54 | for a in array: 55 | hashh[a] = None 56 | result = [] 57 | for a in array: 58 | if s - a in hashh and s - a != a: 59 | result.append([a, s - a]) 60 | del hashh[a] 61 | return result 62 | 63 | 64 | if __name__ == '__main__': 65 | array = [0, 3, 7, 9, 10, 11, 14, 16, 17] 66 | print(two_sum2(array, 3)) 67 | -------------------------------------------------------------------------------- /The beauty of programming/README.md: -------------------------------------------------------------------------------- 1 | 对《[编程之美](http://vdisk.weibo.com/s/dCbnAFkZVs1uo%20%20)》一书中的问题,贡献出Python代码 2 | 本书的编程问题都很有意思,直接去解答,难度不小。转化思路,柳暗花明,对逻辑思维的训练大有裨益! 3 | 4 | ## 书本目录: 5 | - 第1章 [游戏之乐——游戏中碰到的题目](https://github.com/whtlkeep/BAT-algorithms/tree/master/The%20beauty%20of%20programming/chapter-1) 6 | - 1 让CPU占用率曲线听你指挥 7 | - 2 中国象棋将帅问题 8 | - 3 一摞烙饼的排序 9 | - 4 买书问题 10 | - 5 快速找出故障机器 11 | - 6 饮料供货 12 | - 7 光影切割问题 13 | - 8 小飞的电梯调度算法 14 | - 9 高效率地安排见面会 15 | - 10 双线程高效下 16 | - 11 NIM(1)一排石头的游戏 17 | - 12 NIM(2)“拈”游戏分析 18 | - 13 NIM(3)两堆石头的游戏 19 | - 14 连连看游戏设计 20 | - 15 构造数独 21 | - 16 24点游戏 22 | - 17 俄罗斯方块游戏 23 | - 18 挖雷游戏 24 | - 第2章 [数字之魅——数字中的技巧](https://github.com/whtlkeep/BAT-algorithms/tree/master/The%20beauty%20of%20programming/chapter-2) 25 | - 1 求二进制数中1的个数 26 | - 2 不要被阶乘吓倒 27 | - 3 寻找发帖“水王” 28 | - 4 1的数目 29 | - 5 寻找最大的K个数 30 | - 6 精确表达浮点数 31 | - 7 最大公约数问题 32 | - 8 找符合条件的整数 33 | - 9 斐波那契(Fibonacci)数列 34 | - 10 寻找数组中的最大值和最小值 35 | - 11 寻找最近de点对 36 | - 12 快速寻找满足条件的两个数 37 | - 13 子数组的最大乘积 38 | - 14 求数组的子数组之和的最大值 39 | - 15 子数组之和的最大值(二维) 40 | - 16 求数组中最长递增子序列 41 | - 17 数组循环移位 42 | - 18 数组分割 43 | - 19 区间重合判断 44 | - 20 程序理解和时间分析 45 | - 21 只考加法的面试题 46 | - 第3章 [结构之法——字符串及链表的探索](https://github.com/whtlkeep/BAT-algorithms/tree/master/The%20beauty%20of%20programming/chapter-4) 47 | - 1 字符串移位包含的问题 48 | - 2 电话号码对应英语单词 49 | - 3 计算字符串的相似度 50 | - 4 从无头单链表中删除节点 51 | - 5 最短摘要的生成 52 | - 6 编程判断两个链表是否相交 53 | - 7 队列中取最大值操作问题 54 | - 8 求二叉树中节点的最大距离 55 | - 9 重建二叉树 56 | - 10 分层遍历二叉树 57 | - 11 程序改错 58 | - 第4章 [数学之趣——数学游戏的乐趣](https://github.com/whtlkeep/BAT-algorithms/tree/master/The%20beauty%20of%20programming/chapter-4) 59 | - 1 金刚坐飞机问题 60 | - 2 瓷砖覆盖地板 61 | - 3 买票找零 62 | - 4 点是否在三角形内 63 | - 5 磁带文件存放优化 64 | - 6 桶中取黑白球 65 | - 7 蚂蚁爬杆 66 | - 8 三角形测试用例 67 | - 9 数独知多少 68 | - 10 数字哑谜和回文 69 | - 11 挖雷游戏的概率 70 | -------------------------------------------------------------------------------- /The beauty of programming/chapter-2/1-求二进制数中1的个数.py: -------------------------------------------------------------------------------- 1 | def count_one_bit_2(x): 2 | """ 解法二 3 | 时间复杂度是0(v), v是二进制数的位数 4 | """ 5 | count = 0 6 | while x != 0: 7 | if x & 1 == 1: # 二进制数z的最右边的一位是1 8 | count += 1 9 | x = x >> 1 # 右移一位 10 | return count 11 | 12 | 13 | def count_one_bit_3(x): 14 | """ 解法三 15 | 时间复杂度是0(M), M是1的个数 16 | """ 17 | count = 0 18 | while x: 19 | x = x & (x-1) # 此操作可以将最右边的1置为0 20 | count += 1 21 | return count 22 | 23 | 24 | if __name__ == '__main__': 25 | x1 = 7 26 | print(count_one_bit_2(x1)) 27 | print(count_one_bit_3(x1)) 28 | -------------------------------------------------------------------------------- /The beauty of programming/chapter-3/3-计算字符串的相似度.py: -------------------------------------------------------------------------------- 1 | def calculate_string_distance(str_a, start_a, end_a, str_b, start_b, end_b): 2 | if start_a > end_a: 3 | if start_b > end_b: 4 | return 0 5 | else: 6 | return end_b - start_b + 1 7 | if start_b > end_b: 8 | if start_a > end_a: 9 | return 0 10 | else: 11 | return end_a - start_a + 1 12 | if str_a[start_a] == str_b[start_b]: 13 | return calculate_string_distance(str_a, start_a + 1, end_a, str_b, start_b + 1, end_b) 14 | else: 15 | t1 = calculate_string_distance(str_a, start_a + 1, end_a, str_b, start_b, end_b) 16 | t2 = calculate_string_distance(str_a, start_a, end_a, str_b, start_b + 1, end_b) 17 | t3 = calculate_string_distance(str_a, start_a + 1, end_a, str_b, start_b + 1, end_b) 18 | return min(t1, t2, t3) + 1 19 | 20 | 21 | if __name__ == '__main__': 22 | str_a = "aaee" 23 | str_b = "aac" 24 | if calculate_string_distance(str_a, 0, len(str_a) - 1, str_b, 0, len(str_b) - 1) == 1: 25 | print(1) 26 | else: 27 | print(0) 28 | -------------------------------------------------------------------------------- /The beauty of programming/chapter-4/4-点是否在三角形内.py: -------------------------------------------------------------------------------- 1 | class Point(object): 2 | def __init__(self, x, y): 3 | self.x = x 4 | self.y = y 5 | 6 | 7 | def product(p1, p2, p3): # 求叉积 8 | return (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y) 9 | 10 | 11 | def is_in_triangle(A, B, C, D): 12 | if product(A, B, D) >= 0 and product(B, C, D) >= 0 and product(C, A, D) >= 0: 13 | return True 14 | return False 15 | 16 | 17 | if __name__ == '__main__': 18 | # A B C 是逆时针方向 19 | A = Point(0, 1) 20 | B = Point(0, 0) 21 | C = Point(1, 0) 22 | print(is_in_triangle(A, B, C, Point(0.5, 0.5))) 23 | print(is_in_triangle(A, B, C, Point(2, 2))) 24 | -------------------------------------------------------------------------------- /The beauty of programming/chapter-4/6-桶中取黑白球的问题.py: -------------------------------------------------------------------------------- 1 | def black_white_ball(b, w): 2 | """ 3 | 0表示黑球,1表示白球 4 | :param b: 黑球个数 5 | :param w: 白球个数 6 | :return: 最后剩余的球 7 | """ 8 | import random 9 | map_dict = { 10 | 0: "黑球", 11 | 1: "白球", 12 | } 13 | barrel = [0] * b + [1] * w 14 | random.shuffle(barrel) 15 | while len(barrel) > 1: 16 | ball1 = barrel.pop() 17 | ball2 = barrel.pop() 18 | barrel.append(ball1 ^ ball2) 19 | random.shuffle(barrel) 20 | return map_dict[barrel[0]] 21 | 22 | 23 | if __name__ == '__main__': 24 | print(black_white_ball(101, 101)) 25 | -------------------------------------------------------------------------------- /The beauty of programming/chapter-4/7 蚂蚁爬树_题目.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whtlkeep/BAT-algorithms/1a339effd3719be5e94e742490e582bf4a03b7c0/The beauty of programming/chapter-4/7 蚂蚁爬树_题目.png -------------------------------------------------------------------------------- /The beauty of programming/chapter-4/7-蚂蚁爬树.py: -------------------------------------------------------------------------------- 1 | def ant_time(locs, wood_len,speed): 2 | """ 3 | 求所有蚂蚁都离开木杆的最短时间和最长时间 4 | :param locs: 蚂蚁的初始位置 5 | :param wood_len: 木杆的长度 6 | :param speed: 蚂蚁爬行速度,厘米/秒 7 | :return: 最短时间 最长时间 8 | """ 9 | min_distance = [] # 存储蚂蚁到木杆 最近 一端的距离 10 | max_distance = [] # 存储蚂蚁到木杆 最远 一端的距离 11 | for loc in locs: 12 | min_distance.append(min(loc, wood_len - loc)) 13 | max_distance.append(max(loc, wood_len - loc)) 14 | min_time = 1.0 * max(min_distance) / speed 15 | max_time = 1.0 * max(max_distance) / speed 16 | return min_time, max_time 17 | 18 | 19 | if __name__ == '__main__': 20 | locs = [3, 7, 20, 50, 66, 77] 21 | wood_len = 100 22 | speed = 2 23 | print(ant_time(locs, wood_len, speed)) 24 | -------------------------------------------------------------------------------- /Tree/tree.py: -------------------------------------------------------------------------------- 1 | class TreeNode(object): 2 | def __init__(self, x): 3 | self.val = x 4 | self.left = None 5 | self.right = None 6 | 7 | 8 | """ 9 | 5 10 | / \ 11 | 3 6 12 | / \ / 13 | 2 4 7 14 | """ 15 | 16 | 17 | def construct_tree(): 18 | root = TreeNode(5) 19 | root.left = TreeNode(3) 20 | root.right = TreeNode(6) 21 | root.left.left = TreeNode(2) 22 | root.left.right = TreeNode(4) 23 | root.right.left = TreeNode(7) 24 | return root 25 | -------------------------------------------------------------------------------- /Tree/二叉查找树-两数之和.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 给定一个二叉搜索树和一个目标结果,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。 4 | 5 | 二、案例 6 | 输入: 7 | 5 8 | / \ 9 | 3 6 10 | / \ \ 11 | 2 4 7 12 | 13 | Target = 9 14 | 15 | 输出: True(因为存在 2 + 7 = 9) 16 | 17 | 三、思路 18 | 使用中序遍历得到有序数组之后,再利用双指针对数组进行查找。 19 | 应该注意到,这一题不能用分别在左右子树两部分来处理这种思想,因为两个待求的节点可能分别在左右子树中。 20 | """ 21 | 22 | 23 | # Definition for a binary tree node. 24 | class TreeNode(object): 25 | def __init__(self, x): 26 | self.val = x 27 | self.left = None 28 | self.right = None 29 | 30 | 31 | # 中序遍历 非递归实现 32 | def inorder_traversal(root, nums): 33 | stack = [] 34 | cur = root 35 | if root is None: 36 | return nums 37 | while len(stack) != 0 or cur is not None: 38 | while cur is not None: 39 | stack.append(cur) 40 | cur = cur.left 41 | node = stack.pop() 42 | nums.append(node.val) 43 | cur = node.right 44 | 45 | 46 | def findTarget(root, k): 47 | """ 48 | :type root: TreeNode 49 | :type k: int 50 | :rtype: bool 51 | """ 52 | nums = [] 53 | inorder_traversal(root, nums) 54 | start = 0 55 | end = len(nums) - 1 56 | while start < end: 57 | if nums[start] + nums[end] == k: 58 | return True 59 | elif nums[start] + nums[end] < k: 60 | start += 1 61 | else: 62 | end -= 1 63 | return False 64 | 65 | 66 | if __name__ == '__main__': 67 | from Tree.tree import construct_tree 68 | 69 | root = construct_tree() 70 | print(findTarget(root, 9)) 71 | -------------------------------------------------------------------------------- /Tree/二叉查找树-中第K小的元素.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。 4 | 5 | 二、思路 6 | 本题要充分利用二叉搜索树有序的性质。一个节点的左子树上的节点都比该节点小,右子树上的节点都比该节点大; 7 | 因此我们可以求当前节点左子树的个数left_count: 8 | * 如果 left_count == k-1, 则该节点就是第k小的元素; 9 | * 如果 left_count < k-1, 则第K小的数在右子树中,而且在右子树中变成了第 k - left_count - 1 小的数了 10 | * 如果 left_count > k-1, 则第k小的数在左子树中,而且还是第k小的元素 11 | 12 | 三、同 13 | LeetCode-230 14 | """ 15 | 16 | 17 | class TreeNode(object): 18 | def __init__(self, x): 19 | self.val = x 20 | self.left = None 21 | self.right = None 22 | 23 | 24 | # 采用递归的思路求一棵树的节点个数 25 | def count(node): 26 | if node is None: 27 | return 0 28 | return count(node.left) + count(node.right) + 1 29 | 30 | 31 | def kthSmallest(root, k): 32 | leftc = count(root.left) 33 | if leftc == k - 1: 34 | return root.val 35 | if leftc > k - 1: 36 | return kthSmallest(root.left, k) 37 | return kthSmallest(root.right, k - leftc - 1) 38 | -------------------------------------------------------------------------------- /Tree/二叉查找树-从有序数组中构造二叉查找树.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 4 | 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 5 | 6 | 二、思路 7 | 递归大法 8 | 利用二分查找的思路,二分建树。 9 | 二分的中点是根节点(相对而言的,每个子树都有根节点),左边的序列建立左子树,右边的序列建立右子树 10 | 11 | 三、同 12 | LeetCode-108 13 | """ 14 | 15 | 16 | class TreeNode(object): 17 | def __init__(self, x): 18 | self.val = x 19 | self.left = None 20 | self.right = None 21 | 22 | 23 | def to_BST(nums, start, end): 24 | if start > end: 25 | return None 26 | middle = (start + end) >> 1 27 | root = TreeNode(nums[middle]) 28 | root.left = to_BST(nums, start, middle - 1) 29 | root.right = to_BST(nums, middle + 1, end) 30 | return root 31 | 32 | 33 | def sortedArrayToBST(nums): 34 | return to_BST(nums, 0, len(nums) - 1) 35 | -------------------------------------------------------------------------------- /Tree/二叉查找树-从有序链表构造平衡的二叉查找树.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。 4 | 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 5 | 6 | 二、思路 7 | 递归大法 8 | 利用二分查找的思路,二分建树。 9 | 二分的中点是根节点(相对而言的,每个子树都有根节点),左边的序列建立左子树,右边的序列建立右子树 10 | 11 | 三、本题与“从有序数组中构建二叉查找树”的整体思路是一样的 12 | 只是单链表无法直接获得中间节点,必须通过顺序遍历才能得到mid位置的节点。 13 | 遍历的方法是用“快慢指针”: 快指针每走两步,慢指针只走1步,这样快指针走到重点时,慢指针在中间。 14 | 15 | *** “快慢指针”是单链表中的重要方法,很多问题都要用到这个技巧,才能实现最优解 *** 16 | 17 | 四、同 18 | LeetCode-109 19 | """ 20 | 21 | 22 | # Definition for singly-linked list. 23 | class ListNode(object): 24 | def __init__(self, x): 25 | self.val = x 26 | self.next = None 27 | 28 | 29 | # Definition for a binary tree node. 30 | class TreeNode(object): 31 | def __init__(self, x): 32 | self.val = x 33 | self.left = None 34 | self.right = None 35 | 36 | 37 | # 找到单链表中间节点mid的前驱 38 | def pre_Mid(head): 39 | slow = head 40 | fast = head.next 41 | pre = head 42 | while fast is not None and fast.next is not None: 43 | pre = slow 44 | slow = slow.next 45 | fast = fast.next.next 46 | return pre 47 | 48 | 49 | def sortedListToBST(head): 50 | if head is None: 51 | return None 52 | if head.next is None: 53 | return TreeNode(head.val) 54 | 55 | preMid = pre_Mid(head) 56 | mid = preMid.next 57 | preMid.next = None # 断开链表 58 | 59 | t = TreeNode(mid.val) 60 | t.left = sortedListToBST(head) 61 | t.right = sortedListToBST(mid.next) 62 | return t 63 | -------------------------------------------------------------------------------- /Tree/二叉树-的最大深度.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 给定一个二叉树,找出其最大的深度 4 | 5 | 二、思路 6 | 用递归的思路,求左孩子的高度,求右孩子的高度,哪个大,就加1 7 | 8 | 三、同 9 | leetcode-104 10 | """ 11 | 12 | 13 | class TreeNode(object): 14 | def __init__(self, x): 15 | self.val = x 16 | self.left = None 17 | self.right = None 18 | 19 | 20 | def max_depth(root): 21 | if root is None: 22 | return 0 23 | return max(max_depth(root.left) + 1, max_depth(root.right) + 1) 24 | 25 | 26 | if __name__ == '__main__': 27 | from Tree.tree import construct_tree 28 | 29 | root = construct_tree() 30 | print(max_depth(root)) 31 | -------------------------------------------------------------------------------- /Tree/分行打印一棵树.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 分行从上之下打印一颗二叉树 4 | 5 | 例如有如下一棵树: 6 | 5 7 | / \ 8 | 3 6 9 | / \ \ 10 | 2 4 7 11 | 12 | 打印结果是 13 | 5 14 | 3 6 15 | 2 4 7 16 | 二、思路 17 | 在层次遍历的基础加以改进 18 | 如何实现逐行打印,必须要知道每一行的开始和结束。如何准确地获得该信息是关键。 19 | 核心思想:在每一层开始遍历前,队列中存储的就是该行的全部是数据,用size记录,然后一次性全部遍历。 20 | """ 21 | 22 | 23 | class TreeNode: 24 | def __init__(self, x): 25 | self.val = x 26 | self.left = None 27 | self.right = None 28 | 29 | 30 | def print_from_top_to_bottom(root): 31 | if root is None: 32 | return [] 33 | # 用一个list和一个索引index模拟队列的先进先出 34 | queue = [] 35 | index = 0 36 | result = [] 37 | queue.append(root) 38 | while len(queue) > index: 39 | size = len(queue) - index # 新遍历一个层时,队列中存储是该层的全部数据,size记录个数 40 | res = [] 41 | for i in range(size): # 遍历size次 42 | temp = queue[index] 43 | index += 1 44 | res.append(temp.val) 45 | if temp.left is not None: 46 | queue.append(temp.left) 47 | if temp.right is not None: 48 | queue.append(temp.right) 49 | result.append(res) 50 | return result 51 | 52 | 53 | if __name__ == '__main__': 54 | from Tree.tree import construct_tree 55 | 56 | root = construct_tree() 57 | print(print_from_top_to_bottom(root)) 58 | -------------------------------------------------------------------------------- /Tree/字典树-前缀树.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 实现一个字典树or前缀树,包含insert、search和startswith功能 4 | 二、知识讲解 5 | 参考: 6 | https://www.cnblogs.com/bonelee/p/8830825.html 7 | 8 | """ 9 | 10 | 11 | class Node(): 12 | def __init__(self): 13 | self.childs = [None] * 26 14 | self.isLeaf = False 15 | 16 | 17 | class Trie(object): 18 | def __init__(self): 19 | """ 20 | Initialize your data structure here. 21 | """ 22 | self.root = Node() 23 | 24 | def insert(self, word): 25 | """ 26 | Inserts a word into the trie. 27 | :type word: str 28 | :rtype: void 29 | """ 30 | self.inserthelper(word, self.root) 31 | 32 | def inserthelper(self, word, node): 33 | if node == Node: 34 | return 35 | if len(word) == 0: 36 | node.isLeaf = True 37 | return 38 | index = ord(word[0]) - ord('a') 39 | if node.childs[index] is None: 40 | node.childs[index] = Node() 41 | self.inserthelper(word[1:], node.childs[index]) 42 | 43 | def search(self, word): 44 | """ 45 | Returns if the word is in the trie. 46 | :type word: str 47 | :rtype: bool 48 | """ 49 | return self.searchhepler(word, self.root) 50 | 51 | def searchhepler(self, word, node): 52 | if node is None: 53 | return False 54 | if len(word) == 0: 55 | return node.isLeaf 56 | index = ord(word[0]) - ord('a') 57 | return self.searchhepler(word[1:], node.childs[index]) 58 | 59 | def startsWith(self, prefix): 60 | """ 61 | Returns if there is any word in the trie that starts with the given prefix. 62 | :type prefix: str 63 | :rtype: bool 64 | """ 65 | return self.startsWithhelper(prefix, self.root) 66 | 67 | def startsWithhelper(self, prefix, node): 68 | if node is None: 69 | return False 70 | if len(prefix) == 0: 71 | return True 72 | index = ord(prefix[0]) - ord('a') 73 | return self.startsWithhelper(prefix[1:], node.childs[index]) 74 | 75 | 76 | if __name__ == '__main__': 77 | trie = Trie() 78 | trie.insert("apple") 79 | print(trie.search("apple")) 80 | print(trie.search("app")) 81 | print(trie.startsWith("app")) 82 | trie.insert("app") 83 | print(trie.search("app")) 84 | -------------------------------------------------------------------------------- /Tree/完全二叉树-寻找完全二叉树最后一个节点.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 任意给定一个完全二叉树(定义可自行 百度),寻找树的最后一个节点 4 | 5 | 举例 6 | 5 7 | / \ 8 | 3 6 9 | / \ / 10 | 2 4 7 是完全二叉树, 最后一个节点是7 11 | 12 | 5 13 | / \ 14 | 3 6 15 | / \ \ 16 | 2 4 7 不是完全二叉树 17 | 18 | 二、思路 19 | * 暴力求解 O(N) 20 | 可以采用层次遍历,将整个树遍历一遍,最后一个遍历到的节点即为所求。【层次遍历可以参考 /树/层次遍历.py】 21 | * 二分查找 O(logN) 22 | 假如本题是普通二叉树,可能只有采用遍历的方法,但是题目的关键字是“完全树”,必须利用其特性来优化方法。 23 | 值得思考的性质有如下几点:(对照上面例子中的第一棵树考虑) 24 | 1、最后一个节点肯定是在最后一层,例如节点 7 在第 3 层 25 | 2、最后一个节点肯定是在最后一层的最右边; 26 | 3、完全二叉树获取树高最快的方式是从根节点(每一棵子树都有根节点)出发一直向左遍历,遍历到头,经过的节点数目就是树高。 27 | 求树高的复杂度是O(logN) 28 | 29 | 利用以上三点性质,讨论二分查找的思想: 30 | 1、对于根节点,如果左右子树的树高不等,则最后一个节点在左子树中(性质2) 31 | 2、如果左右子树的树高相等,则最后一个节点在右子树中(性质 1) 32 | """ 33 | 34 | 35 | class TreeNode(object): 36 | def __init__(self, x): 37 | self.val = x 38 | self.left = None 39 | self.right = None 40 | 41 | 42 | # 计算一颗完全二叉树的树高 43 | def count_height(node): 44 | height = 0 45 | while node: 46 | height += 1 47 | node = node.left 48 | return height 49 | 50 | 51 | def find_last_node(root): 52 | if root is None: 53 | return 54 | left_count = count_height(root.left) 55 | right_count = count_height(root.right) 56 | if left_count != right_count: # 如果左右子树的树高不等,则最后一个节点在左子树中(性质2) 57 | return find_last_node(root.left) 58 | elif left_count == right_count: 59 | if left_count == 0: # 树没有孩子节点,则该根节点是最后一个节点 60 | return root 61 | elif left_count == 1: # 有左右孩子节点,都是叶节点,右孩子是最后一个节点 62 | return root.right 63 | else: # 如果左右子树的树高相等,则最后一个节点在右子树中 64 | return find_last_node(root.right) 65 | 66 | 67 | if __name__ == '__main__': 68 | root = TreeNode(5) 69 | root.left = TreeNode(3) 70 | root.right = TreeNode(6) 71 | root.left.left = TreeNode(2) 72 | root.left.right = TreeNode(4) 73 | root.right.left = TreeNode(7) 74 | print(find_last_node(root).val) 75 | -------------------------------------------------------------------------------- /Tree/完全二叉树-计算完全二叉树的节点个数.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 任意给定一个完全二叉树(定义可自行 百度),计算节点个数 4 | 5 | 举例 6 | 5 7 | / \ 8 | 3 6 9 | / \ / 10 | 2 4 7 是完全二叉树, 节点个数是6 11 | 12 | 5 13 | / \ 14 | 3 6 15 | / \ \ 16 | 2 4 7 不是完全二叉树 17 | 18 | 二、思路 19 | * 暴力求解 O(N) 20 | 可以采用层次遍历,将整个树遍历一遍,即可计算出节点个数 21 | * 二分查找 O(logN) 22 | 假如本题是普通二叉树,可能只有采用遍历的方法,但是题目的关键字是“完全树”,必须利用其特性来优化方法。 23 | 值得思考的性质有如下几点:(对照上面例子中的第一棵树考虑) 24 | 1、最后一个节点肯定是在最后一层,例如节点 7 在第 3 层 25 | 2、最后一个节点肯定是在最后一层的最右边; 26 | 3、完全二叉树获取树高最快的方式是从根节点(每一棵子树都有根节点)出发一直向左遍历,遍历到头,经过的节点数目就是树高。 27 | 求树高的复杂度是O(logN) 28 | 4、如果能判断一棵树树是满完全二叉树,则树的节点是 pow(2,0) + pow(2,1) + pow(2,2) + ...+ pow(2, h-1), 其中h是树高 29 | 30 | 利用以上三点性质,讨论二分查找的思想: 31 | 1、对于根节点,如果左右子树的树高不等,则右子树是一颗满完全二叉树 32 | 2、如果左右子树的树高相等,则左子树是一颗满完全二叉树 33 | 三、备注 34 | 满完全二叉树:每一层的节点数都达到饱和, 个数是 2^(层数-1) 35 | 36 | """ 37 | 38 | 39 | class TreeNode(object): 40 | def __init__(self, x): 41 | self.val = x 42 | self.left = None 43 | self.right = None 44 | 45 | 46 | # 计算一颗完全二叉树的树高 47 | def countHeight(node): 48 | height = 0 49 | while node: 50 | height += 1 51 | node = node.left 52 | return height 53 | 54 | 55 | def count_complete_Num(height): 56 | """ 57 | 计算一颗满完全二叉树的节点个数 58 | :param height: 树高 59 | :return: 节点个数 60 | """ 61 | count = 0 62 | for h in range(height): 63 | count += pow(2, h) 64 | return count 65 | 66 | 67 | def tree_node_count(root): 68 | if root is None: 69 | return 0 70 | left_count = countHeight(root.left) 71 | right_count = countHeight(root.right) 72 | if left_count != right_count: # 如果左右子树的树高不等,则右子树是一颗满完全二叉树 73 | return 1 + count_complete_Num(right_count) + tree_node_count(root.left) 74 | elif left_count == right_count: 75 | if left_count == 0: # 树没有孩子节点 76 | return 1 77 | elif left_count == 1: # 有左右孩子节点,都是叶节点 78 | return 3 79 | else: # 如果左右子树的树高相等,则左子树是一颗满完全二叉树 80 | return 1 + count_complete_Num(left_count) + tree_node_count(root.right) 81 | 82 | 83 | if __name__ == '__main__': 84 | root = TreeNode(5) 85 | root.left = TreeNode(3) 86 | root.right = TreeNode(6) 87 | root.left.left = TreeNode(2) 88 | root.left.right = TreeNode(4) 89 | root.right.left = TreeNode(7) 90 | print(tree_node_count(root)) 91 | -------------------------------------------------------------------------------- /Tree/最近公共祖先.py: -------------------------------------------------------------------------------- 1 | """ 有序树 2 | 一、题目 3 | 给定二叉查找树(有序的意思),求两个节点p,q的最近公共祖先 4 | _______6______ 5 | / \ 6 | ___2__ ___8__ 7 | / \ / \ 8 | 0 4 7 9 9 | / \ 10 | 3 5 11 | 比如:节点 2 和 8 的最近公共祖先是 6 12 | 节点 3 和 7 的最近公共祖先是 6 13 | 14 | 二、思路 15 | 递归大法,利用有序的性质: 16 | 当节点p,q的值都小于root,则最近公共祖先肯定是在root的左子树中 17 | 当节点p,q的值都大于root,则最近公共祖先肯定是在root的右子树中 18 | 当以上两种情况都不满足,则root就是公共祖先 19 | 20 | 三、同 21 | LeetCode-235 22 | """ 23 | 24 | 25 | def lowestCommonAncestor(root, p, q): 26 | if root.val > p.val and root.val > q.val: 27 | return lowestCommonAncestor(root.left, p, q) 28 | if root.val < p.val and root.val < q.val: 29 | return lowestCommonAncestor(root.right, p, q) 30 | return root 31 | 32 | 33 | """ 无序树 34 | 一、题目 35 | 给定二叉树(无序的意思),求两个节点p,q的最近公共祖先 36 | _______3______ 37 | / \ 38 | ___5__ ___1__ 39 | / \ / \ 40 | 6 2 0 8 41 | / \ 42 | 7 4 43 | 比如:节点 5 和 1 的最近公共祖先是 3 44 | 节点 4 和 5 的最近公共祖先是 5 45 | 46 | 二、思路 47 | 递归大法: 48 | if 当前节点root等于None、p或者q,则该root节点就是公共祖先 49 | else 就在root的左右子树中分别去寻找公共祖先。 50 | 51 | 三、同 52 | LeetCode-236 53 | """ 54 | 55 | 56 | def lowestCommonAncestor(root, p, q): 57 | if root is None or root == p or root == q: 58 | return root 59 | left = lowestCommonAncestor(root.left, p, q) 60 | right = lowestCommonAncestor(root.right, p, q) 61 | if left is None: 62 | return right 63 | elif right is None: 64 | return left 65 | else: 66 | return root 67 | -------------------------------------------------------------------------------- /Tree/遍历-中序遍历-非递归.py: -------------------------------------------------------------------------------- 1 | class TreeNode(object): 2 | def __init__(self, x): 3 | self.val = x 4 | self.left = None 5 | self.right = None 6 | 7 | 8 | def in_order_traversal(root): # 中序 9 | res = [] 10 | stack = [] 11 | if root is None: 12 | return res 13 | cur = root 14 | while len(stack) != 0 or cur is not None: 15 | while cur is not None: 16 | stack.append(cur) 17 | cur = cur.left 18 | node = stack.pop() 19 | res.append(node.val) 20 | cur = node.right 21 | return res 22 | -------------------------------------------------------------------------------- /Tree/遍历-前序遍历-非递归.py: -------------------------------------------------------------------------------- 1 | class TreeNode(object): 2 | def __init__(self, x): 3 | self.val = x 4 | self.left = None 5 | self.right = None 6 | 7 | 8 | def pre_order_traversal(root): # 前序 9 | result = [] 10 | stack = [] 11 | stack.append(root) 12 | while len(stack) != 0: 13 | node = stack.pop() 14 | if node is None: 15 | continue 16 | result.append(node.val) 17 | stack.append(node.right) 18 | stack.append(node.left) 19 | return result 20 | -------------------------------------------------------------------------------- /Tree/遍历-后序遍历-非递归.py: -------------------------------------------------------------------------------- 1 | class TreeNode(object): 2 | def __init__(self, x): 3 | self.val = x 4 | self.left = None 5 | self.right = None 6 | 7 | 8 | def post_order_traversal(root): # 后序 9 | """ 10 | 前序遍历为 root -> left -> right,后序遍历为 left -> right -> root, 11 | 可以修改前序遍历成为 root -> right -> left,那么这个顺序就和后序遍历正好相反 12 | """ 13 | result = [] 14 | stack = [] 15 | stack.append(root) 16 | while len(stack) != 0: 17 | node = stack.pop() 18 | if node is None: 19 | continue 20 | result.append(node.val) 21 | stack.append(node.left) 22 | stack.append(node.right) 23 | return result[::-1] 24 | -------------------------------------------------------------------------------- /Tree/遍历-层次遍历.py: -------------------------------------------------------------------------------- 1 | """ 2 | 一、题目 3 | 按层次的遍历的方法打印一颗树 4 | 5 | 例如有如下一棵树: 6 | 5 7 | / \ 8 | 3 6 9 | / \ \ 10 | 2 4 7 11 | 12 | 打印结果是 13 | 5 3 6 2 4 7 14 | 15 | 二、思路 16 | 层次遍历的步骤是: 17 | 对于不为空的结点,先把该结点加入到队列中 18 | 从队中拿出结点,如果该结点的左右结点不为空,就分别把左右结点加入到队列中 19 | 重复以上操作直到队列为空 20 | 21 | """ 22 | 23 | 24 | class TreeNode: 25 | def __init__(self, x): 26 | self.val = x 27 | self.left = None 28 | self.right = None 29 | 30 | 31 | def hierarchical_traversal(root): 32 | if root is None: 33 | return [] 34 | # 用一个list和一个索引index模拟队列的先进先出 35 | queue = [] 36 | index = 0 37 | result = [] 38 | queue.append(root) 39 | while len(queue) > index: 40 | temp = queue[index] 41 | index += 1 42 | result.append(temp.val) 43 | if temp.left is not None: 44 | queue.append(temp.left) 45 | if temp.right is not None: 46 | queue.append(temp.right) 47 | return result 48 | 49 | 50 | if __name__ == '__main__': 51 | from Tree.tree import construct_tree 52 | 53 | root = construct_tree() 54 | print(hierarchical_traversal(root)) 55 | --------------------------------------------------------------------------------