先问清题目,各方面各种问,题目是什么意思,确认方法的输入输出,希望收到什么样的参数?如果不是这种参数怎么处理,输出什么样的结果?结果的范围是?
和面试官确认边界条件,上限是什么?下限是什么?corner case要充分讨论
写代码时最好不断交流,嘴巴里要说,别就只顾着写
最后要给面试官算法复杂度,注意,这里一定要说清楚是最好、平均、最坏,用词要严谨,这些都是细节
快排
最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。
输入:arr = [0, 8, 0, 3, 8, 2, 5], k=4
输出:[0, 0, 2, 3]
输入:arr = [0, 0, 1, 3, 4, 5, 0, 7, 6, 7], k=9
输出:[0, 0, 0, 1, 3, 4, 5, 6, 7]
若第k小的值出现在左侧向左递归,出现在右侧向右递归。
时间复杂度,N + N/2 + N/4 + ... + N/N = 2N, 最好O(N),最坏O(n^2)
def smallestK(arr: list, k: int):
if k == 0:
return []
if k == len(arr):
return arr
left, right = 0, len(arr) - 1
while left < right:
pivot = partition(arr, left, right)
if pivot < k - 1:
left = pivot + 1
elif pivot > k - 1:
right = pivot - 1
else:
return arr[:k]
return arr[:left + 1]
def partition(array, left, right):
pivot = array[right]
i = left
for j in range(left, right):
if array[j] < pivot:
array[j], array[i] = array[i], array[j]
i += 1
array[right], array[i] = array[i], array[right]
return i
最接近原点的 K 个点
我们有一个由平面上的点组成的列表 points。需要从中找出 K 个距离原点 (0, 0) 最近的点。
输入:points = [[3,3],[5,-1],[-2,4]], K = 2
输出:[[3,3],[-2,4]]
复杂度分析:
时间复杂度 O(N)
空间复杂度 O(N)
def kClosest(points, K):
def dist(i):
return points[i][0] ** 2 + points[i][1] ** 2
def work(i, j, K):
if i >= j:
return
oi, oj = i, j
pivot = dist(i)
basePoint = points[i]
while i < j:
while i < j and dist(j) >= pivot:
j -= 1
points[i] = points[j]
while i < j and dist(i) <= pivot:
i += 1
points[j] = points[i]
points[i] = basePoint
if K < i + 1 - oi:
work(oi, i - 1, K)
else:
work(i + 1, oj, K - (i + 1 - oi))
work(0, len(points) - 1, K)
return points[:K]
回溯
全排列
输入:s = “abc”
输出:[‘abc’, ‘acb’, ‘bac’, ‘bca’, ‘cab’, ‘cba’]
复杂度分析:
时间复杂度 O(N!) : N 为字符串 s 的长度;时间复杂度和字符串排列的方案数成
线性关系,方案数为 N * (N-1) * (N-2) … *2 * 1, ,因此复杂度为 O(N!) 。
空间复杂度 O(N^2): 全排列的递归深度为 N ,系统累计使用栈空间大小为 O(N) ;
递归中累计存储的字符数量最多为 N + (N-1) + ... + 2 + 1 = (N+1)N/2,占用
O(N^2) 的额外空间。
def permutation(s):
s_list = sorted(s)
res = []
def backtrack(s_list, tmp):
if not s_list:
res.append("".join(tmp))
for i in range(len(s_list)):
if i > 0 and s_list[i] == s_list[i - 1]:
continue
backtrack(s_list[:i] + s_list[i + 1:], tmp + [s_list[i]])
backtrack(s_list, [])
return res
组合总和
给定一个无重复元素的数组 nums 和一个目标数 target ,找出 nums 中所有可以使数字和为 target 的组合。
nums 中的数字可以无限制重复被选取。
输入:nums=[2, 3, 6, 7], target=7
输出:[[2, 2, 3], [7]]
def combinationSum(nums, target: int):
nums = sorted(nums)
res = []
def backtrack(s, use, remain):
for i in range(s, len(nums)):
if nums[i] == remain:
res.append(use + [nums[i]])
elif nums[i] < remain:
backtrack(i, use + [nums[i]], remain - nums[i])
else:
return
backtrack(0, [], target)
return res
动态规划
股票的最大利润
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
输入: [7, 1, 5, 3, 6, 4]
输出: 5
复杂度分析:
时间复杂度 O(N) : 其中 N 为 prices 列表长度,动态规划需遍历 prices 。
空间复杂度 O(1) : 变量 cost 和 profit 使用常数大小的额外空间。
def maxProfit(prices):
cost, profit = prices[0], 0
for price in prices:
cost = min(cost, price)
profit = max(profit, price - cost)
return profit
股票的最大利润II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
你可以尽可能地完成更多的交易(多次买卖一支股票)。设计一个算法来计算你所能获取的最大利润。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入: [7,1,5,3,6,4]
输出: 7
复杂度分析:
时间复杂度 O(N) : 其中 N 为 prices 列表长度,动态规划需遍历 prices 。
空间复杂度 O(1) : 变量 cost 和 profit 使用常数大小的额外空间。
def maxProfit(prices):
profit = 0
for i in range(1, len(prices)):
tmp = prices[i] - prices[i - 1]
if tmp > 0:
profit += tmp
return profit
礼物的最大价值
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物
输入:
[
[1, 3, 1],
[1, 5, 1],
[4, 2, 1]
]
输出: 12
"""
解题思路:每个位置的最大值等于左侧和上方的最大值加当前值,最后返回右下角的结果即可。
复杂度分析:
时间复杂度 O(MN) : M,N分别为矩阵行高列宽,动态规划需遍历整个grid矩阵,使用 O(MN)时间。
空间复杂度 O(1) : 原地修改使用常数大小的额外空间。
"""
def maxValue(grid) -> int:
for i in range(len(grid)):
for j in range(len(grid[0])):
if i == 0 and j == 0:
continue
if i == 0:
grid[i][j] += grid[i][j - 1]
elif j == 0:
grid[i][j] += grid[i - 1][j]
else:
grid[i][j] += max(grid[i][j - 1], grid[i - 1][j])
return grid[-1][-1]
青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
复杂度分析:
时间复杂度 O(N) : 计算 f(n) 需循环 n 次,每轮循环内计算操作使用 O(1) 。
空间复杂度 O(1) : 几个标志变量使用常数大小的额外空间。
由于 Python 中整形数字的大小限制取决计算机的内存 (可理解为无限大),因此可不考虑大数
越界问题。
def numWays(n: int) -> int:
a, b = 1, 1
for _ in range(n):
a, b = b, a + b
return a% 1000000007
PS : 为什么要模1000000007(跟我念,一,八个零,七)。
大数相乘,大数的排列组合等为什么要取模
1000000007是一个质数(素数),对质数取余能最大程度避免结果冲突/重复
三步问题
三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。
输入:n = 5
输出:13
状态转移方程 f(i)=f(i-1)+f(i-2)+f(i-3)
时间复杂度 O(N)
空间复杂度 O(1)
def waysToStep(n: int):
a, b, c = 1, 2, 4
if n < 3:
return n
if n == 3:
return 4
for i in range(n - 3):
a, b, c = b, c, (a + b + c) % 1000000007
return c
最长不含重复字符的子字符串
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
输入: “abcabcbb”
输出: 3
复杂度分析:
时间复杂度 O(N): 其中 N 为字符串长度,动态规划需遍历计算 dp 列表。
空间复杂度 O(1) : 字符的 ASCII 码范围为 0 ~ 127 ,哈希表 dic 最多使用
O(128) = O(1)大小的额外空间
def lengthOfLongestSubstring(s: str):
dic, res, tmp = {}, 0, 0
for j in range(len(s)):
i = dic.get(s[j], -1) # 获取索引 i
dic[s[j]] = j # 更新哈希表
tmp = tmp + 1 if tmp < j - i else j - i # dp[j - 1] -> dp[j]
res = max(res, tmp) # max(dp[j - 1], dp[j])
return res
复杂度分析:
时间复杂度 O(N): 其中 N 为字符串长度,遍历计算 dp 列表。
空间复杂度 O(1)
# 双指针
def lengthOfLongestSubstring(s: str):
dic, res, i = {}, 0, -1
for j in range(len(s)):
if s[j] in dic:
i = max(dic[s[j]], i) # 更新左指针 i
dic[s[j]] = j # 哈希表记录
res = max(res, j - i) # 更新结果
return res
按摩师
一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。
输入:[2, 7, 9, 3, 1]
输出: 12
解释: 选择 1 号、 3 号和 5 号预约,总时长 = 2 + 9 + 1 = 12。
输入: [2, 1, 4, 5, 3, 1, 1, 3]
输出: 12
解释: 选择 1 号、 3 号、 5 号和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。
时间复杂度:O(n),其中 n 为预约的个数。我们有 2n 个状态需要计算,每次状态
转移需要 O(1) 的时间,所以一共需要 O(2n)=O(n) 的时间复杂度。
空间复杂度:O(1),只需要常数的空间存放若干变量。
def massage(nums) -> int:
dp0, dp1 = 0, 0
for i in range(len(nums)):
dp0, dp1 = dp1, max(dp0 + nums[i], dp1)
return dp1
零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数
输入:coins = [1, 2, 5], amount = 11
输出:3
时间复杂度:O(Sn),其中 S 是金额,n 是面额数。我们一共需要计算 O(S) 个状态,S 为题目所给的总金额。对于每个
状态,每次需要枚举 n 个面额来转移状态,所以一共需要 O(Sn)的时间复杂度。
空间复杂度:O(S)。DP 数组需要开长度为总金额 S 的空间。
def coinChange(coins, amount: int) -> int:
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for x in range(coin, amount + 1):
dp[x] = min(dp[x], dp[x - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
零钱兑换 II
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
时间复杂度:O(N×amount)。其中 N 为 coins 数组的长度。
空间复杂度:O(amount),dp 数组使用的空间。
def change(coins, amount: int) -> int:
dp = [0] * (amount + 1)
dp[0] = 1
for coin in coins:
for x in range(coin, amount + 1):
dp[x] += dp[x - coin]
return dp[amount]
二分法
在排序数组中查找数字 I
统计一个数字在排序数组中出现的次数。
输入: nums = [5, 7, 7, 8, 8, 10], target=8
输出: 2
"""复杂度分析:
时间复杂度 O(log N): 二分法为对数级别复杂度。
空间复杂度 O(1) : 几个变量使用常数大小的额外空间。"""
def search(nums, target):
# 搜索右边界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
# 搜索左边界 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
旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。
输入:[3,4,5,1,2]
输出:1
复杂度分析:
时间复杂度 O(log_2 N) : 在特例情况下(例如 [1, 1, 1, 1],会退化到 O(N)。
空间复杂度 O(1) : i, j , m 指针使用常数大小的额外空间。
def minArray(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]
0~n-1中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
输入: [0,1,3]
输出: 2
时间复杂度 O(log N): 二分法为对数级别复杂度。
空间复杂度 O(1): 几个变量使用常数大小的额外空间。
def missingNumber(nums) -> int:
i, j = 0, len(nums) - 1
while i <= j:
m = (i + j) // 2
if nums[m] == m:
i = m + 1
else:
j = m - 1
return i
双指针
两数之和
输入:nums = [2, 7, 11, 15], target=9
输出:[2,7] 或者 [7,2]
时间复杂度 O(N) : N 为数组 numsnums 的长度;双指针共同线性遍历整个数组。
空间复杂度 O(1) : 变量 i, j 使用常数大小的额外空间。
def twoSum(nums, target):
i, j = 0, len(nums) - 1
while i < j:
s = nums[i] + nums[j]
if s > target:
j -= 1
elif s < target:
i += 1
else:
return nums[i], nums[j]
return []
时间复杂度 O(N) : N 为数组 numsnums 的长度;
空间复杂度 O(N) :额外空间
def twoSum1(nums, target):
dct = {}
for i, n in enumerate(nums):
if target - n in dct:
return target - n, n
dct[n] = i
调整数组顺序使奇数位于偶数前面
输入:nums = [1,2,3,4]
输出:[1,3,2,4] 或 [3,1,2,4]
复杂度分析:
时间复杂度 O(N): N 为数组 nums长度,双指针 i, j 共同遍历整个数组。
空间复杂度 O(1): 双指针 i, j 使用常数大小的额外空间。
def exchange(nums):
m, n = 0, len(nums) - 1
while m < n:
if nums[m] % 2 == 0:
nums[m], nums[n] = nums[n], nums[m]
n -= 1
else:
m += 1
return nums
和为s的连续正数序列
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
输入:target = 9
输出:[[2,3,4],[4,5]]
滑动窗口解法
复杂度
时间复杂度 O(target): 终点至少需要遍历到target//2+1
空间复杂度 O(1): 只使用了几个变量(结果数组使用的空间不计入内)
def findContinuousSequence(target: int):
i, j, res = 1, 2, []
while j <= target // 2 + 1:
# 一个大于1的正整数,总是小于,它的中值加上一个比中值大的数。
cur_sum = sum(list(range(i, j + 1)))
if cur_sum < target:
j += 1
elif cur_sum > target:
i += 1
else:
res.append(list(range(i, j + 1)))
# 这里用j+=1,i+=1,i+=2都可以,因为是连续的正数序列,所以当sum==target
时, i+1 开头的序列一定无解,左边界可以直接向右移动两位,即i+=2
i += 2
return res
盛最多水的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
输入:[1, 8, 6, 2, 5, 4, 8, 3, 7]
输出:49
复杂度分析:
时间复杂度 O(N): 双指针遍历一次底边宽度 N 。
空间复杂度 O(1): 指针使用常数额外空间。
def maxArea(height) -> int:
res, i, j = 0, 0, len(height) - 1
while i < j:
if height[i] < height[j]:
res = max(res, height[i] * (j - i))
i += 1
else:
res = max(res, height[j] * (j - i))
j -= 1
return res
二叉搜索树
树的遍历方式总体分为两类:深度优先搜索(DFS)、广度优先搜索(BFS);
常见的 DFS : 先序遍历、中序遍历、后序遍历;
常见的 BFS : 层序遍历(按层遍历)。
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
方法一:后序遍历(DFS)
时间复杂度 O(N) : N 为树的节点数量,计算树的深度需要遍历所有节点。
空间复杂度 O(N): 最差情况下(当树退化为链表时),递归深度可达到 N 。
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
方法二:层序遍历(BFS)
时间复杂度 O(N) : N 为树的节点数量,计算树的深度需要遍历所有节点。
空间复杂度 O(N) : 最差情况下(当树平衡时),队列 queue 同时存储 N/2 个节点。
class Solution:
def maxDepth(self, root: TreeNode) -> int:
if not root:
return 0
queue, res = [root], 0
while queue:
tmp = []
for node in queue:
if node.left:
tmp.append(node.left)
if node.right:
tmp.append(node.right)
queue = tmp
res += 1
return res
二叉树的深度
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
层序遍历(BFS)
时间复杂度 O(N) : N 为二叉树的节点数量,即 BFS 需循环 N 次。
空间复杂度 O(N) : 最差情况下,即当树为平衡二叉树时,最多有 N/2 个树节点同时在 queue 中,使用 O(N) 大小的额外空间。
class Solution:
def levelOrder(self, root: TreeNode):
if not root:
return []
res, queue = [], collections.deque()
queue.append(root)
while queue:
node = queue.popleft()
res.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return res
二维数组中的查找
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
复杂度分析:
时间复杂度 O(M+N) :其中,N 和 M 分别为矩阵行数和列数,此算法最多循环 M+N 次。
空间复杂度 O(1) : i, j 指针使用常数大小额外空间。
def findNumberIn2DArray(matrix: list, target: int) -> bool:
i, j = len(matrix) - 1, 0
while i >= 0 and j < len(matrix[0]):
if matrix[i][j] > target:
i -= 1
elif matrix[i][j] < target:
j += 1
else:
return True
return False
链表
# Definition for singly-linked list.
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
环形链表
给定一个链表,判断链表中是否有环。
class Solution:
def hasCycle(self, head: ListNode) -> bool:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
class Solution(object):
def detectCycle(self, head):
fast, slow = head, head
while True:
if not (fast and fast.next):
return
fast, slow = fast.next.next, slow.next
if fast == slow:
break
fast = head
while fast != slow:
fast, slow = fast.next, slow.next
return fast
相交链表
找到两个单链表相交的起始节点。
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
ha, hb = headA, headB
while ha != hb:
ha = ha.next if ha else headB
hb = hb.next if hb else headA
return ha
删除链表的倒数第N个节点
删除链表的倒数第 n 个节点,并且返回链表的头结点。
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode(0, head)
first = head
second = dummy
for i in range(n):
first = first.next
while first:
first = first.next
second = second.next
second.next = second.next.next
return dummy.next
反转链表
双指针
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head:
return head
pre=None
cur=head
while cur:
# cur=cur.next
# pre=cur
# cur.next=pre
cur.next, pre, cur = pre, cur, cur.next
return pre
递归
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
def helper(last,cur):
if not cur:
return last
nex = cur.next
cur.next = last
return helper(cur,nex)
return helper(None,head)
移除链表元素
删除链表中等于给定值 val 的所有节点。
class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode:
ans = ListNode(0)
ans.next = head
prev, curr = ans, head
while curr:
if curr.val == val:
prev.next = curr.next
else:
prev = curr
curr = curr.next
return ans.next
奇偶链表
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
class Solution:
def oddEvenList(self, head: ListNode) -> ListNode:
if not head:
return head
odd = head
even_head = even = head.next
while odd.next and even.next:
odd.next = odd.next.next
even.next = even.next.next
odd, even = odd.next, even.next
odd.next = even_head
return head
回文链表
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
# 快节点每次走两步,走的同时,反转前半部分的链表。然后逐一比较后半部分和前半部分的值。
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
slow, fast, prev = head, head, None
while fast and fast.next:
fast = fast.next.next
slow.next, slow, prev = prev, slow.next, slow
if fast:
slow = slow.next
while prev and prev.val == slow.val:
prev, slow = prev.next, slow.next
return prev is None
合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
class Solution:
def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
cur = dum = ListNode(0)
while l1 and l2:
if l1.val < l2.val:
cur.next, l1 = l1, l1.next
else:
cur.next, l2 = l2, l2.next
cur = cur.next
cur.next = l1 if l1 else l2
return dum.next
其他
数组中重复的数字
先问面试官要时间/空间需求!!!
是否可以使用额外空间,是否可以修改原始数组
只是时间优先就用set,
还有空间要求O(1)原地hash
空间要求O(1)并且不能修改原数组,使用二分法
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,找出数组中重复的数字。
输入:[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
# 哈希表 时间O(n),空间O(n)
def findRepeatNumber(nums) :
s = set()
for num in nums:
if num in s:
return num
s.add(num)
# 原地hash,时间O(n),空间O(1)
def findRepeatNumber(nums):
for idx, val in enumerate(nums):
if idx != val and nums[val] == val:
return val
nums[val], nums[idx] = nums[idx], nums[val]
数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
摩尔投票法:
票数和: 由于众数出现的次数超过数组长度的一半,若记众数的票数为 +1,非众数的票数为 −1 ,
则一定有所有数字的 票数和 > 0 。
票数正负抵消: 设数组 nums 中的众数为 x ,数组长度为 n 。若 nums 的前 a 个数字的
票数和 =0 ,则数组后 (n−a) 个数字的票数和一定仍 >0 (即后 (n-a)个数字的 众数仍为 x )。
复杂度分析:
时间复杂度 O(N) : N 为数组 nums 长度。
空间复杂度 O(1) : votes 变量使用常数大小的额外空间。
def minArray(nums: [int]) -> int:
votes, flag = 0, 0
for v in nums:
if votes == 0:
flag = v
if v == flag:
votes += 1
else:
votes -= 1
return flag if nums.count(flag) > len(nums) // 2 else None
用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
复杂度分析:
由于问题特殊,以下分析仅满足栈初始和结束状态下都为空的情况。
时间复杂度:对于插入和删除操作,时间复杂度均为 O(1)。插入不多说,对于删除操作,虽然看
起来是 O(n) 的时间复杂度,但是仔细考虑下每个元素只会「至多被插入和弹出 stack2 一次」,
因此均摊下来每个元素被删除的时间复杂度仍为 O(1)。
空间复杂度:O(n),需要使用两个栈存储已有的元素。
class CQueue:
def __init__(self):
self.A, self.B = [], []
def appendTail(self, value: int):
self.A.append(value)
def deleteHead(self):
if self.B:
return self.B.pop()
if not self.A:
return -1
while self.A:
self.B.append(self.A.pop())
return self.B.pop()
滑动窗口的最大值
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
输入: nums = [1, 3, -1, -3, 5, 3, 6, 7], k=3
输出: [3, 3, 5, 5, 6, 7]
单调队列
复杂度分析:
时间复杂度 O(n) : 其中 n 为数组 nums 长度;线性遍历 nums 占用 O(N) ;每个元素最多
仅入队和出队一次,因此单调队列 deque 占用 O(2N) 。
空间复杂度 O(k) : deque中最多同时存储 k 个元素(即窗口大小)。
def maxSlidingWindow(nums, k: int):
res, deque = [], []
for i, v in enumerate(nums):
if i >= k and deque[0] <= i - k:
deque.pop(0)
# 只存有可能成为最大值的数字的index进deque
while deque and nums[deque[-1]] <= v:
deque.pop()
deque.append(i)
if i >= k - 1:
res.append(nums[deque[0]])
return res
第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。
s 只包含小写字母。
输入: s = “abaccdeff”
输出: 返回 “b”
复杂度分析:
时间复杂度 O(N) : N 为字符串 s 的长度;需遍历 s 两轮,使用 O(N) ;HashMap 查找的
操作复杂度为 O(1) ;
空间复杂度 O(N): HashMap 使用 O(N) 大小的额外空间。
def firstUniqChar(s: str):
dicts = {}
for i in s:
dicts[i] = dicts.get(i, 0) + 1
for i in s:
if dicts[i] == 1:
return i
return ' '
def firstUniqChar(s: str):
dic = {}
for i in s:
dic[i] = i not in dic
for i in s:
if dic[i]:
return i
return ""
汉诺塔问题
输入:A = [2, 1, 0], B = [], C = []
输出:C = [2, 1, 0]
输入:A = [1, 0], B = [], C = []
输出:C = [1, 0]
def hanota(A, B, C):
def hanoi(n, x, y, z):
if n == 1:
z.append(x.pop())
return
else:
hanoi(n - 1, x, z, y)
hanoi(1, x, y, z)
hanoi(n - 1, y, x, z)
hanoi(len(A), A, B, C)
return C
括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
输入: “()[]{}”
输出: true
def isValid(s: str) -> bool:
dic = {")": "(", "]": "[", "}": "{"}
stack = []
for i in s:
if stack and i in dic:
if stack[-1] == dic[i]:
stack.pop()
else:
return False
else:
stack.append(i)
return not stack
扑克牌中的顺子
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
示例 :
输入: [1, 2, 3, 4, 5] 输出: True
输入:[0, 0, 1, 2, 5] 输出: True
时间复杂度 O(N log N) = O(5 log 5) = O(1)
空间复杂度 O(1)
def isStraight(nums) -> bool:
joker = 0
nums.sort()
for i in range(4):
if nums[i] == 0:
joker += 1
elif nums[i] == nums[i + 1]:
return False
return nums[4] - nums[joker] < 5