面试题3:二维数组的查找
Q:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
A1:遍历整个二位数组(时间复杂度O(n2))
A2:首先选取数组中右上角的数字。如果该数字等于要查找的数字,查找过程结束;如果该数字大于要查找的数字,剔除这个数字所在的列;如果该数字小于要查找的数字,剔除这个数字所在的行。也就是说如果要查找的数字不在数组的右上角,则每一次都在数组的查找范围中剔除一行或者一列,这样每一步都可以缩小查找的范围,直到找到要查找的数字,或者查找范围为空。(左下角同理)(时间复杂度O(n))
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if matrix==[]:
return False
else: #左上角元素值最小,右下角最大
i=0
j=len(matrix[0])-1
while i<=len(matrix)-1 and j>=0:
if matrix[i][j]!=target:
if matrix[i][j]>target:
j-=1
else:
i+=1
else:
return True
break
else:
return False
面试题4:替换空格
Q:请实现一个函数,把字符串中的每个空格替换成"%20"。例如输入“We arehappy.”,则输出“We%20are%20happy.”。
A1:先遍历一次字符串,这样就能统计出字符串中空格的总数,并可以由此计算出替换之后的字符串的总长度。
首先准备两个指针,P1 和 P2。P1 指向原始字符串的末尾,而 P2 指向替换之后的字符串的末尾。接着向前移动指针 P1,逐个把它指向的字符复制到 P2指向的位置,直到碰到第一个空格为止。碰到第一个空格之后,把P1向前移动1格,在P2之前插入字符串"%20"。由于"%20"的长度为3,同时也要把P2向前移动3格。接着向前复制,直到碰到第二个空格。和上一次一样,再把P1向前移动1格,并把P2向前移动3格插入"%20"。此时P1和P2指向同一位置,表明所有空格都已经替换完毕。
class Solution:
def replaceSpace(self, s: str) -> str:
if s==None or len(s)<=0:
return ''
sumSpace=0
for i in s:
if i==' ':
sumSpace+=1
newLen=len(s)+2*sumSpace
newStr=newLen*[None]
indexOfOriginal,indexOfNew=len(s)-1,newLen-1
while indexOfNew>=0 and indexOfNew>=indexOfOriginal:
if s[indexOfOriginal] == ' ':
newStr[indexOfNew - 2: indexOfNew + 1] = ['%', '2', '0']
indexOfNew -= 3
indexOfOriginal -= 1
else:
newStr[indexOfNew] = s[indexOfOriginal]
indexOfNew -= 1
indexOfOriginal -= 1
return "".join(newStr)
A2:直接使用python内置函数replace
语法:
str.replace(old, new[, max])
参数
•old – 将被替换的子字符串。
•new – 新字符串,用于替换old子字符串。
•max – 可选字符串, 替换不超过 max 次。
class Solution:
def replaceSpace(self, s: str) -> str:
return s.replace(" ","%20")
面试题5:从尾到头打印链表
Q:输入一个链表的头结点,从尾到头反过来打印出每个结点的值。
A:先将链表的结点元素储存进列表,再将列表倒序
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
res=[]
while head:
res.append(head.val)
head=head.next
return res[::-1]
面试题6:重建二叉树
Q:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
A:前序遍历列表:第一个元素永远是 【根节点 (root)】
中序遍历列表:根节点 (root)【左边】的所有元素都在根节点的【左分支】,【右边】的所有元素都在根节点的【右分支】
通过【前序遍历列表】确定【根节点 (root)】
将【中序遍历列表】的节点分割成【左分支节点】和【右分支节点】
递归寻找【左分支节点】中的【根节点 (left child)】和 【右分支节点】中的【根节点 (right child)】
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
if not preorder:
return None
loc=inorder.index(preorder[0])
root=TreeNode(preorder[0])
root.left=self.buildTree(preorder[1:loc+1],inorder[:loc])
root.right=self.buildTree(preorder[loc+1:],inorder[loc+1:])
return root
面试题7:用两个栈实现队列
Q:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在队列尾部插入结点和在队列头部删除结点的功能。
A:操作两个“先进后出”的栈实现一个“先进先出”的队列。当 stackout 中不为空时,在stackout中的栈顶元素是最先进入队列的元素,可以弹出。如果stackout为空时,我们把stackin中的元素逐个弹出并压入stackout。由于先进入队列的元素被压到stackin的底端,经过弹出和压入之后就处于stackout的顶端了,又可以直接弹出。
class CQueue:
def __init__(self):
self.stackin=[]
self.stackout=[]
def appendTail(self, value: int) -> None:
self.stackin.append(value)
def deleteHead(self) -> int:
if self.stackout:
return self.stackout.pop()
elif not self.stackin:
return -1
else:
while self.stackin:
self.stackout.append(self.stackin.pop())
return self.stackout.pop()
面试题8:旋转数组的最小数字
Q:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
A:旋转之后的数组实际上可以划分为两个排序的子数组,而且前面的子数组的元素都大于或者等于后面子数组的元素。最小的元素刚好是这两个子数组的分界线。在排序的数组中可以用二分查找法实现O(logn)的查找。
若中点比末尾元素大,则最小数字出现在后半部分;若中点比末尾元素小,则出现在前半部分。
class Solution:
def minArray(self, numbers: List[int]) -> int:
if len(numbers)==1:
return numbers[0]
mid=(len(numbers)-1)//2
if numbers[mid]>numbers[-1]:
return self.minArray(numbers[mid+1:])
elif numbers[mid]<numbers[-1]:
return self.minArray(numbers[:mid+1])
else:
if len(numbers)>1:
numbers.pop(mid)
return self.minArray(numbers)
面试题9:斐波那契数列
Q1:写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项。
A:用递归方法计算的时间复杂度是以 n 的指数的方式递增的。改进的方法并不复杂。递归代码之所以慢是因为重复的计算太多,只要想办法避免重复计算就行了。比如可以把已经得到的数列中间项保存起来,如果下次需要计算的时候先查找一下,如果前面已经计算过就不用再重复计算了。
class Solution:
def Fibonacci(self, n):
# write code here
num1 = 0
num2 = 1
target = 0
for i in range(1, n+1):
num1 = num2
num2 = target
target = num1 + num2
return target
Q2:一只青蛙一次可以跳上1 级台阶,也可以跳上2 级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
A:跳第n阶有两种情况,由倒数第二阶直接跳到第n阶,倒数第一阶直接跳到第n阶。
class Solution:
def numWays(self, n: int) -> int:
if n==0 :
return 1
elif n<=2:
return n
else:
a=1
b=2
res=0 #a保存f(n-2),b保存f(n-1),res保存f(n-1)+f(n-2)
for i in range(3,n+1):
res=a+b
a=b
b=res
return res
面试题10:二进制中1的个数
Q:请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。
A1:如果一个整数不等于0,那么该整数的二进制表示中至少有一位是1。先假设这个数的最右边一位是1,那么减去1时,最后一位变成0而其他所有位都保持不变。也就是最后一位相当于做了取反操作,由1变成了0。接下来假设最后一位不是1 而是0 的情况。如果该整数的二进制表示中最右边1位于第m位,那么减去1时,第m位由1变成0,而第m位之后的所有0都变成1,整数中第m位之前的所有位都保持不变。举个例子:一个二进制数 1100,它的第二位是从最右边数起的一个 1。减去 1 后,第二位变成0,它后面的两位0变成1,而前面的1保持不变,因此得到的结果是1011。在前面两种情况中,我们发现把一个整数减去1,都是把最右边的1变成0。如果它的右边还有0的话,所有的0都变成1,而它左边所有位都保持不变。接下来我们把一个整数和它减去 1 的结果做位与运算,相当于把它最右边的1变成0。还是以前面的1100为例,它减去1的结果是1011。我们再把1100和1011做位与运算,得到的结果是1000。我们把1100最右边的1变成了0,结果刚好就是1000。
把一个整数减去1(都是把最右边的1变成0),再和原整数做与运算,会把该整数最右边一个1变成0。那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作。
class Solution:
def hammingWeight(self, n: int) -> int:
count=0
if n < 0:
n = n & 0xffffffff
while n!= 0:
count += 1
n = (n-1)& n
return count
A2:python内置函数可以直接计算1的个数。
class Solution:
def hammingWeight(self, n: int) -> int:
return bin(n).count('1')
面试题11:数值的整数次方
Q:实现函数 double Power(double base, int exponent),求 base 的exponent次方。不得使用库函数,同时不需要考虑大数问题。
A:快速幂的思想。
向下整除 n // 2 等价于 右移一位 n >> 1 ;
取余数 n % 2 等价于 判断二进制最右一位值 n&1 。
class Solution:
def myPow(self, x: float, n: int) -> float:
if x == 0:
return 0
res = 1
if n < 0:
x, n = 1 / x, -n
while n:
if n & 1:
res *= x
x *= x
n >>= 1
return res
面试题12:打印1到最大的n位数
Q:输入数字n,按顺序打印出从1最大的n位十进制数。比如输入3,则打印出1、2、3一直到最大的3位数即999。
A1:先求出最大的n位数,然后循环打印输出。
class Solution:
def printNumbers(self, n: int) -> List[int]:
ret=[]
m=pow(10,n)-1
for i in range(1,m+1):
ret.append(i)
return ret
A2:需要考虑大数越界的情况,采用字符串替代数字。
class Solution:
def printNumbers(self, n: int) -> List[int]:
if n < 1:
return
string = ['0'] * n #初始化为全0字符串
res = []
while not self.dfs(string): #遍历到最小index时终止,如n=3, 遍历到999,再加1后1000,最小index置位,终止
index = 0
while index < len(string) and string[index] == '0': #消除列表字符串中前面的‘0’
index += 1
res.append(int(''.join(string[index:]))) #注意转换
return res
def dfs(self, string):
isCarry = False
for i in range(len(string)-1, -1, -1): #反向遍历
s = str(int(string[i]) + 1) #当前位字符串加1,低位加到‘9’后,不退出循环,高位加1,低位清0
if int(s) > 9:
string[i] = '0'
if i == 0:
isCarry = True
else: #未到9时,当前位加1
string[i] = s
break
return isCarry
面试题13:在O(1)时间删除链表结点
Q:给定单向链表的头指针和一个结点指针,定义一个函数在 O(1)时间删除该结点。
A:在单向链表中删除一个结点,最常规的做法无疑是从链表的头结点开始,顺序遍历查找要删除的结点,并在链表中删除该结点。这种思路由于需要顺序查找,时间复杂度自然就是O(n)了。
得到要删除的结点的一下结点。如果我们把下一个结点的内容复制到需要删除的结点上覆盖原有的内容,再把下一个结点删除,那是不是就相当于把当前需要删除的结点删除了。
对于 n-1 个非尾结点而言,我们可以在 O(1)时把下一个结点的内存复制覆盖要删除的结点,并删除下一个结点;对于尾结点而言,由于仍然需要顺序查找,时间复杂度是O(n)。因此总的平均时间复杂度是[(n-1)*O(1)+O(n)]/n,结果还是O(1)
class Solution:
def deleteNode(self, head, val):
if head is None or val is None:
return None
if val.next is not None: # 待删除节点不是尾节点
tmp = val.next
val.val = tmp.val
val.next = tmp.next
elif head == val: # 待删除节点只有一个节点,此节点为头节点
head = None
else:
cur = head # 待删除节点为尾节点
while cur.next != val:
cur = cur.next
cur.next = None
return head
面试题14:调整数组顺序使奇数位于偶数前面
Q:调整数组顺序使奇数位于偶数前面
A:维护两个指针,第一个指针初始化时指向数组的第一个数字,它只向后移动;第二个指针初始化时指向数组的最后一个数字,它只向前移动。在两个指针相遇之前,第一个指针总是位于第二个指针的前面。如果第一个指针指向的数字是偶数,并且第二个指针指向的数字是奇数,我们就交换这两个数字。
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
i,j=0,len(nums)-1
while i<j:
while i < j and nums[i] & 1 == 1:
i += 1
while i < j and nums[j] & 1 == 0:
j -= 1
nums[i], nums[j] = nums[j], nums[i]
return nums
面试题15:链表中倒数第k个结点
Q:输入一个链表,输出该链表中倒数第 k 个结点。为了符合大多数人的习惯,本题从1 开始计数,即链表的尾结点是倒数第1 个结点。例如一个链表有6个结点,从头结点开始它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个结点是值为4的结点。
A:双指针。
为了实现只遍历链表一次就能找到倒数第 k 个结点,我们可以定义两个指针。第一个指针从链表的头指针开始遍历向前走k-1,第二个指针保持不动;从第 k 步开始,第二个指针也开始从链表的头指针开始遍历。由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
former, latter = head, head
for _ in range(k):
former = former.next
while former:
former, latter = former.next, latter.next
return latter
面试题16:反转链表
Q:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。
A:需要定义 3 个指针,分别指向当前遍历到的结点、它的前一个结点及后一个结点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
res=None
while head:
res,res.next,head=head,res,head.next
return res
面试题17:合并两个排序的链表
Q:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。
A:比较头节点大小,小的作为合并后链表的头节点,再比较剩余部分和另一个链表的头节点,取小的,然后一直递归此过程。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
p=head=ListNode(None)
while l1 and l2:
if l1.val<=l2.val:
p.next,l1=l1,l1.next
else:
p.next,l2=l2,l2.next
p=p.next
p.next=l1 if l1 else l2
return head.next
面试题18:树的子结构
Q:输入两棵二叉树A和B,判断B是不是A的子结构。
A:要查找树A中是否存在和树B结构一样的子树,我们可以分成两步:第一步在树A中找到和B的根结点的值一样的结点R,第二步再判断树A中以R为根结点的子树是不是包含和树B一样的结构。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
if not A or not B:
return False
return self.isInclude(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)
def isInclude(self, A, B):
if not B:
return True
if not A:
return False
if A.val != B.val:
return False
return self.isInclude(A.left, B.left) and self.isInclude(A.right, B.right)
面试题19:二叉树的镜像
Q:请完成一个函数,输入一个二叉树,该函数输出它的镜像。
A:先前序遍历这棵树的每个结点,如果遍历到的结点有子结点,就交换它的两个子结点。当交换完所有非叶子结点的左右子结点之后,就得到了树的镜像。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def mirrorTree(self, root: TreeNode) -> TreeNode:
if root==None:
return None
root.left,root.right=root.right,root.left
self.mirrorTree(root.left)
self.mirrorTree(root.right)
return root
面试题20:顺时针打印矩阵
Q:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
A:可以用一个循环来打印矩阵,每一次打印矩阵中的一个圈。顺时针打印矩阵的顺序是 “从左向右、从上向下、从右向左、从下向上” 循环。
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix:
return []
l, r, t, b, res = 0, len(matrix[0]) - 1, 0, len(matrix) - 1, []
while True:
for i in range(l, r + 1): # left to right
res.append(matrix[t][i])
t += 1
if t > b:
break
for i in range(t, b + 1): # top to bottom
res.append(matrix[i][r])
r -= 1
if l > r:
break
for i in range(r, l - 1, -1): # right to left
res.append(matrix[b][i])
b -= 1
if t > b:
break
for i in range(b, t - 1, -1): # bottom to top
res.append(matrix[i][l])
l += 1
if l > r:
break
return res
面试题21:包含min函数的栈
Q:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min、push及pop的时间复杂度都是O(1)。
A:设置辅助栈,把每次的最小元素(之前的最小元素和新压入栈的元素两者的较小值)都保存起来放到另外一个辅助栈。
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack=[]
self.minstack=[]
def push(self, x: int) -> None:
self.stack.append(x)
if not self.minstack or x<=self.minstack[-1]:
self.minstack.append(x)
def pop(self) -> None:
if self.stack.pop()==self.minstack[-1]:
self.minstack.pop()
def top(self) -> int:
return self.stack[-1]
def min(self) -> int:
return self.minstack[-1]
# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.min()
面试题22:栈的压入、弹出序列
Q:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1、2、3、4、5 是某栈的压栈序列,序列4、5、3、2、1 是该压栈序列对应的一个弹出序列,但 4、3、5、1、2 就不可能是该压栈序列的弹出序列。
A:只需测试popped序列是否合法。建立一个辅助栈进行模拟,每次push进辅助栈后,和popped序列中的head比较,如果一样就弹出;整个遍历结束后如果stack为空了,说明序列正确。
class Solution:
def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
stack=[]
i=0
for num in pushed:
stack.append(num)
while stack and stack[-1]==popped[i]:
stack.pop()
i+=1
if not stack:
return True
else:
return False
面试题23:从上往下打印二叉树
Q:从上往下打印出二叉树的每个结点,同一层的结点按照从左到右的顺序打印。
A:实质是考查树的遍历算法。每一次打印一个结点的时候,如果该结点有子结点,则把该结点的子结点放到一个队列的末尾。接下来到队列的头部取出最早进入队列的结点,重复前面的打印操作,直至队列中所有的结点都被打印出来为止。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def levelOrder(self, root: TreeNode) -> List[int]:
temp=[root]
ans=[]
while len(temp)>0:
if temp[0] is not None:
ans.append(temp[0].val)
temp.append(temp[0].left)
temp.append(temp[0].right)
temp.pop(0)
return ans
面试题24:二叉搜索树的后序遍历序列
Q:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
A:在后序遍历得到的序列中,最后一个数字是树的根结点的值。数组中前面的数字可以分为两部分:第一部分是左子树结点的值,它们都比根结点的值小;第二部分是右子树结点的值,它们都比根结点的值大。递归
class Solution:
def verifyPostorder(self, postorder: List[int]) -> bool:
if len(postorder)<=1:
return True
rootNum=postorder[-1]
for idx in range(len(postorder)):
if postorder[idx]>=rootNum:
break
left=postorder[:idx]
right=postorder[idx:len(postorder)-1]
for i in right:
if i<rootNum:
return False
leftSign=self.verifyPostorder(left)
rightSign=self.verifyPostorder(right)
if leftSign and rightSign:
return True
return False
面试题25:二叉树中和为某一值的路径
Q:输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
A:前序遍历
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
res, path = [], []
def recur(root, tar):
if not root: return
path.append(root.val)
tar -= root.val
if tar == 0 and not root.left and not root.right:
res.append(list(path))
recur(root.left, tar)
recur(root.right, tar)
path.pop()
recur(root, sum)
return res
面试题26:复杂链表的复制
Q:请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
A:复制各节点,构建拼接链表;构建新链表各节点的 random 指向;拆分原 / 新链表,返回新链表的头节点 res 即可。
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return
cur=head
# 1. 复制各节点,并构建拼接链表
while cur:
tmp = Node(cur.val)
tmp.next = cur.next
cur.next = tmp
cur = tmp.next
# 2. 构建各新节点的 random 指向
cur = head
while cur:
if cur.random:
cur.next.random = cur.random.next
cur = cur.next.next
# 3. 拆分两链表
cur = res = head.next
pre = head
while cur.next:
pre.next = pre.next.next
cur.next = cur.next.next
pre = pre.next
cur = cur.next
pre.next = None # 单独处理原链表尾节点
return res # 返回新链表头节点
面试题27:二叉搜索树与双向链表
Q:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
A:在搜索二叉树中,左子结点的值总是小于父结点的值,右子结点的值总是大于父结点的值。因此我们在转换成排序双向链表时,原先指向左子结点的指针调整为链表中指向前一个结点的指针,原先指向右子结点的指针调整为链表中指向后一个结点指针。中序遍历+递归。
"""
# Definition for a Node.
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
"""
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
def dfs(cur):
if not cur:
return
dfs(cur.left) # 递归左子树
if self.pre: # 修改节点引用
self.pre.right, cur.left = cur, self.pre
else: # 记录头节点
self.head = cur
self.pre = cur # 保存 cur
dfs(cur.right) # 递归右子树
if not root:
return
self.pre = None
dfs(root)
self.head.left, self.pre.right = self.pre, self.head
return self.head
面试题28:字符串的排列
Q:输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。
A:求整个字符串的排列,可以看成两步:首先求所有可能出现在第一个位置的字符,即把第一个字符和后面所有的字符交换。求后面所有字符的排列。这个时候我们仍把后面的所有字符分成两部分:后面字符的第一个字符,以及这个字符之后的所有字符。然后把第一个字符逐一和它后面的字符交换。递归思路。
class Solution:
def permutation(self, s: str) -> List[str]:
res = []
t = []
def dfs(subs):
l = len(subs)
if l == 1:
t.append(subs)
res.append(''.join(t))
t.pop()
return
for i in range(l):
if subs[i] in subs[:i]:
continue
t.append(subs[i])
dfs(subs[:i]+subs[i+1:])
t.pop()
dfs(s)
return res
面试题29:数组中出现次数超过一半的数字
Q:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
A1:把这个数组排序,那么排序之后位于数组中间的数字一定就是那个出现次数超过数组长度一半的数字。也就是说,这个数字就是统计学上的中位数,即长度为n的数组中第n/2 大的数字。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums=sorted(nums)
return nums[len(nums)//2]
A2:数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有数字出现次数的和还要多。因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的一个数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数字相同,则次数加1;如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为零,我们需要保存下一个数字,并把次数设为1。由于我们要找的数字出现的次数比其他所有数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设为 1 时对应的数字。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
count, majority = 1, nums[0]
for num in nums[1:]:
if count == 0:
majority = num
if num == majority:
count += 1
else:
count -= 1
return majority
面试题30:最小的k个数
Q:输入n个整数,找出其中最小的k个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
A:保存前k个值在result中,依次遍历后续n-k个值,如果出现数小于result的最大值,替换这个最大值。
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
if k > len(arr) or k == 0:
return []
result = arr[:k]
for num in arr[k:]:
temp_max = max(result)
if num < temp_max:
result.remove(temp_max)
result.append(num)
return result
面试题31:连续子数组的最大和
Q:输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。
A:动态规划
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
for i in range(1,len(nums)):
nums[i]=nums[i]+max(nums[i-1],0)
return max(nums)
面试题32:从1到n整数中1出现的次数
Q:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次。
A:
找规律:
0 ~ 9 只有一个1,那么如果某数是10的a倍,那么个位上就有a个1;
10 ~ 99 十位上有10个1,即10 ~ 19,那么如果某数是 100 的a倍,是10的b倍,那么十位有a * 10个1,个位有b个1,共 (a * 10 + b * 1) 个1;
100 ~ 999 百位上有100个,即100 ~ 199,那么如果某数是 1000 的a倍, 100 的b倍,是10的c倍,那么百位有a * 100个1,十位有b * 10个1,个位有c个1,共 (a * 100 + b * 10 + c) 个1;
余数规则:
如果某数是100的x倍,根据余数y判断实际十位共有多少1
y >= 20,那么十位有(x + 1) * 10个1
10 <= y <20,那么十位有 x * 10 + (y + 1 - 10), 即y + 1 + (x - 1) * 10
y < 10,那么十位有x * 10个1
class Solution:
def countDigitOne(self, n: int) -> int:
a, b, one_count = 1, 10, 0
while n >= a:
x, y = divmod(n, b)
if y >= a * 2:
one_count += (x + 1) * a
elif y >= a:
one_count += y + 1 + (x - 1) * a
else:
one_count += x * a
a, b = b, b*10
return one_count
面试题33:把数组排成最小的数
Q:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3, 32, 321},则打印出这3个数字能排成的最小数字321323。
A:两两比较,再排序
class cmpSmaller(str):
def __lt__(self, y):
return self + y < y + self # 字符串拼接比较(两两比较)
# 按由小到大来排列
class Solution:
def minNumber(self, nums: List[int]) -> str:
res=sorted(map(str, nums),key=cmpSmaller)
smallest = ''.join(res)
return smallest
面试题34:丑数
Q:我们把只包含因子2、3和5的数称作丑数(UglyNumber)。求按从小到大的顺序的第1500个丑数。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做第一个丑数。
A:记录上一个时刻乘以(2/3/5)的最小时刻,下个丑数及对应的每个时刻乘以2/3/5的最小数
class Solution:
def nthUglyNumber(self, n: int) -> int:
#记录上一个时刻乘以(2/3/5)的最小时刻,下个丑数及对应的每个时刻乘以2/3/5的最小数
ugly = [1] # 利用数组记录
i2 = i3 = i5 =0
while n>1:
u2, u3, u5 = ugly[i2]*2, ugly[i3]*3, ugly[i5]*5
umin = min(u2, u3, u5)
if umin == u2:
i2 += 1
if umin == u3:
i3 += 1
if umin == u5:
i5 += 1
n -= 1
ugly.append(umin)
return ugly[-1]
面试题35:第一个只出现一次的字符
Q:在字符串中找出第一个只出现一次的字符。如输入"abaccdeff",则输出’b’。
A:定义哈希表的键值(Key)是字符,而值(Value)是该字符出现的次数。同时我们还需要从头开始扫描字符串两次。第一次扫描字符串时,每扫描到一个字符就在哈希表的对应项中把次数加1。接下来第二次扫描时,每扫描到一个字符就能从哈希表中得到该字符出现的次数。这样第一个只出现一次的字符就是符合要求的输出。
class Solution:
def firstUniqChar(self, s: str) -> str:
dic = {
}
for c in s:
dic[c] = not c in dic #第一次出现,为true 后面再出现,置为false
for c in s:
if dic[c]: return c
return ' '
面试题36:数组中的逆序对
Q:在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
A:归并排序,用了一个列表记录每次的逆序对。
class Solution:
def reversePairs(self, nums: List[int]) -> int:
if len(nums) <2:
return 0
res = []
def mergesort(nums,res):
size = len(nums)
if size <2:
return nums
middle = size //2
left = mergesort(nums[:middle],res)
right = mergesort(nums[middle:],res)
j = 0
count = 0
for i in range(len(left)):
while j < len(right):
if left[i] > right[j]:
count += len(right) - j
break
else:
j += 1
if j >= len(right):
break
res.append(count)
left += right
left.sort(reverse = True)
return left
mergesort(nums,res)
return sum(res)
面试题37:两个链表的第一个公共结点
Q:输入两个链表,找出它们的第一个公共结点。
A:两个链表长度分别为L1+C、L2+C, C为公共部分的长度,按照楼主的做法: 第一个人走了L1+C步后,回到第二个人起点走L2步;第2个人走了L2+C步后,回到第一个人起点走L1步。 当两个人走的步数都为L1+L2+C时就两个家伙就相爱了。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
node1, node2 = headA, headB
while node1 != node2:
node1 = node1.next if node1 else headB
node2 = node2.next if node2 else headA
return node1
面试题38:数字在排序数组中出现的次数
Q:统计一个数字在排序数组中出现的次数。例如输入排序数组{1,2, 3,3,3,3,4,5}和数字3,由于3在这个数组中出现了4次,因此输出4。
A:二分法
class Solution:
def search(self, nums: List[int], target: int) -> int:
# 搜索右边界 right
i, j = 0, len(nums) - 1
while i <= j:
m = (i + j) // 2
if nums[m] <= target:
i = m + 1
else:
j = m - 1
right = i
# 若数组中无 target ,则提前返回
if j >= 0 and nums[j] != target:
return 0
# 搜索左边界 left
i = 0
while i <= j:
m = (i + j) // 2
if nums[m] < target:
i = m + 1
else:
j = m - 1
left = j
return right - left - 1
面试题39:二叉树的深度
Q1:输入一棵二叉树的根结点,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
A:如果一棵树只有一个结点,它的深度为1。如果根结点只有左子树而没有右子树,那么树的深度应该是其左子树的深度加1;同样如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1。如果既有右子树又有左子树,那该树的深度就是其左、右子树深度的较大值再加1。递归
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
if not root.left and not root.right:
return 1
L=self.maxDepth(root.left)+1
R=self.maxDepth(root.right)+1
return max(L,R)
Q2:输入一棵二叉树的根结点,判断该树是不是平衡二叉树。如果某二叉树中任意结点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
A:用后序遍历的方式遍历二叉树的每一个结点,在遍历到一个结点之前我们就已经遍历了它的左右子树。只要在遍历每个结点的时候记录它的深度(某一结点的深度等于它到叶节点的路径的长度),我们就可以一边遍历一边判断每个结点是不是平衡的。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
return self.treeHeight(root)>=0
def treeHeight(self,root):
if not root:
return 0
leftHeight=self.treeHeight(root.left)
rightHeight=self.treeHeight(root.right)
if leftHeight>=0 and rightHeight>=0 and abs(leftHeight-rightHeight)<=1:
return max(leftHeight,rightHeight)+1
else:
return -1
面试题40:数组中只出现一次的数字
Q:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是 O(n),空间复杂度是O(1)。
A:把原数组分成两个子数组,使得每个子数组包含一个只出现一次的数字,而其他数字都成对出现两次。
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
ret, index = 0, 0
for n in nums:
ret ^= n
# 找从右向左数第几位不同,也就是第index位
while ret & 1 == 0:
index += 1
ret >>= 1
r1, r2 = 0, 0
for n in nums:
if (n >> index) & 1 == 0:
r1 ^= n
else:
r2 ^= n
return [r1, r2]
面试题41:和为s的两个数字VS和为s的连续正数序列
Q1:输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,输出任意一对即可。
A:定义两个指针,第一个指针指向数组的第一个数字 ,第二个指针指向数组的最后一个数字。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
n=len(nums)
l,r=0,n-1
while l<r:
sum=nums[l]+nums[r]
if sum==target:
return [nums[l],nums[r]]
elif sum>target:
r-=1
else:
l+=1
return []
Q2:输入一个正数s,打印出所有和为s的连续正数序列(至少含有两个数)。例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以结果打印出3个连续序列1~5、4~6和7~8。
A:用两个数small和big分别表示序列的最小值和最大值。首先把small初始化为1,big初始化为2。如果从small到big的序列的和大于s,我们可以从序列中去掉较小的值,也就是增大small的值。如果从small到big的序列的和小于s,我们可以增大big,让这个序列包含更多的数字。因为这个序列至少要有两个数字,我们一直增加small到(1+s)/2为止。
class Solution:
def findContinuousSequence(self, target: int) -> List[List[int]]:
res=[]
l,r=1,2
while l<r:
a=[]
sum=(l+r)*(r-l+1)/2
if sum<target:
r+=1
elif sum>target:
l+=1
else:
for i in range(l,r+1):
a.append(i)
res.append(a)
l+=1
r+=1
return res
面试题42:翻转单词顺序 VS左旋转字符串
Q1:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student.",则输出"student.a am I"。
A:双指针
class Solution:
def reverseWords(self, s: str) -> str:
s = s.strip() # 删除首尾空格
i = j = len(s) - 1
res = []
while i >= 0:
while i >= 0 and s[i] != ' ':
i -= 1 # 搜索首个空格
res.append(s[i + 1: j + 1]) # 添加单词
while s[i] == ' ':
i -= 1 # 跳过单词间空格
j = i # j 指向下个单词的尾字符
return ' '.join(res) # 拼接并返
Q2:字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如输入字符串"abcdefg"和数字2,该函数将返回左旋转2位得到的结果"cdefgab"。
A:两次遍历;先遍历后面,再遍历前面。
class Solution:
def reverseLeftWords(self, s, n):
res = ""
for i in range(n, len(s)):
res += s[i]
for j in range(n):
res += s[j]
return res
面试题43:n个骰子的点数
Q:把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
A:用两个数组来存储骰子点数的每一个总数出现的次数。在一次循环中,第一个数组中的第 n 个数字表示骰子和为n出现的次数。在下一循环中,我们加上一个新的骰子,此时和为 n 的骰子出现的次数应该等于上一次循环中骰子点数和为 n-1、n-2、n-3、n-4、n-5与 n-6的次数的总和,所以我们把另一个数组的第 n个数字设为前一个数组对应的第 n-1、n-2、n-3、n-4、n-5与 n-6之和。
class Solution:
def dicesProbability(self, n: int) -> List[float]:
dp = [ [0 for _ in range(6*n+1)] for _ in range(n+1)]
for i in range(1,7):
dp[1][i] = 1
for i in range(2,n+1):
for j in range(i,i*6+1):
for k in range(1,7):
if j >= k+1:
dp[i][j] +=dp[i-1][j-k]
res = []
for i in range(n,n*6+1):
res.append(dp[n][i]*1.0/6**n)
return res
面试题44:扑克牌的顺子
Q:从扑克牌中随机抽 5张牌,判断是不是一个顺子,即这 5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王可以看成任意数字。
A:大、小王是特殊的数字,我们不妨把它们都定义为0,这样就能和其他扑克牌区分开来了。首先把数组排序,再统计数组中0的个数,判断数组内除零外元素是否重复,最后统计排序之后的数组中相邻数字之间的空缺总数。如果空缺的总数小于或者等于0的个数,那么这个数组就是连续的;反之则不连续。
class Solution:
def isStraight(self, nums: List[int]) -> bool:
nums.sort()
numZeros=nums.count(0)
if len(set(nums[numZeros:]))<len(nums[numZeros:]):
return False
return nums[-1]-nums[numZeros]<=4
面试题45:圆圈中最后剩下的数字
Q:0,1,…,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
A:要得到 n个数字的序列中最后剩下的数字,只需要得到 n-1 个数字的序列中最后剩下的数字,并以此类推。当n=1时,也就是序列中开始只有一个数字0,那么很显然最后剩下的数字就是0。
class Solution:
def lastRemaining(self, n: int, m: int) -> int:
if n<1 or m<0:
return -1
last=0
for i in range(2,n+1):
last=(last+m)%i
return last
面试题46:求1+2+…+n
Q:求1+2+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
A:等差数列求和
class Solution:
def sumNums(self, n: int) -> int:
return (n*(n+1))//2 #等差数列求和
面试题47:不用加减乘除做加法
Q:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、×、÷四则运算符号。
A:把二进制的加法用位运算来替代。第一步不考虑进位对每一位相加。0加0、1加1的结果都0,0加1、1加0的结果都是1。我们注意到,这和异或的结果是一样的。对异或而言,0和0、1和1异或的结果是0,而0和1、1和0的异或结果是1。接着考虑第二步进位,对0加0、0加1、1加0而言,都不会产生进位,只有1加1时,会向前产生一个进位。此时我们可以想象成是两个数先做位与运算,然后再向左移动一位。只有两个数都是1的时候,位与得到的结果是1,其余都是0。第三步把前两个步骤的结果相加。第三步相加的过程依然是重复前面两步,直到不产生进位为止。
class Solution:
def add(self, a: int, b: int) -> int:
while b:
result = (a ^ b) & 0xffffffff
carry = ((a & b) << 1) & 0xffffffff
a = result
b = carry
if a <= 0x7fffffff:
result = a
else:
result = -((a - 1) ^ 0xffffffff)
return result
面试题49:把字符串转换成整数
Q:写一个函数 StrToInt,实现把字符串转换成整数这个功能。不能使用 atoi 或者其他类似的库函数。
A:首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。
当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。
该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。
注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。
在任何情况下,若函数不能进行有效的转换时,请返回 0。
class Solution:
def strToInt(self, str: str) -> int:
str = str.strip() # 删除首尾空格
if not str:
return 0 # 字符串为空则直接返回
res, i, sign = 0, 1, 1
int_max, int_min, bndry = 2 ** 31 - 1, -2 ** 31, 2 ** 31 // 10
if str[0] == '-':
sign = -1 # 保存负号
elif str[0] != '+':
i = 0 # 若无符号位,则需从 i = 0 开始数字拼接
for c in str[i:]:
if not '0' <= c <= '9' :
break # 遇到非数字的字符则跳出
if res > bndry or res == bndry and c > '7':
return int_max if sign == 1 else int_min # 数字越界处理
res = 10 * res + ord(c) - ord('0') # 数字拼接
return sign * res
面试题50:树中两个结点的最低公共祖先
Q:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
A:求出到两个节点的路径,然后遍历数组,不相等的前一个为解。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if root == None:
return None
if root==p or root == q:
return root
left = self.lowestCommonAncestor(root.left,p,q) # 左右子树寻找p,q
right = self.lowestCommonAncestor(root.right,p,q)
if left and right: # 都找到了,一边一个
return root
if not left: # 左子树没有,即都在右子树
return right
if not right:
return left