剑指offer刷题思路(C++版本,已调试通过)(1-10题)

1.二维数组中的查找

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数
思路:从左下角元素往上查找,右边元素是比这个元素大,上边是的元素比这个元素小。于是,target比这个元素小就往上找,比这个元素大就往右找。如果出了边界,则说明二维数组中不存在target元素。

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        // array是二维数组,这里没做判空操作
        int rows = array.size();
        int cols = array[0].size();
        int i=rows-1,j=0;//左下角元素坐标
        while(i>=0 && j<cols){//使其不超出数组范围
            if(target<array[i][j])
                i--;//查找的元素较少,往上找
            else if(target>array[i][j])
                j++;//查找元素较大,往右找
            else
                return true;//找到
        }
        return false;
    }
};

2.替换空格

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路:
由于是把一个字符替换成三个字符,我们必须要把空格后面所有的字符都后移两个字节。否则就会有两个字节被覆盖,从前往后替换,后面的字符要不断往后移动,要多次移动,所以效率低下。从后往前,先计算需要多少空间,然后从后往前移动,则每个字符只为移动一次,这样效率更高一点。
因此本题思路:先遍历一遍字符串,统计出字符串中空格的总数,并计算出替换之后的字符串总长度,因此替换以后字符串的长度等于原来的长度加上2再乘以空格数目(时间复杂度为O(n))。

class Solution {
public:
    void replaceSpace(char *str,int length) 
    {
        int count=0;
        for(int i=0;i<length;i++)
        {
            if(str[i]==' ')
                count++;
        }
        for(int i=length-1;i>=0;i--)
        {
            if(str[i]!=' ')
            {
                str[i+2*count]=str[i];
            }
            else
            {
                count--;
                str[i+2*count]='%';
                str[i+2*count+1]='2';
                str[i+2*count+2]='0';
            }
        }
    }
};

3.从头到尾打印链表

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
思路:有三种思路,第一就是利用栈先入后出的特性完成,第二就是存下来然后进行数组翻转。
第三是利用递归。
递归思路:

class Solution {
public:
    vector<int> value;
    vector<int> printListFromTailToHead(ListNode* head) {
        ListNode *p=NULL;
        p=head;
        if(p!=NULL){
            if(p->next!=NULL)
            {
                printListFromTailToHead(p->next);
            }
            value.push_back(p->val);
        }
        return value;
    }
};

4.重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
思路:
先序遍历第一个位置肯定是根节点node, 中序遍历的根节点位置在中间p,在p左边的肯定是node的左子树的中序数组,p右边的肯定是node的右子树的中序数组 ,另一方面,先序遍历的第二个位置到p,也是node左子树的先序子数组,剩下p右边的就是node的右子树的先序子数组,把四个数组找出来,分左右递归调用即可。

class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> vin) {
        if (pre.empty() || vin.empty())
            return NULL;
        //创建根节点
        TreeNode* root;
        //从pre[0]开始扫描
        int indp = 0;
        recurConstruct(vin.begin(), vin.end(), pre, vin, root, indp);
        return root;
    }
    //注意TreeNode* &p要用引用,或者指针的指针,否则改变不了实参的
    void recurConstruct(vector<int>::iterator startin, vector<int>::iterator endin, vector<int> pre, vector<int> vin, TreeNode* &p, int &indp){
        if (indp>vin.size()-1||endin<startin)
            return;
        p=new TreeNode(pre[indp]);
        vector<int>::iterator pos;
        //找到当前根节点的位置
        pos = find(startin, endin, pre[indp]);
        indp++;
        //中序遍历中,根节点左边为左子树,右边为右子树
        if (pos != startin)
            recurConstruct(startin, pos - 1, pre, vin, p->left, indp);
        recurConstruct(pos + 1, endin, pre, vin, p->right, indp);
        return;
    }
};

Python版本

class Solution:
    def reConstructBinaryTree(self, pre, tin):
        if not pre or not tin:
            return None
        root = TreeNode(pre.pop(0))
        index = tin.index(root.val)
        root.left = self.reConstructBinaryTree(pre, tin[:index])
        root.right = self.reConstructBinaryTree(pre, tin[index + 1:])
        return root

5.用两个栈实现队列

题目:用两个栈来实现一个队列,完成队列和push和pop操作,队列中的元素为int类型。
思路:

