剑指OfferC++_51-最后

目录

51、构建乘积数组

52、正则表达式匹配 

53、表示数值的字符串

54、字符流中第一个不重复的字符

55、链表中环的入口结点

56、删除链表中重复的结点

57、二叉树的下一个结点

58、对称的二叉树

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

60、把二叉树打印成多行

61、序列化二叉树

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

63、数据流中的中位数

64、滑动窗口的最大值

65、矩阵中的路径

66、机器人的运动范围

67、剪绳子


51、构建乘积数组

题目:给定一个数组A[0,.,n-1],请构建一个数组B[0,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。
很明显想先计算A[0]*...*A[i-1]  和A[i+1]*.....*A[n-1]  之后再两者相乘,可以看出对于每一个b的前半部分都是前一个再乘以一个数,所以可以找规律。所以B就相当于数组K和M两个数组相乘的结果。K是0 ~ i-1,M是i+1到n-1

n=5的时候 K和M数值如下:


k[0] = 1;                                                        m[0]=a[1]*a[2]*a[3]*a[4]=a[1]*m[1]
k[1] = k[0] * a[0] = a[0]                                  m[1]=a[2]*a[3]*a[4]=a[2]*m[2]
k[2] = k[1] * a[1] = a[0] * a[1];                        m[2]=a[3]*a[4]=a[3]*m[3]
k[3] = k[2] * a[2] = a[0] * a[1] * a[2];              m[3]=a[4]=m[4]*a[4]
k[4] = k[3] * a[3] = a[0] * a[1] * a[2] * a[3];     m[4]=1

vector<int> multiply(const vector<int>& A) {
    vector<int> k(A.size());
    if(A.size()==0) return k;
    k[0]=1;
    for(int i=1;i<A.size();i++)
        k[i]=k[i-1]*A[i-1];
    vector<int> m(A.size());
    m[A.size()-1]=1;
    for(int i=A.size()-2;i>=0;i--)
        m[i]=m[i+1]*A[i+1];
    vector<int> b(A.size());
    for(int i=0;i<A.size();i++)  b[i]=k[i]*m[i];
    return b;
}

52、正则表达式匹配 

题目:请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配。有几个隐含的条件:这个字符串里只能出现字母,点,星号。并且只有第二个字符串里可能有星号。

考虑的时候想要用while循环写,但是其实也可以考虑递归的写法。递归考虑两个字符串当前地址处所在的字符,所以一定要注意不能a[1],而是只能*a。这里面有几种情况,

1、首先如果两者当前不同肯定是false,此外,

2、当第一个字符串不空,但是第二个字符串为空的时候肯定是false。

3、同时如果两个字符串当前都是空,代表一起遍历到最后,则是true。

4、需要注意的是第一个字符串为空,第二个不空可能是对的,因为可能第二个字符串剩下的是e*这个时候e可能一个都没有,这种可能就是对的。

5、所以在确定当前这两个字符串还可能可以匹配的时候,(此时第二个字符串一定不是空)正常我们都会直接看这两个字符串当前指代的是否相同,但是这里我们来看第二个字符串的下一个指代的是不是星号!!!

5.1、如果不是星号就可以直接看两个是否相等了,不相等就返回false,相等,就Match(str+1,pattern+1),(注意这个时候要判断是否是 点 如果是点那么也可以看作相等)

5.2、如果是星号就复杂了,因为如果是星号可能当前两个数值不相等也可以!因为星号指代的那个字符可以不出现。这个时候就要判断是星号的话,当前指代的两个数值是否相等,

5.2.1、如果不相等,并且不是 点 就很简单,代表着pattern这个字符没有出现,那么直接match(str,patter+2)就好了

5.2.2、如果相等那仍然很复杂,因为还会出现2种情况。ba 与 b*ba  、bbba与b*a 。并且还有另一个 点 的情况。  .*其实和相等是一样的情况,因为.可以替代为任何一个字符。而对于相等其实要递归两种。一种是就算相等也要像不相等那样str不动,pattern往后走!因为存在ba和b*ba这种情况,即使是相等的但是其实并没有和当前这个匹配!另一种情况就是正常的可能他匹配了,bbbbba和b*a这种情况,那么str就要往后走,pattern不往走,一直到两者不等了进入5.1。isdigit()和isalpha() 是判断是否是字符,是否是数字。

bool match(char* str, char* pattern){//对于这个题,其中特殊的是 有 * ,且只有pattern里有* 所以以pattern下一位是不是*来判断
     if((*str)=='\0' && (*pattern)=='\0') return true;
     if((*str)!='\0' && (*pattern)=='\0') return false;//因为pattern可能存在b*的情况所以str可以为0,但是pattern不是0
     if(!(isalpha(*pattern)||*pattern=='.')) return false;
     if(*(pattern+1)=='*') //pattern当前一定不是\0。,所以*(pattern+1)一定不越界
//如果下一位是*,那么存在两种情况。一种是当前两个指针对应数相同的情况,一种是当前两个数对应指针不同的情况
        {//相同的情况,还有两种情况,一种是bbba 与 b*ba  和bbba与b*a 。  而有一种情况和相同是一个可能性的就是.*的情况。第二种情况 就是pattern指针不动,str指针加一,相当于一直让pattern指向b。一直到两者不等了,让它进入else {return match(str,pattern+2);}把b*过去。第一种情况是在一段时间考虑pattern不动的情况下,在每次也要考虑如果pattern+2 str的情况。 相当于在patten前几次不动的情况下,最后一次直接跳过*,意味着最后一次的b一定要有一个b和其对应

            if(*str == *pattern || (*str!='\0' && *pattern=='.'))                
                return match(str,pattern+2)||match(str+1,pattern) ;
            else//当前不等代表着这个*一定就是一个数都没有
                return match(str,pattern+2);
        }
        else
        {
            if(*str == *pattern || (*str!='\0'&&*pattern=='.'))//几次pattern出现.的情况一定要首先要求*str不是空!因为在最前面的判断种可能出现str是空的情况。如果str是空,patter是.是有问题。str是空只有pattern是字母+*+'\0'才行
                return match(str+1,pattern+1);
            else  return false;
        }
    }

53、表示数值的字符串

题目:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。*/

方法1:编译原理的方法,画自动机看流程过程,/找正则式
方法2:找临界条件的题,上一题首先看能否用递归,上一个题是对于判断后剩下的字符串可以用相同的方法判断
而本题需要在最开始判断+/-并且e . 等只能出现一次,就不能用递归来用相同的办法去看了。那么就用while循环一直判断到最后一个。首先在第一位如果出现+/-可以,此外只能是数字,并且判断到+/-一定要确定它后面不能是'\0',一定是数字。
之后一直遍历看是不是数字,如果不是数字可能是.和e/E。要有一个判断变量,因为这两个只能出现一次。而这两个后面也不能是空,并且e/E后面可能是正负号,并且如果先遍历到e,那么.也不能出现了。

bool isNumeric(char* string1) {
        if(*string1=='\0') return false;
        if(*string1=='+' || *string1=='-')
            {if(*(string1+1)=='\0') return false;
            else string1++;}//如果出现了+/-后面有东西就ok,没有东西就返回false
        int pandian=1;//看点是否出现了
        int pane=1;//看是否出现了e了
        while(*string1!='\0')
        {
            if(isdigit(*string1))  string1++;
            else if((*string1=='e'||*string1=='E')&&(pane==1))
            {
                pandian=0;  pane=0;//出现e之后后面不能出现点了,因为只能是整数了
                if(*(string1+1)=='\0')  return false;//出现e后面一定要有东西
                if(*(string1+1)=='-'||*(string1+1)=='+')
                {
                    if(*(string1+2)=='\0')     return false;//出现+或-之后一定要有东西
                    else  string1++;
                }
                string1++;
            }
            else if((*string1=='.')&&(pandian==1))
            {
                pandian=0;
                if(*(string1+1)=='\0')  return false;//出现了点之后一定有东西
                string1++;
            }
            else return false;
        }
        return true;
    }

54、字符流中第一个不重复的字符

题目:请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

方法:老老实实用hash+链表!因为一共ascii 256个,其中只有前128个是可见的。因此定义bool类型128维的数组。并且也要定义一个queue,因为要记录第一个只出现一次的数。在我们要查找当前的第一个数的时候,去遍历queue从front开始遍历,看当前top对应的数是不是只出现一次,如果不是就pop,如果是返回。在insert的时候,看这个值是不是出现0次,如果出现0次就Push,否则不Push,不过数组都是要++的。

注意:int数组都初始化为0 使用={0}  queue.front()

int a[128]={0};
queue<char> k;
void Insert(char ch){
     if(a[ch]==0){
         k.push(ch);
         a[ch]++; }
     else a[ch]++;
}
char FirstAppearingOnce(){
     while(k.empty()==0){
        if(a[k.front()]>1) k.pop();
        else return k.front();
        }
        return '#';//说明当前没有只出现一次的返回#
}

55、链表中环的入口结点

题目:给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

明显是追赶问题,有两种方法:1、断点法 2、快慢指针来巧妙解决,快指针 fast 每次移动两个结点

断点法:遍历过的点都将next变为NULL,这样因为环结点会再次遍历到,那么他的next就是空。所以在遍历的时候谁的next是空谁就是环结点的开始。但是如果题目要去不能更改链表就gg了

ListNode* EntryNodeOfLoop(ListNode* pHead)//断点法    {
    if(pHead==NULL || pHead->next==NULL) return NULL;
    while(pHead->next){
        ListNode* next=pHead->next;
        pHead->next=NULL;
        pHead=next;  }
        return pHead;
}

