剑指offer58-67

58 对称二叉树

题目描述

请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

示例1

输入:

{8,6,6,5,7,7,5}

返回值:

true

示例2

输入:

{8,6,9,5,7,7,5}

返回值:

false

思路:

判断左子树和右子树是否对称,相当对判断左子树的左孩子和右子树的右孩子是否相等,左子树的右孩子和右子树的左孩子是否相等。

采用队列存取每个结点

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot) {
        queue<TreeNode*> l;
        queue<TreeNode*> r;
        if(!pRoot)return true;
        l.push(pRoot->left);
        r.push(pRoot->right);
        while(!l.empty()&&!r.empty()){
            TreeNode* tl=l.front();
            TreeNode* tr=r.front();
            if(!tl&&!tr);
            else if(tl&&tr){
                if(tl->val!=tr->val)return false;
                l.push(tl->left);
                l.push(tl->right);
                r.push(tr->right);
                r.push(tr->left);
            }
            else return false;
            l.pop();r.pop();
        }
        return true;
    }
    

};

59 按之字形顺序打印二叉树

题目描述

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

示例1

输入:

{8,6,10,5,7,9,11}

返回值:

[[8],[10,6],[5,7,9,11]]

思路:

维护两个栈,一个保存从前向后的结点,一个保存从后向前的结点。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        stack<TreeNode*> st;
        stack<TreeNode*> st2;
        int flag=1;
        vector<vector<int>> res;
        if(!pRoot)return res;
        st.push(pRoot);
        while(!st.empty()||!st2.empty()){
            int len=st.empty()?st2.size():st.size();
            vector<int> rol;
            while(len>0){
                TreeNode* t;                     
                if(flag==1){         
                    t=st.top();
                    if(t->left)st2.push(t->left);
                    if(t->right)st2.push(t->right);  
                    st.pop();
                }
                else{
                    t=st2.top();
                    if(t->right)st.push(t->right);
                    if(t->left)st.push(t->left);
                    st2.pop();
                }                         
                rol.push_back(t->val);
                len--;               
            }
            flag*=-1;
            res.push_back(rol);
        }
        return res;
    }
    
};

60 把二叉树打印成多行

题目描述

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

示例1

输入:

{8,6,10,5,7,9,11}

返回值:

[[8],[6,10],[5,7,9,11]]

思路:

维护一个队列,广度遍历二叉树,遍历的时候要记住每层的结点数

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
        vector<vector<int> > Print(TreeNode* pRoot) {
            queue<TreeNode*> qu;
            vector<vector<int>> res;
            if(!pRoot)return res;
            qu.push(pRoot);
            while(!qu.empty()){
                TreeNode* t;
                int len=qu.size();
                vector<int> rol;
                while(len>0){
                    t=qu.front();
                    qu.pop();
                    if(t->left)qu.push(t->left);
                    if(t->right)qu.push(t->right);
                    rol.push_back(t->val);
                    len--;
                }
                res.push_back(rol);
            }
            return res;
        }
};

61 序列化二叉树

题目描述

请实现两个函数,分别用来序列化和反序列化二叉树

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树

示例1

输入:

{8,6,10,5,7,9,11}

返回值:

{8,6,10,5,7,9,11}

思路:

先序遍历二叉树,将二叉树的结点转换成字符串,‘#’表示空结点,’!'分割每个结点。

反序列化时以’!'为界将字符串中的数字转化成int类型,并为其创建新结点,以先序遍历的顺序创建树

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    char* Serialize(TreeNode *root) {    
        string s=ser(root);
        char* ch=new char[s.size()];
        strcpy(ch, s.c_str());
        return ch;
    }
    
    string ser(TreeNode* root){
        if(!root)return "#";
        string s=to_string(root->val)+"!";
        s+=ser(root->left);
        s+=ser(root->right);
        return s;
    }
    TreeNode* Deserialize(char *str) {
        int i=0;
        return des(i,str);
    }
    
    TreeNode* des(int& i,char* s){
        if(s[i]=='#'){
            i++;
            return nullptr;
        }
        int num=0;
        while(s[i]!='!'){
            num=num*10+(s[i]-'0');
            i++;
        }
        i++;
        TreeNode* root=new TreeNode(num);
        root->left=des(i,s);
        root->right=des(i,s);
        return root;
    }
};

62 二叉搜索树的第k个结点

题目描述

给定一棵二叉搜索树,请找出其中的第k小的TreeNode结点

输入:

{5,3,7,2,4,6,8},3

返回值:

{4}

思路:

中序遍历二叉搜索树,返回遇到的第k个结点

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
TreeNode* KthNode(TreeNode* pRoot, int k) {
    if (!pRoot)return nullptr;
    int i = k;
    TreeNode* res=nullptr;
    dfs(pRoot, i, res);
    return res;
}
void dfs(TreeNode* root, int& i, TreeNode*& res) {
    if (root->left)dfs(root->left, i, res);
    if (i <= 0)return;   
    if (i == 1)res = root;
	i--;
    if (root->right)dfs(root->right, i, res);
}
};

63 数据流中的中位数

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

思路:

创建一个大顶堆一个小顶堆,

第单数个数放入大顶堆,第偶数个数放入小顶堆

放入大顶堆前先放入小顶堆排序,再将小顶堆的堆顶放入大顶堆,确保大顶堆的数全都小于小顶堆

放入小顶堆前先放入大顶堆排序,再将大顶堆的堆顶放入小顶堆,确保小顶堆的数全都大于大顶堆

数据流有单数个时大顶堆的堆顶就是中位数,偶数时取两个堆顶的和*0.5

c++中priority_queue意为优先队列,有三个参数,第一个是数据类型,第二个是存储容器,第三个是排序方法,默认用vector存储,大顶堆。设置greater参数就是小顶堆

