本文题来自《剑指offer》,大家可在牛客网解题,大部分题解来自大佬Jackcui和小小毛,其他来自牛客网已通过的代码。
文章目录
- 1、二维数组查找
- 2、树的各种遍历
- 3、重建二叉树
- 4、两个栈实现队列
- 5、非递归实现斐波那契数列
- 6、跳台阶
- 7、变态跳台阶
- 8、二进制中1的个数
- 9、调整数组顺序使奇数位于偶数前面
- 10、链表中倒数第k个结点
- 11、反转链表
- 12、树的子结构
- 13、二叉树的镜像
- 14、顺时针打印矩阵
- 15、字符串全排列
- 16、数组中出现次数超过一半的数字
- 17、最小的k个数
- 18、把数组排成最小的数
- 19、两个链表的第一个公共结点
- 20、树的深度
- 21、平衡二叉树
- 22、数组里只出现一次的数字
- 23、和为S的序列
- 24、翻译翻译什么叫顺子
- 25、数组中重复的数字
- 26、表示数值的字符串
- 27、链表中环的入口结点
- 28、
- 29、按之字顺序打印二叉树
- 30、数据流的中位数
1、二维数组查找
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[ [1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]]
# -*- coding:utf-8 -*-
class Solution:
# array 二维列表
def Find(self, target, array):
# write code here
rows = len(array)
cols = len(array[0])
if rows>0 and cols>0:
row = 0
col = cols -1
while row<rows and col>=0:
if target == array[row][col]:
return True
elif target < array[row][col]:
col -= 1
else:
row += 1
思路:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数组,剔除这个数字所在的列;如果该数字小于要查找的数字,剔除这个数字所在的行。也就是说如果要查找的数字不在数组的右上角,则每一次都在数组的查找范围中剔除一行或者一列,这样每一步都可以缩小查找的范围,直到找到要查找的数字,或者查找范围为空。
2、树的各种遍历
#-*- coding: utf-8 -*-
class TreeNode:
def __init__(self,value):
self.value=value
self.left=None
self.right=None
class Tree_Method:
def tree_Create(self,arr):
'''
利用二叉树的三个组成部分:根节点-左子树-右子树;传入的arr是一个多维列表,每一
维最大为3,每一维中的内容依次表示根节点-左子树-右子树。然后递归的进行构建
'''
length=len(arr) #计算每一维的大小
root=TreeNode(arr[0]) #获取每一维的根节点
if length>=2: #判断是否有左子树
root.left=self.tree_Create(arr[1])
if length>=3: #判断是否有右子树
root.right=self.tree_Create(arr[2])
return root
def pre_Order(self,root):
'''前序遍历,遵循根左右的顺序'''
if root==None:
return
print(root.value,end=' ')
self.pre_Order(root.left)
self.pre_Order(root.right)
def mid_Order(self,root):
'''中序遍历,遵循左根右的顺序'''
if root==None:
return
self.mid_Order(root.left)
print(root.value,end=' ')
self.mid_Order(root.right)
def back_Order(self,root):
'''遵循左右根的顺序'''
if root==None:
return
self.back_Order(root.left)
self.back_Order(root.right)
print(root.value,end=' ')
def BFS(self,root):
'''
广度优先遍历,即从上到下,从左到右遍历
主要利用队列先进先出的特性,入队的时候
是按根左右的顺序,那么只要按照这个顺序出队就可以了
'''
if root==None:
return
queue=[]
queue.append(root) #这儿用一个列表模仿入队
while queue:
current_node=queue.pop(0) #将队首元素出队
print(current_node.value,end=' ')
if current_node.left: #判断该节点是否有左孩子
queue.append(current_node.left)
if current_node.right: #判断该节点是否有右孩子
queue.append(current_node.right)
def DFS(self,root):
'''
深度优先遍历,即先访问根结点,然后遍历左子树接着是遍历右子树
主要利用栈的特点,先将右子树压栈,再将左子树压栈,这样左子树
就位于栈顶,可以保证结点的左子树先与右子树被遍历
'''
if root==None:
return
stack=[]
stack.append(root) #这儿用一个列表模仿入队
while stack:
current_node=stack.pop() #将栈顶元素出栈
print(current_node.value,end=' ')
if current_node.right: #判断该节点是否有右孩子,有就入栈
stack.append(current_node.right)
if current_node.left: #判断该节点是否有左孩子,有就入栈。两个判断的顺序不能乱
stack.append(current_node.left)
if __name__=="__main__":
arr=[2,[3,[4],[5]],[2,[4,[7]],[3]]]
op=Tree_Method()
tree=op.tree_Create(arr)
print('前序遍历:',end='')
op.pre_Order(tree)
print()
print('中序遍历:',end='')
op.mid_Order(tree)
print()
print('后序遍历:',end='')
op.back_Order(tree)
print()
print('广度优先遍历:',end='')
op.BFS(tree)
print()
print('深度优先遍历:',end='')
op.DFS(tree)
3、重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{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 len(pre) == 0:
return None
elif len(pre) == 1:
return TreeNode(pre[0])
else:
root = TreeNode(pre[0])
pos = tin.index(pre[0])
root.left = self.reConstructBinaryTree(pre[1:pos+1], tin[:pos])
root.right = self.reConstructBinaryTree(pre[pos+1:], tin[pos+1:])
return root
4、两个栈实现队列
# -*- 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 len(self.stack2) == 0:
while self.stack1:
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
5、非递归实现斐波那契数列
# -*- coding:utf-8 -*-
class Solution:
def Fibonacci(self, n):
# write code here
if n <= 1:
return n
first, second, third = 0, 1, 0
for i in range(2, n+1):
third = first + second
first = second
second = third
return third
6、跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
class Solution:
def jumpFloor(self, number):
# write code here
if number < 3:
return number
first, second, third = 1, 2, 0
for i in range(3, number+1):
third = first + second
first = second
second = third
return third
首先我们考虑最简单的情况。如果只有1级台阶,那么显然只一种跳法。如果有2级台阶,那就有两种跳法:一种是分两次跳,每次跳1级;另一种是一次跳2级。
接着,我们来讨论一般情况。我们把n级台阶时的跳法看成是n的函数,记为f(n)。当n>2时,第一次跳的时候就有两种不同的选择:一是第一次只跳1级,此时跳法数目等于后面剩下的n-1级台阶的跳法数目,即为f(n-1);另外一种选择是跳一次跳2级,此时跳法数目等于后面剩下的n-2级台阶的跳法数目,即为f(n-2)。因此n级台阶的不同跳法的总数f(n)=f(n-1)+f(n-2)。分析到这里,我们不难看出这实际上就是斐波那契数列了。
7、变态跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
# -*- coding:utf-8 -*-
class Solution:
def jumpFloorII(self, number):
# write code here
if number <= 2:
return number
total = 1
for _ in range(1, number):
total *= 2
return total
我想说,这青蛙真变态,真能跳。
当n=1时,结果为1;
当n=2时,结果为2;
当n=3时,结果为4;
以此类推,我们使用数学归纳法不难发现,跳法f(n)=2^(n-1)。
8、二进制中1的个数
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
# -*- coding:utf-8 -*-
class Solution:
def NumberOf1(self, n):
# write code here
count = 0
if n<0:
n = n & 0xffffffff
while n:
count += 1
n = n & (n-1)
return count
更直观的法二,直接位移:
# -*- coding:utf-8 -*-
class Solution:
def NumberOf1(self, n):
# write code here
return sum([(n >> i & 1) for i in range(0,32)])
如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
9、调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
# 双向队列
from collections import deque
class Solution:
def reOrderArray(self, array):
# write code here
odd = deque()
l = len(array)
for i in range(l):
if array[l-i-1] % 2 != 0:
odd.appendleft(array[-i-1])
if array[i] % 2 == 0:
odd.append(array[i])
return list(odd)
列表实现相对简单,这里列出不开辟新空间的方法:
# -*- coding:utf-8 -*-
class Solution:
def reOrderArray(self, array):
# write code here
boarder = -1
for idx in range(len(array)):
if array[idx] % 2:
boarder += 1
array.insert(boarder, array.pop(idx))
return array
思路:创建双向队列,遍历数组,奇数前插入,偶数后插入。最后使用assign方法实现不同容器但相容的类型赋值。
10、链表中倒数第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
if head == None or k == 0:
return None
phead = head
pbehind = head
for i in range(k-1):
if phead.next == None:
return None
else:
phead = phead.next
while phead.next != None:
phead = phead.next
pbehind = pbehind.next
return pbehind
我们可以定义两个指针。第一个指针从链表的头指针开始遍历向前走k-1,第二个指针保持不动;从第k步开始,第二个指针也开始从链表的头指针开始遍历。由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点。
效果示意图,以链表总共6个结点,求倒数第3个结点为例:
除此之外,要注意代码的鲁棒性。需要判断传入参数合法性问题。
11、反转链表
输入一个链表,反转链表后,输出链表的所有元素。
# -*- 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
if not pHead or not pHead.next:
return pHead
last = None
while pHead:
tmp = pHead.next
pHead.next = last
last = pHead
pHead = tmp
return last
这个很简单,我们使用三个指针,分别指向当前遍历到的结点、它的前一个结点以及后一个结点。
在遍历的时候,做当前结点的尾结点和前一个结点的替换。
12、树的子结构
输入两颗二叉树A,B,判断B是不是A的子结构。(PS:我们约定空树不是任意一个树的子结构)。
# -*- 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 or not pRoot2:
return False
return self.HasSubtree(pRoot1.left, pRoot2) or self.HasSubtree(pRoot1.right, pRoot2) or self.is_subtree(pRoot1, pRoot2)
def is_subtree(self, A, B):
if not B:
return True
if not A or A.val != B.val:
return False
return self.is_subtree(A.left, B.left) and self.is_subtree(A.right, B.right)
要查找树A中是否存在和树B结构一样的子树,我们可以分为两步:第一步在树A中找到和B的根结点的值一样的结点R,第二步再判断树A中以R为根节点的子树是不是包含和树B一样的结构。
这里使用递归的方法即可。
13、二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。
如下图所示:
# -*- 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 or (root.left == None and root.right == None)):
return None
tmp = root.left
root.left = root.right
root.right = tmp
if root.left:
self.Mirror(root.left)
if root.right:
self.Mirror(root.right)
先交换根节点的两个子结点之后,我们注意到值为10、6的结点的子结点仍然保持不变,因此我们还需要交换这两个结点的左右子结点。做完这两次交换之后,我们已经遍历完所有的非叶结点。此时变换之后的树刚好就是原始树的镜像。交换示意图如下所示:
14、顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵:
则依次打印出数组:1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10。
# -*- coding:utf-8 -*-
class Solution:
# matrix类型为二维列表,需要返回列表
def printMatrix(self, matrix):
# write code here
rows = len(matrix)
cols = len(matrix[0])
result = []
if rows == 0 and cols == 0:
return result
left, right, top, buttom = 0, cols - 1, 0, rows - 1
while left <= right and top <= buttom:
for i in range(left, right+1):
result.append(matrix[top][i])
for i in range(top+1, buttom+1):
result.append(matrix[i][right])
if top != buttom:
for i in range(left, right)[::-1]:
result.append(matrix[buttom][i])
if left != right:
for i in range(top+1, buttom)[::-1]:
result.append(matrix[i][left])
left += 1
top += 1
right -= 1
buttom -= 1
return result
思路:将结果存入vector数组,从左到右,再从上到下,再从右到左,最后从下到上遍历。
题目变形为,给定数字2,打印
给定数字3,打印
以此类推
def matrix(target):
num = target * target
left, right, top, bottom = 0, target-1, 0, target-1
res = [ [0 for col in range(target)] for row in range(target)]
each = 1
while left <= right and top <= bottom and each <= num:
for i in range(left, right+1):
res[top][i] = each
each += 1
for i in range(top+1, bottom+1):
res[i][right] = each
each += 1
if top != bottom:
for i in range(left, right)[::-1]:
res[bottom][i] = each
each += 1
if left != right and each <= num:
for i in range(top+1, bottom)[::-1]:
res[i][left] = each
each += 1
top += 1
left += 1
bottom -= 1
right -= 1
for i in range(len(res)):
print("\t".join('%s' %id for id in res[i]))
if __name__ == '__main__':
matrix(4)
15、字符串全排列
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
# -*- coding:utf-8 -*-
class Solution:
def Permutation(self, ss):
if len(ss) <=0:
return []
res = list()
self.perm(ss,res,'')
uniq = list(set(res))
return sorted(uniq)
def perm(self,ss,res,path):
if ss=='':
res.append(path)
else:
for i in range(len(ss)):
self.perm(ss[:i]+ss[i+1:],res,path+ss[i])
我们求整个字符串的排列,可以看成两步:首先求所有可能出现在第一个位置的字符,即把第一个字符和后面所有的字符交换。如下图所示:
上图就是分别把第一个字符a和后面的b、c等字符交换的情形。首先固定第一个字符,求后面所有字符的排列。这个时候我们仍把后面的所有字符分为两部分:后面的字符的第一个字符,以及这个字符之后的所有字符。然后把第一个字符逐一和它后面的字符交换。
这个思路,是典型的递归思路。
16、数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
# -*- coding:utf-8 -*-
class Solution:
def MoreThanHalfNum_Solution(self, numbers):
# write code here
result = numbers[0]
times = 1
for i in range(1, len(numbers)):
if times == 0:
result = numbers[i]
times = 1
elif result == numbers[i]:
times += 1
else:
times -= 1
times = 0
for i in range(len(numbers)):
if numbers[i] == result:
times += 1
return result if times > len(numbers) // 2 else 0
数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现次数的和还要多。因此我们可以考虑在遍历数组的时候保存两个值:一个是数组的一个数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零,我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为1时对应的数字。
17、最小的k个数
n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
class Solution:
def GetLeastNumbers_Solution(self, tinput, k):
if len(tinput) < k:
return []
tmp = sorted(tinput[:k])
for each in tinput[k:]:
index = k - 1
flag = False
while index >= 0 and tmp[index] > each:
index -= 1
flag = True
if flag == True:
tmp.insert(index+1, each)
tmp.pop()
return tmp
海量数据可用堆排序:
class Solution:
def HeadAdjust(self, input_list, parent, length):
temp = input_list[parent]
child = 2 * parent + 1
while child < length:
if child + 1 < length and input_list[child] < input_list[child+1]:
child += 1
if temp >= input_list[child]:
break
input_list[parent] = input_list[child]
parent = child
child = 2 * parent + 1
input_list[parent] = temp
def GetLeastNumbers_Solution(self, tinput, k):
# write code here
res = []
length = len(tinput)
change = True
if length <= 0 or k <= 0 or k > length:
return res
res = tinput[:k]
for i in range(k, length+1):
if change == True:
for j in range(0, k//2+1)[::-1]:
self.HeadAdjust(res, j, k)
for j in range(1, k)[::-1]:
res[0], res[j] = res[j], res[0]
self.HeadAdjust(res, 0, j)
chage = False
if i != length and res[k-1] > tinput[i]:
res[k-1] = tinput[i]
chage = True
return res
18、把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
class Solution:
def PrintMinNumber(self, numbers):
# write code here
if len(numbers) == 0:
return ''
# python3没有cmp,需要导库代替或者自己写,但思想是一致的。
compare = lambda a, b:cmp(str(a) + str(b), str(b) + str(a))
min_string = sorted(numbers, cmp = compare)
return ''.join(str(s) for s in min_string)
遇到这个题,全排列当然可以做,但是时间复杂度为O(n!)。在这里我们自己定义一个规则,对拼接后的字符串进行比较。
排序规则如下:
若ab > ba 则 a 大于 b,
若ab < ba 则 a 小于 b,
若ab = ba 则 a 等于 b;
根据上述规则,我们需要先将数字转换成字符串再进行比较,因为需要串起来进行比较。比较完之后,按顺序输出即可。
19、两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
# 法一
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def FindFirstCommonNode(self, pHead1, pHead2):
# write code here
if pHead1 == None or pHead2 == None:
return None
cur1, cur2 = pHead1, pHead2
while cur1 != cur2:
cur1 = cur1.next if cur1 != None else pHead2
cur2 = cur2.next if cur2 != None else pHead1
return cur1
方法一:
我们可以把两个链表拼接起来,一个pHead1在前pHead2在后,一个pHead2在前pHead1在后。这样,生成了两个相同长度的链表,那么我们只要同时遍历这两个表,就一定能找到公共结点。时间复杂度O(m+n),空间复杂度O(m+n)。
方法二:
我们也可以先让把长的链表的头砍掉,让两个链表长度相同,这样,同时遍历也能找到公共结点。此时,时间复杂度O(m+n),空间复杂度为O(MAX(m,n))。
20、树的深度
这道题蛮简单的,求二叉树的深度。可以是递归的方法,属于DFS(深度优先搜索);另一种方法是按照层次遍历,属于BFS(广度优先搜索)。
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def TreeDepth(self, root):
# write code here
if root is None:
return 0
left=self.TreeDepth(root.left)
right=self.TreeDepth(root.right)
print(left,right)
return max(left,right)+1
if __name__=='__main__':
A1 = TreeNode(1)
A2 = TreeNode(2)
A3 = TreeNode(3)
A4 = TreeNode(4)
A5 = TreeNode(5)
A6 = TreeNode(6)
A1.left=A2
A1.right=A3
A2.left=A4
A2.right=A5
A4.left=A6
solution=Solution()
ans=solution.TreeDepth(A1)
print('ans=',ans)
21、平衡二叉树
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
平衡二叉树的定义是:所谓的平衡之意,就是树中任意一个结点下左右两个子树的高度差不超过 1。
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def TreeDepth(self, root):
# write code here
if root is None:
return 0
left = self.TreeDepth(root.left)
right = self.TreeDepth(root.right)
# 其实就是多了一步判断
if abs(left-right)>1:
return False
return max(left,right)+1
if __name__=='__main__':
A1 = TreeNode(1)
A2 = TreeNode(2)
A3 = TreeNode(3)
A4 = TreeNode(4)
A5 = TreeNode(5)
A6 = TreeNode(6)
A1.left=A2
A1.right=A3
A2.left=A4
A2.right=A5
A4.left=A6
solution=Solution()
ans=solution.TreeDepth(A1)
print('ans=',ans)
重复遍历多次:
在遍历树的每个结点的时候,调用函数TreeDepth得到它的左右子树的深度。如果每个结点的左右子树的深度相差都不超过1,则这是一颗平衡的二叉树。这种方法的缺点是,首先判断根结点是不是平衡的,需要使用TreeDepth获得左右子树的深度,然后还需要继续判断子树是不是平衡的,还是需要使用TreeDepth获得子树的左右子树的深度,这样就导致了大量的重复遍历。
只遍历一次:
重复遍历会影响算法的性能,所以很有必要掌握不需要重复遍历的方法。如果我们用后序遍历的方式遍历二叉树的每一个结点,在遍历到一个结点之前我们就已经遍历了它的左右子树。只要在遍历每个结点的时候记录它的深度(某一结点的深度等于它到叶结点的路径的长度),我们就可以一边遍历一边判断每个结点是不是平衡的。
22、数组里只出现一次的数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
# -*- coding:utf-8 -*-
class Solution:
# 返回[a,b] 其中ab是出现一次的两个数字
def FindNumsAppearOnce(self, array):
# write code here
if len(array) <= 0:
return []
resultExclusiveOR = 0
length = len(array)
for i in array:
resultExclusiveOR ^= i
firstBitIs1 = self.FindFisrtBitIs1(resultExclusiveOR)
num1, num2 = 0, 0
for i in array:
if self.BitIs1(i, firstBitIs1):
num1 ^= i
else:
num2 ^= i
return num1, num2
def FindFisrtBitIs1(self, num):
indexBit = 0
while num & 1 == 0 and indexBit <= 32:
indexBit += 1
num = num >> 1
return indexBit
def BitIs1(self, num, indexBit):
num = num >> indexBit
return num & 1
大家首先想到的是顺序扫描法,但是这种方法的时间复杂度是O(n^2)。接着大家又会考虑用哈希表的方法,但是空间复杂度不是O(1)。
应该怎么做才能即满足时间复杂度是O(n)又满足空间复杂度是O(1)的要求呢?
我们可以想一想“异或”运算的一个性质,我们直接举例说明。
举例:{2,4,3,6,3,2,5,5}
这个数组中只出现一次的两个数分别是4和6。怎么找到这个两个数字呢?
我们先不看找到俩个的情况,先看这样一个问题,如何在一个数组中找到一个只出现一次的数字呢?比如数组:{4,5,5},唯一一个只出现一次的数字是4。
我们知道异或的一个性质是:任何一个数字异或它自己都等于0。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字。比如数组{4,5,5},我们先用数组中的第一个元素4(二进制形式:0100)和数组中的第二个元素5(二进制形式:0101)进行异或操作,0100和0101异或得到0001,用这个得到的元素与数组中的三个元素5(二进制形式:0101)进行异或操作,0001和0101异或得到0100,正好是结果数字4。这是因为数组中相同的元素异或是为0的,因此就只剩下那个不成对的孤苦伶仃元素。
现在好了,我们已经知道了如何找到一个数组中找到一个只出现一次的数字,那么我们如何在一个数组中找到两个只出现一次的数字呢?如果,我们可以将原始数组分成两个子数组,使得每个子数组包含一个只出现一次的数字,而其他数字都成对出现。这样,我们就可以用上述方法找到那个孤苦伶仃的元素。
我们还是从头到尾一次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数组的异或结果。因为其他数字都出现了两次,在异或中全部抵消了。由于两个数字肯定不一样,那么异或的结果肯定不为0,也就是说这个结果数组的二进制表示至少有一个位为1。我们在结果数组中找到第一个为1的位的位置,记为第n位。现在我们以第n位是不是1为标准把元数组中的数字分成两个子数组,第一个子数组中每个数字的第n位都是1,而第二个子数组中每个数字的第n位都是0。
举例:{2,4,3,6,3,2,5,5}
我们依次对数组中的每个数字做异或运行之后,得到的结果用二进制表示是0010。异或得到结果中的倒数第二位是1,于是我们根据数字的倒数第二位是不是1分为两个子数组。第一个子数组{2,3,6,3,2}中所有数字的倒数第二位都是1,而第二个子数组{4,5,5}中所有数字的倒数第二位都是0。接下来只要分别两个子数组求异或,就能找到第一个子数组中只出现一次的数字是6,而第二个子数组中只出现一次的数字是4。
23、和为S的序列
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。
# -*- coding:utf-8 -*-
class Solution:
def FindContinuousSequence(self, tsum):
# write code here
result = []
low, high = 1, 2
while low < high:
curSum = (low + high) * (high - low + 1) / 2
if curSum == tsum:
tmp = []
for i in range(low, high+1):
tmp.append(i)
result.append(tmp)
low += 1
elif curSum < tsum:
high += 1
else:
low += 1
return result
这道题还是蛮简单的。
设定两个指针,一个指向第一个数,一个指向最后一个数,在此之前需要设定第一个数和最后一个数的值,由于是正数序列,所以可以把第一个数设为1,最后一个数为2(因为是要求是连续正数序列,最后不可能和第一个数重合)。下一步就是不断改变第一个数和最后一个数的值,如果从第一个数到最后一个数的和刚好是要求的和,那么把所有的数都添加到一个序列中;如果大于要求的和,则说明从第一个数到最后一个数之间的范围太大,因此减小范围,需要把第一个数的值加1,同时把当前和减去原来的第一个数的值;如果小于要求的和,说明范围太小,因此把最后一个数加1,同时把当前的和加上改变之后的最后一个数的值。这样,不断修改第一个数和最后一个数的值,就能确定所有连续正数序列的和等于S的序列了。
注意:初中的求和公式应该记得吧,首项加尾项的和乘以个数除以2,即sum = (a + b) * n / 2。
24、翻译翻译什么叫顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张😊)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何。为了方便起见,你可以认为大小王是0。
class Solution:
def IsContinuous(self, numbers):
# write code here
if len(numbers) < 5:
return False
max_num = -1
min_num = 14
flag = 0
for number in numbers:
if number < 0 or number > 13:
return False
if number == 0:
continue
if (flag >> number) & 1 == 1:
return False
flag |= 1 << number
if number < min_num:
min_num = number
if number > max_num:
max_num = number
if max_num - min_num >= 5:
return False
return True
这题说了一堆,提取主要信息,我们不难整理出,满足如下条件才可以认为是顺子:
输入数据个数为5;
输入数据都在0-13之间;
没有相同的数字;
最大值与最小值的差值不大于5。
PS:大小王可以当成任意数。
这里可以使用一个技巧,即利用一个flag记录每个数字出现的次数。具体实现直接看代码吧,代码有详细注释。
25、数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
class Solution:
# 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
# 函数返回True/False
def duplicate(self, numbers, duplication):
# write code here
n = len(numbers)
if n == 0:
return False
for i in range(n):
if numbers[i] < 0 or numbers[i] > n-1:
return False
for i in range(n):
while numbers[i] != i:
if numbers[i] == numbers[numbers[i]]:
duplication[0] = numbers[i]
return True
numbers[numbers[i]], numbers[i] = numbers[i], numbers[numbers[i]]
return False
还可以把当前序列当成是一个下标和下标对应值是相同的数组(时间复杂度为O(n),空间复杂度为O(1)); 遍历数组,判断当前位的值和下标是否相等:
若相等,则遍历下一位;
若不等,则将当前位置i上的元素和a[i]位置上的元素比较:若它们相等,则找到了第一个相同的元素;若不等,则将它们两交换。换完之后a[i]位置上的值和它的下标是对应的,但i位置上的元素和下标并不一定对应;重复2的操作,直到当前位置i的值也为i,将i向后移一位,再重复2。
本文采用思路3,如果还是不懂,看下面的实例分析就懂了!
举例说明:{2,3,1,0,2,5,3}
0(索引值)和2(索引值位置的元素)不相等,并且2(索引值位置的元素)和1(以该索引值位置的元素2为索引值的位置的元素)不相等,则交换位置,数组变为:{1,3,2,0,2,5,3};
0(索引值)和1(索引值位置的元素)仍然不相等,并且1(索引值位置的元素)和3(以该索引值位置的元素1为索引值的位置的元素)不相等,则交换位置,数组变为:{3,1,2,0,2,5,3};
0(索引值)和3(索引值位置的元素)仍然不相等,并且3(索引值位置的元素)和0(以该索引值位置的元素3为索引值的位置的元素)不相等,则交换位置,数组变为:{0,1,2,3,2,5,3};
0(索引值)和0(索引值位置的元素)相等,遍历下一个元素;
1(索引值)和1(索引值位置的元素)相等,遍历下一个元素;
2(索引值)和2(索引值位置的元素)相等,遍历下一个元素;
3(索引值)和3(索引值位置的元素)相等,遍历下一个元素;
4(索引值)和2(索引值位置的元素)不相等,但是2(索引值位置的元素)和2(以该索引值位置的元素2为索引值的位置的元素)相等,则找到了第一个重复的元素。
class Solution:
# 这里要特别注意~找到任意重复的一个值并赋值到duplication[0]
# 函数返回True/False
def duplicate(self, numbers, duplication):
# write code here
n = len(numbers)
if n == 0:
return False
for i in range(n):
index = numbers[i]
if index >= n:
index -= n
if numbers[index] >= n:
duplication[0] = index
return True
numbers[index] += n
return False
个人感觉最优的方法:题目里写了数组里数字的范围保证在0 ~ n-1 之间,所以可以利用现有数组设置标志,当一个数字被访问过后,可以设置对应位上的数 + n,之后再遇到相同的数时,会发现对应位上的数已经大于等于n了,那么直接返回这个数即可。
26、表示数值的字符串
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示数值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。
class Solution:
def isNumeric(self , str ):
str = str.strip()
met_dot = met_e = met_digited = False
for i, char in enumerate(str):
#重复出现符号时必须跟在e、E后面
if char in '+-':
if i > 0 and str[i - 1] not in 'eE':
return False
#“.“不能跟e、E共线
elif char == '.':
if met_dot or met_e: return False
met_dot = True
#e不能是最后一个或者没有数字
elif char == 'e' or char == 'E':
if met_e or not met_digited or i == len(str) - 1:
return False
met_e, met_digited = True, False
elif char.isdigit():
met_digited = True
else:
return False
return met_digited
这道题还是比较简单的。表示数值的字符串遵循如下模式:
[sign]integral-digits[.[fractional-digits]][e|E[sign]exponential-digits]
其中,(’[‘和’]'之间的为可有可无的部分)。
在数值之前可能有一个表示正负的’+‘或者’-’。接下来是若干个0到9的数位表示数值的整数部分(在某些小数里可能没有数值的整数部分)。如果数值是一个小数,那么在小数后面可能会有若干个0到9的数位表示数值的小数部分。如果数值用科学记数法表示,接下来是一个’e’或者’E’,以及紧跟着的一个整数(可以有正负号)表示指数。
判断一个字符串是否符合上述模式时,首先看第一个字符是不是正负号。如果是,在字符串上移动一个字符,继续扫描剩余的字符串中0到9的数位。如果是一个小数,则将遇到小数点。另外,如果是用科学记数法表示的数值,在整数或者小数的后面还有可能遇到’e’或者’E’。
27、链表中环的入口结点
一个链表中包含环,请找出该链表的环的入口结点。
# -*- coding:utf-8 -*-
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def EntryNodeOfLoop(self, pHead):
# write code here
if pHead == None:
return None
meetingnode = self.MeetingNode(pHead)
if meetingnode == None:
return None
nodeslop = 1
node1 = meetingnode
while node1.next != meetingnode:
node1 = node1.next
nodeslop += 1
node1 = pHead
for _ in range(nodeslop):
node1 = node1.next
node2 = pHead
while node1 != node2:
node1 = node1.next
node2 = node2.next
return node1
def MeetingNode(self, pHead):
slow = pHead.next
if slow == None:
return None
fast = slow.next
while fast != None and slow != None:
if slow == fast:
return fast
slow = slow.next
fast = fast.next
if fast != None:
fast = fast.next
return None
可以用两个指针来解决这个问题。先定义两个指针P1和P2指向链表的头结点。如果链表中的环有n个结点,指针P1先在链表上向前移动n步,然后两个指针以相同的速度向前移动。当第二个指针指向的入口结点时,第一个指针已经围绕着揍了一圈又回到了入口结点。
以下图为例,指针P1和P2在初始化时都指向链表的头结点。由于环中有4个结点,指针P1先在链表上向前移动4步。接下来两个指针以相同的速度在链表上向前移动,直到它们相遇。它们相遇的结点正好是环的入口结点。
现在,关键问题在于怎么知道环中有几个结点呢?
可以使用快慢指针,一个每次走一步,一个每次走两步。如果两个指针相遇,表明链表中存在环,并且两个指针相遇的结点一定在环中。
随后,我们就从相遇的这个环中结点出发,一边继续向前移动一边计数,当再次回到这个结点时,就可以得到环中结点数目了。
28、
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
# -*- coding:utf-8 -*-
# class TreeLinkNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
# self.next = None
class Solution:
def GetNext(self, pNode):
if pNode==None :
return None
if pNode.right :
p = pNode.right
while p.left:
p=p.left
return p
while(pNode.next!=None and pNode.next.right==pNode):
pNode=pNode.next
return pNode.next
我们以上图为例进行讲解,上图二叉树的中序遍历是d,b,h,e,i,a,f,c,g。我们以这棵树为例来分析如何找出二叉树的下一个结点。
如果一个结点有右子树,那么它的下一个结点就是它的右子树的最左子结点。也就是说从右子结点出发一直沿着指向左子树结点的指针,我们就能找到它的下一个结点。例如,图中结点b的下一个结点是h,结点a的下一个结点是f。
接着我们分析一下结点没有右子树的情形。如果结点是它父结点的左子结点,那么它的下一个结点就是它的父结点。例如,途中结点d的下一个结点是b,f的下一个结点是c。
如果一个结点既没有右子树,并且它还是父结点的右子结点,这种情形就比较复杂。我们可以沿着指向父结点的指针一直向上遍历,直到找到一个是它父结点的左子结点的结点。如果这样的结点存在,那么这个结点的父结点就是我们要找的下一个结点。例如,为了找到结点g的下一个结点,我们沿着指向父结点的指针向上遍历,先到达结点c。由于结点c是父结点a的右结点,我们继续向上遍历到达结点a。由于结点a是树的根结点。它没有父结点。因此结点g没有下一个结点。
29、按之字顺序打印二叉树
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
"""docstring for Solution"""
def Print(self, pRoot):
resultArray = []
if not pRoot:
return resultArray
curLayerNodes = [pRoot]
isEvenLayer = True
while curLayerNodes:
curLayerValues = []
nextLayerNodes = []
isEvenLayer = not isEvenLayer
for node in curLayerNodes:
curLayerValues.append(node.val)
if node.left:
nextLayerNodes.append(node.left)
if node.right:
nextLayerNodes.append(node.right)
curLayerNodes = nextLayerNodes
resultArray.append(curLayerValues[::-1]) if isEvenLayer else resultArray.append(curLayerValues)
return resultArray
if __name__ == '__main__':
A1 = TreeNode(1)
A2 = TreeNode(2)
A3 = TreeNode(3)
A4 = TreeNode(4)
A5 = TreeNode(5)
A6 = TreeNode(6)
A7 = TreeNode(7)
A1.left=A2
A1.right=A3
A2.left=A4
A2.right=A5
A3.left=A6
A3.right=A7
solution = Solution()
ans=solution.Print(A1)
print(ans)
我们需要使用两个栈。我们在打印某一行结点时,把下一层的子结点保存到相应的栈里。如果当前打印的是奇数层(第一层、第三层等),则先保存左子树结点再保存右子树结点到第一个栈里。如果当前打印的是偶数层(第二层、第四层等),则则先保存右子树结点再保存左子树结点到第二个栈里。
30、数据流的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
from heapq import *
class Solution:
def __init__(self):
self.A = []#小顶堆,存储较大的一半
self.B = []#大顶堆,存储较小的一半
def Insert(self, num):
# write code here
if len(self.A) == len(self.B):#当A,B元素个数相等时,向A添加元素
heappush(self.B,-num)
heappush(self.A,-heappop(self.B))
else:#当A,B元素个数不等时,向B添加元素
heappush(self.A,num)
heappush(self.B,-heappop(self.A))
def GetMedian(self):
# write code here
if len(self.A) == len(self.B):
return (self.A[0]-self.B[0])/2.0#注意B里存的是相反数,这里写2.0可以让输出结果保留小数位
else:
return self.A[0]
这道题的解法有很多,本文使用最大堆和最小堆实现。
主要思想:
最大堆 | 最小堆
我们将数据分为两部分,位于左边最大堆的数据比右边最小堆的数据要小,左、右两边内部的数据没有排序,也可以根据左边最大的数及右边最小的数得到中位数。
接下来考虑用最大堆和最小堆实现的一些细节。
首先要保证数据平均分配到两个堆中,因此两个堆中数据的数目之差不能超过1.为了实现平均分配,可以在数据的总数目是偶数时把新数据插入到最小堆中,否则插入到最大堆中。
此外,还要保证最大堆中所有数据小于最小堆中数据。所以,新传入的数据需要先和最大堆的最大值或者最小堆中的最小值进行比较。以总数目为偶数为例,按照我们制定的规则,新的数据会被插入到最小堆中,但是在这之前,我们需要判断这个数据和最大堆中的最大值谁更大,如果最大堆中的数据比较大,那么我们就需要把当前数据插入最大堆,然后弹出新的最大值,再插入到最小堆中。由于最终插入到最小堆的数字是原最大堆中最大的数字,这样就保证了最小堆中所有数字都大于最大堆的数字。