题目描述
现在有一个城市销售经理,需要从公司出发,去拜访市内的商家,已知他的位置以及商家的位置,但是由于城市道路交通的原因,他只能在左右中选择一个方向,在上下中选择一个方向,现在问他有多少种方案到达商家地址。
给定一个地图map及它的长宽n和m,其中1代表经理位置,2代表商家位置,-1代表不能经过的地区,0代表可以经过的地区,请返回方案数,保证一定存在合法路径。保证矩阵的长宽都小于等于10。
测试样例:
[[0,1,0],[2,0,0]],2,3
返回:2
注:题目的意思不够清晰,所谓的上中下选一种实际是在上下走向上要求往上走了就只能一路向上了,不可再向下了,但左右不管。但对于左中右选一种则就是管水平走向,走了右便不再有任何往左走的机会了,同理上下它不管。
再直白一点就是只有四种走向,右上走(包括只往右走或只往上走,之后同理)、右下走、坐上走、左下走。
思路
1、先确定起点和终点的位置,保存到变量中。
2、处理特殊情况,起点与终点同位置(之后想想感觉不存在,因为一个元素不可能既赋予1值又赋予2值,它只可能是一个值)
3、给边缘路径赋值(动态规划需要用到之前的值来推出后面的值)
- 边缘值只可能是1或0,值代表有多少条路径会经过次数,因为不可往反方向走,所以边缘要么有一条路过这里,要么一条路都么有。
- 比如,从左上走到右上,只能从左一直往右走一条路,或者因为不通无法前进。不可能出现我先下去,然后再往上走到这个边缘来。
- 这里的边缘不是指地图的边缘,而是指以起点和终点为两个顶点组成的矩形(也可能是一条直线)的边缘。
- 比如此图中,上述所指的边缘是在红色框内部靠着红色框的行和列
- 边缘赋值是在一个新建的二维数据dp[][]来保存的。的方式是,如果路不通(map[][]值为-1)则赋值为0,在通的情况下(map[][]值为0)赋值为其前一个位置的值(也就是1)。
- 这个用于保存经过路径数二维数组起点值初始设置为1(拿此图来说就是dp[0][2]=1),其余默认值0。
- 比如这里的起点1往下走是0通的,那么对应的这个位置dp[1][2]赋值为上一个位置(它的上一个是起点)的值dp[0][2]也就是1,接着往下走也是0,赋值为上一个位置的值1,依次类推走到与终点平齐的那一行结束。往右同理。
4、边缘完成后便是内部赋值。
- 用此实例来说明,用两个for循环,从起点1的右下位置开始,即起点横坐标加1纵坐标加1的位置开始。
- 此位置的值为其左边和上边的值的和,原理很简单,假设这个位置的左边一个位置值为1(即有一条路通过左边这个位置),它上面的位置的值为0。那么通过该位置的路径就是1了,因为有一条左边过来的路径可以经过它,上面没有过来的路径。(动态规划,通过前面的答案推出后面的,并考虑到所有情况)
5、赋值完后,最终终点的赋值就是答案了。
代码
public static int countPath(int[][] map, int n, int m)
{
// 首先找出起点(公司)和终点(商家)的位置
int startx = 0, starty = 0, endx = 0, endy = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
if (map[i][j] == 1)
{
startx = i;
starty = j;
}
else if (map[i][j] == 2)
{
endx = i;
endy = j;
}
}
}
// 两点重合,那么只有原地不动一种路径走法
if (startx == endx && starty == endy) return 1;
// 用于记录可以经过此点的路径个数
int dp[][] = new int[n][m];
// 列数对应左右走向,行数对应上下走向
// 根据起点与终点坐标的差值,来判断往哪个方向走
int leftorRight = endy - starty > 0 ? 1 : -1; // >0往右走(i+),<0往左走(i-)
int uporDown = endx - startx > 0 ? 1 : -1; // >0往下走(y+),<0往上走(y-)
dp[startx][starty] = 1; // 起点的路径数设置为1
// 行数从起点往终点的方向请进一步(水平方向前进一步)的位置开始循环
int row = startx + uporDown;
while ((row - endx) * uporDown <= 0)
{ // 如果不同,此点的路径数就置0,通则置为前一个位置的路径数(边缘也只会是1)
dp[row][starty] = map[row][starty] == -1 ? 0 : dp[row - uporDown][starty];
row += uporDown;
}
// 列数为从起点到终点的方向前进一步(垂直方向)的位置开始循环
int column = starty + leftorRight;
while ((column - endy) * leftorRight <= 0)
{
dp[startx][column] = map[startx][column] == -1 ? 0 : dp[startx][column - leftorRight];
column += leftorRight;
}
// 重置循环的起点横行数
row = startx + uporDown;
while ((row - endx) * uporDown <= 0)
{ // 重置循环起点的列数
column = starty + leftorRight;
while((column - endy) * leftorRight <= 0)
{ // 这里开始给内部赋值(将用上边缘的值和之前的内部值)
dp[row][column] = map[row][column] == -1 ? 0 : dp[row - uporDown][column] + dp[row][column - leftorRight];
column += leftorRight;
}
row += uporDown;
}
return dp[endx][endy];
}
注:参考牛客上大佬的思路,其个人主页https://www.nowcoder.com/profile/587889