广度优先搜索具体操作是一层一层地进行遍历, 每层遍历都是以上一层遍历到的节点作为起点, 遍历其能访问到的所有节点. 需要注意的是, 遍历过的节点不能被再次遍历. 如下:

图片说明

第一层: 0 -> {6, 2, 1, 5};

第二层: 6 -> {4}; 2 -> {}; 1 -> {}; 5 -> {3}

第三层: 4 -> {}; 3 -> {}

每一层遍历的节点到根节点的距离都相同. 假设 di 表示第 i 个节点与根节点的距离, 可以推导出一个结论: 对于先遍历的节点 i 与后遍历的节点 j, 有 di <= dj. 利用这个结论, 可以求解最短路径等 最优解 问题, 即第一次遍历到目标节点时, 所经过的路径为最短路径.

1. 完全平方

给定正整数 n, 找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n. 你需要让组成和的完全平方数的个数最少.

我们可以对问题进行抽象: 将 n 当做一个节点, 和其相连的节点是与其相差一个完全平方数的数, 如下所示, 我们就可以得到一个无权图(可能更像一个多叉树), 这样原问题就转化为: 求这个无权图中从 n 到 0 的最短路径, 使用广度优先搜索.

                                n = 7
                              6       3
                           5    2     2
                          4 1   1     1
                          0 0   0     0

下面使用代码实现 BFS , 需要额外考虑的是:

  • 使用队列: 存储遍历到的节点.

  • 使用布尔数组: 标记遍历过的节点, 防止重复遍历.

public int numSquares(int n) {
    if (n == 0) return 0;
    Queue<Integer> queue = new ArrayDeque<>();
    boolean[] visited = new boolean[n + 1]; // 标记当前节点是否访问过
    // 从n点出发寻找最短路径, 当前步数为0
    queue.add(n);
    visited[n] = true;
    int depth = 0;
    while (!queue.isEmpty()) {
        depth++; // 步数加一
        int size = queue.size(); // 上一层的节点数
        // 以上一层遍历到的节点作为起点
        while (size-- > 0) {
            int cur = queue.poll();
            // 遍历其能访问到的所有节点
            for (int i = 1; i * i <= cur; i++) {
                int next = cur - i * i;
                // 走到节点0找到最短路径, 返回步数
                if (next == 0) return depth;
                // 若当前节点之前访问过, 则跳过(若再走, 步数一定大于之前访问的步数)
                if (visited[next]) continue;
                visited[next] = true;
                queue.add(next);
            }
        }
    }

    // n的组成除1外无其他完全平方数
    return n;
}

Note: 上述解法是标准的bfs模板, 利用queue.size()得到每一层的节点数并进行遍历, 同时也方便记录当前节点所在层数. 当遍历到目标节点时, 层数即为最短路径.

2. 参考