python刷剑指offer(1-20)(一刷)
2019/07/28开始刷每天6题,一共66题,刷三遍,9月1号完成。
1、二维数组中的查找:
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
最开始的思路:
# -*- coding:utf-8 -*- class Solution: # array 二维列表,其实就是矩阵 def Find(self, target, array): # write code here # 矩阵的行 rows = len(array) # 矩阵的列 cols = len(array[0]) # 遍历矩阵的每一个元素 for i in range(rows): for j in range(cols): # 判断矩阵中是否含有该整数 if target == array[i][j]: print("找到啦") return True return False
这样写更好:
class Solution: # array 二维列表,其实就是矩阵 def Find(self, target, array): # write code here # 矩阵的行 rows = len(array) - 1 # 矩阵的列 cols = len(array[0]) - 1 # 矩阵是有序的 i = rows # 最后一行 j = 0 # 第一列 while j <= cols and i >= 0: # 如果该整数小于x if target < array[i][j]: # 行减小 i -= 1 # 如果该整数小于x elif target > array[i][j]: # 列增加 j += 1 # 正好相等,找到了 else: return True # 没找到 return False
2、请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
# -*- coding:utf-8 -*- class Solution: # s 源字符串 def replaceSpace(self, s): # write code here return s.replace(" ", "%20")
不用内置的函数:
def replaceSpace(s): # write code here # 不用内置的函数 # 创建一个新的空字符串 new_str = "" # 遍历源字符串 for ele in s: # print(s) # 判断遍历出的字符i是否为空格 # 不为空格 if ele.strip(): new_str += ele # 空格 else: new_str += "%20" return new_str
3、输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。
解法一:
class Solution: # 返回从尾部到头部的列表值序列,例如[1,2,3] def printListFromTailToHead(self, listNode): # write code here # 注意这是链表 取下一个:listNode.next 取值:listNode.val new_list = [] # 只要链表非空 while listNode: # 在新的序列,每次都是0位置插入值 new_list.insert(0, listNode.val) listNode = listNode.next return new_list
解法二:
class Solution: # 返回从尾部到头部的列表值序列,例如[1,2,3] def printListFromTailToHead(self, listNode): # write code here # 注意这是链表 取下一个:listNode.next 取值:listNode.val new_list = [] # 只要链表非空 while listNode: # 新的序列尾部追加值 new_list.append(listNode.val) listNode = listNode.next # 最后进行逆序 return new_list[::-1]
4、输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
代码:
# -*- coding:utf-8 -*- # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: # 返回构造的TreeNode根节点 def reConstructBinaryTree(self, pre, tin): # write code here # 判断是否为空 if not pre or not tin: return # 不为空 # 1、找到根节点,在前序遍历的首元素 root = TreeNode(pre[0]) # 2、找到根节点在中序遍历序列的位置 index = tin.index(root.val) # 3、利用递归重建二叉树 # 3.1、中序遍历中,根节点左边的是左子树,右边的是右子树 # 3.2、前序遍历中,也可以安装根节点在中序遍历的位置,将前序遍历分为左子树和右子树。 root.left = self.reConstructBinaryTree(pre[1:index+1], tin[:index]) # 前序[去除根节点:左子树],中序[左子树:根节点前一个元素] root.right = self.reConstructBinaryTree(pre[index+1:], tin[index+1:]) # 前序[右子树],中序[根节点后一个元素:右子树] return root
解题思路:采用递归的思想,前序遍历的第一个数字就是根节点,然后扫描中序遍历,确定中序遍历中根结点的位置,根节点位置之前的就是左子树的中序遍历,根节点后面的就是右子树的中序遍历。按照中序遍历根节点的位置,也可以把前序遍历分为左子树的前序遍历和右子树的前序遍历。
5、用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
# -*- coding:utf-8 -*- class Solution: def __init__(self): # 初始化两个栈 self.stack1 = [] self.stack2 = [] def push(self, node): # write code here # 压栈的操作 self.stack1.append(node) def pop(self): # return xx # 弹栈的操作 # 如果尚未在栈(该队列)中添加任何元素,则直接返回空 # if self.stack1 == []: # print("请先在队列中添加元素") # return None # 其实一个栈就可以实现了 # self.stack1.pop(0) if self.stack2 == []: # 一直弹栈,直至队列中元素为空 !!!!!!!!!!!! while self.stack1: # 栈1中弹出 a = self.stack1.pop() # 栈2中压入 self.stack2.append(a) return self.stack2.pop()
6、把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
第一种方法:直接在数组中利用 自带的查找函数找到最小的那个,当然,这样面试官可能直接就让你滚粗了。
第二种方法:稍微优化一下,由题目可知,整个数组可以分为两个子数组,前面的子数组的最后一个元素肯定大于或者等于后面子数组的都一个元素,那么找到第一个前面数字大于或者等于后面数字的情况,那么后面这个较小的数字就是最小值
以上两种方法的时间复杂度均为O(N),在真实面试中肯定不行。
下面介绍面试官想要你给出的解法,即利用二分法进行查找。
以数组a[3,4,5,1,2]为例。
1.设置两个指针low,high,分别指向第一个以及最后一个元素 。
2.设置中间指针mid,指向中间元素,mid=low+(high-low)/2。
3.这里有三种情况:
(1)a[mid]>a[high],此时最小值一定在中间值的右边,设置low=mid+1。
(2)a[mid]>a[low],此时最小值一定在中间值的左边,设置high=mid。
(3) a[mid]=a[high],此时无法判断最小在左边还是右边,只有一个一个判断,即high=high-1。
最后返回a[low]的值。
# -*- coding:utf-8 -*- class Solution: def minNumberInRotateArray(self, rotateArray): # write code here # pre = -7e20 pre = -float("inf") for num in rotateArray: if num < pre : return num pre = num if len(rotateArray) == 0: return 0 return rotateArray[0]
7、大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39
# -*- coding:utf-8 -*- class Solution: def Fibonacci(self, n): # write code here assert n <= 39, "n <= 39" # 斐波那契数列 f_list = [0] # 循环n次 a = 0 b = 1 for i in range(n): a, b = b, a + b f_list.append(a) # 输出数列的第n项 return f_list[n]
8、一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
比较倾向于找规律的解法,f(1) = 1, f(2) = 2, f(3) = 3, f(4) = 5, 可以总结出f(n) = f(n-1) + f(n-2)的规律,但是为什么会出现这样的规律呢?
假设现在6个台阶,我们可以从第5跳一步到6,这样的话有多少种方案跳到5就有多少种方案跳到6;
另外我们也可以从4一次跳两步跳到6,跳到4有多少种方案的话,就有多少种方案跳到6(注意不要多算了),
其他的不能从3跳到6什么的啦,所以最后就是f(6) = f(5) + f(4)。
这样子也很好理解变态跳台阶的问题了。
# -*- coding:utf-8 -*- class Solution: def jumpFloor(self, number): # write code here # 通过分析,知道本质是斐波那契数列问题 # 构造数列 # f_list = [] a = 1 b = 1 for i in range(number): a, b = b, a+b # f_list.append(a) return a
9、变态跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
# -*- coding:utf-8 -*- import math class Solution: def jumpFloorII(self, number): # write code here if number <= 0: return 0 return math.pow(2, number-1)
10、我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
这道题的思路和青蛙跳台阶是一样的
# -*- coding:utf-8 -*- class Solution: def rectCover(self, number): # write code here if number == 0: return 0 a = 1 b = 1 for i in range(number): a, b = b, a+b return a
11、输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
# -*- coding:utf-8 -*- class Solution: def NumberOf1(self, n): # write code here count = 0 for i in range(32): count += (n >> i)&1 return count
12、给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
# -*- coding:utf-8 -*- class Solution: def Power(self, base, exponent): # write code here # 要考虑多种情况 flag = 1 # 初始化输出的结果 result = 1 # 1、exponent为负数 if exponent < 0: flag = -1 # 2、exponent为0 if exponent == 0: return 1 # 正常情况 exponent取绝对值 for i in range(abs(exponent)): result *= base if flag == -1: result = 1 / result return result
13、输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解法一:新建数组
解法二:类似冒泡排序
/**
* 1.要想保证原有次序,则只能顺次移动或相邻交换。
* 2.i从左向右遍历,找到第一个偶数。
* 3.j从i+1开始向后找,直到找到第一个奇数。
* 4.将[i,...,j-1]的元素整体后移一位,最后将找到的奇数放入i位置,然后i++。
* 5.終止條件:j向後遍歷查找失敗。
*/
# -*- coding:utf-8 -*- class Solution: def reOrderArray(self, array): # write code here # 找两个数组 array1 = [] array2 = [] # 遍历原数组 for ele in array: if ele % 2 == 1: array1.append(ele) elif ele % 2 == 0: array2.append(ele) return array1 + array2
14、输入一个链表,输出该链表中倒数第k个结点。
# 输入一个链表,输出该链表中倒数第k个结点。 # -*- coding:utf-8 -*- # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def FindKthToTail(self, head, k): # write code here # 思路: # 1、用两个指针a和b同时指向头节点 # 2、指针a先走k-1步,到达了第k个节点,指针b不动 # 3、此时指针a和b同时移动,指针a走到尾节点终止,此时指针b所在位置即倒数第k个节点 # 特殊情况 # if not head or k <= 0: if head is None or k <= 0: return cursor_a = head cursor_b = head for i in range(k-1): # 注意:这里要考虑k可能大于链表长度的情况 if cursor_a.next != None: cursor_a = cursor_a.next else: return while cursor_a.next != None: cursor_a = cursor_a.next cursor_b = cursor_b.next return cursor_b
解法二:将链表转换成列表
# -*- coding:utf-8 -*- # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def FindKthToTail(self, head, k): # write code here l=[] while head!=None: l.append(head) head=head.next if k>len(l) or k<1: return return l[-k]
15、输入一个链表,反转链表后,输出新链表的表头。
# -*- coding:utf-8 -*- # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: # 返回ListNode def ReverseList(self, pHead): # write code here ''' 先将当前节点数对应的下一个节点数保存下来tmp = pHead.next, 然后将上个节点数last赋值给pHead.next(初始last为None)。 当前节点pHead赋值给last(成为上一个节点), 最后将下一个节点tmp赋值给pHead(成为当前结点)。 ''' if pHead is None or pHead.next is None: return pHead last = None while pHead: # 暂存下一个节点,避免节点丢失 tmp = pHead.next # 链表反转,下一个节点指向上一个节点 pHead.next = last # 当前节点赋值给last last = pHead # 最后将下一个节点赋值给pHead pHead = tmp return last
16、输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
# -*- coding:utf-8 -*- # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: # 返回合并后列表 def Merge(self, pHead1, pHead2): # write code here # 新建一个链表(随便填一个数进去,最后返回的时候返回首节点.next即可),用于保存合并后的链表 new_p = ListNode(666) # 保存这个新链表 p = new_p # 遍历输入的两个链表 while pHead1 and pHead2: if pHead1.val <= pHead2.val: # 新链表节点指向该节点 new_p.next = pHead1 # 该链表要向后移动一个 pHead1 = pHead1.next elif pHead1.val > pHead2.val: # 新链表节点指向该节点 new_p.next = pHead2 # 该链表要向后移动一个 pHead2 = pHead2.next # 每添加一个节点,新链表向后移动一个节点 new_p = new_p.next # 上诉遍历很可能会出现其中一个链表没有遍历完的情况 # 比如极端情况:链表1:[1] 链表2:[2, 3, 4],则链表2根本不会遍历 # while None and 1: 则下面的代码不会执行,即一个链表遍历完了,循环就会结束 # if pHead1: # new_p.next = pHead1 # elif pHead2: # new_p.next = pHead2 # 或者一行代码搞定 new_p.next = pHead1 or pHead2 # 注意返回的是节点的next return p.next
17、输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
思路:
对于两棵二叉树来说,要判断B是不是A的子结构,首先第一步在树A中查找与B根节点的值一样的节点。
通常对于查找树中某一个节点,我们都是采用递归的方法来遍历整棵树。
第二步就是判断树A中以R为根节点的子树是不是和树B具有相同的结构。
这里同样利用到了递归的方法,如果节点R的值和树的根节点不相同,则以R为根节点的子树和树B肯定不具有相同的节点;
如果它们值是相同的,则递归的判断各自的左右节点的值是不是相同。
# -*- coding:utf-8 -*- # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def HasSubtree(self, pRoot1, pRoot2): # write code here ''' 首先第一步在树A中查找与B根节点的值一样的节点。 通常对于查找树中某一个节点,我们都是采用递归的方法来遍历整棵树。 第二步就是判断树A中以R为根节点的子树是不是和树B具有相同的结构。 这里同样利用到了递归的方法,如果节点R的值和树的根节点不相同,则以R为根节点的子树和树B肯定不具有相同的节点; 如果它们值是相同的,则递归的判断各自的左右节点的值是不是相同。 递归的终止条件是我们达到了树A或者树B的叶节点。 ''' result = False if pRoot1 != None and pRoot2 != None: # 1、在树1中查找与数2根节点的值一样的节点 if pRoot1.val == pRoot2.val: # 2.1、判断树1中以R为根节点的子树是不是和树2具有相同的结构。 result = self.is_subtree(pRoot1, pRoot2) # 2.2、到这里说明树1的根节点不是数2的根节点,找树1左子树的节点 if not result: result = self.HasSubtree(pRoot1.left, pRoot2) # 2.3、同理判断树2的右子树 if not result: result = self.HasSubtree(pRoot1.right, pRoot2) return result # 用于递归判断树的每个节点是否相同 # 需要注意的地方是: 前两个if语句不可以颠倒顺序 # 如果颠倒顺序, 会先判断pRoot1是否为None, 其实这个时候pRoot2的结点已经遍历完成确定相等了, 但是返回了False, 判断错误 def is_subtree(self, pRoot1, pRoot2): if pRoot2 == None: return True if pRoot1 == None: return False if pRoot1.val != pRoot2.val: return False # 判断左右节点 return self.is_subtree(pRoot1.left, pRoot2.left) and self.is_subtree(pRoot1.right, pRoot2.right)
18、操作给定的二叉树,将其变换为源二叉树的镜像。
二叉树的镜像定义:源二叉树 8 / \ 6 10 / \ / \ 5 7 9 11 镜像二叉树 8 / \ 10 6 / \ / \ 11 9 7 5
# -*- coding:utf-8 -*- # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: # 返回镜像树的根节点 def Mirror(self, root): # write code here if root != None: # 交换左右节点 root.left, root.right = root.right, root.left # 将交换后的左右节点,依次加入镜像树中 self.Mirror(root.left) self.Mirror(root.right)
19、输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10。
思路:
每次打印并删除矩阵的第一行,然后将矩阵逆时针翻转90度,直至打印出全部结果
# -*- coding:utf-8 -*- class Solution: # matrix类型为二维列表,需要返回列表 def printMatrix(self, matrix): # write code here res = [] while matrix: res += matrix.pop(0) # print(matrix) # 如果matrix为空,就不要翻转了,否则会报错(超出索引) if matrix: matrix = self.rotate_matrix(matrix) return res def rotate_matrix(self, matrix): '''逆时针方向旋转90°矩阵''' # 新矩阵 new_matrix = [] row = len(matrix) col = len(matrix[0]) # 行列互换,每次先遍历列方向,这样可以按照列的方向读取矩阵元素 for i in range(col): new_line = [] # print(" ") for j in range(row): # print(matrix[j][col -1 -i]) # [col-1-i] 在这里是固定的,比如第一次循环,就是倒数第一个 -1,下一次则是-2 new_line.append(matrix[j][col -1 -i]) new_matrix.append(new_line) return new_matrix m = [ [1,2,3], [4,5,6], [7,8,9]] s = Solution() # s.rotate_matrix(m) s.printMatrix(m)
20、定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
# -*- coding:utf-8 -*- class Solution: def __init__(self): self.stack = [] # 创建一个可以瞬间返回最小值的栈,保证这个辅助栈的栈顶元素一直是最小的,取栈顶元素[-1] self.min_stack = [] def push(self, node): # write code here self.stack.append(node) # 如果最小值栈没有元素,或,添加的这个node比最小值栈的元素小,则加入最小值栈 if self.min_stack == [] or node < self.min_stack[-1]: self.min_stack.append(node) def pop(self): # write code here # 如果弹出的元素,主栈和辅助栈相同,则都要弹出 if self.stack[-1] == self.min_stack[-1]: self.min_stack.pop() self.stack.pop() def top(self): # write code here return self.stack[-1] def min(self): # write code here return self.min_stack[-1]