剑指offer-c++_1-20

目录

1、二维数组中的查找

2、替换空格

3、从尾到头打印链表

4、重建二叉树

5、用两个栈实现队列

1-5知识点总结:

6、旋转数组的最小数字

7、斐波那契数列

8、跳台阶

9、变态跳台阶

10、矩形覆盖

6-10知识点总结:

11、二进制中1的个数

11引申:判断一个数是不是2的次方。并求是几次方

12、数值的整数次方

13、调整数组顺序使奇数位于偶数前面

14、链表中倒数第k个结点

15、反转链表

11-15知识点总结:

16、合并两个排序的链表

17、树的子结构

 18、二叉树的镜像

19、顺时针打印矩阵

20、包含min函数的栈

16-20知识点总结:


 

 


 



1、二维数组中的查找

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

解题思路:从左下角或者右上角开始遍历。注意:遇到边界 return false

bool Find(int target, vector<vector<int> > array) {
        int x=0;
        int col=array.size()-1;//多少列
        int row=array[0].size()-1;//注意不是矩阵! 横竖数量不同
        int y=col;
        while(x!=row+1 && y!=-1)
        {
            if(array[x][y]==target) return true;
            else if(array[x][y]>target) y--;
            else x++;
        }
        return false;
    }

2、替换空格

请实现一个函数,将一个字符串中的每个空格替换成“%20”。

例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

注意:C++的副本机制,在函数中使用的是形参的副本空间,你在里面修改指针的位置返回时并不会改变实际的值,
即使是数组传进来依然会为首地址分配一个新的地址,但是返回时候直接把新地址处的值赋给形参,而你在函数中分配新的地址,再将指针指向该地址,函数返回时候仍然是从旧地址读取值赋给形参,所以并不会改变形参的值

要注意的是一个空格占一个位置,要把他变成3个位置的“%20”。需要分配空间。并且需要从后往前替换。

void replaceSpace(char *str,int length) {//length是题里给的str的长度
    int nowlength=0;
    int knum=0;
    while(str[nowlength]!='\0')
        if(str[nowlength++]==' ') knum++;
    if(knum*2+nowlength>length) return;
    for(int i=nowlength;i!=-1;i--)
    {
        if(str[i]!=' ') str[i+knum*2]=str[i];
        else{
            str[i+knum*2]='0';
            str[i+knum*2-1]='2';
            str[i+knum*2-2]='%';
            knum--;
        }
    }
}

3、从尾到头打印链表

输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

解题思路:建立一个vector,存的就是反向之后的链表顺序。每一个值进来都放在begin()的位置上。

vector<int> printListFromTailToHead(ListNode* head) {
    vector<int> j;
    while(head)
    { j.insert(j.begin(),head->val);
        head=head->next;}
    return j;
    }

4、重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

解题思路:前序序列的第一个点就是根节点,我们根据前序遍历的第一个点的值在中序遍历里找相同的点的位置
中序遍历从0开始到那个点就是根节点的左子树,右边就是右子树。两个序列分别可以得到左子树的前序遍历序列和左子树的中序遍历序列。和 右子树的前序遍历序列和中序遍历序列。递归处理左右子树

TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
    if(pre.size()==0) return NULL;
    TreeNode* head=new TreeNode(pre.at(0));//前序遍历第一个点一定是根节点。
    int p = distance(vin.begin(), find(vin.begin(),vin.end(),pre.at(0)));
    if(p!=0){//注意一定要判断是不是0/size()-1。如果是0,表示没有左节点。如果是size()-1没有右节点
    vector<int> pre1(pre.begin()+1,pre.begin()+p+1);
    vector<int> vin1(vin.begin(),vin.begin()+p);
    head->left=reConstructBinaryTree(pre1,vin1);//递归根据中序、前序遍历重新构建左子树
    }
    if(p!=pre.size()-1)
    {
    vector<int> pre2(pre.begin()+p+1,pre.end());
    vector<int> vin2(vin.begin()+p+1,vin.end());
    head->right=reConstructBinaryTree(pre2,vin2);
    }
    return head;
}

5、用两个栈实现队列

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

pop的时候,把stack1 pop出来并且放在stack2里。之后把stack2里的第一个pop出来就是队列的pop结果。

