文章目录

  • 一、二分查找(BinarySearch)
  • 约定
  • 模板 1:while (left <= right)
  • 模板 2:while (left < right),推荐使用
  • 示例
  • 二、广度优先搜索(Breadth First Search,BFS)
  • 示例
  • 三、深度优先搜索(Depth First Search,DFS)
  • 示例

一、二分查找(BinarySearch)

详细请查看:【算法】二分查找(Java 版) 时间复杂度:每次折半查找,所以时间复杂度为 O(logN)。 空间复杂度:只使用了 3 个变量,所以空间复杂度为 O(1)。

约定

nums 为一个升序数组,我们把待查找区间的左边界下标设为 left,右边界下标设为 right,中间位置下标设为 mid。

模板 1:while (left <= right)

思路:在循环体内部查找元素(解决简单问题时有用),即考虑下一轮目标元素应该在哪个区间。

/**
	 * 二分查找法<br>
	 *   <li>模板 1:while (left <= right)</li><br>
	 * @param nums 待查找数组
	 * @param target 待查找目标值
	 * @return 目标值在数组中的下标<br>
	 *         未查找到就返回 -1
	 */
	public static int binarySearch1(int[] nums, int target) {

		// 特殊用例判断
		int len = nums.length;
		if (len == 0) {
			return -1;
		}

		// 在 [left, right] 区间里查找 target
		int left = 0;
		int right = len - 1;
		while (left <= right) {
			// 为了防止 left + right 整形溢出,写成如下形式
			int mid = left + (right - left) / 2;

			// 找到目标值,直接返回
			if (nums[mid] == target) {
				return mid;
			} else if (nums[mid] > target) {
				// [mid, right]都大于目标值,下一轮查找区间:[left, mid - 1]
				right = mid - 1;
			} else {
				// 此时:nums[mid] < target
				// [left, mid]都小于目标值,下一轮查找区间:[mid + 1, right]
				left = mid + 1;
			}
		}

		return -1;
	}

模板 2:while (left < right),推荐使用

思路:在循环体内部排除元素(解决复杂问题时非常有用),即考虑中间元素 nums[mid] 在什么情况下不是目标元素。

/**
	 * 二分查找法<br>
	 *   <li>模板 2(下取整):while (left < right)</li><br>
	 * @param nums 待查找数组
	 * @param target 待查找目标值
	 * @return 目标值在数组中的下标<br>
	 *         未查找到就返回 -1
	 */
	public static int binarySearch2_floor(int[] nums, int target) {

		// 特殊用例判断
		int len = nums.length;
		if (len == 0) {
			return -1;
		}

		// 在 [left, right] 区间里查找 target
		int left = 0;
		int right = len - 1;
		while (left < right) {
			// 选择中位数时下取整
			int mid = left + (right - left) / 2;

			if (nums[mid] < target) {
				// [left, mid]都小于目标值,下一轮查找区间是 [mid + 1, right]
				left = mid + 1;
			} else {
				// 此时:nums[mid] >= target
				// [mid, right]都大于等于目标值,下一轮查找区间是 [left, mid]
				right = mid;
			}
		}

		// 退出循环的时候 left == right,程序只剩下一个元素没有看到。
		// 视情况,是否需要单独判断 left(或者 right)这个下标的元素是否符合题意。
		return nums[left] == target ? left : -1;
	}

示例

【LeetCode】1095. 山脉数组中查找目标值(二分查找)

二、广度优先搜索(Breadth First Search,BFS)

思路:类似于树的按层次遍历的过程,使用队列保存未被检测的节点,节点按照由近及远的次序被检测和进出队列。

广度优先搜索就是把一些问题抽象成图,从一个点开始,向四周开始扩散(齐头并进),可以回答两类问题:

  • 从节点 A 出发,有前往节点 B 的路径吗(连通性)?
  • 从节点 A 出发,前往节点 B 的哪条路径最短(最优解)?
public class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;

    public TreeNode(int x) {
        val = x;
    }
}

public class BFS {

    public static void main(String[] args) {

        TreeNode root = new TreeNode(1);
        root.left = new TreeNode(2);
        root.right = new TreeNode(3);
        root.left.left = new TreeNode(4);

        int target = 4;

        System.out.println(bfs(root, target));
    }

    /**
     * BFS 遍历二叉树
     * @param root 二叉树根节点
     * @param target 目标值
     * @return 返回目标值距根节点的最短距离
     */
    public static int bfs(TreeNode root, int target) {

        // BFS 使用队列
        Queue<TreeNode> q = new LinkedList<>();
        // 访问标志,避免走回头路
        Set<TreeNode> visited = new HashSet<>();

        // 初始节点放入队列 q
        q.offer(root);
        visited.add(root);
        // 记录扩散的步数
        int step = 0;

        while (!q.isEmpty()) {
            int size = q.size();
            // 将当前队列中的所有节点向四周扩散
            for (int i = 0; i < size; i++) {
                TreeNode cur = q.poll();
                // 注意:判断是否到达终点
                if (cur.val == target)
                    return step;
                // 将与 cur 相邻且未被访问的节点加入队列
                // 对于二叉树,就是左右子节点;
                // 对于二维数组,就是未越界的上下左右 4 个元素。
                if (cur.left != null) {
                    if (!visited.contains(cur.left)) {
                        q.offer(cur.left);
                        visited.add(cur.left);
                    }
                }
                if (cur.right != null) {
                    if (!visited.contains(cur.right)) {
                        q.offer(cur.right);
                        visited.add(cur.right);
                    }
                }
            }

            // 划重点:更新步数在这里
            step++;
        }

        return step;
    }
}

示例

【LeetCode】200. 岛屿数量(BFS | DFS)

三、深度优先搜索(Depth First Search,DFS)

思路:类似于树的先根遍历,使用栈保存未被检测的节点,节点按照深度优先的次序被访问并依次被压入栈中,并以相反的次序出栈进行新的检测。

深度优先搜索是一条路径走到底(不撞南墙不回头),可以回答:

  • 从节点 A 出发,有前往节点 B 的路径吗(连通性)?

深度优先搜索实际上采用的就是回溯算法。 解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考 3 个问题: 1、选择路径:已经做出的选择; 2、选择列表:你当前可以做的选择; 3、结束条件:到达决策树底层,无法再做选择的条件。

public class Permute_46 {

    private List<List<Integer>> res = new LinkedList<>();

    public List<List<Integer>> permute(int[] nums) {

        // 记录路径
        LinkedList<Integer> track = new LinkedList<>();

        backTrack(nums, track);

        return res;
    }

    /**
     * DFS 全排列
     * <li>选择路径:记录在track中</li><br>
     * <li>选择列表:nums中不存在于track中的那些元素</li><br>
     * <li>结束条件:nums中的元素都在track中出现</li><br>
     * 
     * @param nums 待排列数组
     * @param track 选择路径
     */
    public void backTrack(int[] nums, LinkedList<Integer> track) {

        // 触发结束条件(找到一种排列)
        if (track.size() == nums.length) {
            res.add(new LinkedList<Integer>(track));
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            // 排除不合法的选择(选择的元素不能重复)
            // contains 的时间复杂度是 O(N),可以通过访问数组用空间换时间
            if (track.contains(nums[i])) {
                continue;
            }

            // 做选择
            track.add(nums[i]);
            // 进入下一层决策树
            backTrack(nums, track);
            // 撤销选择
            track.removeLast();
        }
    }
}

示例

【LeetCode】200. 岛屿数量(BFS | DFS)