N * M网格中的字典编码最小路径

时间:2015-03-31 18:41:18

标签: algorithm graph graph-theory

我在最近的一次采访中发现了这一点。 我们给出了一个由数字组成的N * M网格,网格中的路径是你遍历的节点。我们得到一个约束,我们只能在网格中向右或向下移动。所以给定这个网格,我们需要找到排序后的词典最小路径,从网格的左上角到右下角到达
EG。如果网格是2 * 2
4 3
5 1
那么根据问题的词汇最小路径是" 1 3 4"。 怎么办这样的问题?代码表示赞赏。提前谢谢。

3 个答案:

答案 0 :(得分:4)

您可以使用Dynamic programming来解决此问题。让f(i, j)成为从(i, j)(N, M)仅向右和向下移动的最小字典路径(在排序路径之后)。请考虑以下重复:

f(i, j) = sort( a(i, j) + smallest(f(i + 1, j), f(i, j + 1)))

其中a(i, j)(i, j)网格中的值,smallest (x, y)返回xy之间较小的词典字符串。 +连接两个字符串,sort(str)按字符顺序对字符串str进行排序。

重复的基本情况是:

f(N, M) = a(N, M)

i = Nj = M时也会发生重复更改(确保您看到了这一点)。

考虑以下C++编写的代码:

//-- the 200 is just the array size. It can be modified

string a[200][200];             //-- represent the input grid
string f[200][200];             //-- represent the array used for memoization
bool calculated[200][200];      //-- false if we have not calculate the value before, and true if we have
int N = 199, M = 199;           //-- Number of rows, Number of columns


//-- sort the string str and return it
string srt(string &str){
    sort(str.begin(), str.end());
    return str;
}


//-- return the smallest of x and y
string smallest(string & x, string &y){
    for (int i = 0; i < x.size(); i++){
        if (x[i] < y[i]) return x;
        if (x[i] > y[i]) return y;
    }
    return x;
}



string solve(int i, int j){
    if (i == N && j == M) return a[i][j];       //-- if we have reached the buttom right cell (I assumed the array is 1-indexed
    if (calculated[i][j]) return f[i][j];       //-- if we have calculated this before 
    string ans;
    if (i == N) ans = srt(a[i][j] + solve(i, j + 1));       //-- if we are at the buttom boundary
    else if (j == M) ans = srt(a[i][j] + solve(i + 1, j));  //-- if we are at the right boundary
    else ans = srt(a[i][j] + smallest(solve(i, j + 1), solve(i + 1, j)));       
    calculated[i][j] = true;        //-- to fetch the calculated result in future calls
    f[i][j] = ans;
    return ans;
}


string calculateSmallestPath(){
    return solve(1, 1);
}

答案 1 :(得分:1)

您可以应用动态编程方法在O(N * M * (N + M))时间和空间复杂度中解决此问题。

在下面我要考虑,N是行数,M是列数,左上角的单元格有坐标(0, 0),首先是行和第二列。

允许每个单元格存储按字典顺序排列的按字典顺序排列的最小路径。具有0索引的行和列的答案是微不足道的,因为只有一种方法可以到达每个单元格。对于其余的单元格,您应该选择顶部和左侧单元格的最小路径,并插入当前单元格的值。

算法是:

