剑指offer——C++ 题目略解(一)

本博客主要是用于在实习以及秋招期间进行刷题,主要是帮助自己理解《剑指offer》,希望对自己的思维逻辑以及题目理解有更深的理解:

二维数组的查找

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

题解:
1.暴力搜索法:
当时看到这个题目,最先想到的是利用暴力循环进行查找,对二维数组中的每个数据进行对比,若是匹配成功,则判定为true;若是查询不到,则返回为false。

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        for(int i=0;i<array.size();i++){
            for(int j=0;j<array[0].size();j++){
                if(array[i][j]==target){
                    return true;
                }
            }
        }
        return false;
   }
};
  1. 规律查找法:
    根据数组的规律进行判定,由规律可得:从右上角上的数据进行判定,如果一行右侧的数(简称右数)大于目标,由于矩阵特点,查找数只可能位于右数的左侧;如果右数小于目标,查找数则只可能存在于右数的下侧,不断进行判定。
    代码如下:
class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        int m=array.size();
        int n=array[0].size();
        //for(int i=0;i<m;i++){
        //    for(int j=n-1;j>=0;j--){
        int i=0;
        int j=n-1;
        while(i<m&&j>=0){
                if(array[i][j]==target){
                    return true;
                }
                else{
                    if(array[i][j]>target){
                        --j;
                    }
                    
                    if(array[i][j]<target){
                        ++i;
                    }
                }
        }
        return false;
    }
};
  1. 二分法:
    从把每一行看成有序递增的数组,利用二分查找,通过遍历每一行得到答案。

重建二叉树

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

题解:
递归法:
1)根据前序遍历和中序遍历的特点可知:前序遍历的第一个元素,为树的根节点,中序遍历中根节点的左侧数据为该根节点的左侧数据,右侧数据为该根节点的右侧数据。
2)前序遍历中,属于根节点的左侧数据为左子树的前序遍历,属于根节点的右侧数据为右子树的前序遍历。
3)中序遍历中,根节点左侧数据为左子树的中序遍历,右侧数据为右子树的中序遍历。
4)将左右子树当做单独的树结构,实现1)2)3)进行递归,直到数据遍历完毕。

代码如下:

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        if (pre.size() == 0)return NULL;
        TreeNode* tree = (TreeNode*)malloc(sizeof(TreeNode));
        TreeNode *p;
        tree->val = pre[0];
        tree->left = NULL;
        tree->right = NULL;
        vector<int> pre1;
        vector<int> vin1;
        int i;
        for (i = 0; i<pre.size(); i++) {
            if (vin[i] == tree->val) break;
            pre1.push_back(pre[i + 1]);
            vin1.push_back(vin[i]);
        }
        p = reConstructBinaryTree(pre1, vin1);
        tree->left = p;
        i++;
        pre1.clear();vin1.clear();
        for (; i<pre.size(); i++) {
            pre1.push_back(pre[i]);
            vin1.push_back(vin[i]);
        }
        p = reConstructBinaryTree(pre1, vin1);
        tree->right = p;
        return tree;
    }
};

用两个栈实现队列

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

题解:
首先要知道的是队列和栈的区别:
对于队列来说,是一个先进先出的数据结构;
对于栈而言,则是先进后出的数据结构。
因此这个过程中,我们可以用两个栈的先进先出特性,转化为一个栈先进后出后,再次经过栈先进后出变为先进先出的队列特性。

代码实现如下:

class Solution
{
public:
    void push(int node) {
        stack1.push(node);
    }
    int pop() {
        int res;
        if(stack2.empty()){//用以判断是否stack2中是否还有数据,如果没有,代表之前先进的数据已经pop出去了
            while(stack1.size()){//当Stack2中没数据时,将stack1中的数据反向push进stack2
                stack2.push(stack1.top());
                stack1.pop();
            }
        }
        res=stack2.top();//stack2中有数据,则为之前push进的数据还没有完全pop出去,因此直接pop栈顶数据
        stack2.pop();
        return res;
    }
private:
    stack<int> stack1;
    stack<int> stack2;
};