class Solution
{
public:
    void push(int node) {
        while(!stack2.empty())
        {
            stack1.push(stack2.top());
            stack2.pop();
  }
        stack1.push(node);
    }

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

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

6.旋转数组的最小数字

题目: 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
思路:二分法查找变种,用中间值和高低位进行比较,看处递增还是递减序列,进行操作缩小范围。

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if (rotateArray.empty()) return 0;
        int left = 0, right = rotateArray.size() - 1;
        while (left < right) {
            //确认子数组是否是类似1,1,2,4,5,..,7的非递减数组
            if (rotateArray[left] < rotateArray[right]) return rotateArray[left];
             
            int mid = left + (right - left) / 2;
            //如果左半数组为有序数组
            if (rotateArray[left] < rotateArray[mid])
                left = mid + 1;
            //如果右半数组为有序数组
            else if (rotateArray[mid] < rotateArray[right])
                right = mid;   
            //否则,rotateArray[left] == rotateArray[mid] == rotateArray[right]
            else {
                ++left;
            }
        }
        return rotateArray[left];
    }
};

7.斐波那契数列

题目:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39
思路:动态规划计算,就是通过设计两个变量一直往后推算。

class Solution {
public:
    int Fibonacci(int n) {
        int f = 0, g = 1;
        while(n--) {
            g += f;
            f = g - f;
        }
        return f;
    }
};

8. 跳台阶

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
思路:
对于本题,前提只有 一次 1阶或者2阶的跳法。
a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);
b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2)
c.由a\b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2)
d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2
e.可以发现最终得出的是一个斐波那契数列:
或假设现在6个台阶,我们可以从第5跳一步到6,这样的话有多少种方案跳到5就有多少种方案跳到6,另外我们也可以从4跳两步跳到6,跳到4有多少种方案的话,就有多少种方案跳到6,其他的不能从3跳到6什么的啦,所以最后就是f(6) = f(5) + f(4);

class Solution {
public:
    int jumpFloor(int number) {
        if (number <= 0) {
            return 0;
        }
        if (number == 1) {
            return 1;
        }
        if (number == 2) {
            return 2;
        }
        int first = 1, second = 2, third = 0;
        for (int i = 3; i <= number; i++) {
            third = first + second;
            first = second;
            second = third;
        }
        return third;
    }
};

9.变态跳台阶(贪心算法)

题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路:
链接:https://www.nowcoder.com/questionTerminal/22243d016f6b47f2a6928b4313c85387?f=discussion

1)这里的f(n) 代表的是n个台阶有一次1,2,…n阶的 跳法数。
2)n = 1时,只有1种跳法,f(1) = 1
3) n = 2时,会有两个跳得方式,一次1阶或者2阶,这回归到了问题(1) ,f(2) = f(2-1) + f(2-2)
4) n = 3时,会有三种跳得方式,1阶、2阶、3阶,
那么就是第一次跳出1阶后面剩下:f(3-1);第一次跳出2阶,剩下f(3-2);第一次3阶,那么剩下f(3-3)
因此结论是f(3) = f(3-1)+f(3-2)+f(3-3)
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)
6) 由以上已经是一种结论,但是为了简单,我们可以继续简化:
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) = 2f(n-1)
7) 得出最终结论,在n阶台阶,一次有1、2、…n阶的跳的方式时,总得跳法为:
| 1 ,(n=0 )
f(n) = | 1 ,(n=1 )
| 2
f(n-1),(n>=2)

class Solution{
public:
    int jumpFloorII(int number) {
        //通过移位计算2的次方
        return 1<<(number-1);        
    }
};

10.矩形覆盖(递归)

题目描述
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2n的大矩形,总共有多少种方法?
思路:链接:https://www.nowcoder.com/questionTerminal/72a5a919508a4251859fb2cfb987a0e6?f=discussio
用归纳法归纳如下,
(1)当 n < 1时,显然不需要用2
1块覆盖,按照题目提示应该返回 0。
(2)当 n = 1时,只存在一种情况。
(3)当 n = 2时,存在两种情况。
(4)当 n = 3时,3种情况。
在这里插入图片描述
符合斐波那契数列

class Solution {
public:
    int rectCover(int number) {
    if (number < 1){
        return 0;
    }
        if (number == 1)
        {
        return 1;
    }
        else if (number*2 == 4)
        {
            return 2;
        }
        else{
            return rectCover((number-1))+rectCover(number-2);
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值