目录
剑指 Offer 32 - III. 从上到下打印二叉树 III
剑指 Offer 68 - I. 二叉树搜索树的最近公共祖先
剑指 Offer 07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 例如,给出:
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
思路:前序遍历的第一位是根节点,把中序遍历分割成左右两部分
代码:
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size()==0||inorder.size()==0||preorder.size()!=inorder.size())
return nullptr;
return build(preorder,0,preorder.size()-1,inorder,0,inorder.size()-1);
}
TreeNode *build(vector<int>& preorder,int pbegin,int pend,vector<int>& inorder,int inbegin,int inend){
//if(pbegin==pend)//终止条件搞清楚,是下面的。
if(pbegin>pend||inbegin>inend)
return nullptr;
TreeNode *root=new TreeNode(preorder[pbegin]);//每次都应该新建一个节点,而且要在终止条件之后。不然就多创建了结点。
for(int j=inbegin;j<=inend;++j){
if(inorder[j]==preorder[pbegin]){
root->left=build(preorder,pbegin+1,pbegin+j-inbegin,inorder,inbegin,j-1);//注意这个pbegin+(j-inbegin),很关键
root->right=build(preorder,pbegin+j-inbegin+1,pend,inorder,j+1,inend);
break;
}
}
return root;
}
};
剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
1.可以一个一个比较O(N);
2.二分法: 关键是和右边比较,因为是升序。不能和左边比较
代码:
class Solution {
public:
int minArray(vector<int>& numbers) {
//1暴力法,一个一个比较,
//2有序->二分法
/*存在三种情况://关键是要和右边比,因为是升序,和左边比没意义
1.mid>right
即[3,4,5,1,2] 那么带查找区间在右侧,即left=mid+1;
2.mid==right
即[3,4,1,1,1] 那么需要缩小待查找区间,即right--;
3.mid<right
即[1,2,3,4,5] 那么待查找区间在左侧,即right=mid;*/
if(numbers.empty())
return -1;
int len=numbers.size();
int low=0,high=len-1,mid=0;
while(low<high){
mid=low+((high-low)>>1);
if(numbers[mid]<numbers[high])
high=mid; //不能high=mid-1,如2 1 2 如果mid-1那么1就会被跳过
else if(numbers[mid]>numbers[high])
low=mid+1;
else high--;
}
return numbers[low];
}
};
剑指 Offer 12. 矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
思路:递归回溯
代码:
class Solution {
string word;
public:
bool exist(vector<vector<char>>& board, string word) {
this->word = word;
if(word.empty()||board.empty()) return false;
for(int i=0;i<board.size();i++)
for(int j=0;j<board[0].size();j++)
if(walk(board,0,0,0)) return true;
return false;
}
bool walk(vector<vector<char>>& board, int x, int y, int pos){
if(pos==word.size()-1) return true;
if(board[x][y]!=word[pos]) return false;
char temp=board[x][y];
board[x][y]='/';
int dx[4]={1,-1,0,0};int dy={0,0,1,-1};
for(int i=0;i<4;i++){
if(x+dx[i]>board.size()||x+dx[i]<0||y+dy[i]>board[0].size()||y+dy[i]<0)
continue;
if(walk(board,x+dx[i],y+dy[i],pos+1)) return true;
}
board[x][y]=temp;
return false;
}
};
剑指 Offer 13. 机器人的运动范围
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
思路:深度优先搜索DFS
class Solution { //这就是深度优先搜索了。
public:
int movingCount(int m, int n, int k) {
if(m<1||n<1) return 0;
if(k==0) return 1;
vector<vector<bool>> visited(m,vector<bool>(n,false));
int sum=0;
move(visited,0,0,k,m,n,sum);
return sum;
}
void move(vector<vector<bool>> &visited,int i,int j,int k,int m,int n,int &sum){
if(!leagal(i,j,k))
return ;
int a[2]={1,0};int b[2]={0,1};
if(!visited[i][j]/*&&leagal(i,j,k)*/){
visited[i][j]=true;
sum++;
for(int ii=0;ii<2;++ii){
int x=i+a[ii];int y=j+b[ii];
if(x>=0&&x<m&&y>=0&&y<n)
move(visited,x,y,k,m,n,sum);
}
}
}
bool leagal(int i,int j,int &k){
int sum=0;
while(i>0){
sum+=(i%10);
i=i/10;
}
while(j>0){
sum+=(j%10);
j=j/10;
}
if(sum<=k)
return true;
else return false;
}
};
剑指 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。
1.找规律 3越多数越大。
int sum=1;
if(n<=1) return 0;
else if(n==2)
return 1;
else if(n==3)
return 2;
while(n>0){
if(n<3||n==4)
sum*=n;
else
sum*=3;
n-=3;
}
return sum;
2.动态规划
int cuttingRope(int n) {
if(n<=1) return 0;
if(n>=2&&n<=3) return n-1;
return dfs(n);
}
int dfs(int n){
if(n<=4) return n;//关键,这个指的是分割或者不分割时的最大值
int ans=0;
for(int i=1;i<=n/2;i++){
ans=max(ans,dfs(i)*dfs(n-i));//有重复计算,超时
}
return ans;
}
2.动态规划改进;
int cuttingRope(int n) {
if(n<=1) return 0;
if(n>=2&&n<=3) return n-1;
vector<int> res(n,0);
return dfs(n,res);
}
int dfs(int n,vector<int>&res){
if(n<=4) return n;//关键,这个指的是分割或者不分割时的最大值
int ans=0;
for(int i=1;i<=n/2;i++){//只需要到n/2即可
if(res[i]==0) res[i]=dfs(i,res);//不存在的时候才计算
if(res[n-i]==0) res[n-i]=dfs(n-i,res);
ans=max(ans,res(i)*res(n-i));}
return ans;
}
剑指 Offer 16. 数值的整数次方
实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
思路:快速幂:
我们这里使用快速幂进行求解。我们看一下 n 的二进制形式一定是若干个 1 和 0 构成,比如 9 = 1001 = 1*2^3 + 0*2^2 + 0*2^1 + 1*2^0
x^9=x^{2^0}*x^{2^1*0}*x^{2^2*0}*x^{2^3}
代码:
class Solution {
public:
double myPow(double x, int n) {
double res=1.0;
long t=abs(n);//int范围[−2147483648,2147483647] ,因此当n=−2147483648 时执行
//n =-n=会因越界而赋值出错。解决方法是先将 n 存入 long 变量 t ,后面用 t 操作即可
if(x==0&&t==0)
return -1;
else if(x==0)
return 0;
else if(t==0)
return 1;
else {
while(t){
if(t&1)
res*=x;
x*=x;
t=t>>1;
}
}
return n>0?res:1/res;
}
};
剑指 Offer 24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
1.经典反转:
ListNode* reverseList(ListNode* head) {
if(!head||!head->next) return head;
ListNode* pre=nullptr,*cur=head,*temp=nullptr;
while(cur){
temp=cur->next;
cur->next=pre;
pre=cur;
cur=temp;
}
return pre;
}
2.递归
ListNode* reverseList(ListNode* head) {
if(!head||!head->next) return head;//终止条件是head->next==nullptr,head=nullptr是防止非法输入。
ListNode* =reverseList(head->next);//终止时head->next->next==nullptr. t=head->next即尾结点。
head->next->next=head;
head->next=nullptr;//这两步反转
return t;
}
剑指 Offer 17. 打印从1到最大的n位数
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
思路:主要是要考虑大数:模拟大数的加法
class Solution {
public:
vector<int> res;
vector<int> printNumbers(int n) {
//1.不是很大的数的时候可以这样
/*vector<int> res;
if(n<=0) return res;
int i=1;
long t=pow(10,n);
while(i<t){
res.push_back(i);
i++;
}
return res;*/
//2.大数还是要用字符串才行
if(n<=0) return res;
string s(n,'0');
while(!Increment(s))
save(s);
return res;
}
bool Increment(string &s){
bool isOverFlow=false;//检查是否越界
int nTakeOver=0;//存储进位
int len=s.length();
for(int i=len-1;i>=0;--i){//模拟十进制加法
int nSum=s[i]+nTakeOver-'0';
if(i==len-1)//如果是个位就直接加1
nSum++;
if(nSum>=10){//加1后产生了进位
if(i==0)//如果最高位产生了进位,那么就超过了n位数
isOverFlow=true;
else{
nTakeOver=1;
s[i]=nSum-10+'0';
}
}
else{//没有进位
s[i]=nSum+'0';
break;
}
}
return isOverFlow;
}
void save(string s){//保存的时候 会出现00001243,前面是0的情况,要把前面的0去除。
string tempstr="";
bool flag=false;
for(int i=0;i<=s.size()-1;i++){
if(!flag&&s[i]!='0'){
flag=true;
}
if(flag)
tempstr+=s[i];
}
int num=0;
for(int i=0;i<tempstr.size();++i){
num*=10;
num+=(tempstr[i]-'0');
}
//int num=stoi(tempstr);
res.push_back(num);
}
//我自己的想法:
bool Increment(string &s){
bool flag=false;
bool wei=false;
for(int i=s.size()-1;i>=0;i--){
if(i==s.size()-1){ //个位
if(s[i]-'0'+1==10){//产生进位
wei=true;
s[i]='0';
}
else { //不产生进位加一后直接break;
s[i]+=1;
break;
}
}
else{ //不是个位,那必然是进位了
if(s[i]-'0'+1==10){
wei=true;
s[i]='0';
}
else { //不进位,加一之后直接break;
s[i]+=1;
wei=false;
break;
}
}
if(i==0&&wei) {flag=true;break;}//判断是否越界
}
return flag;
}
void save(string s){
int begin=0;
for(int i=0;i<s.size();i++){
if(s[i]=='0')
continue;
else break;
}
int sum=0;
for(int j=begin;j<s.size();j++){
sum*=10;
sum+=s[j]-'0';
}
res.push_back(sum);
}
};
剑指 Offer 26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
class Solution {//有个大坑,题目说的是子结构,而不是子树。
public:
bool isSubStructure(TreeNode* A, TreeNode* B) {
if(A==nullptr&&B==nullptr) return true;
else if(A==nullptr||B=nullptr) return fasle;
else{
if(A->val==B->val)
return DoesSubTree(A,B);
else return isSubStructure(A->left,B)||isSubStructure(A->right,B);
}
}
bool DoesSubTree(TreeNode* root1, TreeNode* root2){
if(root2==nullptr) return true;
if(root1==nullptr) return false;
if(root1->val!=root2->val) return false;
return DoesSubTree(root1->left,root2->left)&&DoesSubTree(root1->right,root2->right);
}
};
剑指 Offer 42. 连续子数组的最大和
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
//1.动态规划
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n=nums.size();
if(n==0) return 0;
int ans=INT_MIN;
vector<int> dp(n+1,0);//表示前i项中连续子数组的最大和
dp[0]=nums[0];
for(int i=1;i<=n;i++){
dp[i]=max(dp[i-1]+nums[i],nums[i]);
ans=max(ans,dp[i]);;
}
return ans;
}
}
2.贪心算法
int sum=0,Max=nums[0];
for(int i=0;i<nums.size();i++){
sum=max(sum+nums[i],nums[i]);
Max=max(Max,sum);
}
return Max;
剑指 Offer 29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
//1暴力,
//2.定义两个方向d[x]d[y] 和是否走过vis[][] 遇到边界或者走过就顺时针转向
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
if(matrix.empty()) return res;
int m=matrix.size(),n=matrix[0].size();
int dx[]={0,1,0,-1};int dy[]={1,0,-1,0};int d=0;
int x=0,y=0;
vector<vector<bool>> vis(m,vector<bool>(n,false));
for(int k=0;k<m*n;k++){
res.push_back(matrix[x][y]);
vis[x][y]=true;
x+=dx[d],y+=dy[d];
if(x<0||x>=m||y<0||y>=n||vis[x][y]){
x-=dx[d],y-=dy[d];//越界了就回退
d=(d+1)%4;
x+=dx[d],y+=dy[d];//然后换向
}
}
return res;
}
};
剑指 Offer 31. 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
思路:模拟整个压入弹出的过程 看最终是否符合
代码:
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
//模拟pushed数组push过程和poped数组pop过程
if(pushed.size()!=poped.size()) return false;
stack<int> stk;
int k=0;
int n=pushed.size();
for(int i=0;i<n;i++){
stk.push(pushed[i]);
while(!stk.empty()&&k<n&&stk.top()==poped[k]){//用while,需要连续弹出
stk.pop();
k++;
}
}
if(k==n||stk.empty()) return true;
else return false;
}
};
剑指 Offer 32 - III. 从上到下打印二叉树 III
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
剑指 Offer 34. 二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
class Solution {
private:
vector<int<int>> res;
vector<int> temp;
public:
vector<vector<int>> pathSum(TreeNode* root, int sum) {
if(root==nullptr) return res;
|
|
|
|
}
void dfs(TreeNode* root,int sum){
temp.push_back(root->val);
if(!root->left&&!root->right){//到叶子了
if(sum==root->val)
res.push_back(temp);
}
|
|
|
|
|
|
}
};
剑指 Offer 33. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
字符串的排列组合
有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。
1.让每个字符都有在第一的机会,然后递归,把第一位固定,让剩余的子母都有在第二的机会……
vector<string> res;
vector<string> permutation(string S) {
if(S.empty()) return res;
dfs(S,0);
return res;
}
void dfs(string &str,int begin){
if(begin==str.size()) {
res.push_back(str);
return;
}
for(int i=begin;i<str.size();i++){
if(ilegal(str,begin,i)) continue;//考虑重复的
swap(str[i],str[begin]);
dfs(str,begin+1);
swap(str[i],str[begin]);//回溯
}
}
bool ilegal(string &s,int a,int b){
for(int i=a;i<b;i++){
if(s[i]==s[b]) return true;
}
return false;
}
剑指 Offer 55 - I. 二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
//1.递归求 简洁,复杂度o(N) N为节点数。
int maxDepth(TreeNode* root) {
if(!root) return 0;
return max(maxDepth(root->left),maxDepth(root->right))+1;
}
2.按层打印,看一共多少层//o(2N)
if(root==nullptr) return 0;
int cnt=0;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
cnt++;
int len=q.size();
while(len--){
TreeNode* temp=q.front();
if(temp->left) q.push(temp->left) ;
if(temp->right) q.push(temp->right) ;
q.pop();
}
}
return cnt;
3.用路径的方法//dfs||回溯
int sum=0,m=0;
int maxDepth(TreeNode* root) {
if(!root) return 0;
dfs(root);
return m;
}
void dfs(TreeNode* root){
sum++;
if(!root->left&&!root->right) m=max(m,sum);
if(root->left) {
dfs(root->left);
sum--;//回溯
}
if(root->right) {
dfs(root->right);
sum--;//回溯
}
}
剑指 Offer 55 - II. 平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
思路:根据左右子树的深度 但是求深度的时候只能用递归的方法求,不能用回溯,因为主程序里面有isBalanced(root->left) && isBalanced(root->right),而
这个递归里面没法对sum进行回溯。
bool isBalanced(TreeNode* root) {
if (root == nullptr) return true;
return abs(getDepth(root->left) - getDepth(root->right)) <= 1 &&isBalanced(root->left) && isBalanced(root->right);
}
int getDepth(TreeNode* node)
{
if (node == nullptr) return 0;
return 1+max(getDepth(node->left), getDepth(node->right));
}
剑指 Offer 26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
思路:递归判断A的左右子树是否等于B
class Solution {//有个大坑,题目说的是子结构,而不是子树。
public:
bool isSubStructure(TreeNode* A, TreeNode* B) {
if(A==nullptr&&B==nullptr) return true;
if(A==nullptr||B==nullptr) return false;//B肯定是不能为空的
if(A->val==B->val){
if(DoesSubTree(A,B))
return true;
}
return isSubStructure(A->left,B)||isSubStructure(A->right,B);
}
bool DoesSubTree(TreeNode* root1, TreeNode* root2){
if(root2==nullptr) return true;//1为空,2为空//root2先跑完了,就是true,子结构不是子树
if(root1==nullptr) return false;//1或2只一个为空
if(root1->val!=root2->val) return false;
return DoesSubTree(root1->left,root2->left)&&DoesSubTree(root1->right,root2->right);
}
};
剑指 Offer 68 - I. 二叉树搜索树的最近公共祖先
可以利用搜索树的性质:左边永远小于根,右边永远大于根
代码:
LastAncester(TreeNode* root,TreeNode *p,TreeNode *q){
if(!root||p==root||q==root) return root;
while(root){
if(q->val<root->val&&q->val<root->val)
root=root->left;
else if(q->val>root->val&&q->val>root->val)
root=root->right;
else return root;
}
return root;
}
剑指 Offer 68 - II. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(!root||root==p||root==q) return root;
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
if(!left) return right;//左边找不到,那么就在右边
else if(!right) return left;//同理
else return root;//左右都有,那么当前就是祖先
}