旋转数组的最小元素

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

题解:
1)对于这个题目,最简单的是利用暴力穷举,由于数组是一个非递减排序的数组,因此,利用暴力穷举,找到第一个数组[i]<数组[i-1]的位置,则该位置则为我们所求最小的元素。但是对于这个方法来说,其时间复杂度为0(n),是较为费时的算法,其代码如下。

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if(rotateArray.size()==0)
            return 0;
        else{
            int res=rotateArray[0];
            for(int i=0;i<rotateArray.size();i++){
                if(res>rotateArray[i]){
                    res=rotateArray[i];
                    break;
                    }
            }
            return res;
        }
     }
 }

因此采用第二种算法。
2)二分法
由于给定的数组是非递减排序,因此在旋转后的数组可以分为两段,分为两个非递减排序的数组。因此,我们可以根据二分法进行判别。
先判定元素的大小是否为0。
然后接下来进行二分法进行查找。
(1)判定是否左位置的元素是否小于右位置元素,若是,直接输出左位置元素。(如 1 0 1 1 1 1);
(2)首先,从数组的中间元素开始搜索,如果该元素大于左位置元素,代表中间元素位于第一段的非递减排序数组,最小元素在中间位置的右侧,因此将左位置替换到中间位置+1。
(3)如果中间元素小于左位置元素,代表中间元素位于第二段的非递减数组,最小元素则在中间元素的左侧,因此将右位置替换到中间位置。
(4)由于左位置<=右位置,因此还有一种可能是左位置元素=右位置元素,这样可能存在(1 0 1 1 1)的数组,因此,此种情况下,左位置+1即可,然后不断实现(1)(2)(3)步骤,直到查找出来。代码如下,其时间复杂度为O(log n)。

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
         if(rotateArray.size()==0)
            return 0;
        int left=0;
        int right=rotateArray.size()-1;
        while(left<right){
            if (rotateArray[left] < rotateArray[right])
                return rotateArray[left];
            int mid=(left+right)/2;
            if(rotateArray[mid]>rotateArray[left])
                left=mid+1;
            else if(rotateArray[mid]<rotateArray[right])
                right=mid;
            else left++;
        }
        return rotateArray[left];
    }
};

斐波那契数列

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。 n<=39

题解:
1)递归。F(1)=1,F(2)=1.F(n)=F(n-1)+F(n-2) (n>=3)
根据递归方法进行求解。

class Solution {
public:
    int Fibonacci(int n) {
         if(n==1)
            return 1;
        if(n==2)
            return 2;
        if(n>=3)
            return Fibonacci(n-1)+Fibonacci(n-2);
    }
};

但是此法的时间复杂度为O(2^n),过大

2)向量
将第一个数0,push进向量,第二个数1,push进向量,则,第n个数则为上一个数加上上个数。时间复杂度为O(n),空间复杂度O(n)。

class Solution {
public:
    int Fibonacci(int n) {
        vector<int> v;
        v.push_back(0);
        v.push_back(1);
        if(n<=1)
            return n;
        for(int i=2;i<=n;i++)
            v.push_back(v[i-1]+v[i-2]);
        return v[n];
    }
};

3)数据更新
更新数据,进行叠加。时间复杂度为O(n),空间复杂度O(1).
实现如下:

class Solution {
public:
    int Fibonacci(int n) {
        int a=1;
        int b=1;
        int sum=0;
        if(n==0)
            return 0;
        if(n==1||n==2)
            return 1;
        for(int i=3;i<n+1;i++){
            sum=a+b;
            a=b;
            b=sum;
        }
        return sum;
    }
};

爬楼梯

一只青蛙一次可以跳上1阶台阶,也可以跳上2阶。求青蛙跳上一个n阶的台阶有多少种跳法。(先后顺序不同算不同的跳法)

