《剑指Offer》做题总结(一)

来源:互联网 发布:淘宝卖家淘宝客推广 编辑:程序博客网 时间:2024/06/06 02:56

虽然现在离找工作还有一年的时间,但是为了提升自身编程水平,另一方面为了将来工作做准备。

黑色加粗部分基本就是题目给出的部分,红色部分是自己需要注意的知识点。

1.二维数组中的查找

题目描述:

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

思路:

基本思想就是遍历,将最终的数找到,有意思的是vector.at(0)的应用

代码:

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {

int row=array.size();

int col=array.at(0).size();
  for(int i=0;i<row;i++)

{

for(int j=0;j<col;j++)

{

if(array[i][j]==target)

return true;

else 

continue;

}

}

return false;    
    }
};

2.替换空格

题目描述:

请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

思路:

基本思想就是字符串的替换,但是这个里面的坑挺多的,一个就是各种读取情况的判断,还有一个就是字符串末尾的结束符的注意,还有一个就是看题目的时候没看懂的就是length代表不能超过的长度。

代码:

class Solution {
public:
void replaceSpace(char *str,int length) {

int blanks=0;

int oldlength=0;

for(int i=0;str[i]!='\0';i++)

{

if(str[i]==' ')

blanks++;

oldlength++;

}

//判断新生成的字符串是否超过规定的要求

int newlength=oldlength+2*blanks;

if(newlength>length)

return;

char *str1=str+oldlength;

char *str2=str+newlength;

while(str1<str2)

{

if(*str1==' ')

{

*str2--='0';

*str2--='2';

*str2--='%';


} else{

*str2--=*str1;

}

--str1;

}

}
};

3.从头到尾打印链表

题目描述:

输入一个链表,从尾到头打印链表每个节点的值。

思路:

这个问题开始我想简单了,想着直接就是一个输入然后遍历链表,就可以输出了,结果不是这样。这里面有一个要注意的就是开头的vector<int>,这个就表示  返回值是这样的,很多时候我都忽略了返回值的问题。

代码:

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/

class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {

vector<int>result;

std::stack<ListNode*>nodes;

ListNode* pNode=head;

while(pNode)

{

nodes.push(pNode);

pNode=pNode->next;

}

while(!nodes.empty())

{

pNode=nodes.top();

result.push_back(pNode->val);

nodes.pop();

}

return result;


}

};

4.重建二叉树

题目描述:

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

思路:

这个问题开始没有思路的,由于对树的概念掌握也不是很好。首先明确一个概念就是三种遍历,前序根遍历(根左右)、中序根遍历(左根右)、后序根遍历(左右根)。其实这道题也是根据这个来的。题目中提供的是前序和中序。所以可以根据前序把根节点找到,然后根据中序把把左右节点给划分开来。

代码:

class solution
{
    public:
    treeNode* reconstructBinaryTree(vector<int> pre,vector<int> vin){

    int inlen=vin.size();
    if(inlen==0)
        return NULL;
    vector<int>left_pre,right_pre,left_in,right_in;
    //创建根节点,根节点肯定是前序遍历的第一个数
    int gen=0;
    for(int i=0;i<inlen;i++)
    {
        if(vin[i]==pre[0])
        {
            gen=i;
            break;
        }
    }

    //对中序遍历,根节点左边的节点位于二叉树的左边,根节点右边的节点位于二叉树的右边
    //利用上述这点,对二叉树节点进行归并
    for(int i=0;i<gen;i++)
    {
        left_in.push_back(vin[i]);
        left_pre.push_back(pre[i+1]);
        //前序遍历第一个为根节点
    }
    for(int i=gen+1;i<inlen;i++)
    {
        right_in.push_back(vin[i]);
        right_pre.push_back(pre[i]);
    }
    //和shell排序的思想类似,取出前序和中序遍历根节点左边和右边的子树
    //递归,再对其进行上述的步骤 ,即再区分子树的左右子树 直到叶子节点
    head->left=reconstructBinaryTree(left_pre,left_in);
    head->right=reconstructBinaryTree(right_pre,right_in);
    return head;

    
    }
}


