在Young矩阵下的高效查询

一道在蜻蜓FM面试的时候,出的算法题。题目是让在Young氏矩阵中查询一个数,并把他们的坐标都返回。

一个m x n的Young氏矩阵(Young tableau)是一个m x n的矩阵,其中每一行的数据都从左到右排序,每一列的数据都从上到下排序。Young氏矩阵中可能会有一些∞数据项,表示不存在的元素。所以,Young氏矩阵可以用来存放 r ≦ mn 个有限的数。

在面试的时候,我只想到了大致的思路,并没有写出完整的程序。之后思考了下,当时的思路还不够完整,许多方面都没有考虑到。接下来,我讲给出我之后想通了的两种解法。

二分查找

题目很明显是可以用二分查找来做的,但是关键是如何二分。最先想到的合理方式是,通过左对角线找到的中点arr[n / 2][n / 2],可以将矩阵分为4个部分。

如果target小于arr[n / 2][n / 2],那么我们可以排除的矩阵的后1 / 4的部分。之后留给我们是剩下的3 / 4,我们应该如何处理呢?这个也是我面试的时候没有想清楚的。

如果我们仍然把剩下的部分当做一个整体,那么我们处理起来就会有一些问题,因为我们并不能用已有的知识来进行进一步的优化。如果我们将剩下的这个图形拆解开来会不会有更好的效果呢?假设对于一个矩阵,我们去掉的是右下方的1 / 4(下图中的黄色部分),我们将剩下的图形当做两个长方形来看待。

young_matrix_rectangle

这两个长方形也完全符合Young氏矩阵的概念。所以我们依旧可以用上述方法来继续求解。

如果target大于arr[n / 2][n / 2],同理我们使用另外的两个长方形进行求解。

如果相等的话,首先将当前元素的位置加入最后的解集,继续找的范围是左下方的矩阵和右上方的矩阵(严格递增)。

但是在求出结果后,我们会发现有一些元素的位置会重复出现在最后的位置中,并且没有什么特别的规律。所以这里采用了老土的set去重,希望如果有想法的可以在我的博客下留言。

using pos = pair<int, int>;
using vi = vector<int>;
using vvi = vector<vi>;

class Solution {
public:
    vector<pos> findingNumInYoungMatrix(vvi &map, int target) {
        int height, width;
        vector<pos> ret;
        if((height = map.size()) == 0) return ret;
        if((width  = map[0].size()) == 0) return ret;
        set<pos> t;
        worker(map, target, t, make_pair(0, 0), make_pair(height - 1, width - 1));
        return vector<pos> { t.begin(), t.end() };
    }

    void worker(vvi &map, int target, set<pos> &ret, pos lt, pos rb) {
        if(lt.first > rb.first || lt.second > rb.second) return;
        pos mid = make_pair((lt.first + rb.first) / 2, (lt.second + rb.second) / 2);
        int mid_val = map[mid.first][mid.second];
        if(target == mid_val) {
            ret.insert(mid);
            worker(map, target, ret, make_pair(mid.first + 1, lt.second), make_pair(rb.first, mid.second - 1));  
            worker(map, target, ret, make_pair(lt.first, mid.second + 1), make_pair(mid.first - 1, rb.second));
        }
        else if(target > mid_val) {
            worker(map, target, ret, make_pair(mid.first + 1, lt.second), rb);  
            worker(map, target, ret, make_pair(lt.first, mid.second + 1), rb);
        }
        else {
            worker(map, target, ret, lt, make_pair(mid.first - 1, rb.second));
            worker(map, target, ret, lt, make_pair(rb.first, mid.second - 1));
        }
    }
};

右上角比较

这个解法是我的一个同学在听完这个题目后和我说的。我们可以通过用target和右上角的元素进行比较。如果target小于当前元素的话,我们就可以去掉竖排,因为竖排的元素一定大于当前元素;反之,target大于当前元素,我们可以同理去掉横排的元素。要是如果相等的话,加入解集,并且将两排同时去掉。

感觉整个程序的逻辑应该比上面的要简单一些,但是在一些答的数据集中,应该还是上面这种做法效率更加高,因为每次去掉的元素至少是1 / 4

blogroll

social