剑指offer(58---67)

58、对称的二叉树

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

首先考虑递归的方式,也就是对称的二叉树两边的子树要是对称的,也就是左子树的左边和右子树的右边要相同,左子树的右边要和右子树的左边相同,我们此时将根结点的左右子树当成两个数,使用一个函数实现看起是否对称即可(如果两者都是nullptr,直接返回true,任意一方是nullptr,返回false,如果当前结点值不同,直接返回false,否则继续递归p的左子树和q的右子树以及p的右子树和q的左子树)

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
private:
    bool _isSymmetrical(TreeNode* p,TreeNode* q)//让两个树是对称的
    {
        if(p==nullptr && q==nullptr)
            return true;//如果都是空,直接返回true
        //任一为空,返回false
        if(p==nullptr || q==nullptr)
            return false;
        //如果当前结点的值不相同,肯定不对称
        if(p->val != q->val)
            return false;
        //继续递归,看p的左孩子和q的右孩子、p的右孩子和q的左孩子
        return _isSymmetrical(p->left,q->right) && _isSymmetrical(p->right,q->left);
    }
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
        if(pRoot == nullptr)
            return true;
        //左右子树要是对称的
        return _isSymmetrical(pRoot->left,pRoot->right);
    }
};

或者使用非递归的方式,如果pRoot为nullptr,直接返回true,将左孩子和右孩子入栈,然后成对取出栈顶元素右孩子和左孩子,如果都为空,继续循环进行出栈入栈,如果任意一个为空,表示肯定不对称,直接返回false,如果两个栈顶元素的值不同,肯定也不对称,直接返回false,然后将左孩子的左孩子入栈,左孩子的右孩子入栈,右孩子的右孩子入栈,右孩子的左孩子入栈(即成对成对入栈),继续循环执行以上过程,直到栈为空,中间没有返回false,表示对称,返回true;
实现代码:

/*
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)
    {
        if(pRoot==nullptr)
            return true;
        stack<TreeNode*> s;
        s.push(pRoot->left);
        s.push(pRoot->right);
        while(!s.empty())
        {
            //逐对取出
            TreeNode* l=s.top();
            s.pop();
            TreeNode* r=s.top();
            s.pop();
            if(l==nullptr && r==nullptr)//如果两个都为空,继续循环
                continue;
            if(l==nullptr || r==nullptr)
                return false;
            if(l->val!=r->val)
                return false;
            s.push(l->left);
            s.push(r->right);//逐对入栈,即要将左孩子的左边与右孩子的右边进行相比,一起入栈
            
            s.push(l->right);
            s.push(r->left);//逐对入栈,要将左孩子的右边和右孩子的左边进行相比,一起入栈
        }
        //执行完循环,还没有返回,则返回true
        return true;
    }
};
59、按之字形顺序打印二叉树

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

我们知道根结点就是第一行,只有根结点一个结点,而偶数行要从右到左来遍历,考虑到这一点,我们可以使用两个栈s1和s2,s1保存奇数行从左到右的结点,s2保存偶数行从右到左的结点,那么s1先插入根结点(第一个奇数行),此时开始进行以下过程:
先将s1的所有节点逐个取出,并且在取的时候将每次取出的结点的左孩子先放入s2,然后将右孩子放入s2(保证s2中取出节点时是从右到左的),然后每次将s1取出的结点的值放入我们的数组arr1中,将s1取完之后,将arr1尾插到result结果数组即可;然后开始将s2的结点逐个取出,在取的时候将每次取出的结点的右孩子先放入s1,然后将左孩子放入s1(保证s1中取出节点时是从左到右的),然后每次将s2取出的结点的值放入我们的数组arr2中,将s2取完之后,将arr2尾插到result结果数组;
执行上述过程,通过取s1结点将下一行的结点放进s2,再通过取s2结点将下一行的结点放进s1,直到有任意一个栈为空,就结束上述过程,返回result即可。
实现代码:

/*
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) {
        vector<vector<int>> result;//保存结果
        if(pRoot!=nullptr)
        {
            //定义两个栈s1和s2,s1保存奇数行的结点,s2保存偶数行的结点
            stack<TreeNode*> s1,s2;
            //先将pRoot放进s1,它是第一个奇数行,则接下来的偶数行就是s1的栈顶元素的左右孩子,放进s2,这样s2取出就是从右到左的顺序
            //直到s1为空或者s2为空,表示此时二叉树的行已经遍历完成了
            s1.push(pRoot);
            while(!s1.empty() || !s2.empty())
            {
                vector<int> arr1,arr2;//保存奇数行从左到右的值和偶数行从右到左的值
                //先将s1所有的结点取出来,并且将每次取出来的结点的左孩子和右孩子放进s2中
                while(!s1.empty())
                {
                    TreeNode* pCur=s1.top();
                    if(pCur->left)
                        s2.push(pCur->left);
                    if(pCur->right)
                        s2.push(pCur->right);
                    //将每次从s1取出的栈顶元素的值放进arr1中
                    arr1.push_back(pCur->val);
                    s1.pop();//出栈
                }
                if(arr1.size()!=0)//尾插到result中
                    result.push_back(arr1);
                //将s2的所有节点取出来,并且将每次取出来的结点的右孩子和左孩子放进s1中(记得现房右孩子,再放左孩子,这样s1出栈才能保证是从左到右)
                while(!s2.empty())
                {
                    TreeNode* pCur=s2.top();
                    if(pCur->right)
                        s1.push(pCur->right);
                    if(pCur->left)
                        s1.push(pCur->left);
                    //将每次从s2取出的栈顶元素的值放进arr2中
                    arr2.push_back(pCur->val);
                    s2.pop();
                }
                if(arr2.size()!=0)//尾插到result中
                    result.push_back(arr2);
            }
        }
        //走到这里说明此时已经将整个二叉树遍历完了,此时返回result即可
        return result;
    }
};
60、把二叉树打印成多行

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

这个题就是层序遍历,不过不同的是要将每一行的结果保存起来打印,因此,每次在拿完队列中的元素之后,将当前结点的左右孩子放进队列时,都要重新计算队列的大小,它是当前这一行的结点的个数。

/*
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) {
            vector<vector<int>> result;
            if(pRoot==nullptr)
                return result;
            queue<TreeNode*> q;
            q.push(pRoot);
            while(!q.empty())
            {
                //每次重新计算队列的大小,每次队列的大小就是当前这一行结点的数量
                int n=q.size();
                vector<int> ret;//保存每一次每一行的值
                while(n--)
                {
                    TreeNode* node = q.front();
                    ret.push_back(node->val);
                    if(node->left)
                        q.push(node->left);
                    if(node->right)
                        q.push(node->right);
                    q.pop();
                }
                result.push_back(ret);//每次将队列中的拿完放到ret中就插入result
            }
            return result;
        }
};
61、序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树

(1)首先分析序列化,即将一个二叉树转为字符串形式,并且每个结点的字符中间以逗号隔开,如果结点是nullptr,用#表示,我们就可以知道,如果二叉树的当前表示的根结点就是nullptr,此时得到的结果就是"#,",此时s中插入这两个字符即可,否则当前表示的根结点不是nullptr,此时将该节点的值转为字符(to_string),并且再向后插入逗号,然后继续在二叉树的左子树和右子树递归即可(前序遍历的顺序)。
(2)然后是反序列化,即将一个字符串转为二叉树(重构二叉树),那么此时如果字符串的第一个字符是’#’,此时就表示根结点是nullptr,则直接返回nullptr,字符串就是"#,"这两个字符,如果第一个字符不是’#’,那么构造此时的根结点root,它的值为要由字符形式转为整数形式(stoi),然后接下来s就变为从第一个逗号后开始计算的字符串,继续构造root的左子树和右子树(通过递归的方式)即可。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
private:
    //通过前序遍历进行序列化
    void serializeHelper(TreeNode* root,string& s)//结果存在s中
    {
        if(root==nullptr)//如果是空,此时s中插入#和,是递归终止条件
        {
            s.push_back('#');
            s.push_back(',');
            return;
        }
        //不是空,s加上root的值(转为字符形式)
        s+=to_string(root->val);
        s.push_back(',');//加上逗号
        //继续去左子树和右子树递归
        serializeHelper(root->left,s);
        serializeHelper(root->right,s);
    }
    TreeNode* deserializeHelper(string& s)//反序列化
    {
        if(s.empty())
            return nullptr;
        //如果第一个字符就是'#',说明后面就没有了,直接返回
        if(s[0]=='#')
        {
            s=s.substr(2);//包含#和,两个字符
            return nullptr;
        }
        //说明第一个字符不是'#',第一个构造为根结点
        TreeNode* root=new TreeNode(stoi(s));
        //此时在s中开始从下一个结点的值开始,也就是第一个逗号的下一个
        s=s.substr(s.find_first_of(',')+1);
        //继续构造左子树和右子树
        root->left=deserializeHelper(s);
        root->right=deserializeHelper(s);
        return root;
    }
public:
    char* Serialize(TreeNode *root) {
        if(root==nullptr)
            return nullptr;
        string s="";
        serializeHelper(root,s);//结果在s中,此时转为char*形式的
        char* result=new char[s.size()+1];
        strcpy(result,s.c_str());
        return result;
    }
    TreeNode* Deserialize(char *str) {
        if(str==nullptr)
            return nullptr;
        string s(str);
        return deserializeHelper(s);
    }
};
62、二叉搜索树的第k个结点

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

从题可知,是一棵二叉搜索树,因此它的中序遍历结果一定是从小到大的,因此只要得到中序遍历的第k个元素就是二叉搜索树的第k大结点,因此现在根结点的左子树中递归,如果找到了就直接返回找到的这个节点,使用count来统计递归的次数,等到递归的次数是k时,说明当前表示的根结点就是要找的第k大结点,否则继续去右子树中递归。

/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
private:
    int count=0;//统计此时是否与k相等
public:
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        if(pRoot==nullptr)
            return nullptr;
        TreeNode* node=KthNode(pRoot->left,k);//按照中序遍历的顺序,现在左子树中递归查找
        if(node!=nullptr)//说明在当前表示的根结点的左子树中找到了
            return node;
        count++;//否则递归一次++一次
        if(count==k)//说明当前表示的根结点就是第k大
            return pRoot;
        //继续去右子树中递归
        node=KthNode(pRoot->right,k);
        if(node!=nullptr)
            return node;
    }
};
63、数据流中的中位数

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

遇到这个题,许多人第一想法就是将整个数组排序,然后如果是偶数就取中间的两个数的平均值,如果是奇数就取中间那个数,但是这种方式要进行排序(使用sort方法),效率不高,我们这里可以将整个数组分为三部分:比中位数小的数、中位数、比中位数大的数;我们可以维护两个堆,可以动态维护插入和获得中位数的过程,使用一个大顶堆和小顶堆,中位数左边是大顶堆,放比中位数小的数,中位数右边是小顶堆,放比中位数大的数,此时分析过程如下:
注意中位数要保持两个堆的数目之差不能超过1,为了实现平均分配,可以得出
(1)如果当前元素的个数是偶数个(此时就要考虑将数据插入到小根堆中,例如大根堆中有一个元素,有一个中位数,此时小根堆还没有元素,元素个数为偶数,因此要插入到小根堆中),如果待插入元素小于大根堆的最大值,就需要将其插入大根堆,否则直接插入小根堆;
(2)如果当前元素的个数是奇数个(此时就要考虑将数据插入到大根堆中,例如此时大根堆有一个元素,中位数有一个,小根堆也有一个元素,元素个数为奇数,因此要插入大根堆中),如果待插入元素大于小根堆的最小值,就需要将其插入小根堆,否则直接插入大根堆。

class Solution {
private:
    vector<int> min;//表示比中位数大的数
    vector<int> max;//表示比中位数小的数
public:
    void Insert(int num)
    {
        if(((min.size()+max.size())&1)==0)//偶数
        {
            //如果待插入元素小于max大顶堆中最大的数,则插入max中,将大顶堆最大值放入小顶堆
            if(max.size()>0 && num<max[0])
            {
                max.push_back(num);//插入max中
                //将大顶堆先调整好
                push_heap(max.begin(),max.end(),less<int>());//大顶堆,在插入num之后将堆调整好
                //然后此时要将大顶堆的最大元素拿出放入小顶堆中,我们先保存一下最大元素
                num=max[0];
                //交换大顶堆堆顶元素和最后一个叶子结点,然后删除
                pop_heap(max.begin(),max.end(),less<int>());
                max.pop_back();
            }
            //否则此时要直接将num插入小顶堆
            min.push_back(num);
            //将小顶堆调整好
            push_heap(min.begin(),min.end(),greater<int>());
        }
        else//奇数
        {
            //如果待插入元素大于小顶堆最小的元素,num插入小顶堆,然后将小顶堆的最小的元素(堆顶元素)放到大顶堆中
            if(min.size()>0 && num>min[0])
            {
                min.push_back(num);//小顶堆
                push_heap(min.begin(),min.end(),greater<int>());//调整好小顶堆
                //将要从小顶堆里拿出来的最小的元素保存一下
                num=min[0];
                //然后交换小顶堆堆顶元素和最后一个叶子结点进行删除
                pop_heap(min.begin(),min.end(),greater<int>());
                min.pop_back();
            }
            //否则直接插入大顶堆
            max.push_back(num);
            //调整好大顶堆
            push_heap(max.begin(),max.end(),less<int>());
        }
    }

    double GetMedian()
    { 
        int len = min.size()+max.size();
        if(len<=0)
            return 0;
        double me=0;//中位数
        if((len & 1)==0)//偶数
            me = (double)(min[0]+max[0])/2;
        else
            me = (double)min[0];
        return me;
    }
};
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)如果此时队列头超过了滑动窗口的范围(即当前插入的元素的下标i减去队头元素的下标大于等于滑动窗口的大小),就要将队头元素删除;
即每次保证队头元素是当前最大值;
例如上述数组{2,3,4,2,6,2,5,1}的例子,此时它的实现过程就是:
(1)i=0,num[i]=2,队列为空,将2入队,队列中的元素为{2},此时的滑动窗口划过了1个元素{2},还没有到达滑动窗口大小,继续遍历数组,队列中只有2;
(2)i=1,num[i]=3,此时3>2,因此删除队尾元素2,将3入队,此时的滑动窗口划过了2个元素(2,3),还没有到达滑动窗口大小,继续遍历数组,队列中只有3;
(3)i=2,num[i]=4,此时4>3,因此删除队尾元素3,将4入队,此时的滑动窗口划过了3个元素{2,3,4},(说明当前已经有滑动窗口了,即i+1>=size),将此时的队头元素4放到结果数组result中,此时result中为{4};
(4)i=3,num[i]=2,此时2<4,因此直接入队,队列中的元素为{4,2},此时滑动窗口划过了4个元素{2,3,4,2},将此时的队头元素4放到结果数组result中,此时result中为{4,4};
(5)i=4,num[i]=6,此时6>2,2出队,6>4,4出队,6入队,队列中的元素为{6},此时滑动窗口划过了5个元素{2,3,4,2,6},将此时的队头元素6放入result中,此时result为{4,4,6};
(6)i=5,num[i]=2,此时2<6,2直接入队,队列中的元素为{6,2},此时滑动窗口划过了6个元素{2,3,4,2,6,2},将此时的队头元素6放入result中,此时result为{4,4,6,6};
(7)i=6,num[i]=5,此时5>2,2出队,5<6,因此5入队,此时队列中的元素为{6,5},此时滑动窗口划过了7个元素{2,3,4,2,6,2,5},将此时的队头元素6放入result中,此时result为{4,4,6,6,6};
(8)i=7,num[i]=1,此时1<5,1入队,但是此时6已经不在滑动窗口中了,因此删除6,此时队列中的元素为{5,1},将此时的队头元素5放入result中,此时result为{4,4,6,6,6,5};

class Solution {
public:
    vector<int> maxInWindows(const vector<int>& num, unsigned int size)
    {
        vector<int> result;
        if(num.empty() || size==0 || size>num.size())
            return result;
        deque<int> q;//双端队列,队头元素放每次滑动窗口最大的,队尾元素用来比较过滤掉小的
        for(int i=0;i<num.size();++i)
        {
            //如果此时队列不是空的,那么此时当前num的i下标的元素与队尾元素相比,队尾元素出队
            //然后将当前元素下标入队(队列为空时当前元素下标也入队,因此在这个循环外一并实现)
            while(q.size()!=0 && num[i]>=num[q.back()])
            {
                q.pop_back();
            }
            //如果此时队头元素不在滑动窗口中了,就出队
            //判断是否在滑动窗口中,只要确定当前元素的下标减去队头元素的下标是否大于等于滑动窗口的大小,如果是,说明超过了,队头元素出队,否则没有超过
            while(q.size()!=0 && (i-q.front())>=size)
            {
                q.pop_front();
            }
            //当前元素入队
            q.push_back(i);
            //如果入队后此时已经到达了滑动窗口大小,将队头元素插入result中
            if(size && i+1>=size)//i+1就是从0开始计算,只要i+1大于等于滑动窗口大小,就说明当前已经是一个滑动窗口(因为是逐渐向右推移的,因此设置i+1>=size意思就是此时已经有滑动窗口),此时就要将队头元素放到结果数组
                result.push_back(num[q.front()]);
        }
        return result;
    }
};
65、矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。例如:
在这里插入图片描述
矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

拿到这个题,我们要第一时间想到矩阵中的每个点的下一个都是他向左、向右、向下、向上移动一个格子,因此可以想到使用回溯算法,每一个点找下一个都可以递归匹配,而题目中也规定了如果一条路径经过了矩阵中的某一个格子,则该路径并不能再进入该格子,因此也要设置标志位来标志这个格子走了还是没有走,因此可以设置一个标志数组,如果这个格子没走,这个标志位就是false,否则就是true,因此要递归设置的终止条件就是:
(1)是否越界,当前走到的点不能越界;
(2)当前走到的点的值是否与str[k]匹配(k是随着递归次数逐渐+1的,k走到末尾就表示这次走的路径与str匹配成功);
(3)当前这个点如果已经被标志为true,则终止不能递归这个点了。
要注意的是题目中给的matrix是一个一维数组,而每次走的点是在二维矩阵中,因此要通过当前二维矩阵的坐标[i,j]计算出在一维数组中对应的下标index,也就是index=i*cols+j;
实现代码:

class Solution {
private:
    bool judge(char* matrix,int i,int j,bool *flag,int rows,int cols,char* str,int k)//k用来遍历str
    {
        //首先我们要知道matrix是一个二维数组,而我们的矩阵是一个二维数组,一次你二维数组中的[i,j]点要表示在matrix中,必须要进行计算
        //计算方式就是这个点=i*cols+j,就是在一维数组中的表示,例如cols=4,i=2,j=2,在二维数组中是2行2列
        //此时在matrix中的表示就是2*4+2=10,也就是第10个元素
        int  index = i*cols+j;//当前第一个要匹配的点
        //递归终止条件
        //终止条件就是越界/当前匹配到的数组的值不是str当前的值/当前这个点在标志位中已经是true了,说明已经走过了
        if(i<0 || j<0 || i>=rows || j>=cols || matrix[index] != str[k] || flag[index]==true)
            return false;
        //如果k走到了str的末尾,说明前面的全部匹配,说明这条路径可以匹配,此时就要返回true
        if(str[k+1]=='\0')
            return true;
        //标记这个index点访问过
        flag[index]=true;
        //此时这个点走到他的左边、右边、上边、下边;
        //他的左边也可以走到自己的左边、右边、上边、下边;
        //他的右边也可以走到自己的左边、右边。上边、下边
        //他的上边也可以。。。。。
        //他的下边也可以。。。。
        //他的左边的左边也可以。。。。
        //他的左边的右边也可以。。。。
        //(由此回溯,因此这四个点也继续递归)
        //每次递归都是看当前这个index点下一个点应该是自己的上下左右,此时k也要向后走一步
        if(
          judge(matrix,i-1,j,flag,rows,cols,str,k+1) || 
          judge(matrix,i+1,j,flag,rows,cols,str,k+1) ||
          judge(matrix,i,j-1,flag,rows,cols,str,k+1) ||
          judge(matrix,i,j+1,flag,rows,cols,str,k+1))//任一条路径可以匹配就返回true
        {
            return true;
        }
        //说明这条路径是不匹配的,因此将这个点设置回false
        flag[index]=false;
        return false;
    }
public:
    bool hasPath(char* matrix, int rows, int cols, char* str)
    {
        //初始化一个标志数组,先全部初始化为false,表示所有标志位都还能走,如果哪个标志位走过了,设为true
        bool *flag = new bool[rows*cols];
        memset(flag,false,rows*cols);
        //遍历二维数组,从每一个点开始判断从当前[i,j]这个点到他能走的路径是否满足要求
        for(int i=0;i<rows;++i)
        {
            for(int j=0;j<cols;++j)
            {
                if(judge(matrix,i,j,flag,rows,cols,str,0))
                    return true;
            }
        }
        delete[]flag;
        return false;
    }
};
66、机器人的运动范围

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

这个题也是要使用回溯法 与65题不同的是,这个题明显更加简单,构造一个标志位二维数组,表示那个格子已经走过,设为true,递归终止条件是越界、当前点已经走过以及当前点数位之和大于k,只要回溯递归再加上当前点所占的一格即可。

class Solution {
private:
    //计算一个数的数位之和
    int bitSum(int n)
    {
        int sum=0;
        while(n!=0)
        {
            sum+=n%10;
            n/=10;
        }
        return sum;
    }
    //实现函数
    int CountSteps(int threshold,int rows,int cols,int i,int j,vector<vector<bool>>& flag)
    {
        //递归终止条件
        //当前点越界、这个点已经被访问过、这个点的数位和大于threshold
        if (i < 0 || i >= rows || j < 0 || j >= cols || bitSum(i) + bitSum(j)  > threshold || flag[i][j] == true)
            return 0;
        //当前点被访问,这个点的标志位设置为true
        flag[i][j]=true;
        //向上下左右递归(上下左右的点也是要递归,回溯)再加上当前格子所占的一格
        return CountSteps(threshold,rows,cols,i-1,j,flag)+
            CountSteps(threshold,rows,cols,i+1,j,flag)+
            CountSteps(threshold,rows,cols,i,j-1,flag)+
            CountSteps(threshold,rows,cols,i,j+1,flag)+1;
    }
public:
    int movingCount(int threshold, int rows, int cols)
    {
        vector<vector<bool>> flag(rows,vector<bool>(cols,false));
        return CountSteps(threshold,rows,cols,0,0,flag);
    }
};
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。

这个题我们可以来找规律:
n=2和n=3是两种特殊情况,即2:1 * 1,3:2 * 1,
n=4:2 * 2;
n=5:2 * 3;
n=6:3 * 3;
n=7:2 * 2 * 3;
n=8:2 * 3 * 3;
n=9:3 * 3 * 3;
n=10:2 * 2 * 3 * 3;
n=11:2 * 3 * 3 * 3;
由上可知,我们会发现,如果n除以3没有余数,那么它的k[0]。。。k[m]都是3,也就是pow(3,n/3);
而当n除以3的余数是1时,发现都会有两个2相乘,后面都是3,即2 * 2 *pow(3,(n/3)-1);如果余数是2时,发现都会有一个2相乘,即2 * pow(3,n/3);
实现代码:

class Solution {
public:
    int cutRope(int number) {
        if(number==2)
            return 1;
        if(number==3)
            return 2;
        int x=number%3;
        int y=number/3;
        if(x==0)
            return pow(3,y);
        else if(x==1)
            return 2*2*pow(3,y-1);
        else
            return 2*pow(3,y);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值