剑指Offer66题之每日6题 - 第一天

原题链接:

第一题:二维数组中的查找

题目:

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

解析:

利用有序这个条件,可以逐行二分查找。

时间复杂度: O(nlogn)

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        for (int i = 0; i < (int)array.size(); i++)
            if (binary_search(array[i].begin(), array[i].end(), target))
                return true;
        return false;
    }
};

暴力时,从第一行第一列开始查找,查找时按行优先,由于数组向下和向右时递增的,故如果从第一行第一列查找如果target比当前元素大,那么向下走和向右走都是可以的,这样很麻烦,但是如果从左下角或右上角开始查找,问题就变得简单了,以从左下角开始查找为例,如果当前元素比target大,那么就往上走;如果target比当前元素小,就往右走。如果走出了这个矩阵还没有找到target那么返回false

这样做为什么是可行的呢?把数组想象成一个棋盘,起点是左下角,终点是target(棋盘中的某个位置)。 其实就是找一条最短路从起点到终点,那么判断的条件是如果当前元素比target大,那么就往上走;如果target比当前元素小,就往右走,可能你会想,当前元素比target ,往左走也是可以的啊,确实可以,但是你这样不是多走了一些路吗,如果最短路走不到target,那么你往左走也是行不通的。

时间复杂度: O(2n)

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        int n = array.size(), m = array[0].size();
        int r = n - 1, c = 0;
        while (r >= 0 && c < m)
            if (target == array[r][c])
                return true;
            else if (target < array[r][c])
                --r;
            else
                ++c;
        return false;
    }
};

第二题:替换空格

题目:

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

解析:

求出空格数,把str指向的空间容量扩大到能接纳替换后的字符串容量大小,使用realloc函数,然后从原始字符串的尾部把字符一个一个复制到扩容字符串的尾部,遇到空格就替换,只有从尾部开始才能保证数据不被覆盖。

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

第三题:从尾到头打印链表

题目:

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

解析:

两种做法:

  • 栈;
  • 递归。
/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        stack<int> st;
        while (head != nullptr)
            st.push(head->val), head = head->next;
        vector<int> ret;
        while (!st.empty())
            ret.push_back(st.top()), st.pop();
        return ret;
    }
};
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> ret;
        dfs(head, ret);
        return ret;
    }

    void dfs(ListNode *head, vector<int> &ret) {
        if (head == nullptr)
            return ;
        dfs(head->next, ret);
        ret.push_back(head->val);
    }
};

第四题:重建二叉树

题目:

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

解析:

知道前序和中序遍历的结果是可以唯一确定一颗二叉树的,但是知道前序和后序遍历是不可以唯一确定一颗二叉树的。

利用前序遍历第一个数字为根,用这个根在中序遍历中查找,左边的就是左子树,右边的就是右子树,算出左右子树的长度,用其长度在前序遍历中划分出左右子树,重复上述过程,就可以重建这颗二叉树了。

/**
 * 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 *root = dfs(pre, vin, 0, pre.size(), 0, vin.size());
        return root;
    }

    TreeNode *dfs(vector<int> &pre,vector<int> &vin, int p_l, int p_r, int v_l, int v_r) {
        if (p_l == p_r)
            return NULL;
        TreeNode *root = new TreeNode(pre[p_l]);
        int pos = -1;
        for (int i = v_l; i < v_r; i++)
            if (vin[i] == pre[p_l]) {
                pos = i;
                break;
            }

        root->left = dfs(pre, vin, p_l + 1, p_l + 1 + pos - v_l, v_l, pos);
        root->right = dfs(pre, vin, p_l + 1 + pos - v_l, p_r, pos + 1, v_r);
        return root;
    }
};

第五题:用两个栈实现队列

题目:

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

解析:

用一个栈专门来完成push操作;

用另一个栈来完成pop操作,如果这个栈为空,那么就把第一个栈的元素依次出栈然后入栈到该栈,由于元素在第一个栈中是先入后出,经过加粗的步骤,元素就变成了先入先出了,满足队列的性质。

class Solution
{
public:
    void push(int node) {
        stack1.push(node);
    }

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

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

那么,如何用两个队列实现一个栈呢?

  • 两个队列中保持一个队列永远为空
  • push操作:把元素入不为空的那个队列;
  • pop操作:由于一个队列为空,于是把不为空的那个队列的元素出队到为空的这个队列,直到不为空的队列只剩一个元素为止,这个时候把这个元素弹出队列返回,这样就又保证了两个队列中保持一个队列永远为空。
class Solution
{
public:
    void push(int node) {
        que1.empty() ? que2.push(node) : que1.push(node);
    }

    int pop() {
        auto &que_out = que1.empty() ? que2 : que1;
        auto &que_in = que1.empty() ? que1 : que2;
        while (que_out.size() > 1)
            que_in.push(que_out.front()), que_out.pop();
        int ret = que_out.front();
        que_out.pop();
        return ret;
    }

private:
    queue<int> que1;
    queue<int> que2;
};

第六题:旋转数组的最小数字

题目:

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}{1,2,3,4,5}的一个旋转,该数组的最小值为1

NOTE:给出的所有元素都大于0,若数组大小为0,请返回0

解析:

假设没有非递减这个东西,很好考虑,其实有了也无所谓。

首先考虑一个这样的性质:

  • 如果发生了旋转,那么第一个元素一定大于最后一个元素;
  • 如果第一个元素小于最后一个元素,那么第一个元素一定就是答案;
  • 旋转后,数组的前半部分是非递减的,后半部分也是递减的,故这个题要求的就是两个有序序列的分界点。

我们先考虑严格递增,这样根据上面的三条性质很好写出二分的代码:

  • 如果中间元素比左端点大,那么中间元素一定在左边这个有序序列,故可以缩小左端点的范围;
  • 如果中间元素比右端点小,那么中间元素一定在右边这个有序序列,故可以缩小右端点的范围;
  • 直到左右端点重合,答案就是左端点的这个数。

现在我们再来考虑非递减这个条件,在二分的代码中增加一个这样判断条件rotateArray[l] == rotateArray[r] && rotateArray[l] == rotateArray[mid],如果左端点等于右端点,那么只能在左端点和右端点之间顺序查找了(反正范围已经缩减到了左右端点之间),因为如果满足了上述判断,mid这个点无法确定在左区间还是在右区间,只能顺序查找了。

时间复杂度 O(logn) ,最坏情况为 O(n)

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        if (rotateArray.size() == 0)
            return 0;
        int l = 0, r = rotateArray.size() - 1;
        while (l < r && rotateArray[l] >= rotateArray[r]) {
            int mid = (l + r) / 2;
            if (rotateArray[l] == rotateArray[r] && rotateArray[l] == rotateArray[mid]) {
                int ret = rotateArray[l];
                for (int i = l + 1; i <= r; ret = min(ret, rotateArray[i++]));
                return ret;
            }
            int mid = (l + r) / 2;
            if (rotateArray[mid] >= rotateArray[l])
                l = mid + 1;
            else
                r = mid;
        }
        return rotateArray[l];
    }
};

猜你喜欢

转载自blog.csdn.net/FlushHip/article/details/78914719