目录
- 一、剑指offer04:二维数组中的查找
- 二、剑指 Offer 05. 替换空格
- 三、剑指 Offer 06. 从尾到头打印链表
- 四、剑指 Offer 07. 重建二叉树
- 五、剑指 Offer 09. 用两个栈实现队列 || 用两个队列实现栈
- 六、剑指 Offer 10- II. 青蛙跳台阶问题
- 七、剑指 Offer 11. 旋转数组的最小数字
- 八、剑指 Offer 12. 矩阵中的路径
- 九、剑指 Offer 14- I. 剪绳子
- 十、剑指 Offer 15. 二进制中1的个数
- 十一、快速幂
- 十二、剑指 Offer 22. 链表中倒数第k个节点
- 十四、剑指 Offer 24. 反转链表
- 十五、剑指 Offer 25. 合并两个排序的链表
- 十六、剑指 Offer 26. 树的子结构
- 十七、剑指 Offer 27. 二叉树的镜像
- 十八、剑指 Offer 28. 对称的二叉树
- 十九、98. 验证二叉搜索树
- 二十、剑指 Offer 29. 顺时针打印矩阵
- 二十一、剑指 Offer 30. 包含min函数的栈
一、剑指offer04:二维数组中的查找
描述:在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
快排:[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
思路:从数组右上角开始比较查找,比当前元素小的元素一定在该元素的左侧,y–;比当前元素大的元素一定在该元素的下面,x++;
class Solution {
public:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
if(matrix.size()==0) return false;
int x=0,y=matrix[0].size()-1;
while(x<matrix.size() && y>=0){
if(matrix[x][y] > target) y--;
else if(matrix[x][y] < target) x++;
else return true;
}
return false;
}
};
}
二、剑指 Offer 05. 替换空格
描述:请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
输入:s = “We are happy.”
输出:“We%20are%20happy.”
思路:初始化:空格数量 count ,字符串 s 的长度 len ;
统计空格数量:遍历 s ,遇空格则 count++ ;
修改 s 长度:添加完 “%20” 后的字符串长度应为 len + 2 * count ;
倒序遍历修改:i 指向原字符串尾部元素, j 指向新字符串尾部元素;当 i = j 时跳出(代表左方已没有空格,无需继续遍历);
当 s[i] 不为空格时:执行 s[j] = s[i] ;
当 s[i] 为空格时:将字符串闭区间 [j-2, j] 的元素修改为 “%20” ;
返回值:已修改的字符串 s ;
class Solution {
public:
string replaceSpace(string s) {
int cout=0;
int n=s.size();
for(char c:s)
if(c==' ') cout++;
s.resize(n+2*cout);
int j=s.size()-1;
for(int i=n-1;i>=0 && i!=j;i--){
if(s[i]==' '){
s[j--]='0';
s[j--]='2';
s[j--]='%';
}
else s[j--]=s[i];
}
return s;
}
};
三、剑指 Offer 06. 从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
输入:head = [1,3,2]
输出:[2,3,1]
思路:递归:1递推阶段: 每次传入 head.next ,以 head == null(即走过链表尾部节点)为递归终止条件,此时直接返回。
2回溯阶段: 层层回溯时,将当前节点值加入列表,即 res.push_back(head->val);
3最终,将列表 tmp 转化为数组 res ,并返回即可。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
vector<int> reversePrint(ListNode* head) {
vector<int> res;
if(head==NULL) return res;
res=reversePrint(head->next); //先递归接受,再处理 或者理解成接收后面的值,再处理当前值
res.push_back(head->val);
return res;
}
};
四、剑指 Offer 07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
思路:递归:1前序遍历的首元素 为 树的根节点 node 的值。
2在中序遍历中搜索根节点 node 的索引 ,可将 中序遍历 划分为 [ 左子树 | 根节点 | 右子树 ] 。
3根据中序遍历中的左(右)子树的节点数量,可将 前序遍历 划分为 [ 根节点 | 左子树 | 右子树 ] 。
/**
* 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:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
return newtree(preorder,inorder);
}
TreeNode* newtree(vector<int>& preorder, vector<int>& inorder){
TreeNode* head=new TreeNode(0);
if(preorder.size()==0) {return NULL;} // 循环条件判断退出,若传入的左右子树为空,返回空
head->val=preorder[0]; // 当前的头结点的数值为前序遍历数组第一个数
vector<int> pl={}; // 传递裁剪后的数组,而不是传递左右子树组的指针,指针下标会比较麻烦,有映射
vector<int> pr={};
vector<int> il={};
vector<int> ir={};
int k=find_head(preorder,inorder); // 在后序遍历中找到头结点的下标k 前序遍历的左子树数组为下标1--k,右子树数组为下标k---size()-1
// 后序遍历的左子树数组为下标0 --k-1,右子树数组为下标k---size()-1
for(int i=1,j=0;i<=k;i++,j++){ pl.push_back(preorder[i]); il.push_back(inorder[j]);}
for(int i=k+1;i<preorder.size();i++){ pr.push_back(preorder[i]); ir.push_back(inorder[i]);}
head->left=newtree(pl,il); //递归左子树
head->right=newtree(pr,ir); //递归右子树
return head;
}
int find_head(vector<int>& preorder, vector<int>& inorder){
for(int i=0;i<inorder.size();i++) if(inorder[i]==preorder[0]) return i;
return 0;
}
};
五、剑指 Offer 09. 用两个栈实现队列 || 用两个队列实现栈
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
输入:
[“CQueue”,“appendTail”,“deleteHead”,“deleteHead”]
[[],[3],[],[]]
输出:[null,null,3,-1]
思路:用两个栈s1和s2,s1入栈替代增加,s1全部入s2再删除弹出替代队列删除。
收获:学会用类的形式,定义结构,定义行为动作函数
class CQueue {
public:
CQueue() {
}
void appendTail(int value) {
s1.push(value);
}
int deleteHead() {
if(s2.empty()){
while(!s1.empty()){
s2.push(s1.top());
s1.pop();
}
}
if(s2.empty()) return -1;
int k=s2.top();
s2.pop();
return k;
}
private:
stack<int> s1;
stack<int> s2;
};
/**
* Your CQueue object will be instantiated and called as such:
* CQueue* obj = new CQueue();
* obj->appendTail(value);
* int param_2 = obj->deleteHead();
*/
用两个队列实现一个栈列。
思路:用两个队列que1和que2,入栈时,把数压入非空的队列中,删除时,把非空的队列元素除了最后一个全部压入空的队列,并弹出最后一个。
class CQueue {
public:
CQueue() {
}
void appendTail(int value) {
s1.push(value);
}
int deleteHead() {
if(s2.empty()){
while(!s1.empty()){
s2.push(s1.top());
s1.pop();
}
}
if(s2.empty()) return -1;
int k=s2.top();
s2.pop();
return k;
}
private:
stack<int> s1;
stack<int> s2;
};
/**
* Your CQueue object will be instantiated and called as such:
* CQueue* obj = new CQueue();
* obj->appendTail(value);
* int param_2 = obj->deleteHead();
*/
六、剑指 Offer 10- II. 青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1
输入:n = 2
输出:2
输入:n = 0
输出:1
思路:动态规划
class Solution {
public:
int numWays(int n) {
vector<int> dp(n+1,1);
for(int i=2;i<=n;i++){
dp[i]=(dp[i-1]+dp[i-2])%1000000007;
}
return dp[n];
}
};
七、剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为 1。
注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。
输入:numbers = [3,4,5,1,2]
输出:1
输入:numbers = [2,2,2,0,1]
输出:0
思路:初始化: 声明 ii, jj 双指针分别指向 numsnums 数组左右两端;循环二分: 设 m = (i + j) / 2m=(i+j)/2 为每次二分的中点( “/” 代表向下取整除法,因此恒有 i \leq m < ji≤m<j ),可分为以下三种情况:
1.当 nums[m] > nums[j]nums[m]>nums[j] 时: mm 一定在 左排序数组 中,即旋转点 xx 一定在 [m + 1, j][m+1,j] 闭区间内,因此执行 i = m + 1i=m+1;
2.当 nums[m] < nums[j]nums[m]<nums[j] 时: mm 一定在 右排序数组 中,即旋转点 xx 一定在[i, m][i,m] 闭区间内,因此执行 j = mj=m;
3.当 nums[m] = nums[j]nums[m]=nums[j] 时: 无法判断 mm 在哪个排序数组中,即无法判断旋转点 xx 在 [i, m][i,m] 还是 [m + 1, j][m+1,j] 区间中。解决方案: 执行 j = j - 1j=j−1 缩小判断范围,分析见下文。
返回值: 当 i = ji=j 时跳出二分循环,并返回 旋转点的值 nums[i]nums[i] 即可。
class Solution { //每次比较末尾的元素
public:
int minArray(vector<int>& numbers) {
int left=0;
int right=numbers.size()-1;
while(left<right){
int mid=(left+right)/2;
if(numbers[mid]>numbers[right]) left=mid+1;
else if(numbers[mid]<numbers[right]) right=mid;
else right--;
}
return numbers[left];
}
};
八、剑指 Offer 12. 矩阵中的路径
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
思路:动态规划,dp[i]等于dp[i-j](i-j)的最大值(至少三段)以及j(i-j)(至少两段)
cpp
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
for(int i=0;i<board.size();i++)
for(int j=0;j<board[0].size();j++){
if(board[i][j]==word[0]) {
board[i][j]='0';
dfs(board,word,1,i,j); //不要每个位置都进行搜索,会浪费时间,当满足第一个元素是word开头再进行搜索,不过此时应标记为已经选择过
board[i][j]=word[0];
}
}
return res;
}
vector<vector<int>> vis={{0,1},{0,-1},{1,0},{-1,0}};
bool res=false;
void dfs(vector<vector<char>>& board, string word,int cout,int x,int y){
if(cout==word.size()) {res=true;return;}
for(int i=0;i<4 && !res;i++){
if((y+vis[i][1]>=0 && y+vis[i][1]<board[0].size()) && (x+vis[i][0]>=0 && x+vis[i][0]<board.size()) && board[x+vis[i][0]][y+vis[i][1]] == word[cout]){
board[x+vis[i][0]][y+vis[i][1]]='0';
dfs(board,word,cout+1,x+vis[i][0],y+vis[i][1]);
board[x+vis[i][0]][y+vis[i][1]]=word[cout];
}
}
return;
}
};
九、剑指 Offer 14- I. 剪绳子
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
思路:回溯,每个位置都有四个选择,上下左右,用一个数组记录上下左右的变化,递归时后要记录用过的字符,将其改为’0’,回溯时改成word[cout],同时进行上下左右遍历时,当且仅当下标在范围内,且下一个数是word的字母。
class Solution {
public:
int cuttingRope(int n) {
vector<int> dp(n+1,2);
dp[2]=1;
for(int i=3;i<n+1;i++){
for(int j=2;j<=i-2;j++){
dp[i]=max( j*(i-j) ,max(dp[i],dp[j]*(i-j)));
}
}
return dp[n];
}
};
十、剑指 Offer 15. 二进制中1的个数
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为 汉明重量).)。
思路:1.n&1 2.n&(n-1) 表示n的最后一个1个位变成0
class Solution {
public:
int hammingWeight(uint32_t n) {
int cout=0;
uint32_t p=1;
while(n){
if(n&1) cout++;
n=n>>1;
}
return cout;
}
};
十一、快速幂
快速幂算法
思路:1.分治,x^(n) 等于 x的n/2* x的n/2.
2.二分推导: x^n = x^{n/2} \times x^{n/2} = (x2){n/2}x n=x n/2 ×x n/2 =(x 2 ) n/2 ,令 n/2n/2 为整数,则需要分为奇偶两种情况(设向下取整除法符号为 “” ):当 nn 为偶数: x^n = (x2){n//2}x n =(x 2 ) n//2 ;当 nn 为奇数: x^n = x(x2){n//2}x n =x(x 2) n//2 ,即会多出一项 xx ;幂结果获取:根据二分推导,可通过循环 x = x^2x=x 2操作,每次把幂从 nn 降至 n//2n//2 ,直至将幂降为 00 ;设 res=1res=1 ,则初始状态 x^n = x^n \times resx n
=x n ×res 。在循环二分时,每当 nn 为奇数时,将多出的一项 xx 乘入 resres ,则最终可化至 x^n = x^0 \times res = resx n =x 0×res=res ,返回 resres 即可。
class Solution {
public:
double myPow(double x, int n) {
long long N=n;
if(n==0) return 1;
else if(n<0) return myw(1.0/x,-N);
else return myw(x,N);
}
double myw(double x, long long n) {
if(n==1) return x;
double y=myw(x,n/2);
return n%2==0?y*y:y*y*x;
}
};
2
class Solution {
public double myPow(double x, int n) {
if(x == 0) return 0;
long b = n;
double res = 1.0;
if(b < 0) {
x = 1 / x;
b = -b;
}
while(b > 0) {
if((b & 1) == 1) res *= x;
x *= x;
b >>= 1;
}
return res;
}
}
十二、剑指 Offer 22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
给定一个链表: 1->2->3->4->5, 和 k = 2.返回链表 4->5.
思路:双指针,前后间隔k。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode* pre=head;
int n=0;
while(head!=NULL){
head=head->next;
if(n>=k)
pre=pre->next;
n++;
}
return pre;
}
};
`
## 十三、剑指 Offer 22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
>给定一个链表: 1->2->3->4->5, 和 k = 2.返回链表 4->5.
> 思路:双指针,前后间隔k。
```cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
ListNode* pre=head;
int n=0;
while(head!=NULL){
head=head->next;
if(n>=k)
pre=pre->next;
n++;
}
return pre;
}
};
十四、剑指 Offer 24. 反转链表
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
思路:1.迭代 2,递归
class Solution {
public:
ListNode* reverseList(ListNode* head) {
return reverse_list(head);
}
ListNode* reverse_list(ListNode* head){
if(head->next==NULL) return head;
ListNode* root=reverse_list(head->next);
head->next->next=head;
head->next=NULL;
return root;
}
};
十五、剑指 Offer 25. 合并两个排序的链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
思路:1.迭代 :引入伪头节点: 由于初始状态合并链表中无节点,因此循环第一轮时无法将节点添加到合并链表中。解决方案:初始化一个辅助节点 dumdum 作为合并链表的伪头节点,将各节点添加至 dumdum 之后。因此容易想到使用双指针 l_1和 l_2l 遍历两链表,根据 l_1 .val 和 l_2.vall 的大小关系确定节点添加顺序,两节点指针交替前进,直至遍历完毕。
2,递归:两个链表头部值较小的一个节点与剩下元素的 merge 操作结果合并。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/ //递归
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* root=new ListNode(0);
ListNode* currt=root;
while(l1 && l2){
if(l1->val <= l2->val){
root->next=l1;
l1=l1->next;
}
else{
root->next=l2;
l2=l2->next;
}
root=root->next;
}
root->next=( l1==NULL?l2:l1);
return currt->next;
}
};
//2递归
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(!l1) return l2;
if(!l2) return l1;
if(l1->val<l2->val) {
l1->next=mergeTwoLists(l1->next,l2);
return l1;
}
if(l1->val>=l2->val){
l2->next=mergeTwoLists(l1,l2->next);
return l2;
}
return l1;
}
};
十六、剑指 Offer 26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值
输入:A = [3,4,5,1,2], B = [4,1]
输出:true
思路:先遍历A数的每一个节点,用前中后遍历都可以,然后对每个结点调用递归函数判断从该节点开始,B树是否是子树。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
//思路:先遍历A数的每一个节点,用前中后遍历都可以,然后对每个结点调用递归函数判断从该节点开始,B树是否是子树。
class Solution {
public:
bool isSubStructure(TreeNode* A, TreeNode* B) { //遍历A树每一个节点,前中后遍历不影响
if(!A || !B) return false;
bool x=isSubStructure(A->left,B);
bool y=isSubStructure(A->right,B);
bool k=is_deal(A,B);
return x||y||k;
}
bool is_deal(TreeNode* A, TreeNode* B){ //判断从这个结点开始B是否是子树
if(A==NULL && B!=NULL) return false;
if(B==NULL) return true;
if(A->val!=B->val) return false;
return is_deal(A->left,B->left) && is_deal(A->right,B->right);
}
};
十七、剑指 Offer 27. 二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/
2 7
/ \ /
1 3 6 9
镜像输出:
4
/
7 2
/ \ /
9 6 3 1
输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]
思路:初始化节点 tmptmp ,用于暂存 rootroot 的左子节点;
开启递归 右子节点 mirrorTree(root.right)mirrorTree(root.right) ,并将返回值作为 rootroot 的 左子节点 。
开启递归 左子节点 mirrorTree(tmp)mirrorTree(tmp) ,并将返回值作为 rootroot 的 右子节点 。
/**
* 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:
TreeNode* mirrorTree(TreeNode* root) {
if(!root) return NULL;
TreeNode* left=root->left;
root->left=mirrorTree(root->right);
root->right=mirrorTree(left);
return root;
}
};
十八、剑指 Offer 28. 对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/
2 2
/ \ /
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/
2 2
\
3 3
输入:root = [1,2,2,3,4,4,3]
输出:true
思路:递归左子树与右子树镜像
/**
* 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 isSymmetric(TreeNode* root) {
if(!root) return true;
return is_symmetric(root->left,root->right);
}
bool is_symmetric(TreeNode* l,TreeNode* r){
if(!l && !r) return true;
if(!l ||!r) return false;
return l->val==r->val&& is_symmetric(l->left,r->right) && is_symmetric(l->right,r->left);
}
};
十九、98. 验证二叉搜索树
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
示例 1:
入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4 。
思路:不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了。我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。**所以以上代码的判断逻辑是错误的。
1.根据二叉搜索树的特性,可知中序遍历的结果是有序的.根据这个特性,对二叉树进行中序遍历,将值暂存到vector中.然后遍历vector判断有序性.
2.我们可以进一步知道二叉搜索树「中序遍历」得到的值构成的序列一定是升序的,这启示我们在中序遍历的时候实时检查当前节点的值是否大于前一个中序遍历到的节点的值即可。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* pre = NULL; // 用来记录前一个节点
bool isValidBST(TreeNode* root) {
if (root == NULL) return true;
bool left = isValidBST(root->left); //
if (pre != NULL && pre->val >= root->val) return false; // 中序遍历,验证遍历的元素是不是从小到大
pre = root; // 记录前一个节点
bool right = isValidBST(root->right);
return left && right;
}
};
二十、剑指 Offer 29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
思路:空值处理: 当 matrix 为空时,直接返回空列表 [] 即可。
初始化: 矩阵 左、右、上、下 四个边界 l , r , t , b ,用于打印的结果列表 res 。
循环打印: “从左向右、从上向下、从右向左、从下向上” 四个方向循环,每个方向打印中做以下三件事 (各方向的具体信息见下表) ;
根据边界打印,即将元素按顺序添加至列表 res 尾部;
边界向内收缩 11 (代表已被打印);
判断是否打印完毕(边界是否相遇),若打印完毕则跳出。
返回值: 返回 res 即可。
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if(matrix.size()==0) return {};
vector<int> res;
int up=0,low=matrix.size()-1,l=0,r=matrix[0].size()-1; //建立一个4个边界,上下左右,每次循环四次遍历,从左向右,从上向下,从右向左,从下向上
while(1){
for(int i=l;i<=r;i++) res.push_back(matrix[up][i]);
up++;
if(up>low) return res;
for(int i=up;i<=low;i++) res.push_back(matrix[i][r]);
r--;
if(r<l) return res;
for(int i=r;i>=l;i--) res.push_back(matrix[low][i]);
low--;
if(low<up) return res;
for(int i=low;i>=up;i--) res.push_back(matrix[i][l]);
l++;
if(l>r) return res;
}
return res;
}
};
二十一、剑指 Offer 30. 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
思路:要实现o(1)的最小值,使用辅助栈存放最小值。
class MinStack {
private:
stack<int> stack1; //此代码是辅助栈长度与主栈一样,top为当前元素的最小值
stack<int> stack2; //优化:辅助站长度不要这么长,只入栈比当前元素小或者相等的元素
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
if(stack1.empty()) {
stack1.push(x);
stack2.push(x);
}
else{
stack1.push(x);
if(x>stack2.top()) stack2.push(stack2.top());
else stack2.push(x);
}
}
void pop() {
stack1.pop();
stack2.pop();
}
int top() {
return stack1.top();
}
int min() {
return stack2.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(x);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->min();
*/