牛客网-剑指Offer编程题-Python实现
1. JZ1二维数组中的查找
思路:主要是关注右上角的元素,因为该元素是本行最大值,本列最小值,如果target大于该值,那么就需要在下面的行中查找。如果target小于该值,就需要在该列左边的列中查找。可以参考here
class Solution: # array 二维列表 def Find(self, target, array): # write code here if isinstance(array, list): m = len(array) if len(array)>0 and isinstance(array[0], list): n = len(array[0]) start_x, start_y = 0, 0 # 查询的开始位置 end_x, end_y = m-1, n-1 # 查询的结束位置 while start_x <= end_x and start_y <= end_y: right_top_x, right_top_y = start_x, end_y # 右上角元素 right_top_value = array[right_top_x][right_top_y] if right_top_value == target: return True # 表示包含该整数 elif right_top_value <= target: start_x += 1 elif right_top_value >= target: end_y -= 1 return False
2. JZ2替换空格
def replaceSpace(a): return s.replace(" ", "%20")
3. JZ3从尾到头打印链表
输入的是链表,将链表中的值保存到list中,然后逆向输出。
class ListNode: def __init__(self, x): self.val = x self.next = None class Solution: # 返回从尾部到头部的列表值序列,例如[1,2,3] def printListFromTailToHead(self, listNode): # write code here a = listNode vals = [] while a: vals.insert(0, a.val) a = a.next return vals
4. JZ4重建二叉树
思路:明确先序和中序中二叉树节点的顺序。先序遍历是:根左右,中序遍历是:左根右。因此,首先使用先序pre的第一个元素创建root节点,然后在中序tin中发现该元素对应的位置tin_i,使用tin_i左侧的内容构建为root的左子树,使用tin_i右侧的元素构建为root的右子数。
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 len(pre)==0 or len(tin)==0: return None root = TreeNode(pre[0]) # 创建根节点 for tin_i, tin_val in enumerate(tin): if root.val == tin_val: # 寻找tin中根节点所在位置 root.left = self.reConstructBinaryTree(pre[1:tin_i+1], tin[:tin_i]) root.right = self.reConstructBinaryTree(pre[tin_i+1:], tin[tin_i+1:]) return root
5. JZ5用两个栈实现队列
思路:stack1用来存储push元素,而当要pop的时候,因为stack1属于栈,而队列的pop是队首元素,也就是stack1的栈底元素,因此需要借助stack2将stack1的元素倒过来存储在stack2中,此时直接删除stack2的栈顶元素即可。可参考牛客官方题解
即:push时,直接向stack1中加入元素;pop时存在两种情况,第一种是stack2为空,此时将stack1中的元素倒过来放入stack2中,然后删除stack2的栈顶元素;第二种是stack2不为空,直接删除stack2的栈顶元素即可。
# 下面这种使用python的list模仿栈的使用,如果使用python的特性,那么仅使用一个list就可以实现。 class Solution: def __init__(self): self.stack1 = [] self.stack2 = [] def push(self, node): # write code here self.stack1.append(node) def pop(self): if len(self.stack2) == 0: while len(self.stack1) >0: x = self.stack1.pop() self.stack2.append(x) return self.stack2.pop() else: return self.stack2.pop() s = Solution() s.push(1) s.push(2) s.push(3) s.push(4) print(s.stack1, s.stack2) s.pop() print(s.stack1, s.stack2) s.push(5) print(s.stack1, s.stack2) s.pop() print(s.stack1, s.stack2)
# 使用python的list特性,可以直接使用下面的代码实现。 class Solution: def __init__(self): self.stack1 = [] def push(self, node): self.stack1.append(node) def pop(self): return self.stack1.pop(0)
6. JZ6 旋转数组的最小数字
题目描述:输入的是一个非递减数组的旋转(比如输入[3,4,5,1,2],该数组是[1,2,3,4,5]这个数组的旋转),输出旋转数组的最小元素,在这里也就是1.
思路:二分查找。参考牛客官方题解
难点:arr[mid]的比较对象。由于没有target,可以选择和端点比较,这里也就是右端点。
情形1:[3,4,5,1,2],此时arr[mid]=5, arr[right]=2,此时arr[mid]>arr[right],由于数组是非递减的的旋转数组,所以从[first,...,mid]是非递减的,因此,此时的最小值在[mid+1, right]这个范围。
情形2:[4,5,1,2,3],此时arr[mid]=1, arr[right]=3,此时arr[mid]< arr[right], 根据原数组是非递减的,因此可以确定最小值的范围是[left, mid]
情形3:[1,0,1,1,1],[1,1,1,0,1],这种情况时,不能很好地决定最小值所在的范围,因此每次让right-1来逐渐缩小范围。
class Solution: def minNumberInRotateArray(self, rotateArray): # write code here left = 0 right = len(rotateArray)-1 while left<right: mid = int((left+right)/2) if rotateArray[mid] > rotateArray[right]: left = mid + 1 elif rotateArray[mid] < rotateArray[right]: right = mid elif rotateArray[mid] == rotateArray[right]: right -= 1 return rotateArray[left]
7. JZ7 斐波那契数列
思路:第0项为0,第1项为1,之后的第2项为第0,1项之和,第n项为第n-2,n-1项之和。
class Solution: def Fibonacci(self, n): if n < 2: return n a = 0 b = 1 for i in range(1, n): # 注意这里的range起始值。 b, a = a+b, b return b
8. JZ8 跳台阶
思路:不管前面的怎么走,直接分析最后一步。比如台阶数目为n,那么最后一步可以是走2个台阶,也可以是走1个台阶,因此jumpFloor(n)=jumpFloor(n-1)+jumpFloor(n-2).
class Solution: def jumpFloor(self, number): if number <= 2: return number return self.jumpFloor(number-1)+self.jumpFloor(number-2)
但是这种方***超时,主要是重复计算比较多。因为在计算jumFloor(10)的时候,需要计算jumpFloor(9)和jumpFloor(8),而计算jumpFloor(9)的时候又需要计算jumpFloor(8)等,因此考虑存储中间计算的结果。
# 另一种解决方法: class Solution: def jumpFloor(self, number): if number <= 2: return number values = [-1 for i in range(number+1)] values[1] = 1 values[2] = 2 for i in range(3, number+1): values[i] = values[i-1] + values[i-2] return values[-1]
# 或者这样,此时就和斐波那契数列一致了。 class Solution: def jumpFloor(self, number): if number <= 2: return number a = 1 b = 2 for i in range(3, number+1): b,a = a+b, b return b
9. JZ9 变态跳台阶
思路:和每次跳1,2两个台阶没有本质不同。
class Solution: def jumpFloorII(self, number): # write code here if number <= 2: return number values = [-1 for i in range(number + 1)] values[1] = 1 values[2] = 2 for i in range(3, number + 1): values_i = 0 for j in range(1, i): # 当走有i个台阶时,存在的跳法就有从第1个台阶到第i-1个台阶的所有方法之和, # 然后加1,加1表示一次跳上i个台阶。 values_i += values[j] values[i] = values_i + 1 return values[-1]
10. JZ10矩形覆盖
思路:找规律。首先,当n=1是,只有1中方法;当n=2时,有2种方法;当n=3时,有3种方法。此时绘制出n=1,2,3时的图形可以发现,n=3时,相当于n=2时的情况,然后在右侧增加一个2*1的竖着的矩形;同时相当于n=1时,在右侧增加2个2*1的横着的矩形。如下图所示:
如上图,n=3是在n=1的基础上在右侧增加2个横着摆放的21的矩形框,在n=2的基础上在右侧增加1个竖着摆放的矩形框得到23的矩形框,因此f(n)=f(n-1)+f(n-2)
同理,当n=4时,是在n=2和n=3的基础上变化的。
注意:要求是n个2*1的小方块拼凑成2*n的矩形,也就是说高度都是2,因此只能横向增加长度。而竖着1个2*1的矩形可以达到高度2的要求,或者横着2个2*1的矩形可以达到高度2的要求。
# 在了解上述规律的基础上,就可以发现其实该题也就是和斐波那契数列一样了。 class Solution: def rectCover(self, number): # write code here if isinstance(number, int) and number <= 2: return number values = [0, 1, 2] for i in range(3, number+1): values.append(values[-1]+values[-2]) return values[-1]
11. JZ11二进制中1的个数
原码、反码、补码知识:整数的原码、反码、补码是相同的。而负数的补码是符号位不变,其余各位取反,然后末尾加1得到。
Python中的整数是以补码的形式存储的,bin(负数),输出的是它原码的二进制表示加上个负号,如下:
print(bin(10)) # 0b1010 print(bin(-10)) # -0b1010
注意:Python中的数是没有Overflow的,也就是没有位数限制。而补码是相对于位数来说的,比如32位的补码和16位的补码明显是不一样的,由于位数不定,因此要获取负数的补码,首先需要确定使用多少位来表示补码。
因此,通过将负数和0xffffffff进行与操作来获得对应的32位补码表示,然后使用bin函数将结果以二进制显示出来。如下:
print(-10 & 0xff, bin(-10 & 0xff)) # -10的16位补码246,对应的二进制格式为:0b11110110
在8位表示的整数中,-10的原码为1000 1010,保持最高位的1不变,剩余位取反,然后加1可以得到-10的补码:1111 0110。
在了解以上知识的基础上,本题的解题思路如下:
class Solution: def NumberOf1(self, n): # write code here print(bin(n), bin(n & 0xffffffff)) if n >= 0: return bin(n).count('1') else: return bin(n & 0xffffffff).count('1')
换一个思路,主要是利用n&(n-1),首先n&(n-1)的作用是:将n的二进制表示中最低位的1变为0。比如6的二进制表示为110,而7的二进制是在6的二进制基础上增加1,也就是111,因此当7&6时得到的就是110,也就是7的最低位的1会变为0。基于这个思想,就可以通过不断的&操作来实现计数的目的。
class Solution: def NumberOf1(self, n): count = 0 while n & 0xffffffff != 0: count += 1 n = n & (n - 1) return count
12. JZ12数值的整数次方
如果自己实现幂的效果,需要注意指数为负数的情况。
class Solution: def Power(self, base, exponent): if base == 0.0 and exponent == 0: return if base == 0.0: return 0 if exponent == 0: return 1 if exponent < 0: base = 1/base exponent = -exponent result = base for _ in range(1, exponent): result *= base return result
或使用python库文件math
class Solution: def Power(self, base, exponent): if base == 0.0 and exponent == 0: return import math return math.pow(base, exponent)
13. JZ13调整数组顺序使奇数位于偶数前面
思路:可以使用两个list,分别存储奇数和偶数,然后最终再合并起来,如下:
# -*- coding:utf-8 -*- class Solution: def reOrderArray(self, array): # write code here odd = list() even = list() for i in array: if i % 2 == 0: even.append(i) else: odd.append(i) return odd+even
14. JZ14 链表中的第k个节点
思路:首先获取链表长度n,然后使用链表长度减去k,从而得到需要输出的节点的位置为n-k,然后顺着遍历一次即可。
class Solution: def FindKthToTail(self, head, k): len_node = 0 p = head while p: len_node += 1 p = p.next if k < 0 or k > len_node: return for i in range(len_node-k): head = head.next return head
15. JZ15 反转链表
思路:遍历pHead元素,然后在新链表p中使用头部插入的方式插入pHead中元素,从而达到逆序的效果。
# class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: # 返回ListNode def ReverseList(self, pHead): # write code here p = ListNode(0) q = p while pHead: temp_node = ListNode(pHead.val) temp_node.next = q.next q.next = temp_node pHead = pHead.next return p.next
16. JZ16 合并两个排序的链表
思路:创建一个节点pHead3,然后通过迭代的方法来进行下面过程,当pHead1和pHead2当前所指向的位置不为空时,判断当前位置节点的值大小,将pHead3执行值比较小的节点,同时更新对应的指针即可。直到pHead1或者pHead2为空时,就将pHead3指向不为空的链表即可。
首先,链表初始化为:
接着,执行过程如下图,首先构建一个头结点,并pHead3,p3指向该结点;接着比较pHead1.val与pHead2.val的值大小:
- if pHead2.val < pHead1.val,那么p3.next指向pHead2,然后pHead2后移,同时p3后移。比如下图中的①到②之间的变化过程。
- 同理,if pHead1.val < pHead2.val。
最终,上图红线部分的元素所连接的就是pHead1和pHead2排序之后的结果(上图只是部分)。
class Solution: # 返回合并后列表 def Merge(self, pHead1, pHead2): # write code here pHead3 = ListNode(-1) p3 = pHead3 while pHead1 and pHead2: if pHead1.val < pHead2.val: p3.next = pHead1 pHead1 = pHead1.next p3 = p3.next else: p3.next = pHead2 pHead2 = pHead2.next p3 = p3.next if pHead1 == None: p3.next = pHead2 else: p3.next = pHead1 return pHead3.next
17. JZ17 树的子结构
思路:is_subtree是比较当前子树B与当前子树A是否为子树关系,而HasSubtree中需要不断的移动pRoot1指针来确定需要和B比较的部分。当pRoot1指向A树的根节点时,此时直接比较子树B是否为A的子树。如果不是,pRoot1指向A的左子树,然后比较B是否为pRoot1所指向的树的子树,如果不是,就将pRoot1指向A的右子树,然后比较B是否为pRoot1所指向的树的子树。
如下图:
首先,在HasSubtree函数中会调用is_subtree发现B不是当前树的子树,所以pRoot1指向A的左子树,此时发现pRoot1.val=pRoot2.val,因此开始递归执行is_subtree来比较对应的左子树和右子树是否具有相同的值。
步骤1(如上图①,下同):在is_subtree中,由于pRoot1和pRoot2对应值相等,所以此时比较左子树的值,这里使用r1和r2表示。
步骤2:接着比较r1和r2的左子树发现r2的左子树为None,此时可以断定r2的左子树是r1的子树,因此返回True
步骤3:然后比较r1和r2的右子树,发现值相等,均为5,接着继续比较右子树的左子树。
步骤4:此时值均为7,依然相等。
步骤5:...
# -*- 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 if not pRoot1: # 如果A树为空,返回False return False if not pRoot2: # 如果B树为空,返回False return False # 如果A树,B树都不为空,首先比较判断B树是否为A的子树 is_B_A = self.is_subtree(pRoot1, pRoot2) if is_B_A: return True else: # 如果B树不是A的子树,此时判断B树是否为A的左子树的子树 is_B_Aleft = self.HasSubtree(pRoot1.left, pRoot2) if is_B_Aleft: return True else: is_B_Aright = self.HasSubtree(pRoot1.right, pRoot2) if is_B_Aright: return True else: return False def is_subtree(self, r1, r2): if not r2: # 如果r2为空,则表示当前r2位空,此时直接就返回True return True if not r1: # 如果执行到这里,表示r2不为空,而r1为空,此时r2不可能与r1相等,此时直接返回False return False if r1.val == r2.val: # 比较根节点是否相等,如果相等,就比较左右节点是否相等 return self.is_subtree(r1.left, r2.left) and self.is_subtree(r1.right, r2.right) else: # 如果根节点不相等,就直接返回False return False
考虑到上述代码中HasSubtree存在较多冗余,可以将HasSubtree修改为如下:
def HasSubtree(self, pRoot1, pRoot2): def HasSubtree(self, pRoot1, pRoot2): # write code here if not pRoot1: # 如果A树为空,返回False return False if not pRoot2: # 如果B树为空,返回False return False # 如果A树,B树都不为空,首先比较判断B树是否为A的子书 is_B_A = self.is_subtree(pRoot1, pRoot2) if is_B_A: return True else: return self.HasSubtree(pRoot1.left, pRoot2) or self.HasSubtree(pRoot1.right, pRoot2)
18. JZ18 二叉树的镜像
思路:递归左右子树交换即可。
注意:在线平台中Mirror最终不需要返回值,直接输出的是root
# -*- 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 not root: return None if root.left: self.Mirror(root.left) if root.right: self.Mirror(root.right) root.right, root.left = root.left, root.right
19. JZ19顺时针打印矩阵。
思路:遍历的方向是按照右,下,左,上,右,下,左,上...的方向,因此定义direct来控制遍历的方向,使用flag来保存坐标点的是否被访问,通过边界判断和坐标点状态来改变下一次遍历的方向。如下图:
步骤1:向“右”遍历,到达4,此时到了边界,从而需要更改遍历方向为“下”。
步骤2:向“下”遍历,达到16,此时到达边界,从而更改遍历方向为“左”。
步骤3:向“左”遍历,达到13,此时到达边界,从而更改遍历方向为“上”。
步骤4:向“上”遍历,到达5,由于1已经遍历过,所以更改遍历方向为“右”。
步骤5:...
# -*- coding:utf-8 -*- class Solution: # matrix类型为二维列表,需要返回列表 def printMatrix(self, matrix): m = len(matrix) if m > 0: n = len(matrix[0]) else: return direct = [(0, 1), (1, 0), (0, -1), (-1, 0)] # 控制遍历方向:右、下、左、上 flag = [[0 for _ in range(n)] for i in range(m)] # 保存访问状态 res = [] # 保存要输出的数字 point = (0, 0) # 起始点的坐标 i = 0 # 遍历方向索引 res.append(matrix[point[0]][point[1]]) # res中插入起始点位置 flag[point[0]][point[1]] = 1 # 表示坐标点已经访问 count = 1 # 统计遍历次数,最大值为m*n,也就是矩阵元素个数 while count < m*n: i = i % 4 # 求余循环遍历方向 d = direct[i] new_point = (point[0]+d[0], point[1]+d[1]) # 新坐标点坐标 # 边界判断和坐标点状态判断 if new_point[1] >= n or new_point[0] >= m or new_point[1] < 0 or new_point[0] < 0 or flag[new_point[0]][new_point[1]] == 1: i += 1 continue else: point = new_point # 更新遍历坐标的位置 res.append(matrix[point[0]][point[1]]) flag[point[0]][point[1]] = 1 count += 1 return res
输入:[ [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]