【Day7】每天三算法

题目

CC21 包围区域

描述

现在有一个仅包含‘X’和‘O’的二维板,请捕获所有的被‘X’包围的区域

捕获一个被包围区域的方法是将被包围区域中的所有‘O’变成‘X’

例如:

X X X X
X O O X
X X O X
O X X X

执行完你给出的函数以后,这个二维板应该变成:

X X X X
X X X X
X X X X
O X X X
思考

这道题,有两个思路

思路1:

我们找到处在边缘的 O,和这些 O 相连的 O ,就都不是符合条件的 O

我们将它们排除之后,剩下的 O 就是符合条件的 O,我们再将这些符合条件的 O 改成 X

思路2:

对于所有 O,直接进行 DFS,如果其子遍历符合条件,说明当前位置也符合条件,将当前位置修改后,返回

基于思路二,我写出的代码如下:

public void solve(char[][] board) {
        int R = board.length;
        if (R==0) return;
        int C = board[0].length;
        if (C==0) return;
        boolean[][] visited = new boolean[R][C];
        for (int i = 0; i <R; i++) {
            for (int j = 0; j < C; j++) {
                // 当前位置为 O 且还没有被访问过
                // (没访问是因为我们之前的 dfs 操作,可能已经让改点被访问过了)
                if (board[i][j]=='O' && !visited[i][j]) {
                    dfs(visited,board,i,j);
                }
            }
        }
    }

    public boolean dfs(boolean[][]visited,char[][]board,int r,int c) {
        int R = board.length;
        int C = board[0].length;
        // 如果 O 界限的边界触碰到 board 边缘,说明不符合条件
        if (r<0 || r>=R || c<0 || c>=C) return false;
        // 如果是 X 或者是已经访问过了,那么认为这个位置是可行的
        if (board[r][c]=='X' || visited[r][c]) return true;

        visited[r][c]=true;

        int[] rs = {0,0,1,-1};
        int[] cs = {1,-1,0,0};
        boolean res = true;
        for (int i = 0; i < 4; i++) {
            //这里要保证所有方向都符合条件
            res = res && dfs(visited,board,r+rs[i],c+cs[i]);
        }
        if (res) {
            board[r][c]='X';
        }
        return res;
    }

however ,这段代码跑不通力扣的所有用例。

比如说下面这个用例

反例

我们可以发现,这个位置,是某次 dfs 的最底层

依照我们的代码逻辑

// 如果 O 界限的边界触碰到 board 边缘,说明不符合条件
if (r<0 || r>=R || c<0 || c>=C) return false;
// 如果是 X 或者是已经访问过了,那么认为这个位置是可行的
if (board[r][c]=='X' || visited[r][c]) return true;

visited[r][c]=true;

int[] rs = {0,0,1,-1};
int[] cs = {1,-1,0,0};
boolean res = true;
for (int i = 0; i < 4; i++) {
  //这里要保证所有方向都符合条件
  res = res && dfs(visited,board,r+rs[i],c+cs[i]);
}
if (res) {
  board[r][c]='X';
}

我们可以发现,这个位置是完全符合的,我们便会将其修改为 X,然后返回

但是,这个位置的父位置不符合,所以按理来说,这个位置不应该被修改

西卡西 dfs 没法做到反向回溯,它被修改了,就是被修改了

变相说明,这个方法行不通

所以只能考虑思路 1

题解

这里只有思路1可以解决

// 这里我们采用思路2
// 对所有处于边间的 O 进行深度遍历
public void solve(char[][] board) {
  if (board==null) return;
  int R = board.length;
  if (R==0) return;
  int C = board[0].length;
  boolean[][] visited = new boolean[R][C];
  for (int i = 0; i < R; i++) {
    for (int j = 0; j < C; j++) {
      char currSim = board[i][j];
      if (currSim=='X') visited[i][j]=true;
      else {
        // 发现了处于边界的 O
        if (i==0 || i==R-1 || j==0 || j==C-1) {
          dfs(visited,board,i,j);
        }
      }
    }
  }

  // 将符合要求的 O,修改为 X
  for (int i = 0; i < R; i++) {
    for (int j = 0; j < C; j++) {
      if (!visited[i][j]) {
        board[i][j]='X';
      }
    }
  }
}

