文章目录
题1
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
它考察的是程序员的沟通能力,先问面试官要时间/空间需求!!!
只是时间优先就用字典,
还有空间要求,就用指针+原地排序数组,
如果面试官要求空间O(1)并且不能修改原数组,还得写成二分法!!!。
Python3 方法一: 先排序,然后看相邻元素是否有相同的,有直接return。 不过很慢,时间O(nlogn)了,空间O(1)
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
nums.sort()
pred = nums[0]
for i in range(1,len(nums)):
if nums[i] == pred:
return nums[i]
else:
pred = nums[i]
方法二:哈希表 时间O(n),空间O(n)
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
repeatDict = {}
for i in nums:
if i not in repeatDict:
repeatDict[i] = 1
else:
return i
方法三:时间复杂度O(n),空间复杂度O(1)。可以看做是一种原地哈希,不过没有用到字典。具体做法就是因为题目中给的元素是 < len(nums)的,所以我们可以让 位置i 的地方放元素i。如果位置i的元素不是i的话,那么我们就把i元素的位置放到它应该在的位置,即 nums[i] 和nums[nums[i]]的元素交换,这样就把原来在nums[i]的元素正确归位了。如果发现 要把 nums[i]正确归位的时候,发现nums[i](这个nums[i]是下标)那个位置上的元素和要归位的元素已经一样了,说明就重复了,重复了就return
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
for i in range(len(nums)):
while i != nums[i]:
if nums[i] == nums[nums[i]]:
return nums[i]
tmp = nums[i] # tmp表示元素 num[i]的正确位置
nums[i] = nums[tmp]
nums[tmp] = tmp
题2
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解法一:找规律,从左下角开始遍历,当该值小于 target 值时,向右搜索;大于 target 值时,向上搜索。如果找到 target 则返回 True,否则返回 False。
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if not matrix: return False
m, n = len(matrix), len(matrix[0])
row, col = m - 1, 0
while row >= 0 and col < n:
if matrix[row][col] < target:
col += 1
elif matrix[row][col] > target:
row -= 1
else:
return True
return False
- 时间复杂度:O(m+n)。m 和 n 分别为行数和列数。最坏情况下,我们从左下角移动到右上角,经过的路径长度为。
- 空间复杂度:O(1)O(1)。
解法二:
首先在对角线上迭代,对于每一个对角线元素,对该元素所在的行和列使用二分搜索。时间复杂度:O(lgk!),k=min(m,n)。空间复杂度:O(1)O(1)
class Solution:
def binary_search(self, matrix, target, start, vertical):
lo = start
hi = len(matrix) - 1 if vertical else len(matrix[0]) - 1 # 垂直搜索:hi = 行数 - 1
while lo <= hi:
mid = (lo + hi) // 2
if vertical: # 垂直搜索
if matrix[mid][start] < target:
lo = mid + 1
elif matrix[mid][start] > target:
hi = mid - 1
else:
return True
else: # 水平搜索
if matrix[start][mid] < target:
lo = mid + 1
elif matrix[start][mid] > target:
hi = mid - 1
else:
return True
return False
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if not matrix: return False # 边界条件
for i in range(min(len(matrix), len(matrix[0]))):
vertical_found = self.binary_search(matrix, target, i, True) # 垂直方向是否找到
horizontal_found = self.binary_search(matrix, target, i, False) # 水平是否找到
if vertical_found or horizontal_found: # 任一方向找到即可
return True
return False
题3
题目:
解法一:
初始化一个 list ,记为 res ;遍历列表 s 中的每个字符 c :
当 c 为空格时:向 res 后添加字符串 “%20”;当 c 不为空格时:向 res 后添加字符 c ;
将列表 res 转化为字符串并返回。
class Solution:
def replaceSpace(self, s: str) -> str:
res = [] # res=''
for c in s:
if c == ' ': res.append("%20") # res+='%20'
else: res.append(c)
return "".join(res)
时间复杂度 O ( N ) O(N) O(N) 空间复杂度 O ( N ) O(N) O(N)
解法二:使用内置函数
return s.replace(" ","%20");
题4
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
方法一:递归法
解题思路:
利用递归: 先走至链表末端,回溯时依次将节点值加入列表 ,这样就可以实现链表值的倒序输出。
递推阶段: 每次传入 head.next ,以 head == None(即走过链表尾部节点)为递归终止条件,此时返回空列表 [] 。
回溯阶段: 利用 Python 语言特性,递归回溯时每次返回 当前 list + 当前节点值 [head.val] ,即可实现节点的倒序输出。
时间复杂度 O(N),空间复杂度 O(N)
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
return self.reversePrint(head.next) + [head.val] if head else []
方法二:辅助栈法
时间复杂度 O(N),空间复杂度 O(N)
class Solution:
def reversePrint(self, head: ListNode) -> List[int]:
stack = []
while head:
stack.append(head.val)
head = head.next
return stack[::-1]
# stack[i::-1]表示从第i个元素开始翻转读取
题5:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
题6:两个栈实现队列
class CQueue:
def __init__(self):
self.stack1 = []
self.stack2 = []
def appendTail(self, value: int) -> None:
# 1 -> 2
while self.stack1:
self.stack2.append(self.stack1.pop())
# add value
self.stack1.append(value)
# 1 <- 2
while self.stack2:
self.stack1.append(self.stack2.pop())
return self.stack1
def deleteHead(self) -> int:
if not self.stack1: return -1
return self.stack1.pop()
题7:斐波那契数列
动态规划
class Solution:
def fib(self, n: int) -> int:
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a % 1000000007
递归:
class Solution:
def fib(self, n: int) -> int:
if n==0:return 0
if n==1:return 1
return (self.fib(n-1)+self.fib(n-2))%1000000007
时间复杂度:2**N。
空间复杂度:N,即为递归栈的最大深度。
应用:青蛙跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
设跳上 nn 级台阶有 f(n)f(n) 种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 11 级或 22 级台阶。
当为 11 级台阶: 剩 n-1n−1 个台阶,此情况共有 f(n-1)f(n−1) 种跳法;
当为 22 级台阶: 剩 n-2n−2 个台阶,此情况共有 f(n-2)f(n−2) 种跳法。
f(n)f(n) 为以上两种情况之和,即 f(n)=f(n-1)+f(n-2)f(n)=f(n−1)+f(n−2) ,以上递推性质为斐波那契数列。本题可转化为 求斐波那契数列第 nn 项的值 ,与 面试题10- I. 斐波那契数列 等价,唯一的不同在于起始数字不同。
青蛙跳台阶问题:
f
(
0
)
=
1
,
f
(
1
)
=
1
,
f
(
2
)
=
2
;
f(0)=1 , f(1)=1, f(2)=2;
f(0)=1,f(1)=1,f(2)=2;
斐波那契数列问题:
f
(
0
)
=
0
,
f
(
1
)
=
1
,
f
(
2
)
=
1
f(0)=0 , f(1)=1 , f(2)=1
f(0)=0,f(1)=1,f(2)=1
题8 旋转数组中的最小数字
寻找旋转数组的最小元素即为寻找 右排序数组 的首个元素
n
u
m
s
[
x
]
nums[x]
nums[x],称 x 为 旋转点 。
排序数组的查找问题首先考虑使用 二分法 解决,其可将 遍历法 的 线性级别 时间复杂度降低至 对数级别 。
算法流程:
初始化: 声明
i
,
j
i,j
i,j双指针分别指向
n
u
m
s
nums
nums数组左右两端;
循环二分: 设
m
=
(
i
+
j
)
/
2
m = (i + j) / 2
m=(i+j)/2 为每次二分的中点( “/” 代表向下取整除法,因此恒有
i
≤
m
<
j
i \leq m < j
i≤m<j),可分为以下三种情况:
(1)当
n
u
m
s
[
m
]
>
n
u
m
s
[
j
]
nums[m] > nums[j]
nums[m]>nums[j]时:
m
m
m 一定在左排序数组中,即旋转点
x
x
x一定在
[
m
+
1
,
j
]
[m + 1, j]
[m+1,j]闭区间内,因此执行 i = m + 1i=m+1;
当 nums[m] < nums[j]nums[m]<nums[j] 时: mm 一定在 右排序数组 中,即旋转点 xx 一定在[i, m][i,m] 闭区间内,因此执行
j
=
m
j=m
j=m;
当
n
u
m
s
[
m
]
=
n
u
m
s
[
j
]
nums[m] = nums[j]
nums[m]=nums[j] 时: 无法判断
m
m
m 在哪个排序数组中,即无法判断旋转点
x
x
x 在
[
i
,
m
]
[i, m]
[i,m]还是
[
m
+
1
,
j
]
[m + 1, j]
[m+1,j] 区间中。解决方案: 执行
j
=
j
−
1
j = j - 1
j=j−1 缩小判断范围。
返回值: 当
i
=
j
i =j
i=j 时跳出二分循环,并返回 旋转点的值
n
u
m
s
[
i
]
nums[i]
nums[i]即可
实现代码如下:
class Solution:
def minArray(self, numbers: [int]) -> int:
i, j = 0, len(numbers) - 1
while i < j:
m = (i + j) // 2
if numbers[m] > numbers[j]: i = m + 1
elif numbers[m] < numbers[j]: j = m
else: j -= 1
return numbers[i]
实际上,当出现 n u m s [ m ] = n u m s [ j ] nums[m] = nums[j] nums[m]=nums[j] 时,一定有区间 [ i , m ] [i, m] [i,m]内所有元素相等或 区间 [ m , j ] [m, j] [m,j]内所有元素相等(或两者皆满足)。对于寻找此类数组的最小值问题,可直接放弃二分查找,而使用线性查找替代。
class Solution:
def minArray(self, numbers: [int]) -> int:
i, j = 0, len(numbers) - 1
while i < j:
m = (i + j) // 2
if numbers[m] > numbers[j]: i = m + 1
elif numbers[m] < numbers[j]: j = m
else: return min(numbers[i:j])
return numbers[i]
复杂度分析:
时间复杂度
O
(
log
2
N
)
O(\log_2 N)
O(log2N),在特例情况下(例如 [1, 1, 1, 1]),会退化到 O(N)。
空间复杂度 O(1) :
i
,
j
,
m
i,j,m
i,j,m变量使用常数大小的额外空间。