5.用两个栈实现队列

题目描述:

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

思路:

对于这道题,我第一反应是基本概念,对就是将栈和队列的概念理清。队列是先进先出,栈是先进后出。发一张基本原理的示意图。大家就一目了然了。


本质上就是入队压入stack1,出队的时候将stack1的元素push进stack2中,然后将stack2中的顶端元素pop出来。当然 这里面需要涉及判断的过程。就是要全面考虑具体是什么时候弹出(在stack2的时候弹出)什么时候压栈(在stack1的压栈)

代码:

class Solution
{
public:
    void push(int node) {

        if(!stack1.empty())
        {
             stack1.push(node);
            
        }else
            {
            int a;
            while(!stack2.empty())
                {
                a=stack2.top();
                stack1.push(a);
                stack2.pop();
                
            }
            stack1.push(node);
        }
        
       
        
       
    }


    int pop() {
  int a;
        if(stack1.empty())
            {
         
            a=stack2.top();
            
            stack2.pop();
            return a;
            
            
        }else{
            int a;
            while(!stack1.empty())
                {
                a=stack1.top();
                
                stack2.push(a);
                stack1.pop();
                
            }
            a=stack2.top();
            stack2.pop();
            return a;
        }   
       
    }



private:
    stack<int> stack1;
    stack<int> stack2;
};

还需要说的是两个队列实现栈。入栈:将元素进入list1,出栈:判断list1中的元素是否为1个,如果为一个则出栈,如果不是就将其导出在list2中,留下一个用于出栈。

6.旋转数组的最小数字

题目描述:

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

思路:

这一题其实没啥思路可以说的,但是这一题是可以炫技的,我用的还是比较笨的方法,直接比较过去的。可以直接用比较聪明的方法。比如下面这个。另外还有一个就是旋转数组有特殊性,就是最小的数在分界线。

代码:

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {

    sort(rotateArray.begin(),rotateArray.end());
        return rotateArray[0];
    }
};

7.斐波那契数列

题目描述:

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。

n<=39

思路:

这一题开始的时候我也陷入了常规的错误。错误是什么呢。我打算采用递归的方法求解这道题目。但是递归是非常消耗计算资源的,一般递归那么几次就很厉害了。所以最后还是采用的数组的动态分配。但是有一个地方有个坑(但是也是我自己学习不扎实,int* m=new int[n])大体呢就是这么些。

代码:

class Solution {
public:
    int Fibonacci(int n) {

        if(n<=1)
            return n;
        else{
        int* m=new int[n+1];
        m[0]=0;
        m[1]=1;

        for(int i=2;i<=n;i++)
            m[i]=m[i-1]+m[i-2];
        
        return m[n];
        }
            

    }
};


8.跳台阶

题目描述:

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

思路:

这一题开始的时候,我想的简单了。我以为有多少种1,2的组合,没想到还必须有1,2的排序。后来经过一番分析:如果第一次跳知识1阶那么剩下的就是n-1阶,如果第一次跳的2阶,那么剩下的就是n-2阶。由之前的假设可以得出f(n)=f(n-1)+f(n-2);因此最终得出的就是斐波那契数列

代码:


class Solution {
public:
    int jumpFloor(int number) {

        int result[2]={0,1};
        if(number<2)
            return result[number];

        long long fib_one=1;
        long long fib_two=0;
        long long fibN;
        for(unsigned int i=1;i<=number;++i)
            {
            fibN=fib_one+fib_two;
            fib_two=fib_one;
            fib_one=fibN;
            

        }
        return fibN;
    }
};


9.变态跳台阶

题目描述:

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

思路:

这一题采用数学归纳法得出,比较简单

代码:

class Solution {
public:
    int jumpFloorII(int number) {

return pow(2,number-1);
    }
};

10.矩形覆盖

