剑指Offer 第34-44题
AcWing 46. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。
如果是则返回true,否则返回false。
假设输入的数组的任意两个数字都互不相同。
样例
输入:[4, 8, 6, 12, 16, 14, 10]
输出:true
题解:
- 中序(二叉搜索树的中序是从小到大的结果)+后序建树
- 后序遍历最后一个数为根,二叉搜索树的性质,我们直接从头开始找到比根小的值,这便是左子树;右边部分便是右子树,如果不符合便是错误。
class Solution {
public:
vector<int> seq;
bool verifySequenceOfBST(vector<int> sequence) {
seq = sequence;
if(seq.empty()) return true;
return dfs(1, seq.size() - 1);
}
bool dfs(int l, int r){
if(l >= r) return true;
int root = seq[r];
int k = 0;
while(k < r && seq[k] < root) k++;
for(int i = k; i < r; i++)
{A
if(seq[i] < root) return false;
}
return dfs(l, k - 1) && dfs(k, r - 1);
}
};
AcWing 47. 二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。
从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
样例
给出二叉树如下所示,并给出num=22。
dfs
/**
* 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>> res;
vector<vector<int>> findPath(TreeNode* root, int sum) {
if(root == NULL) return res;
vector<int> path;
dfs(root, sum, path);
return res;
}
void dfs(TreeNode* root, int sum, vector<int>&path){
if(root->left == NULL && root->right == NULL)
{
if(sum - root->val == 0)
{
path.push_back(root->val);
res.push_back(path);
path.pop_back();
}
return;
}
path.push_back(root->val);
if(root->left != NULL) dfs(root->left, sum - root->val, path);
if(root->right != NULL) dfs(root->right, sum - root->val, path);
path.pop_back();
}
};
AcWing 48. 复杂链表的复刻
请实现一个函数可以复制一个复杂链表。
在复杂链表中,每个结点除了有一个指针指向下一个结点外,还有一个额外的指针指向链表中的任意结点或者null。
注意:
函数结束后原链表要与输入时保持一致。
- hash表做映射 时间O(n) 空间O(n)
/**
* Definition for singly-linked list with a random pointer.
* struct ListNode {
* int val;
* ListNode *next, *random;
* ListNode(int x) : val(x), next(NULL), random(NULL) {}
* };
*/
class Solution {
public:
ListNode *copyRandomList(ListNode *head) {
if(!head) return head;
unordered_map<ListNode*, ListNode*> pos;
for(auto p = head; p; p = p -> next)
{
pos[p] = new ListNode(p->val);
}
pos[NULL] = NULL;
for(auto p = head; p; p = p -> next)
{
pos[p]->next = pos[p->next];
pos[p]->random = pos[p->random];
}
return pos[head];
}
};
/**
* Definition for singly-linked list with a random pointer.
* struct ListNode {
* int val;
* ListNode *next, *random;
* ListNode(int x) : val(x), next(NULL), random(NULL) {}
* };
*/
class Solution {
public:
ListNode *copyRandomList(ListNode *head) {
// 每一个节点后面加复制
for(auto p = head; p; p = p->next)
{
auto np = new ListNode(p->val);
auto next = p->next;
p->next = np;
np->next = next;
p = np;
}
// 复制random指针
for(auto p = head; p; p = p->next->next)
{
if(p->random)
p->next->random = p->random->next;
}
// 分离两个链表
auto dummy = new ListNode(-1);
auto cur = dummy;
for(auto p = head; p; p = p->next)
{
cur->next = p->next;
cur = cur->next;
p->next = p->next->next;
}
return dummy->next;
}
};
AcWing 49. 二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。
要求不能创建任何新的结点,只能调整树中结点指针的指向。
注意:
需要返回双向链表最左侧的节点。
例如,输入下图中左边的二叉搜索树,则输出右边的排序双向链表。
解题思路
实现返回每一个子树的两端节点即可
AcWing 50. 序列化二叉树
AcWing 51. 数字排列(good)
输入一组数字(可能包含重复数字),输出其所有的排列方式。
样例
输入:[1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
题解
根以前的枚举不一样,需要用占位的思想去枚举,预先开出n个坑位,将nums每一个数往里面放,一个数只能放在前一个相同数字的后面。
class Solution {
public:
int n = 0;
vector<vector<int> > ans;
vector<int> path;//位置
//u表示枚举到nums第几个数了,start表示从空位的第几位开始可以放
void dfs(int u, int start, int state, vector<int>& nums)
{
if(u == n)
{
ans.push_back(path);
return;
}
// 头一个数或者与上一个数不同
if(u == 0 || nums[u] != nums[u-1]) start = 0;
for(int i = start; i < n; i++)
{
if(!(state>>i&1))
{
path[i] = nums[u];
dfs(u + 1, i + 1, state+(1<<i), nums);
}
}
}
vector<vector<int>> permutation(vector<int>& nums) {
sort(nums.begin(), nums.end());
n = nums.size();
path.resize(n);
dfs(0, 0, 0, nums);
return ans;
}
};
库函数
class Solution {
public:
vector<vector<int>> permutation(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<vector<int>> res;
do
{
res.push_back(nums);
}while (next_permutation(nums.begin(),nums.end()));
// do res.push_back(nums); while (next_permutation(nums.begin(), nums.end()));
return res;
}
};
AcWing 52. 数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
假设数组非空,并且一定存在满足条件的数字。
思考题:
假设要求只能使用 O(n) 的时间和额外 O(1) 的空间,该怎么做呢?
样例
输入:[1,2,1,1,3]
输出:1
题解
抵消(配对)的思想。
循环数组
- count == 0: value=a[i], count=1
- count != 0:
- value != a[i] : count -= 1
- value == a[i] : count += 1
n个村民
好人数量 > n/2 (回答的是正确)
坏蛋数量 < n/2 (回答随机)
o(n)次询问找出坏蛋。
count: 当前人被信任的次数,0
val: 人是谁,-1
从头循环,
- count == 0,首先问自己是否被信任,
回答是好人,count += 1,val = id
回答坏蛋的一定是坏蛋,好人只会回答自己是好人。 - count != 0, 问val十分是好人,若val是好人,则count += 1。
若是坏人,则count -= 1。count减到0,则替换
好人的信任值。
好人的信用量一定大于坏人的信用量,消耗一个好人肯定需要消耗一个坏人。
class Solution {
public:
int moreThanHalfNum_Solution(vector<int>& nums) {
int cnt = 0, val = -1;
for(auto it: nums)
{
if(cnt == 0)
{
val = it;
cnt ++;
}
else
{
if(val == it) cnt ++;
else cnt --;
}
}
return val;
}
};
AcWing 53. 最小的k个数
输入n个整数,找出其中最小的k个数。
注意:
数据保证k一定小于等于输入数组的长度;
输出数组内元素请按从小到大顺序排序;
样例
输入:[1,2,3,4,5,6,7,8] , k=4
输出:[1,2,3,4]
用大根堆维护一下即可,(优先队列)
class Solution {
public:
vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
priority_queue<int> heap;
for(auto it : input)
{
if(heap.size() >= k)
{
if(it < heap.top())
{
heap.pop();
heap.push(it);
}
}
else
heap.push(it);
}
vector<int> res;
for(int i = 0; i < k; i++)
{
res.push_back(heap.top());
heap.pop();
}
reverse(res.begin(), res.end());
return res;
}
};
class Solution {
public:
vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
priority_queue<int> heap;
for (auto x : input)
{
if (heap.size() < k || heap.top() > x) heap.push(x);
if (heap.size() > k) heap.pop();
}
vector<int> res;
while (heap.size()) res.push_back(heap.top()), heap.pop();
reverse(res.begin(), res.end());
return res;
}
};
AcWing 54. 数据流中的中位数
如何得到一个数据流中的中位数?
如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。
如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
样例
输入:1, 2, 3, 4
输出:1,1.5,2,2.5
解释:每当数据流读入一个数据,就进行一次判断并输出当前的中位数。
题解
维护两个堆,大根堆维护较小的那部分,小根堆维护较大那部分,保证大跟堆的数量最多只比小根堆的数量多1个即可,代码中实现的是比较简捷的做法。
class Solution {
public:
priority_queue<int, vector<int>, greater<int> > heap_u;
priority_queue<int> heap_d;
int n = 0;
void insert(int num){
n++;
heap_d.push(num);
while(heap_u.size() != 0 && heap_d.top() > heap_u.top())
{
int maxx = heap_d.top(), minn = heap_u.top();
heap_d.pop(), heap_u.pop();
heap_d.push(minn), heap_u.push(maxx);
}
if(heap_d.size() - heap_u.size() > 1)
{
heap_u.push(heap_d.top());
heap_d.pop();
}
}
double getMedian(){
if(n & 1)
return heap_d.top();
else
return (heap_d.top() + heap_u.top()) / 2.0;
}
};
AcWing 55. 连续子数组的最大和
输入一个 非空 整型数组,数组里的数可能为正,也可能为负。
数组中一个或连续的多个整数组成一个子数组。
求所有子数组的和的最大值。
要求时间复杂度为O(n)。
样例
输入:[1, -2, 3, 10, -4, 7, 2, -5]
输出:18
题解
状态表示:
集合划分:以a[k] 为结尾的连续子数组
- 只有a[k]一个数
- 至少两个数,一定选a[k],所以可以去掉a[k]
相当于f(k-1) + a[k]
f(k) = max{a[k], f(k-1) +a[k]}
f(k) = a[k] + max(0, f(k-1))
S = a[k] + max(0, S)