题解:
1)递归法:爬楼梯的算法,其实是比较经典的题目,通过观察不同阶梯数可以观察到:
n=1,f(1)=1;
n=2,f(1)=2;
n=3,f(1)=3;
n=4,f(1)=5;
… ,…
由此可见,对于楼梯n的跳法,是符合斐波那契数列,因此可以通过递归方法进行计算。

class Solution {
public:
    int jumpFloor(int number) {
        if(number==1)
            return 1;
        if(number==2)
            return 2;
        if(number>2)
            return jumpFloor(number-1)+jumpFloor(number-2);
    }
};

另外一种斐波那契数列计算方法:

class Solution {
public:
    int jumpFloor(int number) {
        int a=1;
        int b=1;
        int sum=0;
        if(number==1)
            return 1;
        for(int i=2;i<number+1;i++){
            sum=a+b;
            a=b;
            b=sum;
        }
        return sum;
    }
};

2)排列组合方法:
对此方法,是通过排列组合的方法来进行计算。
首先,将n阶台阶看作为全跳1阶的跳法。n=(1 1 1… 1 1 )
其次,从n阶台阶中若跳一次2阶台阶,那么,总跳次数为n-1,那一次跳2阶的,可以在n-1次中的任一一次中,这个时候进行排列组合,即若跳一次2阶台阶,则有n-1中跳法;若跳2次二阶,则跳n-2次,其中在n-2次中任选其中两次,进行排列组合。。。。
直到跳n/2次二阶,同样进行排列组合。
代码如下:

class Solution {
public:
    int jumpFloor(int number) {
        if(number<2)
            return number;
        int sum=1;//全跳1阶,至少有1种方法
        for(int i=1;i<number/2+1;i++){
            int num=number-i;//共跳i次二阶台阶,共跳num次
            long long fenzi=1;
            long long fenmu=1;
            int temp=0;
            //排列组合计算
            while(temp<i){
                long long temp1=number-i-temp;
                long long temp2=temp+1;
                fenzi=fenzi*temp1;
                fenmu=fenmu*temp2;
                temp++;
            }
            sum+=fenzi/fenmu;
        }
        return sum;
    }
};

变态跳台阶

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

题解:
1)递归法,根据之前的简单爬楼梯的方法,可以同样利用递归的方法进行递归,求解。
d[i]=d[i-1]+d[i-2]+d[i-3]+…+d[1]+d[0]

class Solution {
public:
    int jumpFloorII(int number) {
        int sum=0;
        if(number==0)
            return 1;
        if(number==1)
            return 1;
        if(number==2)
            return 2;
        if(number==3)
            return 4;
        for(int i=1;i<=number;i++){
            sum+=jumpFloorII(number-i);
        }
        return sum;
    }
};

2)规律法
根据题目要求,可以得到:
d[i]=d[i-1]+d[i-2]+d[i-3]+…+d[1]+d[0];
d[i-1]=d[i-2]+d[i-3]+d[i-4]…+d[1]+d[0];
两项相减得:d[i]=2*d[i-1];
代码如下:

class Solution {
public:
    int jumpFloorII(int number) {
        sum=0;
        sum=pow(2,(number-1));
        return sum;
    }
};

矩形覆盖

我们可以用21的小矩形横着或竖着去覆盖更大的矩形,请问用n个21个小矩形去覆盖2*n的大矩形,请问有多种覆盖方法?

题解:
此题仍然可以看作递归的方法,如下图所示:
可以看作是f(n)=f(n-1)+f(n-2),因而根据斐波那契数列可以得到代码如下:

f(n)=f(n-1)+f(n-2)

class Solution {
public:
    int rectCover(int number) {
        if(number<=3)
            return number;
        int res=0;
        int a=1;
        int b=1;
        for(int i=1;i<number;i++){
            res=a+b;
            a=b;
            b=res;
        }
        return res;
    }
};
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值