算法练习(特辑)常见算法的基本结构

本文详细探讨了二叉树的深度优先搜索(DFS)和广度优先搜索(BFS)算法,包括递归实现的中序遍历、路径总和、构造二叉树、最近公共祖先问题,以及迭代方法如层序遍历。同时涉及单调栈和回溯在算法中的应用,如二分查找和全排列问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、二叉树的DFS(深度优先算法)

1、递归

  • 基本递归(以中序遍历为例)
	public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result=new ArrayList<>();
        order(root,result);
        return result;
    }

    public void order(TreeNode node,List<Integer> result){	//递归携带的参数看情况设置
        if(node==null){	//这里要写一个递归终止条件
            return;
        }
        //这里可插入其他业务逻辑
        order(node.left,result);    //进入左节点
        result.add(node.val);		//业务代码,此处为中序遍历例
        order(node.right,result);	//进入右节点
        //这里可插入其他业务逻辑
    }
  • 递归+前缀和+从下到上传递累加值(力扣437:路径总和)
	public int pathSum(TreeNode root, int targetSum) {
        Map<Long,Integer> prefix=new HashMap<Long,Integer>();
        prefix.put(0L,1);
        return dfs(root,prefix,0,targetSum);
    }

    public int dfs(TreeNode root,Map<Long,Integer> prefix,long sum,int targetSum){
        if(root==null){
            return 0;
        }
        int result=0;	//结果累加值
        sum+=root.val;	//当前值
        result=prefix.getOrDefault(sum-targetSum,0);	//若前缀和Map中存在计算总值-目标值对应的值,说明计算总值-这段前缀和=目标值
        prefix.put(sum,prefix.getOrDefault(sum,0)+1);	//存储前缀和
        result+=dfs(root.left,prefix,sum,targetSum);	//左递归
        result+=dfs(root.right,prefix,sum,targetSum);	//右递归
        prefix.put(sum,prefix.getOrDefault(sum,0)-1);	//从当前节点返回后,要把对应前缀和去掉
        return result;	//传递累加值
    }
  • 递归+分治(力扣105:从前序和中序遍历序列构造二叉树)
	int[] preorder;
    HashMap<Integer, Integer> dic = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        this.preorder = preorder;
        for(int i = 0; i < inorder.length; i++)
            dic.put(inorder[i], i);
        return dfs(0, 0, inorder.length - 1);
    }

    public TreeNode dfs(int curr,int left,int right){
        if(left>right){
            return null;
        }
        TreeNode node=new TreeNode(preorder[curr]);
        int i=dic.get(preorder[curr]);
        node.left=dfs(curr+1,left,i-1);
        node.right=dfs(curr+i-left+1,i+1,right);
        return node;
    }
  • 递归+标记(力扣236:二叉树的最近公共祖先)
	public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null||root==p||root==q){	//若相等,则将该节点标记并往上传
            return root;
        }
        TreeNode left=lowestCommonAncestor(root.left,p,q);	//左递归
        TreeNode right=lowestCommonAncestor(root.right,p,q);	//右递归
        //放在递归最后是因为要从下往上排查
        if(left==null&&right==null){	//若两边都没有找到目标节点,则返回的是null
            return null;
        }
        if(left==null){		//如果只是一边没找到,说明另外一边找到了
            return right;
        }
        if(right==null){	//同理
            return left;
        }
        return root;	//如果左右两边都找到了,说明这就是最近的公共祖先,直接往上传
    }

2、迭代

  • 基本迭代(中序遍历例)
	public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        Deque<TreeNode> stack = new LinkedList<TreeNode>();	//Stack类已过时,使用Deque代替栈
        while (root != null || !stack.isEmpty()) {	//
            while (root != null) {	//后进先出,从最右侧的节点开始
                stack.push(root);	//push入栈
                root = root.left;
            }
            //这里可插入其他业务逻辑
            root = stack.pop();		//pop出栈
            res.add(root.val);		//业务代码,此处为中序遍历例
            root = root.right;		//回溯时再进入右侧节点
            //这里可插入其他业务逻辑
        }
        return res;
    }

二、二叉树的BFS(广度优先算法、层序遍历)

  • 迭代
public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        if (root != null) {	//队列中加入初始节点
            queue.offer(root);
        }
        while (!queue.isEmpty()) {	//只要队列不为空就一直进行
            int n = queue.size();
            List<Integer> level = new ArrayList<>();
            for (int i = 0; i < n; i++) { 	//只循环当前层拥有的节点次数
                TreeNode node = queue.poll();
                level.add(node.val);
                if (node.left != null) {	//若不为空就把下一层的节点加入,并且排到后面
                    queue.offer(node.left);
                }
                if (node.right != null) {	//左右各一次
                    queue.offer(node.right);
                }
            }
            res.add(level);
        }
        return res;
    }

三、图论的DFS

  • 核心思想在于上下左右的操作,以及边界的判断
  • 例题:力扣200.岛屿数量
	int result=0;

    public int numIslands(char[][] grid) {
        int result=0;
        for(int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                if(grid[i][j]=='1'){
                    result++;
                    dfs(grid,i,j);
                }
            }
        }
        return result;
    }

    public void dfs(char[][] grid, int r, int c) {
        // 判断 base case
        if (!inArea(grid, r, c)) {
            return;
        }
        // 如果这个格子不是岛屿,直接返回
        if (grid[r][c] != '1') {
            return;
        }
        grid[r][c] = '0'; // 将格子标记为「已遍历过」
        
        // 访问上、下、左、右四个相邻结点
        dfs(grid, r - 1, c);
        dfs(grid, r + 1, c);
        dfs(grid, r, c - 1);
        dfs(grid, r, c + 1);
    }

    // 判断坐标 (r, c) 是否在网格中
    public boolean inArea(char[][] grid, int r, int c) {
        return 0 <= r && r < grid.length 
                && 0 <= c && c < grid[0].length;
    }

四、二分查找

  • 核心思想在于两点,一是退出循环的条件是左小于右,二是每次都把范围缩小一半
  • 参考(力扣704.二分查找
    public int search(int[] nums, int target) {
        int left=0,right=nums.length-1;
        while(left<=right){
            int mid=(right-left)/2+left;
            int num=nums[mid];
            if(num==target){
                return mid;
            }else if(num>target){
                right=mid-1;
            }else{
                left=mid+1;
            }
        }
        return -1;
    }

五、单调栈

  • 核心思想在于维护栈内保持单调,当栈顶元素小于或大于要放入的元素时,就持续弹出
 Stack<Integer> stack = new Stack<>();
 int[] nums = new int[]{9, 6, 4, 3, 1, 2, 6, 3};
 for (int num : nums) {
 	//如果栈顶元素小于要放入的元素,就弹出。
     while (!stack.isEmpty() && stack.peek() < num) {	//核心步骤
         stack.pop();
        //后续业务
     }
     stack.push(num);
 }

六、回溯

  • 回溯的核心思想是当后续流程走完后,返回到原始状态
  • 如题:力扣46.全排列
	public void dfs(List<Integer> list,int n,int[] nums){
        if(n==nums.length){
            result.add(new ArrayList(list));
            return;
        }
        for(int i=0;i<nums.length;i++){
            if(flag[i]==0){
                list.add(nums[i]);	//满足条件后进行标记
                flag[i]=1;
                dfs(list,n+1,nums);	//递归到下一层
                flag[i]=0;			//返回到该层时,把原先的标记清除
                list.remove(list.size()-1);
            }
        }
    }

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值