0826-0901 各种面试笔试题算法题整理
目录
1. 最长回文子串
2. 设计模式里和单一职责原则冲突的是?
3. int array[] = {10,20,30} cout<<-2[array-1] 是多少
4. python 定义 @class method 直接对类修改变量值和建立对象后通过对象修改变量值,最后的结果是多少
5. LRU缓存
6. 二叉树的最近公共祖先
7. transformer如何做位置编码
8. 搜索旋转排序数组
9. 二叉树的右视图
10. 前k个高频元素
1. 最长回文子串
从一个字符串里找到最长的回文子串 子串表示连续
用动态规划来做,dp数组表示:
dp[i,j] = dp[i+1,j-1] || str[i]=str[j] 只有当i+1到j-1 为回文串且i和j相等时,i到j是回文
初始化:
子串长度为1:一定是回文;子串长度为2:当两个相等时为回文
我自己想的做法,就是构建上三角形,循环顺序不是逐行逐列,而是从最长的对角线往右上角走,这样每个dp回溯上一个的时候上一个的值都取好了。
string longestPalindrome(string s) {
int m = s.size();
vector<vector<int>> dp(m,vector<int>(m,0));
int maxi=0,maxj=0,maxval=0;
for(int x = 0;x<m;x++){
for(int i =0;i<m;i++){
int j = i+x;
if(i+x < m){
if(i==j){
dp[i][j] = 1;
}else if(j-i == 1){
if(s[i] == s[j]) dp[i][j] = 1;
}else{
if(s[i] == s[j])
dp[i][j] = dp[i+1][j-1];
}
if(dp[i][j] == 1 && j-i+1 >maxval){
maxval = j-i+1;
maxj = j;
maxi = i;
}
}
}
}
return s.substr(maxi,maxval);
}
2. 设计模式里和单一职责原则冲突的是?
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
单例模式定义:确保一个类最多只有一个实例,并提供一个全局访问点
单一职责原则指的是一个类只有一个引起他变化的原因。
开闭原则指的是类、函数等是可以扩展的,但是不能修改。
3. int array[] = {10,20,30} cout<<-2[array-1] 是多少
-2[array-1] 是 -(&array -1)[2]
4. python 定义 @class method 直接对类修改变量值和建立对象后通过对象修改变量值,最后的结果是多少
@classmethod 定义类方法,这样的方法可以在不创建类的实例的情况下直接调用。
5. LRU缓存
好麻烦,这个连模板都不好抄,太复杂了,只能现做了
做法就是,建立一个unorderedmap存key到node,一个双向链表存node的顺序
每次根据key放一个数,就是在双向链表找到这个数的值,并且把这个node放在链表头,如果没找到,就新建一个放到链表头,如果链表大小超过容量就把队尾删除
每次取一个数,就判断key在不在,不在就返回-1
struct DLinkedNode {
DLinkedNode * prev, * next;
int key, value;
DLinkedNode ():key(0),value(0), prev(nullptr),next(nullptr){};
DLinkedNode (int k,int v):key(k),value(v), prev(nullptr),next(nullptr){};
};
class LRUCache {
public:
unordered_map<int, DLinkedNode *> cache;
DLinkedNode * head, * tail;
int size; //当前存储的大小
int capacity; // 总容量
LRUCache(int _capacity) {
capacity = _capacity;
size = 0;
// 创建空的头尾指针,并且连起来
head = new DLinkedNode ();
tail = new DLinkedNode ();
head->next = tail;
tail->prev = head;
}
int get(int key) {
if(!cache.count(key)){
return -1;
}
// 如何key存在,就把key指向的双向链表指针移动到链表的头
DLinkedNode * node = cache[key];
moveToHead(node);
return node->value;
}
void put(int key, int value) {
if(!cache.count(key)){
// key不存在,就创建一个新的
DLinkedNode * node = new DLinkedNode (key,value);
cache[key] = node;
addToHead(node);
size++;
if(size>capacity){
// 删除尾部节点
DLinkedNode * removed = removeTail();
cache.erase(removed->key);
delete removed;
--size;
}
}else{
DLinkedNode * node = cache[key];
node->value = value;
moveToHead(node);
}
}
void removeNode(DLinkedNode * node){
node->prev->next = node->next;
node->next->prev = node->prev;
}
void addToHead(DLinkedNode * node){
// node插入到head的后面
node->prev = head;
node->next = head->next;
head->next->prev = node;
head->next = node;
}
void moveToHead(DLinkedNode * node){
removeNode(node);
addToHead(node);
}
DLinkedNode * removeTail(){
DLinkedNode * node = tail->prev;
removeNode(node);
return node;
}
};
6. 二叉树的最近公共祖先
一个点是p和q的最近公共祖先,则这个点满足:
(1)左子树包含p,右子树包含q
或者(2)自己本身是p,左子树或者右子树包含q,反之
这题有必要再做一遍,完全看题解的思路写的,回溯
回溯不会写,关键在于return的是什么!这里return的是:root是否包含p,q任意一个节点,只要左右节点都满足包含,就说明它一定是公共节点!(因为不可能左右节点都包含p或者都包含q,所以包含就说明一定是一个包含p,一个包含q)
class Solution {
public:
// dfs判断cur是否包含p,q中任意一个节点
TreeNode * ans;
bool dfs(TreeNode* cur,TreeNode* p, TreeNode* q) {
if(cur == nullptr) return false;
bool lres = dfs(cur->left,p,q);
bool rres = dfs(cur->right,p,q);
if(lres && rres){
ans = cur;
}else if((lres && (cur->val == p->val)) || (lres && (cur->val == q->val))){
ans = cur;
}else if((rres && (cur->val == p->val)) || (rres && (cur->val == q->val))){
ans = cur;
}
return lres || rres || cur->val == p->val || cur->val == q->val;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
dfs(root,p,q);
return ans;
}
};
7. transformer如何做位置编码
还有其他网络模型,在输入的时候也有必要做位置编码,比如把化学式的位置信息编码进去?
因为transformer没有明确的关于源句子中位置的相对和绝对的信息,是把一个句子一起输入进去的,所以用位置编码技术给每个单词额外添加一个编码表示它在序列中的位置
假设整个句子的长度为1,那么其中某两个单词的距离为0.5。但是如果句子变长,那么相对的单词之间的距离也会变长,但这不合适,所以位置编码应该是让不同长度的句子之间,两个词之间的差值保持一致,但是编码值也要在一个范围内。
编码方式有三种:
(1)Learned Positional Embedding编码绝对位置
直接对不同的位置随机初始化一个postion embedding,加到word embedding上输入模型,作为参数进行训练。
bert的输入有三个:
token embeddings:就是对单词内容进行embedding,
segment embeddings:标识这个单词的segment,属于哪一段
position embeddings:标识这个单词的位置,从0到1,2,3.。。
这些都是先得到一个数字,再embedding的,不是直接把数字加上去的
(2)相对位置编码
使用正余弦函数得到绝对位置,通过两者乘积得到相对位置。
这样设计的好处是位置pos+k的positional encoding可以被位置pos线性表示,反应其相对位置关系。周期函数也就是用来考虑相对位置的。
input = input_embedding + positional_encoding
8. 搜索旋转排序数组
我是傻子,我想出来的做法是先找到分割点,再前后做二分
但实际上找分割点的操作复杂度就On了,那还二分什么啊,不如直接搜索一个数啊
但是题解的做法我也没看懂,用了另一个做法:先找到旋转数组的最小值(用二分法找),也就是分割点,然后再对左右做二分,那样复杂度就是3O(logn),应该可以
找最小值刚好是另一道题:旋转数组的最小值
做法就是,判断mid和right的关系,如果nums[mid]>nums[right],说明最小值在mid右边,否则就在左边
这里判断最小值是逼近法,二分到只剩一个区间了,那他就是最小值。
class Solution {
public:
int findmin(vector<int>& nums,int left, int right){
int mid = (left + right) / 2;
if(left == right) return mid;
if(nums[mid] > nums[right]){
return findmin(nums,mid+1, right);
}else{
return findmin(nums,left,mid);
}
}
int bsearch(vector<int>& nums, int left, int right,int target){
if(left > right) return -1;
int mid = int((left+right)/2);
if(nums[mid] == target){
return mid;
}else if (nums[mid] > target){
return bsearch(nums,left,mid-1,target);
}else{
return bsearch(nums,mid+1,right,target);
}
}
int search(vector<int>& nums, int target) {
int idx = findmin(nums,0,nums.size()-1);
int res = bsearch(nums,0,idx-1,target);
if(res == -1)
return bsearch(nums, idx,nums.size()-1,target);
else return res;
}
};
9. 二叉树的右视图
层序遍历 每次可以根据当前队列的长度知道该层的数量,输出最后一个就行了
vector<int> rightSideView(TreeNode* root) {
vector<int> res;
deque<TreeNode *> que;
if (root == nullptr) return res;
que.push_back(root);
while(!que.empty()){
int layer_len = que.size();
for(int i =0;i<layer_len;i++){
TreeNode* cur = que.front();
que.pop_front();
if(cur->left != nullptr) que.push_back(cur->left);
if(cur->right != nullptr) que.push_back(cur->right);
if(i == layer_len-1) res.push_back(cur->val);
}
}
return res;
}
10. 前k个高频元素
面试会惩罚每一个没好好复习的人。。之前大根堆没搞懂糊弄过去了,这题总觉得用优先队列过于简单但也糊弄过去了
一到面试就完蛋
首先,用优先队列是基于小根堆!而不是大根堆!
创建一个k大小的小根堆,遍历数组中每一个元素,如果堆的大小=k且这个元素比堆顶元素大,就会替换堆顶元素(相当于把最小的放在最上面用来淘汰)这样的时间复杂度是O(nlogk)
我一直以为是用大根堆,把所有数组里的数都存进去,然后弹出前k个,这样复杂度是nlogn!就不对,nlogn是排序的复杂度啊
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> minheap;
for(int num : nums){
if(minheap.size() < k){
minheap.push(num);
}else if( num > minheap.top()){
minheap.pop();
minheap.push(num);
}
}
return minheap.top();
}
第二种做法是用快排的思路。每次迭代让左边的数小于它,右边的数大于它。
30. 分页存储
32. 分词器?有哪些 中英文 现在输入到神经网络里还需要用到分词器吗
33. Greedy Search贪婪搜索解码流程(用于机器翻译预测, transformer的预测过程)Transformer系列:Greedy Search贪婪搜索解码流程原理解析 - 简书
NLP实验3——基于Transformer的机器翻译_nlptransformer翻译-CSDN博客
34. Seq2Seq模型、GRU、RNN、LSTM在nlp上的 应用
35. bleu评价指标
36. 最大似然函数
37. 逻辑回归是什么?
42. LRU缓存,页面,判断是否hit。miss
43. struct占用的内存大小