难度中等62
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
示例 1:
输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
输出:true
解释:我们可以按以下顺序执行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
输出:false
解释:1 不能在 2 之前弹出。
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> s;
//做模拟???
int left=0;
for(int i=0;i<pushed.size();i++){
s.push(pushed[i]);
while(!s.empty()&&s.top()==popped[left]){
s.pop();left++;
}
}
if(left!=pushed.size()) return false;
return true;
}
};
难度中等33
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回:
[3,9,20,15,7]
vector<int> levelOrder(TreeNode* root) {
vector<int> ans;
queue<TreeNode*> q;
if(root==NULL) return ans;
q.push(root);
while(!q.empty()){
TreeNode* now=q.front();q.pop();
ans.push_back(now->val);
if(now->left!=NULL) q.push(now->left);
if(now->right!=NULL) q.push(now->right);
}
return ans;
}
剑指 Offer 32 - II. 从上到下打印二叉树 II
难度简单41
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[9,20],
[15,7]
]
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
queue<TreeNode*> q;
if(root==NULL) return ans;
q.push(root);
vector<int> tmp;
while(!q.empty()){
int size=q.size();tmp.clear();
while(size--){
TreeNode* now=q.front();q.pop();
tmp.push_back(now->val);
if(now->left!=NULL) q.push(now->left);
if(now->right!=NULL) q.push(now->right);
}
ans.push_back(tmp);
}
return ans;
}
};
剑指 Offer 32 - III. 从上到下打印二叉树 III
难度中等38
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]
也可以用双端队列,
奇偶层的打印顺序不一样是相反的,可以利用层数偶数与否调用reverse来解决,但是海量数据时这个效率很低,不推荐。
因为奇数层的打印顺序是从左到右,偶数层的打印顺序是从右到左,可以利用STL容器deque中
push_back(),push_front(),front(),back(),pop(),popfront()来实现
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
queue<TreeNode*> q;
if(root==NULL) return ans;
q.push(root);
vector<int> tmp;
int cnt=0;
while(!q.empty()){
int size=q.size();tmp.clear();
while(size--){
TreeNode* now=q.front();q.pop();
// ans[cnt].push_back(now->val);
tmp.push_back(now->val);
if(now->left!=NULL) q.push(now->left);
if(now->right!=NULL) q.push(now->right);
}
cnt++;
if(cnt%2==0) reverse(tmp.begin(),tmp.end());
ans.push_back(tmp);
}
return ans;
}
难度中等93
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true
,否则返回 false
。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5
/ \
2 6
/ \
1 3
示例 1:
输入: [1,6,3,2,5]
输出: false
示例 2:
输入: [1,3,2,6,5]
输出: true
class Solution {
public:
bool verify(vector<int> postorder,int l,int r){
if(l>=r) return true;
int root=postorder[r];
int i=l;//左子树
while(i<r&&postorder[i]<postorder[r]) i++;
int tmp=i;
//l 到 i-1为左子树
while(i<r){
if(postorder[i]<root) return false;
i++;
}
return verify(postorder,l,tmp-1)&&verify(postorder,tmp,r-1);
}
bool verifyPostorder(vector<int>& postorder) {
return verify(postorder,0,postorder.size()-1);
}
};
单调栈解决:
class Solution {
public boolean verifyPostorder(int[] postorder) {
Stack<Integer> stack = new Stack<>();
int root = Integer.MAX_VALUE;
for(int i = postorder.length - 1; i >= 0; i--) {
if(postorder[i] > root) return false;
while(!stack.isEmpty() && stack.peek() > postorder[i])
root = stack.pop();
stack.add(postorder[i]);
}
return true;
}
}
难度中等69
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
示例:
给定如下二叉树,以及目标和 sum = 22
,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
void get(vector<int> temp,int nowsum,int sum,vector<vector<int>> &ans,TreeNode* root){
if(root==NULL) return;
temp.push_back(root->val);
nowsum+=root->val;
if(root->left==NULL&&root->right==NULL&&nowsum==sum){
ans.push_back(temp);
return;
}
get(temp,nowsum,sum,ans,root->left);
get(temp,nowsum,sum,ans,root->right);
temp.pop_back();
}
vector<vector<int>> pathSum(TreeNode* root, int sum) {
// 从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
vector<vector<int>> ans;
vector<int> temp;
get(temp,0,sum,ans,root);
return ans;
}
难度中等80
请实现 copyRandomList
函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next
指针指向下一个节点,还有一个 random
指针指向链表中的任意节点或者 null
。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]
示例 3:
输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]
示例 4:
输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
看不懂先跳过
难度中等86
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
为了让您更好地理解问题,以下面的二叉搜索树为例:
我们希望将这个二叉搜索树转化为双向循环链表。链表中的每个节点都有一个前驱和后继指针。对于双向循环链表,第一个节点的前驱是最后一个节点,最后一个节点的后继是第一个节点。
下图展示了上面的二叉搜索树转化成的链表。“head” 表示指向链表中有最小元素的节点。
特别地,我们希望可以就地完成转换操作。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继。还需要返回链表中的第一个节点的指针。
先跳过、
难度中等79
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]
//回溯、交换、剪枝的做法
class Solution {
public:
vector<string> ans;
void dfs(int x,string s){
if(x==s.length()-1){
ans.push_back(s);return;
}
unordered_map<char,int> mp;
for(int i=x;i<=s.length()-1;i++){
if(mp.count(s[i])) continue;
mp[s[i]]=1;
swap(s[i],s[x]);
dfs(x+1,s);
swap(s[i],s[x]);
}
}
vector<string> permutation(string s) {
dfs(0,s);
return ans;
}
};
难度简单49
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
输出: 2
本题常见解法如下:
哈希表统计法: 遍历数组 nums ,用 HashMap 统计各数字的数量,最终超过数组长度一半的数字则为众数。此方法时间和空间复杂度均为 O(N)O(N)O(N) 。
数组排序法: 将数组 nums 排序,由于众数的数量超过数组长度一半,因此 数组中点的元素 一定为众数。此方法时间复杂度 O(Nlog2N)O(N log_2 N)O(Nlog2N)。
摩尔投票法: 核心理念为 “正负抵消” ;时间和空间复杂度分别为 O(N)O(N)O(N) 和 O(1)O(1)O(1) ;是本题的最佳解法。
作者:jyd
链接:https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/solution/mian-shi-ti-39-shu-zu-zhong-chu-xian-ci-shu-chao-3/
//主要是因为出现了超过一半.
int majorityElement(vector<int>& nums) {
int count = 1, cur = nums[0];
for(int i = 1; i < nums.size(); i++) {
if(cur != nums[i]) {
--count;
if(count < 0) {
cur = nums[i];
count = 1;
}
}
else ++count;
}
return cur;
}
难度简单119
输入整数数组 arr
,找出其中最小的 k
个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
限制:
0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000
我们用一个大根堆实时维护数组的前 k小值。首先将前 k个数插入大根堆中,随后从第 k+1个数开始遍历,如果当前遍历到的数比大根堆的堆顶的数要小,就把堆顶的数弹出,再插入当前遍历到的数。最后将大根堆里的数存入数组返回即可。在下面的代码中,由于 C++ 语言中的堆(即优先队列)为大根堆,我们可以这么做。而 Python 语言中的对为小根堆,因此我们要对数组中所有的数取其相反数,才能使用小根堆维护前 k小值。
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int>vec(k, 0);
if (k == 0) return vec; // 排除 0 的情况
priority_queue<int>Q;
for (int i = 0; i < k; ++i) Q.push(arr[i]);
for (int i = k; i < (int)arr.size(); ++i) {
if (Q.top() > arr[i]) {
Q.pop();
Q.push(arr[i]);
}
}
for (int i = 0; i < k; ++i) {
vec[i] = Q.top();
Q.pop();
}
return vec;
}
};
class Solution {
public:
//快排 时间复杂度O(N)
int partition(int left,int right,vector<int> &nums){
int tmp=left;
int pivot=nums[left];
//从left+1开始
for(int i=left+1;i<=right;i++){
if(nums[i]<pivot){
tmp++;//在前面...
swap(nums[i],nums[tmp]);
}
}
//最后呢pivot的位置就是left
swap(nums[tmp],nums[left]);
return tmp;
}
void quicksort(vector<int> &nums,int left,int right,int k) {
int j=partition(left,right,nums);
if(j==k) return;
else if(k<j)
quicksort(nums,left,j-1,k);
else
quicksort(nums,j+1,right,k);
}
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> res(k);
if (k == 0) {
return res;
} else if (arr.size() <= k) {
return arr;
}
quicksort(arr, 0, arr.size()- 1, k);
// 数组的前 k 个数此时就是最小的 k 个数,将其存入结果
for (int i = 0; i < k; i++)
res[i] = arr[i];
return res;
}
};
在面试中,另一个常常问的问题就是这两种方法有何优劣。看起来分治法的快速选择算法的时间、空间复杂度都优于使用堆的方法,但是要注意到快速选择算法的几点局限性:
第一,算法需要修改原数组,如果原数组不能修改的话,还需要拷贝一份数组,空间复杂度就上去了。
第二,算法需要保存所有的数据。如果把数据看成输入流的话,使用堆的方法是来一个处理一个,不需要保存数据,只需要保存 k 个元素的最大堆。而快速选择的方法需要先保存下来所有的数据,再运行算法。当数据量非常大的时候,甚至内存都放不下的时候,就麻烦了。所以当数据量大的时候还是用基于堆的方法比较好。
作者:nettee
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/tu-jie-top-k-wen-ti-de-liang-chong-jie-fa-you-lie-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
// left_index = parent_index * 2 + 1;
// right_index = parent_index * 2 + 2;
// parent_index = (left_index - 1) / 2;
// parent_index = (right_index - 1) / 2;
//本题用到的是最大堆啊!!!!!!!!!!!!!!!!!!!!!
// 对于比较大的值,做下沉操作,得到我们的最小堆(从非叶子结点开始)
void downAdjust(vector<int>& nums,int index){
int j=index*2+1;
int parent=nums[index];
while(j<nums.size()){
//较小的上浮
if(j+1<nums.size()&&nums[j+1]>nums[j]) j=j+1;
if(parent>=nums[j]) break;
// cout<<nums[index]<<" "<<nums[j];
nums[index]=nums[j];
index=j;
j=2*index+1;
}
nums[index]=parent;
//也可以避免重复交换,也就是在最后确认parent的坐标后再写nums[index]=parent;
}
void buildHeap(vector<int> &nums){
for(int i= nums.size()/ 2;i>=0;i--) downAdjust(nums,i);
}
vector<int> getLeastNumbers(vector<int>& arr, int k) {
if(k == 0) return vector<int>();
vector<int> max_heap_vec(arr.begin(), arr.begin()+k);
buildHeap(max_heap_vec);
for(int i=0;i<k;i++) cout<<" "<<max_heap_vec[i];
for(int i = k; i<arr.size(); ++i){
// 出现比堆顶元素小的值, 置换堆顶元素, 并调整堆
if(arr[i] < max_heap_vec[0]){
max_heap_vec[0]=arr[i];
downAdjust(max_heap_vec,0);
}
}
return max_heap_vec;
}
};
/*
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
if(k == 0) return vector<int>();
vector<int> max_heap_vec(arr.begin(), arr.begin()+k);
buildMaxHeap(max_heap_vec);
for(int i = k; i<arr.size(); ++i){
// 出现比堆顶元素小的值, 置换堆顶元素, 并调整堆
if(arr[i] < max_heap_vec[0]){
emplacePeek(max_heap_vec, arr[i]);
}
}
return max_heap_vec;
}
private:
void buildMaxHeap(vector<int>& v){
// 所有非叶子节点从后往前依次下沉
for(int i = (v.size()-2) / 2; i>=0; --i){
downAdjust(v, i);
}
}
void downAdjust(vector<int>& v, int index){
int parent = v[index];
// 左孩子节点索引
int child_index = 2*index + 1;
while(child_index < v.size()){
// 判断是否存在右孩子, 并选出较大的节点
if(child_index + 1 < v.size() && v[child_index+1] > v[child_index]){
child_index += 1;
}
// 判断父节点和子节点的大小关系
if(parent >= v[child_index]) break;
// 较大节点上浮
v[index] = v[child_index];
index = child_index;
child_index = 2*index + 1;
}
// 避免重复交换的无用工作
v[index] = parent;
}
void emplacePeek(vector<int>& v, int val){
v[0] = val;
downAdjust(v, 0);
}
};
*/
应该面试常问的问题吧:十亿个杂乱无章的数中找出前1000个最小的数
我当时先给出的回答是分块处理,例如一亿数据为一组,找出一组中的前1000个最小的数,然后再在10*1000个数据中找出前1000个最小的数。
面试官问怎么找出一亿数据中的Top1000?我先回答说排序,用快排。
面试官说复杂度有些高呀,可以优化么?我想了一下,只需要找出前1000个数,并不要求有序,排序肯定是不必要的,但总不能遍历1000次数组找每次的最小值吧。虽然不合理,但是避免冷场,我还是说出来了。
面试官说,也算是一种方法,还可以优化么。我首先想到的是,设置1000大小的窗口,当每次来一个数的时候,判断它和窗口的上边界的大小再去插入,但是有一个问题就是这个数据如何插入。因为输出结果并不要求有序,所以需要一种数据结构可以每次插入之后自动调整出最大值
呐,这不就是最大堆的性质么,然后我就哇啦哇啦的说了一堆,下面再介绍,也不知道面试官是否满意它也没说话。
作者:zuo-10
链接:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/solution/c-dui-pai-xu-jie-jue-topkwen-ti-pei-tu-by-zuo-10/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。