从零学算法2850

2850.给你一个大小为 3 * 3 ,下标从 0 开始的二维整数矩阵 grid ,分别表示每一个格子里石头的数目。网格图中总共恰好有 9 个石头,一个格子里可能会有 多个 石头。
每一次操作中,你可以将一个石头从它当前所在格子移动到一个至少有一条公共边的相邻格子。
请你返回每个格子恰好有一个石头的 最少移动次数 。
示例 1:
输入:grid = [[1,1,0],[1,1,1],[1,2,1]]
输出:3
解释:让每个格子都有一个石头的一个操作序列为:
1 - 将一个石头从格子 (2,1) 移动到 (2,2) 。
2 - 将一个石头从格子 (2,2) 移动到 (1,2) 。
3 - 将一个石头从格子 (1,2) 移动到 (0,2) 。
总共需要 3 次操作让每个格子都有一个石头。
让每个格子都有一个石头的最少操作次数为 3 。
示例 2:
输入:grid = [[1,3,0],[1,0,0],[1,0,3]]
输出:4
解释:让每个格子都有一个石头的一个操作序列为:
1 - 将一个石头从格子 (0,1) 移动到 (0,2) 。
2 - 将一个石头从格子 (0,1) 移动到 (1,1) 。
3 - 将一个石头从格子 (2,2) 移动到 (1,2) 。
4 - 将一个石头从格子 (2,2) 移动到 (2,1) 。
总共需要 4 次操作让每个格子都有一个石头。
让每个格子都有一个石头的最少操作次数为 4 。

  • 思路是很容易想到的,无非就是记录下有多余石头的坐标数组 more,然后记录缺少石头的坐标数组 less,然后 dfs,尝试把 more 中的每个多余的石头给 less 中每个坐标的可能性,比如示例 1 中 more 为 [(2,1)],less 为 [(0,2)],那就只有一种给的可能性,需要的步数很简单 abs(0-2)+abs(2-1) 就是 3。这种关于二维坐标的小学数学我就不多说什么了。再看示例 2。more 为 [(0,1),(2,2)],less 为 [(0,2),(1,1),(1,2),(2,1)],由于 more 中有多余石头的坐标,多出不止一块石头,所以为了方便组合出全部的可能性,我们多出几个就视为几个坐标,即 more 为 [(0,1),(0,1),(2,2),(2,2)](我原本还想记录成坐标 xx 多出石头 xx 个,后来发现太复杂了)。此时就是尝试 more 中的四个坐标分配给 less 中四个坐标的可能性,然后看谁结果更小。虽说思路很清晰简单,不过我还是写不出来,最终参照他人代码,最终未经优化的代码如下:
  •   List<Integer> more = new ArrayList<>();
      List<Integer> less = new ArrayList<>();
      // 18 的意思就是遇到类似只有左上角有 9 块石头这种最坏的情况
      // 此时需要的最少步数是 18
      int ans = 18;
      public int minimumMoves(int[][] grid) {
          for(int i=0;i<3;i++){
              for(int j=0;j<3;j++){
              	// 为了方便存储把二维坐标转成一维数字存入
                  if(grid[i][j]==0)less.add(i*3+j);
                  else if(grid[i][j]>1){
                      for(int k=0;k<grid[i][j]-1;k++){
                          more.add(i*3+j);
                      }
                  }
              }
          }
          // 要是没有多的那说明不需要移动,直接返回 0
          if(more.size()==0)return 0;
          dfs(0,0);
          return ans;
      }
      // totalStep:当前情况下所需步数
      // curLess:遍历的当前 less 的数组下标
      public void dfs(int totalStep,int curLess){
      	// 要是计算到现在需要的步数大于等于我当前计算出的最小步数 ans 
      	// 那要你何用,再计算下去也是浪费时间,直接 return
          if(totalStep>=ans)return;
          // more 用完了就可以更新 ans 了
          // 大于 ans 的话已经在上面被 return 了,所以此时 totalStep 小于 ans
          // 所以可以直接更新
          if(more.size()==0)ans=totalStep;
          // 遍历 more
          for(int i=more.size()-1;i>=0;i--){
          	// 弹出 more,很好理解,用过了不能再用了
              int moreIndex = more.remove(i);
              int moreX = moreIndex/3;
              int moreY = moreIndex%3;
              int lessIndex = less.get(curLess);
              int lessX = lessIndex/3;
              int lessY = lessIndex%3;
              int needStep = totalStep + Math.abs(lessX-moreX) + Math.abs(lessY-moreY);
              dfs(needStep,curLess+1);
              // 复原 more。
              // less 会随着 dfs 不断遍历,
              // 而 more 为了保证每个坐标都尝试过先用来填补 less,就需要这个恢复的操作
              // 如果没有这个复原操作,在最外层第一次循环时,随着 dfs 不断深入
              // 第一次递归结束后回溯到最外层时,此时 more 已经为空了,remove(i) 直接报错
              // 比如 more 有两个坐标,less 有两个坐标,最开始进 dfs,i 为 1,more.remove(1)
              // 然后进内层 dfs,内层 dfs 遍历 more,i 从 0 开始,more.remove(0),然后再 dfs 返回了
              // 这时回溯到外层时 i 继续遍历为 0,但是这时 more 已经为空,所以程序会报错
              // 就算不报错,more 也被你用完了,也就是说你把其他可能性都给断绝了
              // 因为这样仅仅只计算了一种可能性而已
              more.add(i,moreIndex);
          }
      }
    
  • 不难想到这里是可能存在重复的匹配的,所以可以添加记忆化避免重复匹配,我一开始想用位运算,不过能力有限。所以直接放别人用位掩码的原文吧:原文

你可能感兴趣的:(算法学习,#,递归,算法,深度优先)