注意1:stack2要清空。    注意2:要注意stack有空的,不能pop了。


public:
void push(int node) {stack1.push(node);}//以stack1为主队列
int pop() {//pop的时候,
    if(stack1.empty())
    {cout<<"Queue is empty"<<endl;
    return NULL;}
    while(!stack1.empty())//先把所有的数给stack2,这个时候stack2的top就是stack1的最后一个了
    {
        stack2.push(stack1.top());
        stack1.pop();
    }
    int ans=stack2.top();//得到stack2的第一个
    stack2.pop();//把第一个去掉
    while(!stack2.empty())//再把pop之后的stack重新退回给stack1
    {
        stack1.push(stack2.top());
        stack2.pop();
    }
    return ans;
    }
private:
    stack<int> stack1;
    stack<int> stack2;

1-5知识点总结:

1)vector使用:
h.push_back(now);  /   arrayy.size()  /  arrayy[x][y] / j.insert(j.begin(),head->val);

裁剪 vector  :  

vector<int> pre2(pre.begin()+p+1,pre.end());  有第p+1个点。begin()是第一个点。begin()+1是第二个点。 [a,b)
找一个点在该vector中的位置  :#include <algorithm>   int p = distance(vin.begin(), find(vin.begin(),vin.end(),pre.at(0)));

2) 输入带空格的字符串  

char k[N]; gets(k);  // scanf("%[^\n]", msg); \n作为字符串输入的结束符  //string s; getline(cin,s);

3)对于一个结构体,我们可以在内部写好他的创建方式,这样就不需要每一个指针建立都要赋值。
struct ListNode {
        int val;
        struct ListNode *next;
        ListNode(int x) {
            val=x;
            next=NULL; }
 };//注意这里有分号
 使用的时候直接  next=new ListNode(y);

4)#include <stdio.h> freopen("a.txt","r",stdin);、

5)#define N 8 //后面没有分号

6)stack用法:push() empty()=1是空 pop() clear() top()  正常的stack的pop是不返回int的

      queue用法 : 没有top() 是front()

7)树前序序列第一个点是根节点,我们根据前序遍历的第一个点的值在中序遍历里找相同的点的位置a,那么在前序遍历中0-a就是左子树的前序遍历,a-end就是右子树的前序遍历。在中序遍历中0-a就是左子树的中序遍历,a-end就是右子树的中序遍历。

 

6、旋转数组的最小数字

输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

解题思路:从左向右遍历,找到比第一个数字小的就是最小值。
注意:如果到最后还没有找到比第一个数的小,第一个数就是最小值。不要忘了

int minNumberInRotateArray(vector<int> rotateArray) {
       if(rotateArray.size()==0) return 0;
    int now=rotateArray.at(0);//这样就没找到比now小的那么就输出第一个数
 for(int i=1;i<rotateArray.size();i++)
     if(rotateArray.at(i)<now) return rotateArray.at(i);
     return now;  
}

7、斐波那契数列

int Fibonacci(int n) {
     if(n==0) return 0;
if(n==1 || n==2) return 1;
return Fibonacci(n-1)+Fibonacci(n-2);}

8、跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。该青蛙跳上n级台阶总共有多少种跳法(先后次序不同算不同的结果)。

解题思路:如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2)。由这两种跳法可以得出总跳法为: f(n) = f(n-1) + f(n-2)

之后考虑临界条件:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2,可以发现最终得出的是一个斐波那契数列:

                   | 1, (n=1)

     f(n) =     | 2, (n=2)

                  | f(n-1)+f(n-2) ,(n>2,n为整数)

 

int jumpFloor(int number) {
    if(number==1 || number ==2)  return number;
    return jumpFloor(number-1)+jumpFloor(number-2); }

9、变态跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

解题思路:我们之前用f(n-1)表示直接跳1层之后可以有多少中跳法。用f(n-2)表示直接跳2层有多少种跳法。所以我们用f(n-i)表示直接跳i层之后的可能。我们知道其中f(n-n)=1。

n = 1时,只有1种跳法,f(1) = 1

n = 2时,会有两个跳得方式,一次1阶或者2阶,这回归到了问题(1) ,f(2) = f(2-1) + f(2-2)=f(0)+f(1)

