(3月5日)今天早上10点有美团的笔试,搞笑的事我看了公司不在广州,就没投递简历,就填了个内推码,好像也没提交,就通知我笔试了,怎么这么神奇。
(3月6日)昨天笔试去了,美团笔试五道写出了三道,有进步,今天努力20道算法(补回来)哈哈哈,加油!!!
算法题(牛客网)
1.合并两个有序的数组
仔细看题目,要求将数组B合并到数组A,如果创建了新数组就错了,第一反应是先从小的来,结果发现数组A不好处理,那就反过来,先从大处理,发现A后面的空位正好利用上,nice!!!
代码详情:
class Solution {
public:
void merge(int A[], int m, int B[], int n) {
int l = m-1,r = n-1; //创建双指针分别指A末尾和B末尾
int i = m+n-1;
while(i >= 0 && l >= 0 && r >= 0){
if (A[l] >= B[r]){
A[i--] = A[l--]; //将大的放末尾
}
else{
A[i--] = B[r--]; //同理
}
}
while(l >= 0){ //判断是否存在剩下的数,全部放入A中就行
A[i--] = A[l--];
}
while(r >= 0){
A[i--] = B[r--];
}
}
};
2.链表中环的入口结点
昨天把那道检查是否有环路,想成了这道了,所以这道直接就会了!!!
代码详情:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead) {
ListNode* fast = pHead,* slow = pHead; //创建快慢指针
do{
if (!fast || !fast->next) return nullptr; //检查是否有环路
fast = fast->next->next;
slow = slow->next;
}while(fast != slow);
fast = pHead; //将快指针移动到头节点
while(fast != slow){ //快慢指针都只走一步
fast = fast->next;
slow = slow->next;
}
return fast;
}
};
3. 有效括号序列
记得第一次写这题的时候,想了超久,现在觉得很简单,用栈保存遍历过的符号,当遇到后部分符号,再读栈配对是否符合就行。
代码详情:
class Solution {
public:
/**
*
* @param s string字符串
* @return bool布尔型
*/
bool isValid(string s) {
// write code here
if (s.size() % 2 == 1) return false;
unordered_map<char, char> map ={ //创建哈希表,为了比较符号是否搭配
{')','('},
{']','['},
{'}','{'}
};
stack<char> stack;
for(char c:s){
if (c != ']' && c != '}' && c != ')'){ //前半部分符号直接压入栈
stack.push(c);
}
else{
if (!stack.empty() && map[c] == stack.top()){ //判断是否匹配
stack.pop(); //匹配成功,删除符号
}
else{
return false; //不成功,直接false
}
}
}
return stack.empty()?true:false; //如果栈还有符号,表明没匹配完
}
};
4. 删除链表的倒数第n个节点
链表不能倒过来读,那怎么办呢?使用快慢指针,让快指针先走n步,慢指针保持和快指针n步的距离往前走,快指针到末尾时,慢指针是不是就在倒数n步的位置啦。
代码详情:
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
class Solution {
public:
/**
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
ListNode* removeNthFromEnd(ListNode* head, int n) {
// write code here
if (head == nullptr){
return nullptr;
}
ListNode* fast = head;
ListNode* slow = new ListNode(-1); //创建空节点
slow->next = head;
head = slow;
for(int i = 0;i<n;++i){ //让快指针先走n步
fast = fast->next;
}
while (fast != nullptr){ //快慢指针同时走
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next; //删除慢指针下一节点
return head->next;
}
};
5.大数加法
一开始我直接string转long long,然后加在一起转回string,没考虑到数据溢出哈哈哈,果然中等题目没那么简单。
就以小学学的加法表达式来写了,从数组最后一位开始加,重点考虑最后要不要进一就行。
代码详情:
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* 计算两个数之和
* @param s string字符串 表示第一个整数
* @param t string字符串 表示第二个整数
* @return string字符串
*/
string solve(string s, string t) {
// write code here
if (s.size() < t.size()) swap(s,t); //让s保持最长,在最长数组进行数据保存
int m = s.size()-1,n = t.size()-1; //m为s的长度,n为t的长度
if (m < 0) return t; //其中一个为空,返回另一个
if (n < 0) return s;
if (n < 0 && m < 0) return 0; //两个都为空,返回0
bool enter = false; //是否要向前进1
while(m >= 0 || n >= 0){
int a = 0,b = 0,tem;
if (m >= 0) a = s[m] - '0'; //s的数字
if (n >= 0) b = t[n] - '0'; //t的数字
tem = a + b; //对齐相加
if (enter){ //进一
++tem;
enter = false;
}
if (tem > 9){ //判断是否要往前进一
enter = true;
tem = tem - 10;
}
s[m] = ('0' + tem);
m--,n--;
}
if (enter){ //检查是否还有进数
s = '1' + s;
}
return s;
}
};
6.按之字形顺序打印二叉树
跟层序遍历一样,多了个奇数从左到右遍历,偶数从右向左遍历而已,加个判断条件就可以。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
if (!pRoot) return {}; //边界条件
queue<TreeNode*> que; //创建队列
que.push(pRoot); //将根节点压入队列
bool flag = true; //设置标志,true从左向右,false从右向左
vector<vector<int>> res; //存储最终答案
while(!que.empty()){
int size = que.size();
vector<int> ans; //存储一层路径
while(size--){
pRoot = que.front(); //获取节点
que.pop();
ans.push_back(pRoot->val);
if (pRoot->left) que.push(pRoot->left); //压入左右节点
if (pRoot->right) que.push(pRoot->right);
}
if (!flag) reverse(ans.begin(), ans.end()); //转换数组
flag = !flag;
res.push_back(ans);
}
return res;
}
};
7.最长公共子串
看了答案提示怎么动态规划,动态规划的题就难在怎么设值,最后substr忘了怎么用了,原来第一个参数为起始下标,第二个参数是子串长度。
代码详情:
class Solution {
public:
/**
* longest common substring
* @param str1 string字符串 the string
* @param str2 string字符串 the string
* @return string字符串
*/
string LCS(string str1, string str2) {
// write code here
int m = str1.size(),n = str2.size();
vector<vector<int>> dp(m+1,vector<int>(n+1,0)); //多出来的一是哨兵
int Maxsize = 0; //记录最长长度
int lMax = 0; //记录最长子串下标
for(int i = 0; i < m; ++i){
for(int j = 0; j < n; ++j){
if (str1[i] == str2[j]){ //如果尾部相同,获取前一个尾部的相同子串数
dp[i+1][j+1] = dp[i][j] + 1;
if (dp[i+1][j+1] > Maxsize){ //判断当前子串是否更长
Maxsize = dp[i+1][j+1]; //更新最长子串长度
lMax = j-Maxsize+1; //起始下标
}
}
}
}
return str2.substr(lMax,Maxsize);
}
};
8.两个链表的第一个公共结点
这题有个小技巧,只要将两个链表长度变成一致就很好遍历了。
(图为牛客网@牛客题霸)
代码详情:
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
ListNode* p1 = pHead1,* p2 = pHead2;
while(p1 != p2){
p1 = p1?p1->next:pHead2; //当p1遍历结束转p2
p2 = p2?p2->next:pHead1; //同理
}
return p1;
}
};
你可能会说,那岂不是不停的循环嘛,由于一样长了,最后肯定会同时遍历到nullptr的。
9.链表相加(二)
跟上面的字符串相加一样,从最后开始加(链表反转),然后注意进位,和最后一位的进位就可以。
代码详情:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
/**
*
* @param head1 ListNode类
* @param head2 ListNode类
* @return ListNode类
*/
ListNode* addInList(ListNode* head1, ListNode* head2) {
// write code here
//边界判断
if (!head1) return head2;
if (!head2) return head1;
if (!head1 && !head2) return nullptr;
int length = 0;
ListNode* node = head1;
while(node){ //计算哪条链表长
node = node->next;
++length;
}
node = head2;
while(node){
node = node->next;
--length;
}
if (length < 0){ //令head1为最长链表
node = head2;
head2 = head1;
head1 = node;
}
head1 = reversal(head1); //反转链表
head2 = reversal(head2); //同理
ListNode* root = head1; //记录head1根节点
int enter = 0; //是否进一
ListNode* pre = nullptr; //方便最后一位进一用的
while(head1 || head2){
int a, b, tem;
a = head1?head1->val:0;
b = head2?head2->val:0;
tem = a + b + enter;
enter = 0; //进位结束归0
if (tem > 9){ //处理进位
enter = 1;
tem = tem - 10;
}
head1->val = tem;
pre = head1;
head1 = head1->next;
head2 = head2?head2->next:head2; //如果head2为空则不能下一条
}
if (enter == 1){ //最后一位进位
node = new ListNode(1);
pre->next = node;
}
return reversal(root);
}
//链表反转函数
ListNode* reversal(ListNode* right){
ListNode* left = nullptr;
ListNode* tem_right = nullptr;
while(right){
tem_right = right->next;
right->next = left;
left = right;
right = tem_right;
}
return left;
}
};
10.在二叉树中找到两个节点的最近公共祖先
从底向上查询,但分别在左右子树时,该节点就是公共祖先节点。
代码详情:
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
class Solution {
public:
/**
*
* @param root TreeNode类
* @param o1 int整型
* @param o2 int整型
* @return int整型
*/
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
// write code here
return helper(root,o1,o2)->val;
}
//辅助函数
TreeNode* helper(TreeNode* root,int o1,int o2){
if (!root || root->val == o1 || root->val == o2){ //如果为空,或是查找的节点就返回
return root;
}
TreeNode* left = helper(root->left,o1,o2); //查找左子树是否有查找节点
TreeNode* right = helper(root->right,o1,o2); //同理
if (!left) return right; //左子树没有查找节点,说明在右子树上
if (!right) return left; //同理
//左右子树都存在,说明当前节点就是公共祖先
return root;
}
};
11.反转字符串
使用双指针,直接使用swap函数,进行原地交换。
代码详情:
class Solution {
public:
/**
* 反转字符串
* @param str string字符串
* @return string字符串
*/
string solve(string str) {
// write code here
int left = 0,right = str.size()-1; //设置头尾双指针
while(left < right){
swap(str[left++],str[right--]); //原地交换
}
return str;
}
};
12. 螺旋矩阵
没想出来,看答案提示后慢慢写出来的,重点在于遍历时条件判断,判断的不好容易漏或者重复。
代码详情:
class Solution {
public:
vector<int> spiralOrder(vector<vector<int> > &matrix) {
int m = matrix.size(),n = m ? matrix[0].size() : 0; //矩阵长宽
//定位上下左右边界
int top = 0, botton = m - 1, left = 0, right = n - 1;
vector<int> ans; //记录答案
while(top <= m / 2 && left <= n / 2){ //螺旋遍历
for(int i = left;top <= botton && i <= right; ++i){ //遍历顶部
ans.push_back(matrix[top][i]);
}
for(int i = top + 1; left <= right && i <= botton; ++i){ //遍历右部
ans.push_back(matrix[i][right]);
}
for(int i = right - 1;top < botton && i >= left; --i){ //遍历底部
ans.push_back(matrix[botton][i]);
}
for(int i = botton - 1;left < right && i > top; --i){ //遍历左部
ans.push_back(matrix[i][left]);
}
++left,++top,--right,--botton; //遍历一圈后往里收一圈
}
return ans;
}
};
面试题
1. 函数重载与重写
重载(overriding):子类改写父类方法,函数名必须相同,参数表必须不同,返回值类型可以不同。
覆写(overloading):派生类重写基类的函数,同一个函数的不同版本,相同的函数名,相同的参数列表,相同的返回值类型。
23点半了,不卷了不卷了,那道面试题让我疑惑了好久,才知道重写和覆写是一个东西。。。