class Solution {
public:
    priority_queue<int> maxHeap;
    priority_queue<int,vector<int>,greater<int>> minHeap;
    void Insert(int num) {
        if(maxHeap.size()==minHeap.size()){
            minHeap.push(num);
            maxHeap.push(minHeap.top());
            minHeap.pop();
        }
        else{
            maxHeap.push(num);
            minHeap.push(maxHeap.top());
            maxHeap.pop();
        }
    }

    double GetMedian() { 
        int lenMax=maxHeap.size(),lenMin=minHeap.size();
        return (lenMax==lenMin)?(maxHeap.top()+minHeap.top())*0.5:maxHeap.top();
    }

};

64 滑动窗口的最大值

题目描述

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

窗口大于数组长度的时候,返回空

示例1

输入:

[2,3,4,2,6,2,5,1],3

返回值:

[4,4,6,6,6,5]

思路:

创建一个双端队列,保存数组的下标,当队首不在窗口内出队,每加入一个新元素就和队尾比较,确保队列是一个降序队列,队首元素是当前滑动窗口最大值

class Solution {
public:
    vector<int> maxInWindows(const vector<int>& num, unsigned int size) {
        deque<int> dqu;
        vector<int> res;
        if(num.size()==0||size>num.size()||size==0)return res;
        for(int i=0;i<num.size();i++){            
            while(!dqu.empty()&&i-dqu.front()+1>size)dqu.pop_front();
            while(!dqu.empty()&&num[dqu.back()]<num[i])dqu.pop_back();
            dqu.push_back(i);
            if(i+1>=size)res.push_back(num[dqu.front()]);
        }
        return res;
    }
};

65 矩阵中的路径

题目描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如
a b c e s f c s a d e e \begin{matrix} a&b&c&e\\ s&f&c&s\\ a&d&e&e\\ \end{matrix} asabfdcceese
矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

示例1

输入:

"ABCESFCSADEE",3,4,"ABCCED"

返回值:

true

思路:

用回溯法解决。在搜索中,遇到这条路不可能和目标字符串匹配成功的情况(例如:此矩阵元素和目标字符不同、此元素已被访问,超出矩阵范围),则应立即返回,称之为可行性剪枝 。

每一个元素都可能是起点,所以要遍历矩阵找出符合条件的字符。

深度搜索当前字符的前后左右,搜索过的元素标为空,防止重复搜索,若不匹配返回false

class Solution {
public:
    bool res=false;
    bool hasPath(string matrix, int rows, int cols, string str) {
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                if(dfs(matrix,str,rows,cols,0,i,j))return true;;
            }
        }
        return false;
    }
    
    bool dfs(string matrix,string str,int rows, int cols,int pre,int i,int j){
        if(i>=rows||i<0||j>=cols||j<0||matrix[j+i*cols]!=str[pre])return false;
        if(pre==(str.size()-1))return true;
        matrix[j+i*cols]='\0';
        bool res=dfs(matrix,str,rows,cols,pre+1,i+1,j)||dfs(matrix,str,rows,cols,pre+1,i-1,j)
            ||dfs(matrix,str,rows,cols,pre+1,i,j+1)||dfs(matrix,str,rows,cols,pre+1,i,j-1);
        return res;
    }
};

66 机器人的运动范围

题目描述

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

示例1

输入:

5,10,10

返回值:

21

思路:

从第一个位置开始,深度搜索上下左右四个位置,判断当前格子是否符合条件,不符合返回0,且不往这个格子后搜索,相当于路被堵住了。符合则返回值+1

设置flag数组记录走过的格子,避免重复

class Solution {
public:
    int row,col;
    int movingCount(int threshold, int rows, int cols) {
        int res=0;
        row=rows;col=cols;
        vector<vector<bool>> flag(rows,vector<bool>(cols,true));
        res=Compare(threshold, 0, 0, flag);
        return res;
    }
    
    int Compare(int k,int i,int j,vector<vector<bool>>& flag){
        if(i>=row||i<0||j>col||j<0||!flag[i][j])return 0;
        string s1=to_string(i);
        string s2=to_string(j);
        int num=0;
        for(auto c:s1)num+=(c-'0');
        for(auto c:s2)num+=(c-'0');
        flag[i][j]=false;
        if(num>k)return 0;
        int res=Compare(k, i+1, j, flag)+Compare(k, i-1, j, flag)
            +Compare(k, i, j+1, flag)+Compare(k, i+1, j-1, flag)+1;
        return res;
    }
};

67 剪绳子

题目描述

给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

输入:

8

返回值:

18

思路:

利用动态规划的思想

我们想要求长度为n的绳子剪掉后的最大乘积,可以从前面比n小的绳子转移而来
用一个dp数组记录从0到n长度的绳子剪掉后的最大乘积,也就是dp[i]表示长度为i的绳子剪成m段后的最大乘积,初始化dp[2] = 1
我们先把绳子剪掉第一段(长度为j),如果只剪掉长度为1,对最后的乘积无任何增益,所以从长度为2开始剪
剪了第一段后,剩下(i - j)长度可以剪也可以不剪。如果不剪的话长度乘积即为j * (i - j);如果剪的话长度乘积即为j * dp[i - j]。取两者最大值max(j * (i - j), j * dp[i - j])
第一段长度j可以取的区间为[2,i/2+1],大于i/2+1会剪重复,对所有j不同的情况取最大值,因此最终dp[i]的转移方程为
dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
最后返回dp[n]即可

class Solution {
public:
    int cutRope(int number) {
        vector<int> dp(number+1);
        dp[2]=1;
        for(int i=3;i<=number;i++){
            for(int j=2;j<=(i/2)+1;j++){
                dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]));
            }
        }
        return dp[number];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值