path[0][0] <- a[0][0]
path[i][0] <- insert(a[i][0], path[i - 1][0])
path[0][j] <- insert(a[0][j], path[0][j - 1])
path[i][j] <- insert(a[i][j], min(path[i - 1][j], path[i][j - 1])

答案 2 :(得分:1)

如果没有重复数字,也可以在O (NM log (NM))中实现。

<强>直觉:

假设我将左上角(a,b)和右下角(c,d)的网格标记为G(a,b,c,d)。由于您要获得按字典顺序排列的最小字符串 AFTER 对路径进行排序,因此目标应该是每次在G中找到最小值。如果达到这个最小值,比如(i,j),那么G(i,b,c,j)G(a,j,i,d)将无法用于搜索我们的下一分钟(路径)。也就是说,我们所希望的路径的值永远不会出现在这两个网格中。证明?如果遍历这些网格中的任何位置,则不会让我们达到G(a,b,c,d)中的最小值((i,j)处的最小值)。而且,如果我们避免(i,j),我们构建的路径不能在字典上最小。

enter image description here

所以,首先我们找到G(1,1,m,n)的分钟。假设它在(i,j)。标记分钟。然后,我们在G(1,1,i,j)G(i,j,m,n)中找到 min ,并对它们执行相同操作。继续这样,直到最后,我们有m+n-1个标记的条目,这将构成我们的路径。线性遍历原始网格G(1,1,m,n),如果标记了值,则报告值。

<强>方法

每次在G中查找分数都很昂贵。如果我们将网格中的每个值映射到它的位置怎么办? - 遍历网格并维护字典Dict,其中键为(i,j)处的值,值为元组(i,j)。最后,您将获得一个涵盖网格中所有值的键值对列表。

现在,我们将维护一个有效网格列表,我们将在其中找到我们路径的候选者。第一个有效网格为G(1,1,m,n)

对键进行排序,并从排序键集S中的第一个值开始迭代。

维护一个有效网格树T(G),以便每个G(a,b,c,d) in TG.left = G(a,b,i,j)G.right = G(i,j,c,d) (i,j) = location of min val in G(a,b,c,d)

现在算法:

for each val in sorted key set S do
    (i,j) <- Dict(val)
    Grid G <- Root(T)
    do while (i,j) in G
       if G has no child do
           G.left <- G(a,b,i,j)
           G.right <- G(i,j,c,d)
       else if (i,j) in G.left
           G <- G.left
       else if (i,j) in G.right
           G <- G.right
       else 
           dict(val) <- null
           end do
       end if-else
    end do
end for
for each val in G(1,1,m,n)
    if dict(val) not null
        solution.append(val)
    end if
end for
return solution

Java代码:

class Grid{
    int a, b, c, d;
    Grid left, right;
    Grid(int a, int b, int c, int d){
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
        left = right = null;
    }
    public boolean isInGrid(int e, int f){
        return (e >= a && e <= c && f >= b && f <= d);
    }
    public boolean hasNoChild(){
        return (left == null && right == null);
    }
}
public static int[] findPath(int[][] arr){
        int row = arr.length;
        int col = arr[0].length;
        int[][] index = new int[row*col+1][2];
        HashMap<Integer,Point> map = new HashMap<Integer,Point>();
        for(int i = 0; i < row; i++){
            for(int j = 0; j < col; j++){
                map.put(arr[i][j], new Point(i,j));
            }
        }
        Grid root = new Grid(0,0,row-1,col-1);
        SortedSet<Integer> keys = new TreeSet<Integer>(map.keySet());
        for(Integer entry : keys){
            Grid temp = root;
            int x = map.get(entry).x, y = map.get(entry).y;
            while(temp.isInGrid(x, y)){
                if(temp.hasNoChild()){
                    temp.left = new Grid(temp.a,temp.b,x, y);
                    temp.right = new Grid(x, y,temp.c,temp.d);
                    break;
                }
                if(temp.left.isInGrid(x, y)){
                    temp = temp.left;
                }
                else if(temp.right.isInGrid(x, y)){
                    temp = temp.right;
                }
                else{
                    map.get(entry).x = -1;
                    break;
                }
            }

        }
        int[] solution = new int[row+col-1];
        int count = 0;
        for(int i = 0 ; i < row; i++){
            for(int j = 0; j < col; j++){
                if(map.get(arr[i][j]).x >= 0){
                    solution[count++] = arr[i][j];
                }
            }
        }
        return solution;
    }

空间复杂性由字典 - O(NM)和树 - O(N+M)的维护构成。总体而言:O(NM)

填写然后对字典进行排序的时间复杂度 - O(NM log(NM));用于检查每个NM值的树 - O(NM log(N+M))。总体而言 - O(NM log(NM))

当然,如果重复这些值,这将不起作用,因为我们对网格中的单个值有多个(i,j),并且决定选择哪个不再满足贪婪的做法。

额外的FYI:我之前听到的与此类似的问题有一个额外的网格属性 - 没有值重复且数字来自1 to NM。在这种情况下,复杂性可能会进一步降低到O(NM log(N+M)),因为您可以简单地使用网格中的值作为数组的索引(不需要排序)。