知识迁移能力
- 剑指 Offer 53 - I. 在排序数组中查找数字 I
- 剑指 Offer 53 - II. 0~n-1中缺失的数字
- 剑指 Offer 54. 二叉搜索树的第k大节点
- 剑指 Offer 55 - I. 二叉树的深度
- 剑指 Offer 55 - II. 平衡二叉树
- 剑指 Offer 56 - I. 数组中数字出现的次数
- 剑指 Offer 56 - I. 数组中数字出现的次数
- 剑指 Offer 57. 和为s的两个数字
- 剑指 Offer 57 - II. 和为s的连续正数序列
- 剑指 Offer 58 - I. 翻转单词顺序
- 剑指 Offer 58 - II. 左旋转字符串
- 剑指 Offer 59 - I. 滑动窗口的最大值
- 剑指 Offer 59 - II. 队列的最大值
剑指 Offer 53 - I. 在排序数组中查找数字 I
题目
代码
class Solution {
public:
//寻找右边界
int searchrb(vector<int>& nums, int target) {
int sz = nums.size();
int left = 0, right = sz - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] <= target) {
left = mid + 1;
}else {
right = mid - 1;
}
}
return left;
}
int search(vector<int>& nums, int target) {
//二分法,找到目标数字的左右边界,或者查找target和target-1的右边界
return searchrb(nums, target) - searchrb(nums, target - 1);
}
};
剑指 Offer 53 - II. 0~n-1中缺失的数字
题目
代码
class Solution {
public:
int missingNumber(vector<int>& nums) {
//二分法
int sz = nums.size();
int left = 0, right = sz - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] == mid) {
left = mid + 1;
}else {
right = mid - 1;
}
}
return left;
}
};
剑指 Offer 54. 二叉搜索树的第k大节点
题目
代码
法一:中序遍历+vector存值
/**
* 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:
int sz = 0;
vector<int> walk;
void re(TreeNode* root) {
if(root == nullptr) {
return;
}
re(root->left);
walk.push_back(root->val);
sz++;
re(root->right);
}
int kthLargest(TreeNode* root, int k) {
//前遍历的倒数第三个节点
re(root);
return walk[sz - k];
}
};
法二:
中序遍历倒着来,右根左
/**
* 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:
int res;
void re(TreeNode* root, int& k) {
if(root == nullptr) {
return;
}
re(root->right, k);
k--;
if(k == 0) {
res = root->val;
return ;
}
re(root->left, k);
}
int kthLargest(TreeNode* root, int k) {
//前遍历的倒数第三个节点
re(root, k);
return res;
}
};
剑指 Offer 55 - I. 二叉树的深度
题目
代码
/**
* 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:
int dfs(TreeNode* root) {
if(root == nullptr) {
return 0;
}
int l = dfs(root->left);
int r = dfs(root->right);
//最大深度等于左子树和右子树深度额较大值加一
return max(l,r) + 1;
}
int maxDepth(TreeNode* root) {
//递归
return dfs(root);
}
};
剑指 Offer 55 - II. 平衡二叉树
题目
代码
方法一:
/**
* 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:
bool flag = true;
int depth(TreeNode* root) {
if(root == nullptr) {
return 0;
}
int ld = depth(root->left);
int rd = depth(root->right);
if(ld - rd > 1 || rd - ld > 1) {
flag = false;
}
return max(ld, rd) + 1;
}
bool isBalanced(TreeNode* root) {
depth(root);
return flag;
}
};
方法二:
剪枝
/**
* 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:
int depth(TreeNode* root) {
if(root == nullptr) {
return 0;
}
int ld = depth(root->left);
if(ld == -1) {
return -1;
}
int rd = depth(root->right);
if(rd == -1) {
return -1;
}
if( ld - rd > 1 || rd - ld > 1 ) {
return -1;
}else {
return max(ld, rd) + 1;
}
}
bool isBalanced(TreeNode* root) {
if(depth(root) != -1) {
return true;
}else
return false;
}
};
剑指 Offer 56 - I. 数组中数字出现的次数
题目
代码
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
//异或也太妙了
int z = 0;
for(auto num: nums) {
//z就是两个目标数字的异或值
z = z ^ num;
}
int m = 1;
//找到z中为1的位
while((m & z) == 0) {
m <<= 1;
}
int a = 0, b = 0;
for(auto num : nums) {
//num & m可以把数组分为两部分,两个单独的数字一定会被分开
if(num & m) {
a = a ^ num;
}else {
b = b ^ num;
}
}
return vector<int>{a, b};
}
};
剑指 Offer 56 - I. 数组中数字出现的次数
题目
代码
方法:有限状态机
不是很懂
class Solution {
public:
int singleNumber(vector<int>& nums) {
int one = 0, two = 0;
for(auto num : nums) {
one = one ^ num & ~two;
two = two ^ num & ~one;
}
return one;
}
};
剑指 Offer 57. 和为s的两个数字
题目
代码
能做出来
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int sz = nums.size() ;
int left = 0, right = sz - 1;
vector<int>res;
while(left < right) {
if(nums[left] + nums[right] == target) {
res.push_back(nums[left]);
res.push_back(nums[right]);
return res;
}else if(nums[left] + nums[right] < target) {
left++;
}else
right--;
}
return res;
}
};
剑指 Offer 57 - II. 和为s的连续正数序列
题目
代码
class Solution {
public:
vector<vector<int>> findContinuousSequence(int target) {
//用滑动窗口,考虑窗口内所有值
vector<vector<int>>res;
//s是元素和
int left = 1, right = 2, s = 3;
while(left < right) {
if(s == target) {
vector<int>ans;
for(int i = left; i <= right; i++) {
ans.push_back(i);
}
res.push_back(ans);
}
if(s >= target) {
//左边界向右移动
s = s - left;
left ++;
}else {
//右边界向右移动
right++;
s = s + right;
}
}
return res;
}
};
剑指 Offer 58 - I. 翻转单词顺序
题目
代码
感觉不太会。。。
方法:双指针、倒序
class Solution {
public:
string reverseWords(string s) {
//忽略头和尾部的空格
string res;
int sz = s.size();
int right = sz - 1;
while(right >= 0) {
//从后往前寻找第一个字符
while(s[right] == ' ') {
right--;
}
//寻找第一个空格
int left = right;
while(left >= 0 && s[left] != ' ' ) {
left--;
}
//添加单词
res = res + s.substr(left + 1, right - left);
res = res + ' ';
//继续往前找
right = left;
}
//去除最后一个字符多加的空格
if(!res.empty()) {
res.pop_back();
}
return res;
}
};
剑指 Offer 58 - II. 左旋转字符串
题目
代码
法一:自己写的,用时和空间消耗都很大
class Solution {
public:
string reverseLeftWords(string s, int n) {
//左旋n个字母,按顺序把开头的n个字母依次放到最后
int sz = s.size() - 1;
for(int i = 0; i < n; i++) {
s = s + s[i];
}
return s.substr(n, sz + n);
}
};
法二:两部分用substr()拼接起来
class Solution {
public:
string reverseLeftWords(string s, int n) {
return s.substr(n, s.size() - 1 + n) + s.substr(0, n);
}
};
法三:遍历拼接加取余
class Solution {
public:
string reverseLeftWords(string s, int n) {
string res = string(s);
int len = s.length();
for(int i =0 ;i<len;++i){
res[(i+len-n)%len]=s[i];
}
return res;
}
};
剑指 Offer 59 - I. 滑动窗口的最大值
题目
代码
法一:单调队列
deque 内的元素非严格递减
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
int sz = nums.size();
vector<int> res;
if(sz == 0) {
return res;
}
res.resize(sz - k + 1);
deque<int>dq;
//还未形成窗口
for(int i = 0; i < k; i++) {
//如果双端队列末尾的元素小于当前元素,弹出
while(!dq.empty() && dq.back() < nums[i]) {
dq.pop_back();
}
dq.push_back(nums[i]);
}
//已经形成窗口
//这一步不能忘
res[0] = dq.front();
for(int i = k; i < sz; i++) {
//队首元素等于被删除元素,弹出
if(dq.front() == nums[i - k]) {
dq.pop_front();
}
while(!dq.empty() && dq.back() < nums[i]) {
dq.pop_back();
}
dq.push_back(nums[i]);
res[i - k + 1] = dq.front();
}
return res;
}
};
方法二:
对于「最大值」,我们可以想到一种非常合适的数据结构,那就是优先队列(堆),其中的大根堆可以帮助我们实时维护一系列元素中的最大值。
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
//优先队列,大根堆,存入数组元素及其索引
priority_queue<pair<int, int>>pq;
int sz = nums.size();
vector<int> res;
if(sz == 0) {
return res;
}
for(int i = 0; i < k; i++) {
pq.push({nums[i], i});
}
res.push_back({pq.top().first});
for(int i = k; i < sz; i++) {
pq.push({nums[i], i});
//弹出滑出窗口的元素
while(pq.top().second <= i - k) {
pq.pop();
}
res.push_back(pq.top().first);
}
return res;
}
};
剑指 Offer 59 - II. 队列的最大值
题目
代码
本算法基于问题的一个重要性质:当一个元素进入队列的时候,它前面所有比它小的元素就不会再对答案产生影响。
咱也不知这个均摊时间复杂度都是O(1)要怎么看
class MaxQueue {
public:
queue<int>q;
//双端单调队列
deque<int>dq;
MaxQueue() {
}
int max_value() {
if(q.empty()) {
return -1;
}
return dq.front();
}
void push_back(int value) {
//弹出小于value的值
while(!dq.empty() && dq.back() < value) {
dq.pop_back();
}
dq.push_back(value);
q.push(value);
}
int pop_front() {
if(q.empty()) {
return -1;
}
int res = q.front();
//q队首元素要么是dq的队首元素,要么是已经被dq弹出的元素
if(res == dq.front()) {
dq.pop_front();
}
q.pop();
return res;
}
};
/**
* Your MaxQueue object will be instantiated and called as such:
* MaxQueue* obj = new MaxQueue();
* int param_1 = obj->max_value();
* obj->push_back(value);
* int param_3 = obj->pop_front();
*/