名企高频笔试题目(1)
二叉树的之字形层次遍历
题目描述
给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替)
例如:
给定的二叉树是{3,9,20,#,#,15,7},
该二叉树之字形层序遍历的结果是
[
[3],
[20,9],
[15,7]
]
示例1
输入
{1,#,2}
输出
[[1],[2]]
分析:
经典的二叉树层次遍历,需要做一点点的改动。
vector<vector<int> > zigzagLevelOrder(TreeNode* root) {
// write code here
queue<TreeNode*> q; //双端队列,保存每一层的节点
vector<vector<int>> res;
if (!root) return res;
q.push(root); //根节点入队
int k = 0;
while (!q.empty()) {
vector<int> temp;
int len = q.size(); //亦可设置哨兵为每一层的最后节点,不过当前做法写起来舒适一些
for (int i = 0; i < len; ++i)
{
if (q.front()->left) q.push(q.front()->left);
if (q.front()->right) q.push(q.front()->right);
temp.push_back(q.front()->val);
q.pop();
}
if (k % 2) reverse(temp.begin(), temp.end());
++k;
res.push_back(temp); //局部变量temp自动clear()
}
return res;
}
二叉树的镜像
题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
输入描述:
二叉树的镜像定义:源二叉树
8
/ \
6 10
/ \ / \
5 7 9 11
镜像二叉树
8
/ \
10 6
/ \ / \
11 9 7 5
class Solution {
public:
void Mirror(TreeNode *pRoot) { //递归处理
if(!pRoot) return;
swap(pRoot->left,pRoot->right); //交换指针
Mirror(pRoot->left);
Mirror(pRoot->right);
}
};
设计getMin功能的栈
题目描述
实现一个特殊功能的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。
示例1
输入
[[1,3],[1,2],[1,1],[3],[2],[3]]
输出
[1,2]
备注:
有三种操作种类,op1表示push,op2表示pop,op3表示getMin。你需要返回和op3出现次数一样多的数组,表示每次getMin的答案
1<=操作总数<=1000000
-1000000<=每个操作数<=1000000
数据保证没有不合法的操作
分析:
这个题目需要保存最小值,肯定不能每次取出所有元素然后排序,这做法开销忒大。
一般做法就是已知范围内的最小值与当前节点关联起来。可以额外开一个大小一样的最小值栈,也可以如下。
class Solution {
public:
/**
* return a array which include all ans for op3
* @param op int整型vector<vector<>> operator
* @return int整型vector
*/
stack<pair<int, int>> stk;
vector<int> getMinStack(vector<vector<int> >& op) {
// write code here
vector<int> res;
for (auto opt : op)
if (opt[0] == 1) push(opt[1]);
else if (opt[0] == 2) pop();
else res.push_back(stk.top().second); //first是当前节点,second是最小节点
return res;
}
void push(int elem) {
if (!stk.empty() && elem > stk.top().second)
stk.push(make_pair(elem, stk.top().second));
else stk.push(make_pair(elem, elem)); //stk空或当前节点更小
}
void pop() {
stk.pop();
}
};
最长的括号子串
题目描述
给出一个仅包含字符'('和')'的字符串,计算最长的格式正确的括号子串的长度。
对于字符串"(()"来说,最长的格式正确的子串是"()",长度为2.
再举一个例子:对于字符串")()())",来说,最长的格式正确的子串是"()()",长度为4.
示例1
输入
"(()"
输出
2
利用栈记录字符串的下标
int longestValidParentheses(string s) {
// write code here
stack<int> st;
int res = 0, len = s.length(), cur = -1;
for (int i = 0; i < len;++i) {
if (s[i] == ')')
if (st.empty()) cur = i; //没有左括号可以匹配
else {
st.pop();
if (st.empty())
res = max(res, i - cur); //所有的左括号都匹配成功
else
res = max(res, i - st.top()); //有左括号未匹配成功
}
else st.push(i); //左括号下标入栈
}
return res;
}
重复数字有序数组的二分查找
题目描述
请实现有重复数字的有序数组的二分查找。
输出在数组中第一个大于等于查找值的位置,如果数组中不存在这样的数,则输出数组长度加一。
输入
5,4,[1,2,4,4,5]
输出
3
这里实现的是STL中lower_bound()的功能,看一个vs2019的实现
// FUNCTION TEMPLATE lower_bound
template <class _FwdIt, class _Ty, class _Pr>
_NODISCARD _CONSTEXPR20 _FwdIt lower_bound(_FwdIt _First, const _FwdIt _Last, const _Ty& _Val, _Pr _Pred) {
// find first element not before _Val, using _Pred
_Adl_verify_range(_First, _Last); //检查区间合法性
auto _UFirst = _Get_unwrapped(_First); //left指针
_Iter_diff_t<_FwdIt> _Count = _STD distance(_UFirst, _Get_unwrapped(_Last)); //区间长度
while (0 < _Count) { // divide and conquer, find half that contains answer
const _Iter_diff_t<_FwdIt> _Count2 = _Count / 2;
const auto _UMid = _STD next(_UFirst, _Count2);
if (_Pred(*_UMid, _Val)) { // try top half
_UFirst = _Next_iter(_UMid);
_Count -= _Count2 + 1;
} else {
_Count = _Count2;
}
}
_Seek_wrapped(_First, _UFirst);
return _First;
}
template <class _FwdIt, class _Ty>
_NODISCARD _CONSTEXPR20 _FwdIt lower_bound(_FwdIt _First, _FwdIt _Last, const _Ty& _Val) {
// find first element not before _Val, using operator<
return _STD lower_bound(_First, _Last, _Val, less<>()); //默认情况下序列升序排列
}
针对这个题目的简化版:
int upper_bound_(int n, int v, vector<int>& a) {
// write code here
if(a.back() < v) return n + 1;
int l = 0,r = n,m = 0; //区间左闭右开
while(l != r){
m = (l + r)>>1; //上取整
if(a[m] >= v) r = m;
else l = m + 1;
}
return l + 1;
}
二叉树是否存在节点和为指定值的路径
题目描述
给定一个二叉树和一个值sum,判断是否有从根节点到叶子节点的节点值之和等于sum的路径,
例如:
给出如下的二叉树,sum=22
返回true,因为存在一条路径 5→ 4→11→2的节点值之和为 22
这个题目跟华为13年的题整型数组分组类似,考察逻辑运算符“或”的递归
bool hasPathSum(TreeNode* root, int sum) {
// write code here
if(root){
if(!root->left&&!root->right&&sum == root->val) return true;
else return hasPathSum(root->left,sum - root->val)||hasPathSum(root->right,sum - root->val);
}
else return false; //节点为空,返回假
}
数组相加和为零的三元组
题目描述
给出一个有n个元素的数组S,S中是否有元素a,b,c满足a+b+c=0?找出数组S中所有满足条件的三元组。
注意:
- 三元组(a、b、c)中的元素必须按非降序排列。(即a≤b≤c)
- 解集中不能包含重复的三元组。
例如,给定的数组 S = {-1 0 1 2 -1 -4},解集为(-1, 0, 1) (-1, -1, 2)
class Solution {
public:
vector<vector<int> > threeSum(vector<int>& num) {
sort(num.begin(), num.end()); //排序
vector<vector<int> >res;
int len = num.size();
for (int i = 0; i < len; ++i) {
if (num[i] > 0) break;
else if (i > 0 && num[i] == num[i - 1]) continue; //去重
int j = i + 1; //双指针夹逼
int k = len - 1;
while (j < k)
{
if (num[i] + num[j] + num[k] == 0) {
res.emplace_back(vector<int>{num[i], num[j], num[k]});
while (j < k && num[j] == num[j + 1]) ++j; //去重
while (j < k && num[k] == num[k - 1]) --k; //去重
++j;
--k;
}
else if (num[i] + num[j] + num[k] < 0) ++j;
else --k;
}
}
return res;
}
};
合并有序列表
题目描述
将两个有序的链表合并为一个新链表,要求新的链表是通过拼接两个链表的节点来生成的。
class Solution {
public:
/**
*
* @param l1 ListNode类
* @param l2 ListNode类
* @return ListNode类
*/
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// write code here
ListNode* res = new ListNode(0); //头节点
ListNode* cur = res; //遍历指针
while (l1 && 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 == nullptr ? l2 : l1; //若有链表不为空则加入新链表尾部
return res->next;
}
};
判断链表中是否有环
题目描述
判断给定的链表中是否有环
扩展:
你能给出空间复杂度的解法么?
双指针法。
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head == nullptr || head->next == nullptr)
return false;
ListNode* fast = head->next->next;
ListNode* slow = head;
while(fast->next&&fast){ //链表长度可能是奇数或者偶数
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
return true;
}
return false;
}
};
寻找第K大的数
题目描述
有一个整数数组,请你根据快速排序的思路,找出数组中第K大的数。
给定一个整数数组a,同时给定它的大小n和要找的K(K在1到n之间),请返回第K大的数,保证答案存在。
测试样例:
[1,3,5,2,2],5,3
返回:2
topK的题目,指定用快排思想的话,每次只排含有第K大数的一边。
class Finder {
public:
int findKth(vector<int> a, int n, int K) {
// write code here
int start = 0, end = n - 1,cur = -1; //初始化
--K; //数组下标从零开始,K从1开始
while (cur!=K) {
cur = partition(a, start, end);
if (cur > K)
end = cur - 1;
else
start = cur + 1;
}
return a[K];
}
int partition(vector<int>& a, int start, int end) //从大到小排序
{
int i = start, j = end, temp = a[start];
while (i < j) {
while (a[j] < temp && i != j) //从右往左找大于支点
--j;
if (i < j) //右往左调整
a[i++] = a[j];
while (a[i] > temp && i != j) //从左往右找小于支点
++i;
if (i < j) //左往右调整
a[j--] = a[i];
}
a[i] = temp; //回填支点
return i;
}
};
求解平方根
题目描述
实现函数 int sqrt(int x).
计算并返回x的平方根
int sqrt(int x) {
// write code here
float x0 = 1.0, x1 = x0 - (pow(x0, 2) - x) / (2 * x0); //牛顿迭代法,二阶可导收敛
while (fabs(x1 - x0) > 1e-7) { //迭代终止条件
x0 = x1;
x1 = x0 - (pow(x0, 2) - x) / (2 * x0);
}
return x1;
}
设计LRU缓存结构
题目描述
设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
- set(key, value):将记录(key, value)插入该结构
- get(key):返回key对应的value值
[要求]
- set和get方法的时间复杂度为O(1)
- 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
- 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,输出一个答案
c++
class Solution {
public:
vector<int> LRU(vector<vector<int> >& operators, int k) {
// write code here
vector<int> res;
len = k;
for (auto opt : operators)
if (opt[0] == 1)
set(opt[1], opt[2]);
else
res.push_back(get(opt[1]));
return res;
}
void set(int key, int value) //设置键值对
{
inUse.push_front(make_pair(key, value)); //最近使用在前
m[key] = inUse.begin(); //使用iterator,而不是下标,因为链表变更
if (inUse.size() > len)
{
m.erase(inUse.back().first); //同步维护哈希表和链表
inUse.pop_back();
}
}
int get(int key) //获取并更新键值对
{
auto it = m.find(key);
if (it == m.end()) return -1; //未加入或已移出
else {
inUse.splice(inUse.begin(), inUse, it->second); //splice函数把原片段拼接后删除原片段
return it->second->second;
}
}
private:
int len; //缓存结构长度
list<pair<int, int>> inUse; //正在使用中的链表
unordered_map<int, list<pair<int, int>>::iterator> m; //使set,get复杂度为O(1)
};
链表第一个公共节点
题目描述
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
class Solution {
public:
ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2) {
stack<ListNode*> st1, st2; //关键是理解两链表的形状是‘Y’型
ListNode* ans = nullptr;
while (pHead1){ //链表一入栈
st1.push(pHead1);
pHead1 = pHead1->next;
}
while (pHead2) { //链表二入栈
st2.push(pHead2);
pHead2 = pHead2->next;
}
while (!st1.empty() && !st2.empty() && st1.top() == st2.top())
{
ans = st1.top();
st1.pop(); //弹出公共节点
st2.pop();
}
return ans;
}
};
合并K个已排序的链表
题目描述
合并 k 个已排序的链表并将其作为一个已排序的链表返回。分析并描述其复杂度。
使用优先队列
class Solution {
public:
struct compare{ //需要自定义比较结构体
bool operator()(const ListNode* l1,const ListNode* l2)const{
return l1->val > l2->val;
}
};
ListNode *mergeKLists(vector<ListNode *> &lists) {
priority_queue<ListNode*,deque<ListNode*> ,compare> pq;
for(auto l:lists)
if(l) pq.push(l); //将各个链表的头节点入队
ListNode* res = new ListNode(0); //头节点
ListNode* p = res;
while(!pq.empty())
{
ListNode* q = pq.top();
pq.pop();
if(q->next) pq.push(q->next); //逐个处理各个元素
p = p->next = q; //链式赋值,从右到左
}
return res->next;
}
};