快指针慢指针法:一个链表里面有环。假设环的长度为L,链表的表头到环的起始点为k,有两个指针slow和fast。slow一个时间走1,fast一个时间走2。当两者相遇的时候,fast比slow多走了L,所以t=L多以slow其实走了L。那么如果slow接着走下去,那么距离到达环的时间为t=L-(L-k)=k。所以在slow和fast相遇的时候。另一个slow1从head位置开始走,同时slow也走那么,当两者应该在k时刻相遇,并且相遇的位置就是环的开始位置。

ListNode* EntryNodeOfLoop(ListNode* pHead){
     if(pHead==NULL || pHead->next==NULL|| pHead->next->next==NULL)   return NULL;
     ListNode* slow=pHead->next;//这里不能是pHead,因为那样fast就等于slow了!!!
     ListNode* fast=pHead->next->next;
     while(fast!=slow){
         if(fast->next->next!=NULL && slow->next!=NULL){
         fast=fast->next->next;
         slow=slow->next;}
         else return NULL;//一定要注意!要判断有没有环!
        }
     ListNode* now=pHead;//注意这里是开始!位置!
     while(now!=slow){
         slow=slow->next;
          now=now->next;
        }
        return now;
}

慢指针走的路程为Sslow = x + m * c + a  快指针走的路程为Sfast = x + n * c + a
2 *Sslow = Sfast 及 2 * ( x + m*c + a ) = (x + n *c + a)
从而可以推导出: x = (n - 2 * m )*c - a= (n - 2 *m -1 )*c + c - a 即环前面的路程 = 数个环的长度(为可能为0) + c - a
什么是c - a?这是相遇点后,环后面部分的路程。所以,我们可以让一个指针从起点A开始走,让一个指针从相遇点B开始继续往后走,2个指针速度一样,那么,当从原点的指针走到环入口点的时候(此时刚好走了x)从相遇点开始走的那个指针也一定刚好到达环入口点。所以2者会相遇,且恰好相遇在环的入口点。

