Leetcode Tree and Binary Tree

Leetcode Tree and Binary Tree

LeetCode 144. Binary Tree Preorder Traversal

Description

Given the root of a binary tree, return the preorder traversal of its nodes' values.

Solution one - Recursive

class Solution {
    private List<Integer> list = new LinkedList<>();
    public List<Integer> preorderTraversal(TreeNode root) {
        // if root is null
        if(root == null) {
            List<Integer> list2 = new LinkedList<>();
            return list2;
        } else {
            list.add(root.val);
        }
        // preorder traversal is root , left child, right child
        if (root.left != null) preorderTraversal(root.left);
        if (root.right != null) preorderTraversal(root.right);

        return list;
    }
}

Solution two - Stack

class Solution {
    private List<Integer> list = new LinkedList<>();
    public List<Integer> preorderTraversal(TreeNode root) {
        // use stack
        Stack<TreeNode> stack = new Stack<TreeNode>();
        // if root is null
        if(root == null) {
            return list;
        } else  {
            stack.push(root);
            while(!stack.isEmpty()) {
                TreeNode temp = stack.pop();
                list.add(temp.val);
                // 因为栈是先进后出,为了保证先序遍历根节点之后是先读取左子树的节点,所以左子树节点后进栈
                if(temp.right != null) {
                    stack.push(temp.right);

                }
                if(temp.left != null) {
                    stack.push(temp.left);

                }
            }
        }
        return list;
    }
}

LeetCode 94. Binary Tree Inorder Traversal

Description

Given the root of a binary tree, return the inorder traversal of its nodes' values.

Solution one - Resursive

class Solution {
    List<Integer> list = new LinkedList<Integer>();
    public List<Integer> inorderTraversal(TreeNode root) {
        if( root == null) {
            List<Integer> temp = new LinkedList<Integer>();
            return temp;
        } 
        // Inorder traversal: left child, root, right child
        if(root.left != null) inorderTraversal(root.left);
        list.add(root.val);
        if(root.right != null) inorderTraversal(root.right);
        return list;
    }
}

Solution two - Stack

class Solution {
    private List<Integer> list = new LinkedList<>();
    public List<Integer> inorderTraversal(TreeNode root) {
        // use stack
        Stack<TreeNode> stack = new Stack<TreeNode>();
        // if root is null
        if(root == null) {
            return list;
        } else  {
            // 当栈不为空,或者当前节点不为空时继续遍历
            while(root != null || !stack.isEmpty()) {
                // 不断将左节点压入栈,直到最左边
                while (root != null) {
                    stack.push(root);
                    root = root.left;
                }
                // 弹出栈顶元素,并存入列表
                root = stack.pop();
                list.add(root.val);
                // 遍历当前节点右子树
                root = root.right;
            }
        }
        return list;
    }
}

LeetCode 145. 二叉树的后序遍历

Description

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。

Solution one - Rescursive

class Solution {
    List<Integer> list = new LinkedList<Integer>();
    public List<Integer> postorderTraversal(TreeNode root) {
        if(root == null) return new LinkedList<Integer>();
        if(root.left != null)  postorderTraversal(root.left);
        if(root.right != null) postorderTraversal(root.right);
        list.add(root.val);
        return list;
    }
}

LeetCode 701. 二叉树搜索树中的插入操作

Description

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

Solution one - 通过递归找到值应该插入的位置

class Solution {
    public TreeNode insertIntoBST(TreeNode root, int val) {
        // 如果树为空,直接插入
        if(root == null) {
            root = new TreeNode(val);
        } else {
            if(val < root.val) {
                root.left = insertIntoBST(root.left, val);
            } else {
                root.right = insertIntoBST(root.right, val);
            }
        }
        return root;
    }
}

LeetCode 700. 二叉搜索树中的搜索

Description

给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。

Solution one - recursive

class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
        // 递归到某结点,如果传进来的root为空,或者该节点的值就是要找的值,直接返回该节点
        if(root == null || root.val == val) return root;
        if(root.val < val) {
            // 该节点的值小于要找的值,递归右子树
            root = searchBST(root.right, val);
        } else {
            root = searchBST(root.left, val);
        }
        return root;
    }
}

