设想有个机器人坐在一个网格的左上角,网格 r 行 c 列。机器人只能向下或向右移动,但不能走到一些被禁止的网格(有障碍物)。设计一种算法,寻找机器人从左上角移动到右下角的路径。

网格中的障碍物和空位置分别用 1 和 0 来表示。
返回一条可行的路径,路径由经过的网格的行号和列号组成。左上角为 0 行 0 列。如果没有可行的路径,返回空数组。
示例 1:

解释:
说明:r 和 c 的值均不超过 100。
这道题是一道经典的网格迷宫问题,我们可以利用回溯法去解决。
这道题我们只需要找到一条可以返回的路径就完全ok了。所以我们先要想想我们需要在什么地方去返回。首先,你碰到了石头,就要返回把。其次,超出了边界,是不是也要返回?到达了终点是不是更要返回?
还有,我们需要一个二维数组去确定自己走过的路径,走过的路径不可能再走一遍,所以也要返回。我们也需要用一个标志位,代表我已经找到了一条路径,找到了就把标志位变位,这样再遇到其他路径的时候就直接不走,也算是一种返回吧。这个其实就是回溯算法中的剪枝思想。
把所有的边界条件想清楚后,这道题,也就不是一道难题了,十分好理解的。我们通过不断地尝试新的路径,并通过回溯的方式来回到之前的状态。就完全可以解出这道题了。
具体的代码如下:
class Solution {
public:
vector<vector<int>> result; //最终的结果集
vector<vector<int>> pathWithObstacles(vector<vector<int>>& obstacleGrid) {
int row = obstacleGrid.size();
int cal = obstacleGrid[0].size();
if(obstacleGrid[0][0] == 1 || obstacleGrid[row-1][cal-1] == 1) return {};
vector<vector<bool>> flag (row,vector<bool>(cal,false)); //记录走过的路径
bool path = false;//找到一条路径就可以返回了
backtracking(obstacleGrid,flag,row,cal,0,0,path);
return result;
}
void backtracking(vector<vector<int>>& obstacleGrid,vector<vector<bool>>& flag, int row,int cal,int i, int j,bool& path){
if(i >= row || j >= cal || obstacleGrid[i][j] == 1 || path || flag[i][j] )
return ;
if(i == row -1 && j == cal -1){
result.push_back({i,j});
path = true;
return;
}
flag[i][j] = true;
result.push_back({i,j});
backtracking(obstacleGrid,flag,row,cal,i+1,j,path);
backtracking(obstacleGrid,flag,row,cal,i,j+1,path);
if(!path) result.pop_back();
}
};

当obstacleGrid的大小为m行n列时,该代码的时间复杂度和空间复杂度如下:
时间复杂度:O(mn * 2^(m+n) )。其中,m*n是obstacleGrid的大小,2^(m+n)是所有可能的路径数量。在backtracking函数中,每个格子有两个方向可以探索(向下和向右),因此,每个格子的搜索分支最多有两个。对于每一个分支,都需要遍历整个obstacleGrid,并递归探索下一个格子。因此,时间复杂度是O(mn * 2^(m+n))。
空间复杂度:O(mn + 2^(m+n) )。其中,mn是flag数组的大小,2(m+n)是结果数组result的大小。在backtracking函数中,需要使用一个二维的flag数组来记录已经访问过的格子,因此,空间复杂度是O(mn)。同时,结果数组result也需要记录所有的路径,因此空间复杂度也是O(2(m+n))。因此,总的空间复杂度是O(mn + 2^(m+n))。
需要注意的是,由于时间复杂度和空间复杂度都与obstacleGrid的大小呈指数关系,因此,当obstacleGrid的大小比较大时,该算法的时间复杂度和空间复杂度都会变得非常高,导致算法的性能下降。因此,需要对算法进行优化,以降低时间复杂度和空间复杂度。
用动态规划的方法与用回溯法的方法,在某些地方是一样的。比如第一开始的条件判断。若起点终点有障碍物,我们就直接返回。
不一样的点在于,回溯法,是一步一步的去试探,这道道路能不能走到终点,如果走不到,我们就原路返回,换条路再走走看。
动态规划法,是检测有多少条路径能够到达终点,如果大于0,就直接构造路径。如果等于0就返回空数组。
而有关动态规划的题,它是有特定的解题步骤的。
我们就用动态规划的五部曲,来去分析一下这道题,我们是怎么做的:
第一步:确定dp数组的下标以及含义
dp[i][j] :i是纵坐标,j是横坐标,dp[i][j]代表的是到达坐标为 (i,j) 这个格子的路径数量第二步:确定(递推)推导公式
由题意可知,机器人只能向下或向右移动,因此到达坐标(i,j)的格子的路径数量只可能是从(i-1,j)或(i,j-1)格子的状态转移而来。因此,我们可以得到递推公式:
dp[i][j] = dp[i-1][j] + dp[i][j-1];特别需要注意的是,在obstacleGrid[i][j]的值为1的情况下(也就是这个格子上有障碍物),dp[i][j]的值要为0(也就是表示没有一条路径可以到达这个格子)。所以,最终的递推公式应该长这样:
dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : dp[i-1][j] + dp[i][j-1];第三步:初始化dp数组
由于机器人只能选择向下走或向右走,除第一行与第一列外的格子,需要由第一行与第一列的dp值去推导出来,所以我们要去初始化dp数组的第一行与第一列。因为现在起点是没有石头的,所以dp[0][0] = 1。
在推导的时候需要注意的是,dp值由两个要素决定,一个是你obstacleGrid数组中对应的值是是否为0(为0代表这个格子没有障碍物),第二个是你前一个dp值是否为1(为1就是有路径可以到达这个格子),这两个条件缺一不可。
知道了这些前提条件后,我们就可以用for循环来初始化第一行与第一列啦。具体代码如下:
for(int i = 1;i < row; ++i)
if(obstacleGrid[i][0] == 0 && dp[i-1][0] == 1)
dp[i][j] = 1
for(int j = 1;j < cal; ++i)
if(obstacleGrid[0][j] == 0 && dp[0][j-1] == 1)
dp[i][j] = 1
第四步,确定变量顺序
具体的代码长这样:
for(int i = 1; i < row; ++i)
for(int j = 1; j < cal; ++j)
if(obstacleGrid[i][j] == 0)
dp[i][j] = dp[i-1][j]%10000000 + dp[i][j-1]%10000000;
第五步,举例推导dp数组