56、删除链表中重复的结点

题意:在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

方法:设置pre和now指针,因为相等的时候,整个这个数都要删掉,所以要保留上一个指针。如果now不是空,now->next也不是空的时候我们就进入循环判断两者是都相等。如果两个val值相等,那么我们while看这个值到哪里停下来,也就是while只要Now不是null,now->val不是Null,并且两者相等。那么now后移,while结束我们让pre->next=null/now->next。null是在Now是空的时候。  注意!存在112233这样的链表所以我们有一个trick,在链表的开头加入-1位。这样112233这样的就返回一个NULL。

ListNode* deleteDuplication(ListNode* pHead)//注意存在112233这样的情况
{
    if(pHead==NULL) return NULL;
    ListNode* b=new ListNode(-1);//因为存在112233的情况所以在最开头s加入了一个-1位!!
    b->next=pHead;
    ListNode* pre=b,now=pHead;
    while(now!=NULL && now->next!=NULL) //注意要判断now是不是空!
    {
        if(now->next->val == now->val)
            {
                while(now!=NULL && now->next!=NULL  && now->next->val == now->val)//一定要注意这里要判断now->next和now是不是空
                    now=now->next;//如果当前的now与下一个值相等,则now向后移动,一直移动到now与下一个不等了,这个时候pre-》next就指向now的下一个。now=now->next
                if(now!=NULL)
                {pre->next=now->next;
                now=now->next;}
                else//注意now很可能是null,这个时候就是最后一个数是重复的
                { pre->next=NULL;
                now=NULL;}
            }
            else{ pre=now;
                now=now->next;}
        }
        return b->next;//注意b 指代的是-1。
    }