4) n = 3时,会有三种跳得方式,1阶、2阶、3阶,f(3) = f(3-1)+f(3-2)+f(3-3)=f(0)+f(1)+f(2)

5) n = n时,会有n中跳的方式,1阶、2阶…n阶,f(n) = f(n-1)+f(n-2)+…+f(n-(n-1)) + f(n-n) => f(0) + f(1) + f(2) + f(3) + … + f(n-1)

简化:  f(n-1) = f(0) + f(1)+f(2)+f(3) + … + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + … + f(n-2)

          f(n) = f(0) + f(1) + f(2) + f(3) + … + f(n-2) + f(n-1) = f(n-1) + f(n-1)

          可以得出:f(n) = 2*f(n-1)。因此:

              | 1       ,(n=0 )

f(n) =     | 1       ,(n=1 )

              | 2*f(n-1),(n>=2)

int jumpFloorII(int number) {
 if(number==1 || number==0) return number;
 return jumpFloorII(number-1)*2;}

10、矩形覆盖

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。用n个2*1的小矩形无重叠地覆盖2*n的大矩形,总共有多少种方法?

解题思路: 如果第一步选择竖方向填充,则剩下的填充规模缩小为n-1;如果第一步选择横方向填充,则剩下的填充规模缩小为n-2,因为第一排确定后,第二排也就确定了,则剩下的填充规模缩小为n-2。因此tectCover(n)= tectCover(n-1)+ tectCover(n-2);
注意:!!一定不要忘记输入为 0  的情况

int rectCover(int number) {
 if(number==1 || number==2|| number==0)  return number;
    return rectCover(number-1)+rectCover(number-2); }

6-10知识点总结:

7-10是4个动态规划题。

11、二进制中1的个数

方法1:看一个数里有几个1,其实就是和n个1分别与,如果结果与得到的结果不是0,那么就意味着那一位就是1.sum++

方法2:把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。理解:减1的结果是把从右边开始的第一个1变为0.之后与原数与得到的结果是那个1前面的内容自己与自己不变。那个1后面的内容因为-1都被取反,取反的数与原数与得到的是0.当与的结果是0意味着当前数已经没有1了。与了多少次原数就有多少个1。例如:10010100 -1 为 10010011 & 1001100->10010000 之后-1 得到 10001111 & 10010000-> 10000000在-1得到01111111 & 10000000 ->0。停止。与了3次就说明有3个1.

int  NumberOf1(int n) {//方法1
    int coun=0;
    int a=1;
    while(a!=0){左移,高位溢出后,为0
         if((n&a)!=0) coun++;
        a=a<<1;}
    return coun;  }
int NumberOf1_(int n) {//方法2
        int count=0;
        while(n!=0){
            n=n&(n-1);
            count++;}
        return count; }

11引申:判断一个数是不是2的次方。并求是几次方

解题思路:判断一个数是不是2的次方:num&(num-1)

理解:2的次方一定10000...所以如果是2的次方,那么这个数-1并且与原数与。结果一定是0。不是0那么就不是2的次方。

几次方:递归判断,将这个数不断左移,一直到为1;左移多少次就是几次(递归可以用while(n!=1) 实现)

int log2(int value)   //递归判断一个数是2的多少次方
{ if (value == 1)    return 0;
    else  return 1+log2(value>>1);}

int main(void)
{  int num;
    scanf("%d",&num);
    if(num&(num-1))  //使用与运算判断一个数是否是2的幂次方
        printf("%d不是2的幂次方!\n",num);
    else  printf("%d是2的%d次方!\n",num,log2(num));
    return 0;}

12、数值的整数次方

给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

解题思路:1、临界条件注意!: 整数幂是0 0^0没有意义,可考虑输出1或0; /
2、负数的情况!!1(-1也要像1那样单独考虑),小于0时需要先求|e|,再求倒数,此时要求base!=0;
3、求幂要递归着求,不要一个一个求,那样太慢了!!!

double Power(double base, int exponent) {
 if(exponent==0)   return 1.0;
 if(exponent==1)   return base;
 if(exponent==-1)  return 1/base;
 int n=abs(exponent);//使用整数来求指数
 double ans=Power(base,(n>>1));//递归求
 double result=ans*ans;
 if((n&1)==1)  result*=base;
 if(exponent<0) result=1/result;//如果指数是负数,结果取倒数
 return result; }