题目描述:

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

思路:

这一题开始,我假设最多可能的情况,发现本质上这道题也就是青蛙跳阶梯的题目。为什么这么说呢?首先摆矩形最基本的形状是全是竖着和横着的,再其次就是横着和竖着的交替摆放的。其实就相当于青蛙跳一阶和跳两阶的区别。而本质上青蛙跳台阶就是斐波那契数列,在复习一遍斐波那契数列。

代码:

class Solution {
public:
    int rectCover(int number) {

//变相的青蛙跳台阶,也就是实现斐波那契数列
        if(number==0)
            return 0;
        if(number==1)
            return 1;
        if(number==2)
            return 2;
        int f1=1;
        int f2=2;
        int fn;
        for(int i=3;i<=number;i++)
            {
            fn=f1+f2;
            f1=f2;
            f2=fn;
            

        }
        return fn;
    }
};


11.二进制中的1的个数

题目描述:

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

思路:

这一题开始我只考虑了正数的情况,对于负数的情况没有考虑。导致出现了错误。最终的解题也是通过一种很巧妙的方法。一个数的二进制存在多少个1。如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。

举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

代码:

class Solution {
public:
     int  NumberOf1(int n) {

        //本质上是整除和取余的应用
        int count=0;
         while(n!=0)
             {
             count++;
             n=n&(n-1);

             
         }
         return count;
     }
};

12.数值的整数次方

题目描述:

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

思路:

思路很简单,但是容易思考不全。主要的三个情况,=0、>0、<0

代码:

class Solution {
public:
    double Power(double base, int exponent) {
     double final_number=1;
        if(exponent==0)
        {
            return 1.0;
        }
        else if(exponent>0)
            {
            while(exponent)
                {
                final_number=final_number*base;
                exponent--; 
            }
        }
        else if(exponent<0)

            {
            while(exponent)
                {
                final_number=final_number/base;
                exponent++;
                }
        }
       return final_number;
    }
};

13.调整数组顺序使奇数在偶数前面

题目描述:

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

思路:

思路比较简单就是遍历,然后将奇数先push_back,然后遇到偶数再push_back

代码:

class Solution {
public:
    void reOrderArray(vector<int> &array) {

        vector<int>result;
        for(int i=0;i<array.size();i++)
            {
            if(array[i]%2==1)
                result.push_back(array[i]); 
        }
        for(int i=0;i<array.size();i++)
            {
            if(array[i]%2==0)
                result.push_back(array[i]); 

        }
        array=result;
    }

};

14.链表中倒数第k个节点

题目描述:

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

思路:

这一题思路比较重要,首先是设置两个指针分别指向链表的头,然后第一个指针向后走k-1个位置,然后第二个节点开始出发,当第一个节点到达末尾的时候,第二个节点就到达了k位置。需要注意空非空以及第k个位置的判断。

代码:

/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {

    if(!pListHead||k==0)
            return NULL;
        ListNode* front=pListHead;
        ListNode* back=pListHead;
        while(front->next && k>1)
            {
            front=front->next;
            --k;
            
        }
        if(front==NULL || k>1)
            return NULL;
        
        while(front->next)
            {
            front=front->next;
            back=back->next;
            
        }
        return back;
            
            
    }
};


15.反转链表

题目描述:

输入一个链表,反转链表后,输出链表的所有元素。

思路:

这一题呢,就是前面的给最后面的,当前的给前面的,最后面给当前的,如果遇到最后的为NULL,就返回当前的值。

代码:

/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead)
{
if(pHead==NULL)
            return pHead;
        ListNode* pre=NULL;
        ListNode* cur=pHead;
        ListNode* nxt=NULL;
        ListNode* res=NULL;
        
        while(cur!=NULL)
            {
            nxt=cur->next;
            cur->next=pre;
            if(nxt==NULL)
                {
                break;
            }
            pre=cur;
            cur=nxt;

            
        }
        return cur;
    }
};
原创粉丝点击