57、二叉树的下一个结点

题目:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

方法:中序遍历(左根右)的下一个节点一共有3种可能性。

1)如果找到的点右子树不为NULL。那么下一个就是右子树的最左侧点,
如果右子树为NULL,那么分两种情况。2)她本身是父节点的左节点,那么下一个点就是父节点。
3)她本身是父节点的右节点,就比较复杂了。要看它的父节点是父节点的父节点的左节点还是右节点。

       6
    8
 2     3
4 -1 -1 5
比如对于5这个点,他的右节点为空,她又是父节点的右节点,那么她的下一个应该看3是父节点的什么节点,3是父节点8的右节点。所以还要去看8是父节点的什么节点.8是父节点6的左节点。所以5的中序列遍历下一个是6.所以对于某个节点。如果她是父节点的右节点,我们要一直让该节点更新为该节点的父节点,知道该点是父节点的左节点。那么此时的点的父节点就是所求。
 

TreeLinkNode* GetNext(TreeLinkNode* pNode){
    if(pNode==NULL) return NULL;
    if(pNode->right!=NULL){//如果当前点的右子树不为空,那么他的下一个节点是右子树的最左边的节点。
         TreeLinkNode* r=pNode->right;
         while(r->left)  r=r->left;
         return r; }
     while(pNode->next!=NULL){//next 指代的是父节点。如果当前点的右子树为空,我们就找到当前点的每一个层级的父节点一直到当前点是当前父节点的左子树里所在的点。或者为Null,Null代表当前点是中序遍历的最后一个点。
         if(pNode==pNode->next->left)   return pNode->next; 
         pNode=pNode->next; }
     return NULL;
    }

58、对称的二叉树

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
方法:我们首先看1的l 和 r 是否一致。 之后我们看的是 l->l (l2——》l)是否与r->r (r2——>r)一致 和  l->r (l2——》r)是否与r->l (r2——>l)一致。并且递归得到(l3,r3) 和 (l7 r7) 分别进入两个t函数,再次判断 l->l (l3——》l)是否与r->r (r3——>r)一致 和  l->r (l3——》r)是否与r->l (r3——>l)一致。一个二叉树对称,就是根节点的左子树的左子树与右子树的右子树相同 ; 左子树的右子树与右子树的左子树相同

bool isSym(TreeNode* l,TreeNode* r){
    if(l==NULL && r==NULL)  return true;
    if(l==NULL || r==NULL) return false;
    if(l->val!=r->val) return false;
    return isSym(l->left,r->right) && isSym(l->right,r->left);
}
 
bool isSymmetrical(TreeNode* pRoot)//一个二叉树对称,就是根节点的左子树的左子树与右子树的右子树相同 ; 左子树的右子树与右子树的左子树相同
{
    if(pRoot==NULL) return true;
    return isSym(pRoot->left,pRoot->right);
}

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

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

方法:奇数行从左至右偶数行从右向左,首先想到的是用两个队列模拟这个过程,并且用一个标志变量表示是奇数还是偶数

vector<vector<int> > Print(TreeNode* pRoot) {
    stack<TreeNode*> a,b;//a,b分别是奇数层和偶数层。
    vector<vector<int> > ans;
    if(pRoot==NULL) return ans;
    int p=1;//奇数层p=1,偶数层p=0,用p来判定是在a还是b里找。但是其实a/b同时只能有一个不为空,所以其实不需要这个。
    a.push(pRoot);
    while((p &&!a.empty())||(!p && !b.empty())){
        vector<int> now;
        if(p==1){
             int l=a.size();
             for(int i=0;i<l;i++){
                  now.push_back(a.top()->val);
                  if(a.top()->left!=NULL)   b.push(a.top()->left);
                  if(a.top()->right!=NULL)   b.push(a.top()->right);
                  a.pop(); }
         } else{
              int l=b.size();
              for(int i=0;i<l;i++)
               {
                  now.push_back(b.top()->val);
                  if(b.top()->right!=NULL)  a.push(b.top()->right);
                  if(b.top()->left!=NULL)   a.push(b.top()->left);
                  b.pop();  }
         }
         ans.push_back(now);
         p=!p;
        }
        return ans;
}