13、调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

冒泡排序方法解题。如果出现前面的数是偶数,后面的数是奇数。就颠倒他们。这样最后面的一个肯定是最后一个偶数。
 

void reOrderArray(vector<int> &array1) {
      for(int i=0;i<array1.size();i++)
            for(int j=0;j<array1.size()-i-1;j++)
      { if(array1[j]%2==0 && array1[j+1]%2==1)
          {  int y=array1[j];
              array1[j]=array1[j+1];
              array1[j+1]=y;
           }
       }  }

14、链表中倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。

解题思路:两个指针,先让第一个指针和第二个指针都指向头结点,然后再让第一个指正走(k-1)步,
到达第k个节点。然后两个指针同时往后移动,当第一个结点到达末尾的时候,第二个结点所在位置就是倒数第k个节点了。

ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
    ListNode* pre=pListHead;
    ListNode* endd=pListHead;
    for(int i=0;i<k;i++)
    { if(endd==NULL)  return NULL;
        endd=endd->next;
    }//当前endd指向从前往后数第K个,当前pre和endd一共差K个,
    while(endd)//接着endd和pre一起往后移动一直到endd到NULL。
    { endd=endd->next;
        pre=pre->next;}
    return pre;
    }

15、反转链表

输入一个链表,反转链表后,输出新链表的表头。

解题思路:使用三个指针pPrev,pNode,pNext完成反转解题思路

ListNode* ReverseList(ListNode* pHead) {
     ListNode* a1=NULL;  //重点!NULL开始!原第一个的next是空,相当于原第0个是空。
     ListNode* a3=pHead;
    while(a3)
    { //每次 第一个 第二个 第三个。每次保留第三个指针临时变量
      //之后让第二个的next指向第一个,之后让第三个等于第二个,第二个等于第一个。递归。
        ListNode* a=a3->next;
        a3->next=a1;
        a1=a3;
        a3=a; }
    return a1;
    }

11-15知识点总结:

1)移位、与等操作:

一个数一直 左移a=a<<1;,相当于除2,高位溢出后,为0,可以作为有没有溢出的标志

num&(num-1)  判断一个数是否是2的幂次方 ,是2的次方 为0 ,不是为1

n&1 判断一个数是否是偶数 ,是偶数为0,不是偶数为1

2)gg(vector<int> &array1)  函数里要更改 vector并且传回去需要加&

 

16、合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。已知两个链表本身满足单调不减。

对两个链表进行依次比对,一直到有一个为空,另一个就直接剩下的接过去就行。注意:头结点一定要先new,再next

  ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1==NULL) return pHead2;
        if(pHead2==NULL) return pHead1;//如果有一个是空,直接返回另一个
        ListNode* head ;//建立头节点。注意一定要确保头结点指针不是空结点
        if(pHead2->val<pHead1->val)
            {head= pHead2;
            pHead2=pHead2->next;}
            else{
                head=pHead1;
                pHead1=pHead1->next;}
        ListNode* now=head;
        while(pHead1)
        {
            if(pHead2==NULL)
            {
                if(pHead1)
                    now->next=pHead1;
                break;
            }
            if(pHead1->val<pHead2->val)
               {now->next=pHead1;
                   pHead1=pHead1->next;}
               else
               { now->next=pHead2;
                   pHead2=pHead2->next;}
               now=now->next;}
        if(pHead2)
              now->next=pHead2;
        return head;
    }

17、树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

明显使用DFS双层递归。先在外层 找值相同的点。没找到,就遍历左子树和右子树找。
找到了再递归 看左子树都相等不,右子树都相等不。
注意!!!!  pRoot2(B)结束了,但是pRoot1(A)没结束是ok的!!!!

