剑指Offer系列

温故而知新

回溯

机器人的运动范围

题目:
地上有一个m行和n列的方格。一个机器人从座标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行座标和列座标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

思路:

代码:

#include<vector>
class Solution {
public:
    int movingCount(int threshold, int rows, int cols)
    {
        vector<vector<bool> > visited(rows, vector<bool>(cols));
        return countHelper(threshold, rows, cols, 0, 0, visited);
    }

    int countHelper(const int &threshold, const int &rows, const int &cols, int rPos, int cPos, vector<vector<bool> > &visited) {
        int count = 0;
        if(checkPosOk(threshold, rows, cols, rPos, cPos, visited)) {
            visited[rPos][cPos] = true;
            count = 1 
                + countHelper(threshold, rows, cols, rPos-1, cPos, visited)
                + countHelper(threshold, rows, cols, rPos, cPos-1, visited)
                + countHelper(threshold, rows, cols, rPos+1, cPos, visited)
                + countHelper(threshold, rows, cols, rPos, cPos+1, visited);
        }
        return count;
    }

    bool checkPosOk(const int &threshold, const int &rows, const int &cols, int rPos, int cPos, vector<vector<bool> > &visited) {
        return rPos>=0 && rPos<rows && 
            cPos>=0 && cPos<cols && 
            !visited[rPos][cPos] &&
            (getDigitSum(rPos) + getDigitSum(cPos) <= threshold);
    }

    int getDigitSum(int num) {
        int next = num / 10;
        int result = num % 10;
        while(next!= 0) {
            result += next % 10;
            next = next / 10;
        }
        return result;
    }


};

矩阵中的路径

题目:
题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的3 X 4 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

关键:
1. 对回溯失败的处理

代码:

#include <cstring>
#include <vector>
using namespace std;
class Solution {
public:
    bool hasPath(char* matrix, int rows, int cols, char* str)
    {
        vector<vector<bool> > visited(rows, vector<bool>(cols));
        int pathIndex = 0;
        for(int i=0;i<rows;i++) {
            for(int j=0;j<cols;j++) {
                if(pathHelper(matrix, rows, cols, str, i, j, pathIndex, visited))
                    return true;
            }
        }
        return false;
    }

    bool pathHelper(char* matrix, int rows, int cols, char* str, int i, int j, int &pathIndex, vector<vector<bool> > &visited) {
        if(pathIndex==(int)strlen(str))
            return true;
        bool pathijOk = false;
        if(i>=0 && i<rows && j>=0 && j<cols && str[pathIndex]==matrix[i*cols+j] && !visited[i][j]) {
            // forward
            visited[i][j] = true;
            pathIndex++;
            pathijOk = pathHelper(matrix, rows, cols, str, i-1, j, pathIndex, visited) ||
                       pathHelper(matrix, rows, cols, str, i+1, j, pathIndex, visited) ||
                       pathHelper(matrix, rows, cols, str, i, j+1, pathIndex, visited) ||
                       pathHelper(matrix, rows, cols, str, i, j-1, pathIndex, visited);

            if(!pathijOk) {
                // if fail, then backward
                pathIndex--;
                visited[i][j] = false;
            }
        }
        return pathijOk;
    }


};

游走

滑动窗口的最大值

题目:
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

注意:
1. size的范围检查
2. size的类型时unsigned int, 当size = 3时,会发生类型转换,出现很诡异的1 – 3 > 0

代码:

class Solution {
public:
    vector<int> maxInWindows(const vector<int>& num, unsigned int size)
    {
        deque<int> state;
        vector<int> result;
        if (size > num.size() || size <= 0)
            return result;
        for(int i = 0; i < num.size(); i++) {
            if(!state.empty() && i - (int)size >= state.front())
                state.pop_front();
            while(!state.empty() && num[state.back()] <= num[i])
                state.pop_back();
            state.push_back(i);

            if(i >= (int)size - 1)
                result.push_back(num[state.front()]);
        }
        return result;
    }

};

数据流中的中位数

题目:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

