如何通过递归来解决这个问题?

时间:2015-08-13 17:18:56

标签: algorithm recursion dynamic-programming

我通过递归来解决这个问题需要一些帮助。

问题:

一个懒惰的游客希望尽可能多地访问一个城市中的许多有趣的地点,而不需要更进一步。从他位于城市西北角的酒店出发,他打算到城市的东南角散步,然后再往后走。走到东南角时,他只会向东或向南走,当走回西北角时,他只会向北或向西走。在研究了城市地图之后,他意识到任务并非如此简单,因为某些区域被封锁了。所以他恳请你写一个程序来解决他的问题。

给定城市地图(2D网格),其中可步行区域用"。"标记,有趣的位置用" *"标记,被阻挡的区域用&标记。 #34;#",确定他可以访问的最大感兴趣的位置数。访问过两次的地点只计算一次。

*........
.....**#.
..**...#*
..####*#.
.*.#*.*#.
...#**...
*........
答案:7。

我最初的想法是分两部分来解决这个问题。

1)当游客从左上角开始。

2)当游客从右下角开始时。

对于答案,将它们添加到两个部分,就是这样。

这是第一部分的方法。

 i and j were initially 0, 0.
 path(int i, int j){

    if(i == H && j == W){
        return (strV[i][j] == '*')?1:0;
    }
    if(i >H || j >W || i<0 || j<0){
        return 0;
    }
    if(visited[i][j] == 1){
        //cout<<"vis";
        return 0;
    }

    if(strV[i][j] == '#' ){
        return 0;
    }

    visited[i][j] =1;
    cout<<i<<" "<<j<<"\n";

    // as i can go only down and right ,for the first part.
    // south and east.
    return max(path(i+1, j), path (i, j+1) ) + (strV[i][j] == '*')?1 :0;

}

同样地,我尝试计算第二部分。 由于我维护了访问过的阵列,因此不可能重新审视同一点。 希望这应该满足他们在问题中提到的条件。 我不确定这是否会解决问题,它会给出错误答案。

以下是问题链接:http://www.spoj.com/problems/TOURIST/

感谢您的时间。

3 个答案:

答案 0 :(得分:2)

一些观察结果将帮助您更轻松地解决这个问题:

  
      
  1. 在回程中,游客只能在网格中向左或向上移动。这相当于在第一次旅行中向右或向下移动。所以   基本上这两次旅行没有区别。
  2.   
  3. 由于两次旅行基本相同,我们只需要考虑第一次旅行。我们可以从(0,0)开始同时派2名游客   细胞。所以我们的状态将由(x1,y1,x2,y2)组成,其中(x1,y1)是   第一位游客的位置和(x2,y2)是第二位游客的位置   网格。
  4.   
  5. 在每个步骤中,游客可以向右或向下移动,因此我们有4个移动选择(每个游客有2个选择)。
  6.   
  7. 如果两个游客都在同一个小区(x1 == x2和y1 == y2),那么如果该小区是特殊的,我们只能添加1个结果。
  8.   
  9. 该算法的时间复杂度为O(n ^ 4),可能无法及时运行。我们可以将复杂度降低到O(n ^ 3)。如果我们知道   第一位游客的位置是(x1,y1)第二位的x坐标   游客是x2然后我们必须有x1 + y1 = x2 + y2,因为它们都覆盖相同   相同时间的距离。所以y2 = x1 + y1-x2,我们的状态取决于   仅限于(x1,y1,x2)。
  10.   

代码:

const int N 100+5

vector<string> board;

int dp[N][N][N];  // initialize with -1

int calc(int x1, int y1, int  x2) {
    int n=board.size(), m=board[0].size();
    int y2=x1+y1-x2;
    if(x1>=n || x2>=n || y1>=m || y2>=m) return 0;   // out of range
    if(board[x1][y1]=='#' || board[x2][y2]=='#') return 0;  // path blocked so its an invalid move
    if(dp[x1][y1][x2]!=-1) return dp[x1][y1][x2];  // avoid recalculation
    int res=0;
    if(board[x1][y1]=='*') res++;
    if(board[x2][y2]=='*') res++;
    if(board[x1][y1]=='*' && x1==x2 && y1==y2) res=1;  // both tourist on same spot
    int r=calc(x1+1, y1, x2);        // first tourist down second tourist right
    r=max(r, calc(x1+1, y1, x2+1));  // first tourist down second tourist down
    r=max(r, calc(x1, y1+1, x2));    // first tourist right second tourist right
    r=max(r, calc(x1, y1+1, x2+1));  // first tourist right second tourist down
    res+=r;
    dp[x1][y1][x2]=res;  // memoize
    return res;
} 