LeetCode 450. 删除二叉搜索树中的节点

Description

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。

Solution one

思路:找到删除的结点可以使用递归找到该节点,但是删除了该节点之后,二叉搜索树变成了什么样子?

  1. 如果删除的是叶子结点,直接删除
  2. 如果删除的不是叶子结点,为保持二叉搜索树的性质,可以找该节点的左子树中的最大节点,或者右子树的最小节点,替换掉该节点,
    1. 删除节点只有左子树,将左子树作为新的子树,返回要删除节点的左孩子
    2. 删除节点只有右子树,将右子树作为新的子树,返回要删除节点的右孩子
    3. 删除节点左右孩子都有
      1. 找到左子树中的最大节点,这个左子树最大节点一定没有右孩子,在这个左子树中最大节点调用删除递归,并返回值,然后更新root并返回
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
// 1. 如果删除的是叶子结点,直接删除
// 2. 如果删除的不是叶子结点,为保持二叉搜索树的性质,可以找该节点的左子树中的最大节点,或者右子树的最小节点,替换掉该节点,
//    1. 删除节点只有左子树,将左子树作为新的子树,返回要删除节点的左孩子
//    2. 删除节点只有右子树,将右子树作为新的子树,返回要删除节点的右孩子
//    3. 删除节点左右孩子都有
//       1. 找到左子树中的最大节点,这个左子树最大节点一定没有右孩子,在这个左子树中最大节点调用删除递归,并返回值,然后更新root并返回

        // 处理空树
        if(root == null) return root;
        // 找到要删除的节点
        if(root.val < key) {
            root.right = deleteNode(root.right, key);
            return root;
        }
        if(root.val > key) {
            root.left = deleteNode(root.left, key);
            return root;
        }
        if(root.val == key) {
            if(root.left == null && root.right == null) { return null;}
            if(root.left != null && root.right == null) { return root.left; }
            if(root.left == null && root.right != null) { return root.right; }
            if(root.left != null && root.right != null) {
                // 找到左子树的最大节点,再递归左子树
                TreeNode temp = root.left;
                while(temp.right != null) {
                    temp = temp.right;
                }
                root.left = deleteNode(root.left, temp.val);  // 删除左子树最大节点
                temp.left = root.left;      // 左子树最大节点值,替换key值
                temp.right = root.right;
                return temp;
            }
        }
        return root;
    }
}

LeetCode 104. 二叉树的最大深度

Description

给定一个二叉树 root ,返回其最大深度。

二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

Solution one - BFS 层次遍历

遍历每一层,路径上节点数+1

class Solution {
    public int maxDepth(TreeNode root) {
        int depth = 0;
        if(root == null) return depth;
        Queue<TreeNode> dq = new LinkedList<TreeNode>();
        dq.add(root);
        while(!dq.isEmpty()) {
            int size = dq.size();
            for(int i = 0; i < size; i++) {
                TreeNode temp = dq.poll();
                if(temp.left != null) dq.add(temp.left);
                if(temp.right != null) dq.add(temp.right);
            }
            depth++;
        }
        return depth;
    }
}

Solution two - DFS Recursive

class Solution {
    int res = 0;
    public int maxDepth(TreeNode root) {
        // DFS
        if(root == null) return 0;
        int leftHeight = maxDepth(root.left);
        int rightHeight = maxDepth(root.right);
        return Math.max(leftHeight, rightHeight) + 1;
    }
}

Read more

如何设计秒杀系统