关键:
1. 维护两个堆,分别为大小堆,我们只需要两个堆的堆顶,为数据流中中间的两个数
2. 注意维护小堆的堆顶 > 大堆的堆顶

代码:

class Solution {
private:
    // minheap[0] > maxheap[0]
    vector<int> minheap;
    vector<int> maxheap;
public:
    void Insert(int num)
    {
        if((minheap.size()+maxheap.size()) % 2 == 0) {
            // insert to minheap
            if(maxheap.size()>0 && num < maxheap[0]) {
                maxheap.push_back(num);
                push_heap(maxheap.begin(), maxheap.end(), less<int>());

                num = maxheap[0];
                pop_heap(maxheap.begin(), maxheap.end(), less<int>());
                maxheap.pop_back();
            }
            minheap.push_back(num);
            push_heap(minheap.begin(), minheap.end(), greater<int>());
        }
        else {
            // insert to maxheap
            if(minheap.size()>0 && num > minheap[0]) {
                minheap.push_back(num);
                push_heap(minheap.begin(), minheap.end(), greater<int>());

                num = minheap[0];
                pop_heap(minheap.begin(), minheap.end(), greater<int>());
                minheap.pop_back();
            }
            maxheap.push_back(num);
            push_heap(maxheap.begin(), maxheap.end(), less<int>());
        }
    }

    double GetMedian()
    {
        int size = minheap.size() + maxheap.size();
        if (size == 0) return -1;
        if (size % 2 == 1) return (double)minheap[0];
        return (double)(minheap[0] + maxheap[0])/2;
    }

};

二叉树

关键:
1. 想要追踪结果需要的是引用变量,所以是void Inorder(TreeNode* p, int &i, int k, TreeNode* &result) 而不是 void void Inorder(TreeNode* p, int &i, int k, TreeNode* result)
2. 同时要注意剪枝if(p == NULL || result != NULL) return;
代码:

/* struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) { } }; */
class Solution {
public:
    TreeNode* KthNode(TreeNode* pRoot, int k)
    {
        int i = 0;
        TreeNode* result = NULL;
        Inorder(pRoot, i, k, result);
        return result;
    }

    void Inorder(TreeNode* p, int &i, int k, TreeNode* &result) {
        if(p == NULL || result != NULL) return;
        Inorder(p->left, i, k, result);
        if(++i == k) result = p;
        //cout<<i<<" "<<k<<" "<<result->val<<endl;
        Inorder(p->right, i, k, result);
    }

};

流取样

从数据流中随机取m个数

题目:
有一个很大很大的输入流,大到没有存储器可以将其存储下来,而且只输入一次,如何从这个输入流中等概率随机取得m个记录。

解答:
开辟一块容纳m个记录的内存区域,对于数据流的第n个记录,以m/n的概率将其留下(前m个先存入内存中,从第m+1个开始),随机替换m个已存在的记录中的一个,这样可以保证每个记录的最终被选取的概率都是相等的。

证明:
数学归纳法
part1:
假设,现在流入第n+1个数据,而前面的n个数据中,每个数据在内存区域的概率为 mn m n

  • 对于新流入数据,其保存在内存区域的概率为 mn+1 m n + 1
  • 对于前面的n个数据,其保存在内存区域的概率为 mn+1(mnm1m)+(1mn+1)mn=mn+1 m n + 1 ∗ ( m n ∗ m − 1 m ) + ( 1 − m n + 1 ) ∗ m n = m n + 1

part2:
当n=6时
* 对于新流入数据,其保存在内存区域的概率为 56 5 6
* 对于前面的5个数据,其保存在内存区域的概率为 16+56(115)=56 1 6 + 5 6 ∗ ( 1 − 1 5 ) = 5 6

得证

二叉树

序列化二叉树

题目:
请实现两个函数,分别用来序列化和反序列化二叉树

关键:
1. substract(2)是因为只有在”#,xxxxxxxxxxx”这种情形下才继续
2. data一直在削

实现:
链接中的Serialize and Deserialize Binary Tree

点赞