但是其实对于每一层来说,两个队列中只能有一个不为空,所以其实可以用一个队列模拟就可以了。

vector<vector<int> > Print(TreeNode* pRoot) {
    vector<vector<int> > res;
    if(pRoot == NULL)   return res;
    queue<TreeNode*> que; que.push(pRoot);
    bool even = false;
    while(!que.empty()){
         vector<int> vec;
         const int size = que.size();
         for(int i=0; i<size; ++i){
             TreeNode* tmp = que.front();
             que.pop();
             vec.push_back(tmp->val);
             if(tmp->left != NULL)   que.push(tmp->left);
             if(tmp->right != NULL) que.push(tmp->right);
            }
            if(even) reverse(vec.begin(), vec.end());//如果是偶数颠倒
            res.push_back(vec);
            even = !even;
        }
        return res;
}

60、把二叉树打印成多行

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

方法:正常肯定是想用队列。

vector<vector<int> > Print(TreeNode* pRoot) {
        queue<TreeNode*> a;
        vector<vector<int> > ans;
        if(pRoot!=NULL)
        a.push(pRoot);
        while(!a.empty()) 
        {
            vector<int> now;
            int l=a.size();
            for(int i=0;i<l;i++)  {
                now.push_back(a.front()->val);
                if(a.front()->left!=NULL)  a.push(a.front()->left);
                if(a.front()->right!=NULL)  a.push(a.front()->right);
                a.pop(); }
            ans.push_back(now);
        }
        return ans;
}

递归来进行层次遍历。传递变量ceng,来看当前是哪一层 

void bian(TreeNode* pRoot,int ceng,vector<vector<int> > &ans)
{
    if(pRoot==NULL)  return ;
    if(ceng>=ans.size())
    {
        vector<int> now;
        ans.push_back(now);
    }
    ans[ceng].push_back(pRoot->val);
    bian(pRoot->left,ceng+1,ans);
    bian(pRoot->right,ceng+1,ans);
 
}
 
 vector<vector<int> > Print(TreeNode* pRoot)//使用递归进行层次遍历。相当于左中右遍历,加一个标志位看是哪层。对于某一层肯定是按顺序遍历,所以vector内部是顺序的。
 //而遍历到的该节点对应的那层就是第几个vector
 {
vector<vector<int> > ans;
bian(pRoot,0,ans);
return ans;
 }

61、序列化二叉树

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

方法:使用#代表遇到了叶子节点,!来分割每一个数字

string itos(int y)
{
    stringstream ss;
    ss<<y;
    string j;
    ss>>j;
    return j;
}
 
TreeNode* dfs2(char **str){
   if((**str)=='#' || (**str)=='\0'){
        (*str)+=1;//地址走向下一个 #代表子树结束 正常不会遍历到\0
        return NULL; }
   int y=0,i=0;
   while((**str)!='\0' && (**str)!='!') {//!是把每一个数分开,因为进来的是节点的值,每个数值之间用!隔开
       y=y*10+(**str-'0');
       (*str)++;}
   TreeNode* r=new TreeNode(y);
   if((**str)=='\0') return r;//注意这个地方在数字结束后面可能是!也可能是\0!
   (*str)++;
   r->left=dfs2(str);
   r->right=dfs2(str);
   return r;
}

string dfs(TreeNode* root){
    if(root==NULL){
       string ans="#";
       return ans;}
    string ans;
    ans=itos(root->val);
    ans+="!";
    ans+=dfs(root->left);
    ans+=dfs(root->right);
    return ans;
}
 
 
TreeNode* Deserialize(char *str) {
    if(str==NULL || (*str)=='\0' ) return NULL;
    TreeNode* head;
    head=dfs2(&str);//传进来的是*str,所以要传这个字符串需要&str!,并且在函数里是**str
    return head;
}
 
 
char* Serialize(TreeNode *root) {
    char* j;
    if(root==NULL)  return NULL;//不能 return j,这个时候是一个空的地址,表明一个字符串是空的也要return NULL.
    string ans;
    ans=dfs(root);//返回的是string,要复制给char* 注意返回值,所以要new一个char[]
    j=new char[ans.size()+1];
    for(int i=0;i<ans.size();i++)  j[i]=ans[i];
    j[ans.size()]='\0';//一定注意最后要给上'/0'
    return j;
}
 

重要的知识点:对于char* !