// 深度遍历,标记所有不符合要求的 O 的位置
public void dfs(boolean[][] visited,char[][]board,int r,int c) {
  int R = board.length;
  int C = board[0].length;
  if (r<0 || r>=R || c<0 || c>=C) return;
  // dfs 终点
  if (board[r][c]=='X') return;
  if (visited[r][c]) return;

  visited[r][c]=true;

  int[] rs = {0,0,1,-1};
  int[] cs = {1,-1,0,0};
  for (int i = 0; i < 4; i++) {
    dfs(visited,board,r+rs[i],c+cs[i]);
  }

}
小结

这道题告诉我们,有时候正过来思考问题发现行不通,那就需要反过来思考一下

还有一点就是,牛客上跑题发现没法AC,可以去力扣上找一样的题再跑一次,因为力扣会显示跑不通的用例

CC22 二叉树根节点到叶子节点的所有路径和

描述

给定一个仅包含数字 0−9 的二叉树,每一条从根节点到叶子节点的路径都可以用一个数字表示。
例如根节点到叶子节点的一条路径是1→2→3,那么这条路径就用 123 来代替。
找出根节点到叶子节点的所有路径表示的数字之和

例如下面这棵树:

例子

这颗二叉树一共有两条路径,
根节点到叶子节点的路径 1→2 用数字 12 代替
根节点到叶子节点的路径 1→3 用数字 13 代替
所以答案为 12+13=25

思考

这里可以说是一个经典的回溯题目了

我们使用 dfs ,存储每个节点的数字,当到达叶子的时候,存储路径的数字

然后使用回溯的方式,遍历另一个分支

结束之后,我们将所有存储的路径的值进行相加

题解
public int sumNumbers (TreeNode root) {
  List<String> res = new ArrayList<>();
  dfs(root,new StringBuilder(),res);
  //System.out.println(res);
  int sum = 0;
  for (String re : res) {
    sum+=Integer.parseInt(re);
  }
  return sum;
}

public void dfs(TreeNode root, StringBuilder track, List<String> res) {
  if (root==null) return;
  if (root.left==null && root.right==null) {
    track.append(root.val);
    res.add(track.toString());
    removeLast(track);
    return;
  }
  track.append(root.val);
  if (root.left!=null) dfs(root.left,track,res);
  if (root.right!=null) dfs(root.right,track,res);
  // 回溯
  removeLast(track);
}

// 将 StringBuilder 的最后一个元素移除,方便后面的回溯操作
private static void removeLast(StringBuilder sb) {
  sb.replace(sb.length()-1,sb.length(),"");
}
小结

不难

CC23 最长的连续元素序列长度

描述

给定一个无序的整数类型数组,求最长的连续元素序列的长度。

例如:

给出的数组为[1000, 4, 2000, 1, 3, 2],

最长的连续元素序列为[1, 2, 3, 4]. 返回这个序列的长度:4

你需要给出时间复杂度在O(n)之内的算法

思考

思路1:

看到这个问题,我们第一反应,就是先排序,然后再遍历

虽然排序可以到 nlogn 的复杂度,但是遍历的复杂度是 n^2

思路2:

我们可以对上面的做法进行优化

我们可以发现,上面的操作有许多重复的判断

比如 1,2,3,4,7;我们 判断从 1 开始,长度为四

但是下一次,我们又从 2 开始了,这是完全没有必要的,我们完全可以从 7 开始判断,这就要求我们记录之前判断的长度,然后跳着进行判断,这样可以大幅优化时间

思路3:

However,题目要求时间复杂度在 O(n)了,我们上面的方法因为用到了排序,再怎么优化都到不了 O(n)

所以,我们必须另辟蹊径

这里,我们可以使用 set 来存储我们的数

题解

思路3

public int longestConsecutive (int[] num) {
  Set<Integer> set = new HashSet<>();
  for (int n : num) {
    set.add(n);
  }
  int res = Integer.MIN_VALUE;
  for (Integer n : set) {
    // 保证该元素之前的元素没有进行过判断
    // 避免重复劳动
    int count = 1;
    if (!set.contains(n-1)) {
      while (set.contains(++n)) count++;
    }
    res = Math.max(res,count);
  }
  return res;
}
小结

题目不难,但要持续思考怎么进行优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FARO_Z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值