算法&&八股文&&其他
一、算法篇
- 输入一个长度为n的整型数组nums,数组中的一个或连续多个整数组成一个子数组。求所有子数组的乘积的最大值。
1.子数组是连续的,且最小长度为1,最大长度为n
2.长度为1的子数组,乘积视为其本身,比如[4]的乘积为4
分析:简单题(lc121)
动态规划 :根据正负性进行分类讨论
考虑当前位置如果是一个负数的话,那么我们希望以它前一个位置结尾的某个段的积也是个负数,这样就可以负负得正,并且我们希望这个积尽可能「负得更多」,即尽可能小。如果当前位置是一个正数的话,我们更希望以它前一个位置结尾的某个段的积也是个正数,并且希望它尽可能地大。于是这里我们可以维护一个fmin (i)和fmax(i),它表示以第 i 个元素结尾的乘积最小和最大子数组的乘积,那么动态规划转移方程:
fmax(i) = max(nums[i], nums[i] * fmax(i-1), nums[i] * fmin(i-1));
fmin(i) = min(nums[i], nums[i] * fmax(i-1), nums[i] * fmin(i-1));
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
# @param nums int整型一维数组
# @return int整型
#
class Solution:
def maxProduct(self , nums: List[int]) -> int:
lenth = len(nums)
minmul, maxmul = nums[0], nums[0]
ans = maxmul
for num in nums[1:]:
minmul, maxmul = min(num, minmul * num, maxmul * num), max(num, minmul * num, maxmul * num)
ans = max(ans, maxmul)
return ans
# write code here
- 对于长度为n的一个字符串A(仅包含数字,大小写英文字母),请设计一个高效算法,计算其中最长回文子串的长度。
分析:middle(lc5)
马拉车算法O(n)
线性的算法–Manacher,可以将动态规划情况下的复杂度由 n2 的复杂度降到线性。Manacher算法能将奇偶长度的子串归为一类,它在原字符串中插入特殊字符,例如插入#后原字符串变成’#3#5#5#3#4#3#2#1#’。现在我们对新字符串使用中心扩展发即可,中心扩展法得到的半径就是子串的长度。
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
# @param A string字符串
# @return int整型
#
class Solution:
def getLongestPalindrome(self , A: str) -> int:
return Palindrome().getLongestPalindrome(A, len(A))
class Palindrome:
def getLongestPalindrome(self, A, n):
if n <= 1: return n
# 每个字符之间插入 #
ss = '$#' + '#'.join([x for x in A]) + '#`'
p = [1] * len(ss)
center = 0
mx = 0
max_str = ''
for i in range(1, len(p)-1):
if i < mx:
j = 2 * center - i # i 关于 center 的对称点
p[i] = min(p[j],mx-i)
# 尝试继续向两边扩展,更新 p[i]
while ss[i - p[i] ] == ss[i + p[i] ]: # 不必判断是否溢出,因为首位均有特殊字符,肯定会退出
p[i] += 1
# 更新中心
if i + p[i] - 1 > mx:
mx = i + p[i] - 1
center = i
# 更新最长串
if 2 * p[i]-1 > len(max_str):
max_str = ss[i - p[i]+1 : i + p[i]]
maxLen = len(max_str.replace('#', ''))
return maxLen
- 给定一个整数数组 nums ,其中可能包含重复元素,请你返回这个数组的所有可能子集。
返回的答案中不能包含重复的子集,将答案按字典序进行排序。
分析:middle(nc221)
回溯 :考虑递归函数backtrack(start, temp),其功能是:
对于起点 start 元素,考虑在当前的 temp 基础上,将从start开始到数组末尾的所有元素都尝试在temp上添加一次,这样操作保证了字典序
O(n×2n),一共 2n 个状态,每个状态需要O(n)的时间代价来构造
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
# @param nums int整型一维数组
# @return int整型二维数组
#
class Solution:
def subsets(self , nums: List[int]) -> List[List[int]]:
# write code here
res = []
nums.sort()
def backtrack(start, temp):
res.append(temp)
if start == len(nums):
return
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i-1]:
continue
backtrack(i+1, temp+[nums[i]])
backtrack(0, [])
return res
- 给你一个字符串 s ,考虑其所有 重复子串 :即 s 的(连续)子串,在 s 中出现 2 次或更多次。这些出现之间可能存在重叠。
返回 任意一个 可能具有最长长度的重复子串。如果 s 不含重复子串,那么答案为 “”
分析:hard(lc1044)
二分 + 字符串哈希 :参考链接
class Solution {
public:
int n;
unsigned long long prime = 31;
string longestDupSubstring(string s) {
n = s.size();
int l = 1;
int r = n - 1;
int pos = -1;
int len = 0;
auto find = [&](int len){
unsigned long long hash = 0;
unsigned long long power = 1;
for (int i = 0; i < len; i++) {
hash = hash * prime + (s[i] - 'a');
power *= prime;
}
unordered_set<unsigned long long> exist{hash};
for(int i = len; i < n; i++) {
hash = hash * prime - power * (s[i-len] - 'a') + (s[i] - 'a');
if (exist.count(hash)) return (i - len + 1);
exist.insert(hash);
}
return -1;
};
while(l <= r) {
int mid = (l + r) / 2;
int start = find(mid);
if (start != -1) {
len = mid;
pos = start;
l = mid + 1;
} else {
r = mid - 1;
}
}
if (pos == -1) return "";
else return s.substr(pos, len);
}
};
- 给一个01矩阵,1代表是陆地,0代表海洋, 如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。
岛屿: 相邻陆地可以组成一个岛屿(相邻:上下左右) 判断岛屿个数。
分析:middle(lc200)
法1深度优先搜索 :我们可以将二维网格看成一个无向图,竖直或水平相邻的 11 之间有边相连。
为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 11,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 11 都会被重新标记为 00。
最终岛屿的数量就是我们进行深度优先搜索的次数。
法2广度优先搜索:为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 11,则将其加入队列,开始进行广度优先搜索。在广度优先搜索的过程中,每个搜索到的 11 都会被重新标记为 00。直到队列为空,搜索结束。
法三并查集:为了求出岛屿的数量,我们可以扫描整个二维网格。如果一个位置为 11,则将其与相邻四个方向上的 11 在并查集中进行合并。
最终岛屿的数量就是并查集中连通分量的数目。
##深度优先搜索 (时间空间都是O(MN)复杂度)
class Solution:
def dfs(self, grid, r, c):
grid[r][c] = 0
nr, nc = len(grid), len(grid[0])
for x, y in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
if 0 <= x < nr and 0 <= y < nc and grid[x][y] == "1":
self.dfs(grid, x, y)
def numIslands(self, grid: List[List[str]]) -> int:
nr = len(grid)
if nr == 0:
return 0
nc = len(grid[0])
num_islands = 0
for r in range(nr):
for c in range(nc):
if grid[r][c] == "1":
num_islands += 1
self.dfs(grid, r, c)
return num_islands
#广度优先搜索 (空间复杂度:O(min(M,N))
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
nr = len(grid)
if nr == 0:
return 0
nc = len(grid[0])
num_islands = 0
for r in range(nr):
for c in range(nc):
if grid[r][c] == "1":
num_islands += 1
grid[r][c] = "0"
neighbors = collections.deque([(r, c)])
while neighbors:
row, col = neighbors.popleft()
for x, y in [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]:
if 0 <= x < nr and 0 <= y < nc and grid[x][y] == "1":
neighbors.append((x, y))
grid[x][y] = "0"
return num_islands
#并查集 (空间复杂度O(MN))
class UnionFind:
def __init__(self, grid):
m, n = len(grid), len(grid[0])
self.count = 0
self.parent = [-1] * (m * n)
self.rank = [0] * (m * n)
for i in range(m):
for j in range(n):
if grid[i][j] == "1":
self.parent[i * n + j] = i * n + j
self.count += 1
def find(self, i):
if self.parent[i] != i:
self.parent[i] = self.find(self.parent[i])
return self.parent[i]
def union(self, x, y):
rootx = self.find(x)
rooty = self.find(y)
if rootx != rooty:
if self.rank[rootx] < self.rank[rooty]:
rootx, rooty = rooty, rootx
self.parent[rooty] = rootx
if self.rank[rootx] == self.rank[rooty]:
self.rank[rootx] += 1
self.count -= 1
def getCount(self):
return self.count
#并查集
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
nr = len(grid)
if nr == 0:
return 0
nc = len(grid[0])
uf = UnionFind(grid)
num_islands = 0
for r in range(nr):
for c in range(nc):
if grid[r][c] == "1":
grid[r][c] = "0"
for x, y in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
if 0 <= x < nr and 0 <= y < nc and grid[x][y] == "1":
uf.union(r * nc + c, x * nc + y)
return uf.getCount()
- 给定一个长度为n的数组nums,请你找到峰值并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个所在位置即可。
1.峰值元素是指其值严格大于左右相邻值的元素。严格大于即不能有等于
2.假设 nums[-1] = nums[n] = −∞
3.对于所有有效的 i 都有 nums[i] != nums[i + 1]
分析:middle(lc162)
二分O(logn) :上坡一定有波峰,下坡不一定有波峰
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
# @param nums int整型一维数组
# @return int整型
#
class Solution:
def findPeakElement(self , nums: List[int]) -> int:
l = 0
r = len(nums) - 1 #[l,r]为一定存在峰值的区间
while l < r: #不断二分压缩区间,最后的单点集即为峰值索引
mid = (l + r) >> 1
if nums[mid] < nums[mid+1]:
l = mid + 1 #l, r的更新必须保证[l,r]一定存在峰值
else:
r = mid
return l
# write code here
- 给定一个长度为 n 的无序数组 A ,包含正数、负数和 0 ,请从中找出 3 个数,使得乘积最大,返回这个乘积。
要求时间复杂度: O(n) ,空间复杂度: O(1)
分析:简单题(lc628)
n > 3时,三个数的最大乘积来源可能有两种
- 第一种:最大的三个都是正数,即三个最大的数相乘
- 第二种:绝对值最大的三个数中有两个负数,即两个最小的数和一个最大的数相乘
class Solution:
def solve(self,A):
#max1, max2, max3分别为当前第一大、第二大、第三大
max1 = max2 = max3 = -1e5
#min1, min2分别为当前第一小、第二小
min1 = min2 = 1e5
for i in range(len(A)):
max1, max2, max3 = max(A[i], max1), max(min(A[i], max1), max2), max(min(A[i], max2), max3)
min1, min2 = min(A[i], min1), min(max(A[i], min1), min2)
return max(max1*max2*max3, min1*min2*max1)
- 输入一颗二叉树的根节点root和一个整数expectNumber,找出二叉树中结点值的和为expectNumber的所有路径。
1.该题路径定义为从树的根结点开始往下一直到叶子结点所经过的结点
2.叶子节点是指没有子节点的节点
3.路径只能从父节点到子节点,不能从子节点到父节点
4.总节点数目为n
分析:middle(lc113)
法一递归 :
- 递归函数 dfs(root, target, tmpSum, tmpList, res)功能为从root节点出发,找和为sum的路径;
- 递归终止条件:当root节点为叶子节点并且sum==root.val, 表示找到了一条符合条件的路径
- 递归表达式:如果左子树不空,递归左子树 ;如果右子树不空,递归右子树
法二层序遍历 :
遍历这棵树。当我们遍历到叶子节点,且此时路径和恰为目标和时,我们就找到了一条满足条件的路径。
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
# @param root TreeNode类
# @param target int整型
# @return int整型二维数组
#
#递归
class Solution_dfs:
def FindPath(self , root: TreeNode, target: int) -> List[List[int]]:
if not root: return []
return dfs(root, target)
def dfs(root, target, tmpSum = 0, tmpList = [], res = []):
tmpSum += root.val #tmpSum为加入root前的路径和
tmpList.append(root.val) #tmpList记录当前路径
if not root.left and not root.right: #判断是否为leafnode
if tmpSum == target: res.append(tmpList.copy())
if root.left: dfs(root.left, target, tmpSum, tmpList, res)
if root.right: dfs(root.right, target, tmpSum, tmpList, res)
tmpList.pop() #IMPORTANT:回溯,代表当前path中的root节点已经不需要了
return res
# write code here
#非递归
class Solution:
def FindPath(self , root: TreeNode, target: int) -> List[List[int]]:
from collections import deque
if not root: return []
que, res = deque([(root, [])]), []
while que:
node, path = que.popleft()
path.append(node.val)
if not node.left and not node.right and sum(path) == target:
res.append(path)
if node.left:
que.append((node.left, path[:]))
if node.right:
que.append((node.right, path[:]))
return res
- 给出一组可能包含重复项的数字,返回该组数字的所有排列。结果以字典序升序排列。
分析:middle(lc47)
使用一个vis数组标记使用过的数字,如果使用过了就回溯。
如果当前的选项num[i],与同一层的前一个选项num[i-1]相同且num[i-1]已经使用了,表示同一层是相同的数字
# @param num int整型一维数组
# @return int整型二维数组
#
class Solution:
def permuteUnique(self , num ):
num.sort()
used = [0] * len(num)
ans = []
return dfs(num, used, [], ans)
def dfs(num, used, temp, ans):
if len(temp) == len(num): ans.append(temp.copy())
else:
for i in range(len(num)):
if used[i] == 1: continue
if i > 0 and num[i] == num[i - 1] and not used[i -1]: continue
temp.append(num[i])
used[i] = 1
dfs(num, used, temp, ans)
temp.pop()
used[i] = 0
return ans
二、八股文
1.密度聚类和k-means的差别 (1, 2, 3)
2. cnn+lstm和convlstm的区别(1, 2, 3)
3. 朴素贝叶斯和XGBoost区别(1)
4. 卷积的数学物理意义是什么,其他形式的卷积(1, 2, 3, 4)
5. 特征工程简介(1)
6. sigmoid和softmax的区别(1, 2)
7. 封装、继承、多态(1)
8. 时序预测中树模型和神经网络区别(1, 2, 3)
三、其他
- 一根绳子分三段能组成三角形的概率(1/4)
待解决 (欢迎评论区或私信解答)
-
给出一个有序数组A和一个常数C,求所有长度为C的子序列中的最大的间距D。
一个数组的间距的定义:所有相邻两个元素中,后一个元素减去前一个元素的差值的最小值. 比如[1,4,6,9]的间距是2.
例子:A:[1,3,6,10], C:3。最大间距D应该是4,对应的一个子序列可以是[1,6,10]。 -
给定一个数字矩阵和一个数字target,比如5。从数字1开始(矩阵中可能有多个1),每次可以向上下左右选择一个方向移动一次,可以移动的条件是下个数字必须是上个数字+1,比如1必须找上下左右为2的点,2必须找上下左右为3的点,以此类推。求到达target一共有几个路径。
-
给定一个只包含0和1的字符串,判断其中有无连续的1。若有,则输出比该串大的无连续1的最小值串。若无,则不做操作
例:给定 ‘11011’ ,则输出 ‘100000’ ;给定 ‘10011’ ,则输出 ‘10100’
(参考:感觉有点像字符串匹配,只要第一次匹配到’011’模式串就改成’100’,然后后面全部置0,仅供参考) -
给定两个字符串 target 和 block,对bolck进行子串选取,选取出的子串可对target进行重构。问最少需要选取多少block子串进行重构。(子串须保持相对顺序,但不要求连续)
例:(1)target = ‘aaa’ ,block = ‘ab’ ,输出为3。即分别选取block子串中的 ‘a’、 ‘a’、 ‘a’;(2)target = ‘abcd’ ,block = ‘bcad’ ,输出为2。即分别选取子串 ‘a’、‘bcd’
(参考:双指针 i,j 分别遍历target和block,j会回溯。时间复杂度是len(target)*len(block))