什么是秒杀设计 秒杀设计 是一种针对高并发、大流量场景的系统设计,通常应用于电商活动中(如限时抢购、促销等),用户在非常短的时间内大量涌入系统,抢购有限的商品或优惠。这种场景下,系统需要能够承受巨大的瞬时并发请求,同时保证数据的一致性和业务的正确性。 秒杀技术分析 秒杀系统贯穿活动、商品和下单三个领域,涵盖了页面静态化、接口限流、Redis预减库存、异步下单和接口动态化等技术。 需要迎接的挑战有: 1. 高并发和压力测试:秒杀活动会带来巨大的流量,服务器和数据库的并发处理能力是关键。 2. 保证数据一致性:抢购涉及到商品库存的实时减少,需要保证在高并发场景下库存数据的准确性和一致性 3. 防止超卖和重复购买:确保同一商品不会被重复购买,同时避免超卖,即使是在极端的高并发情况下 4. 分布式锁和限流:使用分布式锁来保护关键资源,限制用户访问频率以免系统崩溃 5. 性能优化:包括代码层面的优化、数据库的优化、缓存的使用等,以提高系统性能和响应速度。 页面静态化 秒杀页面静态化是将动态生成的秒杀页面转换为静态HTML页面,从而提高页面响应速度和系统性能

By Yucan Huang

分布式系统下雪花算法生成全局唯一ID

雪花算法 在分布式系统中,为了避免多个节点生成相同的唯一标识符id,我们通常使用一种全局唯一ID生成策略,而Snowflake Algorithm就是广泛使用的解决方案之一。 雪花算法简介 雪花算法生成的 ID 是一个 64 位的二进制整数,具有以下组成部分: 部分 字节长度 描述 符号位 1bit 固定为0,因为生成的ID是整数 时间戳 41bits 表示时间戳,单位是毫秒,可以存储大约68年的时间 机器ID 10bits 用于标识不同的机器(节点)。10位可以将其分数据中心ID(5位)和机器ID(5位),共支持1024台机器同时生成ID 序列号 12bits 同一毫秒内的序列号,用来区分在同一台机器、同一时间戳下生成的多个ID,最大支持同一毫秒生成4096个唯一ID 代码实现 package com.can.springbootmessage; public class SnowflakeIdGenerator { // 起始时间戳,Long是64位,

By Yucan Huang

责任链设计模式

什么是责任链模式 项目有个请求,需要有对应的服务来处理,然后这个请求可能需要被很多个层级权限的服务来处理。我们将这些处理该请求的服务放在一条链上,链从前往后,是层级更高的服务,第一个服务处理不了,传递到链上的下一个服务,直到这个请求被处理成功。 责任链模式(Chain of Responsibility)是一种处理请求的模式,它让多个处理器都有机会处理该请求,直到其中某个处理器成功处理该请求,责任链模式把多个处理器串成链,然后让请求在链上传递。 1. 如何把多个处理器串成链,然后让请求在链上传递 客户端发送请求,处理类去处理它的请求,所以有个处理方法(handleRequest),处理类要连接在一起,所以**处理类要有一个方法(成员变量nextHandler)**指向下一个处理类。 我们抽象出一个公共的父类,然后去定义不同的处理类,这些处理类通过nextHandler连接起来。 2. 代码演示 package interview.pattern; public class ChainRespPattern { Handl

By Yucan Huang
外卖点餐系统07

外卖点餐系统07

缓存菜品 问题说明 用户通过微信小程序查询菜品,小程序会将请求发送给后端服务,后端就会查询MySQL数据库。 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大,就会造成系统响应慢,用户体验差。 解决思路 * 通过Redis来缓存菜品数据,减少数据库查询操作。 微信小程序查询数据后,会向后端服务发送请求,判断请求的数据在缓存中是否存在,如果存在直接读取缓存,不存在再查询MySQL,并将该数据载入缓存。 * 缓存逻辑分析 微信小程序展示菜品粒度是根据分类展示,所以缓存数据应该根据分类缓存菜品。 1. 每个分类下的菜品保存一份缓存数据 * 使用分类id作为key,分类下的菜品数据使用String字符串保存,分类下的菜品应该是List集合,在Java中可以通过序列化将这个集合转换成Redis的字符串类型 2. 数据库中菜品数据有变更时及时清理缓存数据 代码开发 1. 修改用户端接口 DishContr

By Yucan Huang