1)不能 return j,这个时候是一个空的地址,表明一个字符串是空的也要return NULL.

2) ans=dfs(root);//返回的是string,要复制给char* 注意返回值,所以要new一个char[]
       j=new char[ans.size()+1];  for(int i=0;i<ans.size();i++)  j[i]=ans[i];
       j[ans.size()]='\0';//一定注意最后要给上'/0'

3)传进来的是*str,所以要传这个字符串需要&str!,并且在函数里是**str

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

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

方法:对于二叉搜索树,他的中序遍历就是从小到大的结果。给定一个全局变量ndex。从最左侧得到一个数字开始自增。自增到K return 当前节点,否则就是return NULL

 int  index=0;//注意!在要你写函数的题里也可以用当前类的全局变量!
 TreeNode* KthNode(TreeNode* pRoot, int k){
     if(pRoot==NULL)    return NULL;
     TreeNode* l=KthNode(pRoot->left,k);//左
     if(l!=NULL)  return l;//只有遍历到Index的位置才有返回值,所以当返回了最终结果之后就一直返回的最终结果
     index++;
     if(index==k)  return pRoot;
     TreeNode* r=KthNode(pRoot->right,k);
     if(r!=NULL) return r;
     return NULL;
}

63、数据流中的中位数

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

方法:使用堆,一个大堆一个小堆,大堆里所有的数比堆顶都小。小堆里所有的数都比堆顶都大。如果让两个堆的个数保持相同,不相等,只能大堆比小堆多1,那么结果可以确保中位数在大堆的堆顶或者大小堆顶的平均。(之前堆的博客里写过一次)

priority_queue<int, vector<int>, less<int> > p;
priority_queue<int, vector<int>, greater<int> > q;
void Insert(int num){
        if(p.size()!=0 && num<=p.top())  p.push(num); //如果num小于大堆的堆顶,那么放在大堆里。
         else if(q.size()!=0 && num>q.top()) q.push(num);//如果num大于小堆的堆顶,那么放小堆里。
        else{ //如果在中间,那么谁的size小放谁里面。
            if(p.size()<=q.size()) p.push(num); }
        while(p.size()>(q.size()+1))//放完如果两者size差大于1.那么要调整。
        {
            int y=p.top(); //把多的那个top放入另一个里面,一直到两者size差小于1.
            p.pop();
            q.push(y);
        }
        while(q.size()>(p.size()+1))
        {
            int y=q.top();
            q.pop();
            p.push(y);
        }
        return ;
    }
 
double GetMedian(){
        if(p.size()==q.size()) return ((p.top()+q.top())/2.0);
        else if(p.size()>q.size()) return p.top();
        else return q.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]}。

方法:使用队列,先进先出。这样当进来一个值的时候可以把所有队列里的比它小的都去掉,并且对于坐标不满足size的时候也去掉。这样队列的front就是最大的了

vector<int> maxInWindows(const vector<int>& num, unsigned int size1){
    vector<int> k;
    if(num.size()==0 || size1==0)   return k;
    if(size1==1)  return num;
    deque<int> s;//需要注意!因为我们同时要使用滑动窗口和求最大值,所以s里存位置比数值更好,适合滑动窗口
    for(int i=0;i<num.size();i++){
        while(!s.empty() && num[s.back()]<num[i])
           //我们把最大的值放在s的最前面,当前s是一个递减序列,因为我们希望front是当前队列里得最大值(坐标),那么对于每一个push()之前都会确保之前得所有比他小得数都被删掉了,那么其实当前s里就是一个递减序列了。所以你要删掉比当前小得就去掉back,直到back比当前大停下来,或者s是空了停下来
            s.pop_back();//从后往前遍历,比当前值小的都去掉,之后把当前值放在末尾
            while(!s.empty() && (i-s.front())>=size1)//先进先出,先进得肯定是离i远得,那么一直删除到距离i size1-1的距离的
            s.pop_front();//看当前最大值是否距离i小于sizel
           s.push_back(i);
           if(i>=size1-1)
            k.push_back(num[s.front()]);
       }
       return k;
    }