答案 1 :(得分:1)

你的方法有一些缺陷。有些使得它提供了错误的解决方案,而有些只会导致搜索效率低下。

导致错误解决方案的缺陷是:

  • 在每条路上添加最佳路径上的有趣位置将不是一个有效的答案,因为您还没有打算从您在返回路径的路径中单向访问过的有趣景点。 (这也只是意味着你的回答只会是单向数的两倍,因为最好的路径反向是相同的路径)。实际上,您需要从前进的最后阶段开始搜索,以便正确地考虑已经访问过的
  • 在一个值为0的块路径中终止搜索。需要高度惩罚(至少H + V-2)所以只是到达一个区块并放弃(没有到达角落)赢得了被视为有效路径
  • 当您回溯时,不会访问位置。这将导致不计算可能在与被评估的路径不同的路径中访问过的有趣位置
  • 在访问过的位置终止。问题陈述中没有任何内容禁止两次穿越同一位置。无论如何只考虑其中一个旅行方向,它永远不会发生,因为你只能走两个不允许回头的方向

导致搜索效率低下的缺陷并不是在搜索最佳路径之前简化搜索域。通过在它们之间存在有效(非阻塞)路径时创建仅包含有趣位置作为节点和它们之间的连接的图形,可以显着减少搜索空间(取决于有趣位置的稀疏程度和块的丰富程度)

尽可能接近您的尝试我的建议是:

  • 访问某个位置时,只需添加一个,这样您就可以在返回最佳路径之前删除一个(如果您在搜索返回路径时也只是设置为零,则可能会出现问题)
  • 当你以一种方式到达路径的尽头时(i == H-1&amp;&amp; j == W-1,因为你从0开始而不是1,1)以相反的方式进行类似的搜索保留访问过的位置信息
  • 将您感兴趣的位置增加更改为(已访问[i] [j] == 0)&amp;&amp; (strV [i] [j] ==&#39; *&#39;)所以你不能在同一条路径上两次计算有趣的位置
  • 通过至少返回 - (H + V-2)来惩罚进入某个区块,但MIN_INT_VALUE或其间的任何内容也会起作用
  • 当你到达你已经去过的地方时不要放弃

希望这是有帮助的

答案 2 :(得分:1)

这是JavaScript的解决方案。它递归尝试所有有效方向SE和NW,并保存其结果,如果它大于当前最大结果。

function solve(grid) {
    var maxVisited = 0;
    var rows = grid.length;
    var cols = grid[0].length;
    // used to make a new copy of the visited set
    function copy(obj) {
      return JSON.parse(JSON.stringify(obj));
    }
    // used to test a current location returning false or the grid value
    function move(r, c) {
      if(r < 0 || c < 0 || r >= rows || c >= cols) { return false; }
      if(grid[r][c] === "#") { return false; }
      return grid[r][c];
    }
    // recursively solve the problem
    function solveRec(r, c, grid, goBack, visited) {
      // end trip, check if visited is greater than current max
      if(goBack === true && r === 0 && c === 0) {
        var count = Object.keys(visited).length;
        if(count > maxVisited) { maxVisited = count; }
        return;
      }
      var m = move(r, c);
      // invalid move
      if(m === false) { return; }
      // place of interest, add to visited set
      if(m === "*") { visited["" + r + ":" + c] = true; }
      // try a move, if at S.E. go back home (turn around)
      if(goBack === true || (r === rows-1 && c === cols-1)) {
        // north west
        solveRec(r-1, c, grid, true, copy(visited));
        solveRec(r, c-1, grid, true, copy(visited));
      } else { 
        // south east
        solveRec(r+1, c, grid, false, copy(visited));
        solveRec(r, c+1, grid, false, copy(visited));
      }
    }
    solveRec(0, 0, grid, false, {});
    return maxVisited;
}

console.log(solve([
  "*........".split(""),
  ".....**#.".split(""),
  "..**...#*".split(""),
  "..####*#.".split(""),
  ".*.#*.*#.".split(""),
  "...#**...".split(""),
  "*........".split("")
])); // 7

编辑以下是jsfiddle http://jsfiddle.net/reeyws86/您可能需要添加逻辑以避免角网情况,例如网格为空或只有1平方时。