至此,动态规划的部分结束了。动态规划部分最重要的是推断出是否有路径能从左上角到达右下角。那如果dp[i][j] = 0,则其实就代表没有路径可以到达终点,我们返回空数组就可以了。
那其他情况,就不管有几条路径可以到达终点,我们自己构造一条路径不就好了嘛?
从终点往起点去推,先设置两个变量,将终点的坐标表示出来:
int i = row - 1, j = cal - 1;--i ,--j。那如果i先减到0了,那就代表我们已经回到了第一行,该--j了。如果j先减到0了,那就代表我们已经回到了第一列,该--i了。当i与j都小于0时,就代表着循环该结束了。由此,我们就构建出了一条由终点指向起点的路径。我们再reverse一下,不就得到了一条由起点指向终点的路径嘛?
但我们不能从起点往终点去构建路径,因为我发现,如果这么去构建路径了话,会出现遇到石头,但它不停下的情况。
整个题解的具体代码如下:
class Solution {
public:
vector<vector<int>> result; //最终的结果集
vector<vector<int>> pathWithObstacles(vector<vector<int>>& obstacleGrid) {
int row = obstacleGrid.size();
int cal = obstacleGrid[0].size();
if(obstacleGrid[0][0] == 1 || obstacleGrid[row-1][cal-1] == 1) return {};
vector<vector<long long>> dp(row,vector<long long>(cal,0)); // 记录到达每个格子的路径数量
dp[0][0] = 1;
// 初始化第一行和第一列
for(int i = 1; i < row; i++){
if(obstacleGrid[i][0] == 0 && dp[i-1][0] == 1) dp[i][0] = 1;
}
for(int j = 1; j < cal; j++){
if(obstacleGrid[0][j] == 0 && dp[0][j-1] == 1) dp[0][j] = 1;
}
// 动态规划
for(int i = 1; i < row; i++){
for(int j = 1; j < cal; j++){
if(obstacleGrid[i][j] == 0){
dp[i][j] = dp[i-1][j]%10000000 + dp[i][j-1]%10000000;
}
}
}
// 如果到达终点的路径数量为0,则无法到达终点
if(dp[row-1][cal-1] == 0) return {};
// 构造路径
int i = row - 1, j = cal - 1;
while(i >= 0 && j >= 0){
result.push_back({i,j});
if(i == 0) j--;
else if(j == 0) i--;
else{
if(dp[i-1][j] > 0) i--;
else j--;
}
}
reverse(result.begin(), result.end());
return result;
}
};

时间复杂度分析:
空间复杂度分析:
因此,该算法的时间复杂度为 O(row * cal),空间复杂度为 O(row * cal)。
比起回溯法,动态规划的时间复杂度与空间复杂度已经降了很低很低了。
这道题是一道用来练习动态规划与回溯法的好题。你可以仔细去揣摩里面的每一个步骤,它为什么要这么做?会导致什么后果。做完这道题我受益良多。
我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我想用ruby编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序
我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此
我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r
刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr
我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R
如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否
是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在
前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源