引申:deque双向开口可进可出的容器。我们知道连续内存的容器不能随意扩充,因为这样容易扩充别人那去,deque却可以,它创造了内存连续的假象.其实deque由一段一段构成 ,他是分段连续,而不是内存连续 当走向段的尾端时候自动跳到下一段 所以支持迭代器++ 操作,自动跳到下一段的方法由operator++实现。deque每次扩充 申请一个段
#include <deque>
deque<int> c1;
c1.push_back(6); //尾插入
c1.push_front(0);//头插入
c1.pop_back(); //弹尾元素
 c1.pop_front();//弹头元素
c1.insert(c1.begin()+3, 10);   //指定位置插入元素


65、矩阵中的路径

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

方法:回溯剪枝

注意:注意经过的格子不能再进入了!!!,需要注意的是matrix是字符串,不是矩阵所以如果当前列数是-1,那么少一行,如果当前列数是cols则到下一行。

bool judge(char* matrix, int rows, int cols, bool* ok,char* str ,int nowr,int nowc,int now){
	if(nowc==-1)//如果当前列数是-1,那么少一行, 
	{	nowr--;nowc=cols-1; } 
	 else if(nowc==cols)
	 {	nowr++;nowc=0; }
	if(*str=='\0') return true;
	else if(nowr<0||nowr>=rows) return false;
	else if(ok[cols*nowr+nowc]==true || *(str+now)!=*(matrix+cols*nowr+nowc)) return false;
	else
	{
		ok[cols*nowr+nowc]=true;
		if(*(str+now+1)=='\0') return true;
		bool sign=judge(matrix, rows, cols,ok, str,nowr+1,nowc,now+1)
	    ||judge(matrix, rows, cols,ok, str,nowr-1,nowc,now+1)
		||judge(matrix, rows, cols,ok, str,nowr,nowc+1,now+1)
		||judge(matrix, rows, cols,ok, str,nowr,nowc-1,now+1);   			 
        ok[cols*nowr+nowc]=false;
		return sign;
	}
}
      
     bool hasPath(char* matrix, int rows, int cols, char* str)//rows有几行/cols有几列 
    {
    	if(str==NULL||rows<=0||cols<=0) return false;
    	bool *ok=new bool[rows*cols]();
    	for(int i=0;i<rows;i++)
    	for(int j=0;j<cols;j++){
    		if(judge(matrix, rows, cols,ok, str,i,j,0)==true) return true;
		}
		return false;
    }

知识点:

char* string转换  const char *p = a.data();
const char *p = a.c_str();
char *p = const_cast<char*>(a.c_str());

char * strc = new char[strlen(str.c_str())+1];
strcpy(strc, str.c_str());

string s;
char *p = "hello";//直接赋值
s = p;
string到int的转换

string result = "10000";

int n = 0;

stream << result;

stream >> n;  //n等于10000

获取字符串长度 sizeof(k)/sizeof(k[0])

char* 的裁剪 substr(a,b)

vector<int> a(10,1) 初始化10个1
 

66、机器人的运动范围

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

int dx[4]={0,0,1,-1};//方向矩阵
int dy[4]={1,-1,0,0};
int num(int i)
{
    int n=0;
    while(i)
    {
        n+=i%10;
        i/=10;
    }
    return n;
}
int help(int thred,int rows,int cols,int* flag,int i,int j,int* k)
{
    if(i<0 || i>=rows || j<0 ||j>=cols || flag[i*cols+j]==1)   return 0;
    flag[i*cols+j]=1;
    int y=0;
    if((k[i]+k[j])<=thred)y=1;
    else  return 0;//一定注意!!如果不符合就遍历不到那么它的上下左右的也都不能遍历到
    for(int u=0;u<4;u++)  y+=help(thred,rows,cols,flag,i+dx[u],j+dy[u],k);
    return y;
}

int movingCount(int threshold, int rows, int cols){
        int flag[rows*cols];
        for(int i=0;i<rows*cols;i++) flag[i]=0;
        int j=rows;
        if(cols>rows)  j=cols;
        int k[j];
        for(int i=0;i<j;i++)  k[i]=num(i);
        return help(threshold,rows,cols,flag,0,0,k);
}

67、剪绳子

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

方法:看num%3得到几,如果整除,那么就直接得到这些3的乘积。如果余1,那么就将3+1分为2+2。如果余2,就3乘之后乘2 

int cutRope(int num) {
        if(num==2) return 1;
        if(num==3) return 2;
        if(num==4) return 4;
        if(num%3==0)  return pow(3,num/3);
        if(num%3==1) return pow(3,num/3-1)*4;
		if(num%3==2) return pow(3,num/3)*2;	
    }

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值