bool equal_tree(TreeNode* pRoot1, TreeNode* pRoot2)
{
if(pRoot2==NULL)//可以子树是空,父树不是空。如果遍历到空了,说明他的父节点都是对的,那么返回ture
    return true;
if(pRoot1==NULL)
    return false;
//如果两个树当前节点相同,且左右子树也都相同
return (pRoot1->val==pRoot2->val) && equal_tree(pRoot1->left,pRoot2->left) && equal_tree(pRoot1->right,pRoot2->right);
}

 bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
    if(pRoot2==NULL || pRoot1==NULL)
        return false;
    bool j=false;
    if(pRoot1->val==pRoot2->val)
        j=equal_tree(pRoot1,pRoot2);
    //如果当前出现了子树,或者左子树里出现了子树或者右子树里出现了子树就返回true
    return j||HasSubtree(pRoot1->left,pRoot2)||HasSubtree(pRoot1->right,pRoot2);
    }

 18、二叉树的镜像

操作给定的二叉树,将其变换为源二叉树的镜像。

每一个点都求他的镜像,子节点镜像好了之后将左右结点对调。如果是叶子结点和空结点直接返回。
注意! 函数传的是指针变量的时候,改指针,不需要返回值,本身就改变了。在c++里函数参数有指针、string、vector都是在改变本身。递归过程中除非是要max这样的值,不然不要改函数参数的值,会出错。而本题要得到的就是改完之后的,所以不需要再赋值

void Mirror(TreeNode *pRoot) {
    if(pRoot==NULL)   return ;
    if(!pRoot->left && !pRoot->right) return ;
    TreeNode* r,l;
    r=pRoot->left;
    l=pRoot->right;
    Mirror(r);
    Mirror(l);
    pRoot->left=l;
    pRoot->right=r;
    }

19、顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

其实就是找临界条件的题。cout<<""一定是双引号不能是单引号

vector<int> printMatrix(vector<vector<int> > matrix) {
    vector<int> ans;
    int w=matrix.size();
    if(w==0)  return ans;
    int h=matrix[0].size();// w:有多少行 h :多少列
    if(h==0) return ans;
    int a1=0;//左临界,上临界
    int a3=w-1;//右临界
    int a2=h-1;//下临界
    while(a1<=a2 && a1<=a3)//临界条件,有=号
    {
        for(int i=a1;i<=a2;i++)//从左边第一个走到右边最后一个
        ans.push_back(matrix[a1][i]);
        for(int i=a1+1;i<=a3;i++)//从右边最后一排的上面第二个到下边最后一个
        ans.push_back(matrix[i][a2]);
        for(int i=a2-1;i>=a1;i--)//从最后一排的右边数第二个到左边第一个
        ans.push_back(matrix[a3][i]);
        for(int i=a3-1;i>a1;i--)//从最左一排下面数第儿个到上面数第二个
        ans.push_back(matrix[i][a1]);
        a1++;
        a2--;
        a3--;
    }
    return ans;
    }

20、包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

不能直接只保存一个Min全局Int变量。因为会有pop操作。所以用另一个栈保存当前栈这些数中最小的数,之后这个栈也在push和pop过程中push和pop。

stack<int> mystack,minstack;//mystack是目标栈、minstack保存最小值得栈
    void push(int value) {
        mystack.push(value);
        if(minstack.size()==0||minstack.top()>=value)
            minstack.push(value);
        else
            minstack.push(minstack.top());}
    void pop() {
        mystack.pop();
        minstack.pop();}
    int top() {return mystack.top();}
    int min() { return minstack.top();}

16-20知识点总结:

1)queue<TreeNode*> tree; front()

2) 树简单操作:

定义:

struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) {
	    val=x;
            left=NULL;
            right=NULL;}
};

创建:

TreeNode* create()
{
    TreeNode* head;
    int u; cin>>u;
    head=new TreeNode(u);
    TreeNode* now=head;
    queue<TreeNode*> tree;
    tree.push(now);
    while(tree.empty()==0)
    {
        cin>>u;
        TreeNode* r=tree.front();
        tree.pop();
        if(u==-1) r->left=NULL;
        else
        {
            TreeNode* l=new TreeNode(u);
            r->left=l;
            tree.push(l);
        }
        cin>>u;
        if(u==-1)  r->right=NULL;
        else
        {
            TreeNode* rr=new TreeNode(u);
            r->right=rr;
            tree.push(rr);
        }
    }
    return head;
}
    

显示:

void show(TreeNode* head)
{
    if(head==NULL)  return ;
    show(head->left);
    cout<<head->val<<" ";
    show(head->right);
}

3)c ++中 cout<<""一定是双引号不能是单引号

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值