博客

  • LeetCode150-回溯

    面试150题-回溯

    📕文章题目选自LeetCode面试150题-回溯
    😊该文章内容基于作者本人思考总结,如有错误欢迎指正
    🏠题单地址:https://leetcode.cn/studyplan/top-interview-150/

    回溯

    LeetCode150-电话号码的字母组合-LC17

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/9 16:08
     * @Email: lihh53453@hundsun.com
     * @Description: 电话号码的字母组合
     * 1.回溯 确定dfs停止位置
     * 2.遍历要形成组合的集合
     * 3.每次递归index都会改变 当index为digits的长度时单词构建完毕将其添加到结果列表中
     */
    public class LC17 {
        private Map<Character,String> map;
        public List<String> letterCombinations(String digits) {
            map = new HashMap<>();
            if(digits.isEmpty()) return new ArrayList<>();
            List<String> list = new ArrayList<>();
            map.put('2',"abc");
            map.put('3',"def");
            map.put('4',"ghi");
            map.put('5',"jkl");
            map.put('6',"mno");
            map.put('7',"pqrs");
            map.put('8',"tuv");
            map.put('9',"wxyz");
            dfs(list,digits,map,0,new StringBuffer());
            return list;
        }
    
        private void dfs(List<String> res, String digits, Map<Character, String> map, int index, StringBuffer stringBuffer) {
            if(index == digits.length()){
                res.add(stringBuffer.toString());
            }else{
                String s = map.get(digits.charAt(index));
                for (int i = 0; i < s.length(); i++) {
                    stringBuffer.append(s.charAt(i));
                    dfs(res, digits, map, index+1, stringBuffer);
                    stringBuffer.deleteCharAt(index);
                }
            }
        }
    
    }
    

    LeetCode150-组合-LC77

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/9 16:51
     * @Email: lihh53453@hundsun.com
     * @Description: 组合
     * 剪枝 假如 n = 4  k = 15  temp.size() == 1 此时已经有了一个数 再选n - temp.size()个数
     * 搜索起点的上界 + 接下来要选择的元素个数 - 1 = n  ->  13 + 3 - 1 = 15
     * 最小上界为 13 -> 13 14 15
     * 最小上界为 k - n - (k - path.size()) + 1
     *
     */
    public class LC77 {
        public List<List<Integer>> combine(int n, int k) {
            List<List<Integer>> res = new ArrayList<>();
            Deque<Integer> temp = new LinkedList<>();
            dfs(temp,res,1,n,k);
            return res;
        }
    
        private void dfs(Deque<Integer> temp, List<List<Integer>> res, int begin, int n, int k) {
            if(temp.size() == k){
                res.add(new ArrayList<>(temp));
            }else{
                //优化剪枝
                //for(int i = begin;i <= n;i++){
                for(int i = begin;i <= k - n - (k - temp.size()) + 1;i++){
                    temp.addLast(i);
                    dfs(temp, res, i + 1, n, k);
                    temp.removeLast();
                }
            }
        }
    
    }
    

    LeetCode150-全排列-LC46

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/11 14:32
     * @Email: lihh53453@hundsun.com
     * @Description: 全排列
     * 考虑 res添加时要创建新副本
     * 组合的逻辑:交换+回溯
     * 交换的起点为index 选中之后将不在选中
     */
    public class LC46 {
        public List<List<Integer>> permute(int[] nums) {
            List<List<Integer>> res = new ArrayList<>();
            List<Integer> temp = new ArrayList<>();
            for(int num : nums){
                temp.add(num);
            }
            int n = nums.length;
            dfs(res,temp,0,n);
            return res;
        }
    
        private void dfs(List<List<Integer>> res, List<Integer> temp, int index, int n) {
            if(index == n){
    //            res.add(temp);
                //要拷贝一个新的副本 否则会直接引用原来的list集合
                res.add(new ArrayList<>(temp));
            }else{
    //            for (int i = 0; i < n; i++) {
                // 如果i从0开始 dfs启动永远都只会是0坐标开始
                for (int i = index; i < n; i++) {
                    Collections.swap(temp,index,i);
                    dfs(res,temp,index+1,n);
                    Collections.swap(temp,index,i);
                }
            }
        }
    
        public static void main(String[] args) {
            int[] a = new int[]{1,2,3};
            LC46 lc46 = new LC46();
            lc46.permute(a);
        }
    }

    组合排列回溯算法总结

    相同点
    1.两个有需要使用结果list集合和暂存集合保存顺序结果
    2.使用dfs+回溯算法
    3.res添加结果时要添加新副本 res.add(new ArrayList<>(temp));
    4.递归起点相同 都为[index,n)
    不同点
    1.组合使用queue添加数字,可以考虑剪枝,区间替换为[index,k - n - (k - temp.size())+1]
    2.排列使用交换,可以使用Collections.swap()方法

    LeetCode150-组合总和-LC39

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/11 15:08
     * @Email: lihh53453@hundsun.com
     * @Description: 组合总和
     * 思路:回溯+target-目标值+跳过节点
     */
    public class LC39 {
        public List<List<Integer>> combinationSum(int[] candidates, int target) {
            List<List<Integer>> res = new ArrayList<>();
            List<Integer> temp = new ArrayList<>();
            dfs(candidates,target,0,res,temp);
            return res;
        }
    
        private void dfs(int[] candidates, int target, int index, List<List<Integer>> res, List<Integer> temp) {
            if(target == 0){
                res.add(new ArrayList<>(temp));
                return;
            }
            if(index == candidates.length){
                return;
            }
            dfs(candidates,target,index+1,res,temp);
            if(target - candidates[index] >= 0){
                temp.add(candidates[index]);
                dfs(candidates,target - candidates[index],index,res,temp);
                temp.remove(temp.size() - 1);
            }
        }
    }
    

    LeetCode150-N皇后II-LC52

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/11 15:37
     * @Email: lihh53453@hundsun.com
     * @Description: N皇后II 求符合N皇后的个数问题
     * 1.使用三个Set集合判断是否存在 如果存在则该节点的要求
     * 2.三个集合的规则 row+col为右上到左下的斜线  row-col为左上到右下的斜线  row为斜线
     * 3.col从0开始 row使用回溯 同时也从0开始
     * 4.二进制优化 目前没看懂
     */
    public class LC52 {
        public int totalNQueens(int n) {
            Set<Integer> row_set = new HashSet<>();
            Set<Integer> dig_set_y = new HashSet<>();
            Set<Integer> dig_set_n = new HashSet<>();
            return dfs(n,0,row_set,dig_set_y,dig_set_n);
        }
    
        private int dfs(int n, int row, Set<Integer> rowSet, Set<Integer> digSetY, Set<Integer> digSetN) {
            if(row == n){
                return 1;
            }else{
                int count = 0;
    //            for (int i = row; i < n; i++) {
                //这不是求组合问题 每次dfs都需要从0坐标位置开始
                for (int col = 0; col < n; col++) {
                    if(rowSet.contains(col)) continue;
                    if(digSetY.contains(row - col)) continue;
                    if(digSetN.contains(row + col)) continue;
                    rowSet.add(col);
                    digSetY.add(row - col);
                    digSetN.add(row + col);
                    count +=dfs(n,row + 1,rowSet,digSetY,digSetN);
                    rowSet.remove(col);
                    digSetY.remove(row - col);
                    digSetN.remove(row + col);
                }
                return count;
            }
        }
    }

    LeetCode150-括号生成-LC22

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/11 16:06
     * @Email: lihh53453@hundsun.com
     * @Description: 括号生成
     * 思路:dfs条件 左括号数量 < n(最大括号数) 可以不断添加
     *             右括号数量 < 左括号数 可以不断添加
     * 更新括号数量
     * dfs结束条件:StringBuilder的长度为n*2
     */
    public class LC22 {
        public List<String> generateParenthesis(int n) {
            List<String> res= new ArrayList<>();
            StringBuilder stringBuilder = new StringBuilder();
            dfs(stringBuilder,res,0,0,n);
            return res;
        }
    
        private void dfs(StringBuilder stringBuilder, List<String> res, int left, int right, int n) {
            if(stringBuilder.length() == n * 2){
                res.add(stringBuilder.toString());
            }else{
                if(left < n){
                    stringBuilder.append("(");
                    dfs(stringBuilder,res,left+1,right,n);
                    stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                }
                if(left > right){
                    stringBuilder.append(")");
                    dfs(stringBuilder,res,left,right + 1,n);
                    stringBuilder.deleteCharAt(stringBuilder.length() - 1);
                }
            }
        }
    }
    

    LeetCode150-单词搜索-LC79

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/11 16:27
     * @Email: lihh53453@hundsun.com
     * @Description: 单词搜索
     * 思路:
     * dfs逻辑:1.最后一个字母与单词word最后一个字母相同并且长度与word长度相同
     *         2.匹配index++位置的单词
     * 回溯逻辑:visit[i][j] = true -> false; 匹配完该节点重新将该节点状态返回
     */
    public class LC79 {
        private boolean flag = false;
        private boolean[][] visit;
        private final int[][] dict = new int[][]{{1,0},{-1,0},{0,1},{0,-1}};
        public boolean exist(char[][] board, String word) {
            visit = new boolean[board.length][board[0].length];
            for (int i = 0; i < board.length; i++) {
                for (int j = 0; j <board[0].length; j++) {
                    if(board[i][j] != word.charAt(0)) continue; //剪枝
                    dfs(i, j, word, 0, board);
                    if(flag) return true;
                }
            }
            return false;
        }
    
        private void dfs(int i, int j, String word, int index, char[][] board) {
            if(flag) return;//剪枝
            if(board[i][j] != word.charAt(index)){
                return;
            }
            if(index == word.length() - 1){
                flag = true;
            }
            visit[i][j] = true;
            for(int[] arr:dict){
                int new_i = arr[0] + i;
                int new_j = arr[1] + j;
                if(new_i >=0 && new_j >=0 && new_i < board.length && new_j < board[0].length){
                    if(!visit[new_i][new_j]){
                        dfs(new_i,new_j,word,index+1,board);
                    }
                }
            }
            visit[i][j] = false;
        }
    }
    

    回溯总结

    1. 电话号码的数组组合map集合,遍历数字位置map获取单词,index标志结束位
    2. 组合:起点index1,选取数的范围n,终点长度为k,index == k标志结束,副本更新
    3. 排列:起点index0,交换,index == target结束,副本更新
    4. 组合总和:起点index为0,for循环从0开始 因为可以无限选,无限选要保证跳过的逻辑,target-当前值
    5. N皇后:三条斜线Set集合保存当前节点row-col,row,row+rol,回溯节点状态
    6. 括号生成:结束位置为n*2,记录括号左右数量,数量增加逻辑(left<n,right<left)->dfs
    7. 单词搜索:小岛数量(回溯版),回溯visit节点状态,减枝
  • LeetCode150-树&图论

    面试150题-树+图论

    📕文章题目选自LeetCode面试150题-树、图论部分
    😊该文章内容基于作者本人思考总结,如有错误欢迎指正
    🏠题单地址:https://leetcode.cn/studyplan/top-interview-150/

    LeetCode150-二叉树的最大深度-LC104

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/5 10:16
     * @Email: lihh53453@hundsun.com
     * @Description: 二叉树的最大深度
     * 递归
     */
    public class LC104 {
        public int maxDepth(TreeNode root) {
            if(root == null) return 0;
            int left = maxDepth(root.left);
            int right = maxDepth(root.right);
            return Math.max(left,right) + 1;
        }
    }

    LeetCode150-相同的树-LC104

    
    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/5 10:21
     * @Email: lihh53453@hundsun.com
     * @Description: 相同的树
     * 递归
     */
    public class LC100 {
        public boolean isSameTree(TreeNode p, TreeNode q) {
            if(p == null && q == null){
                return true;
            }else if(p == null || q == null){
                return false;
            } else if (p.val != q.val) {
                return false;
            }else{
                return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
            }
        }
    }

    LeetCode150-反转二叉树-LC226

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/5 10:38
     * @Email: lihh53453@hundsun.com
     * @Description: 反转二叉树
     */
    public class LC226 {
        public TreeNode invertTree_dfs(TreeNode root) {
            if(root == null) return null;
            TreeNode left = invertTree_dfs(root.left);
            TreeNode right = invertTree_dfs(root.right);
            root.left = right;
            root.right = left;
            return root;
        }
    }

    LeetCode150-对称二叉树-LC101

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/5 10:59
     * @Email: lihh53453@hundsun.com
     * @Description:
     */
    public class LC101 {
        public boolean isSymmetric(TreeNode root) {
            return check(root.left,root.right);
        }
        private boolean check(TreeNode left,TreeNode right){
            if(left == null && right == null){
                return true;
            }else if(left == null || right == null){
                return false;
            }else{
                return left.val == right.val && check(left.right,right.left) && check(right.right,left.left);
            }
        }
    }

    LeetCode150-从前序与中序遍历序列构造二叉树-LC105

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/5 16:45
     * @Email: lihh53453@hundsun.com
     * @Description:
     */
    public class LC105 {
        private Map<Integer,Integer> map;
        public TreeNode buildTree(int[] preorder, int[] inorder) {
            map = new HashMap<>();
            int length = inorder.length;
            for(int i = 0; i < inorder.length;i++){
                map.put(inorder[i],i);
            }
            return setTree(preorder,inorder,0,length - 1,0, length - 1);
        }
    
        private TreeNode setTree(int[] preorder, int[] inorder, int pre_start, int pre_end, int ino_start, int ino_end) {
            if(pre_start > pre_end) return null; //坐标相遇即停止
            int root_value = preorder[pre_start];
            int root_index_in_inorder = map.get(root_value);
            int size_left_subtree = root_index_in_inorder - ino_start;
            TreeNode root = new TreeNode(root_value);
            // 递归构建左子树 形成对应关系
            // 左子树在前序遍历中的边界是:从pre_start + 1(根节点已使用)到pre_start + size_left_subtree
            // 左子树在中序遍历中的边界是:从ino_start到ino_end - 1(排除根节点)
            root.left = setTree(preorder, inorder, pre_start + 1, pre_start + size_left_subtree, ino_start, root_index_in_inorder - 1);
            // 递归构建右子树
            // 右子树在前序遍历中的边界是:从pre_start + size_left_subtree + 1到pre_end
            // 右子树在中序遍历中的边界是:从root_index_in_inorder + 1到ino_end
            root.right = setTree(preorder, inorder, pre_start + size_left_subtree + 1, pre_end, root_index_in_inorder + 1, ino_end);
            return root;
        }
    }
    

    LeetCode150-从中序与后序遍历序列构造二叉树-LC106

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/5 17:18
     * @Email: lihh53453@hundsun.com
     * @Description:
     */
    public class LC106 {
        private Map<Integer,Integer> map;
        public TreeNode buildTree(int[] inorder, int[] postorder) {
            map = new HashMap<>();
            int n = inorder.length;
            for(int i = 0; i < inorder.length;i++){
                map.put(inorder[i],i);
            }
            return setTree(inorder,postorder,0,n - 1,0,n - 1);
        }
    
        private TreeNode setTree(int[] inorder, int[] postorder, int ino_start, int ino_end, int post_start, int post_end) {
            if(ino_start > ino_end) return null;
            int root_index = map.get(postorder[post_end]);
            int left_tree_len = root_index - ino_start;
            TreeNode root = new TreeNode(postorder[post_end]);
            // 递归构建左子树。
            // 左子树在中序遍历中的边界是从ino_start到ino_start + left_tree_len - 1。
            // 左子树在后序遍历中的边界是从post_start到post_start + left_tree_len - 1。
            root.left = setTree(inorder, postorder, ino_start, root_index - 1, post_start, post_start + left_tree_len - 1);
    
            // 递归构建右子树。
            // 右子树在中序遍历中的边界是从root_index + 1到ino_end。
            // 右子树在后序遍历中的边界是从post_start + left_tree_len到post_end - 1。
            root.right = setTree(inorder, postorder, root_index + 1, ino_end, post_start + left_tree_len, post_end - 1);
            return root;
        }
    }

    两个数组实现中序遍历 先序遍历 后序便利的转换总结

    先序遍历

    // 递归构建左子树
    root.left = setTree(preorder, inorder, 
                        pre_start + 1, // 左子树前序遍历起始索引
                        pre_start + size_left_subtree, // 左子树前序遍历结束索引
                        ino_start, // 左子树中序遍历起始索引
                        root_index_in_inorder - 1); // 左子树中序遍历结束索引(排除根节点)
    
    // 递归构建右子树
    root.right = setTree(preorder, 
                         inorder, 
                         pre_start + size_left_subtree + 1, // 右子树前序遍历起始索引
                         pre_end, // 右子树前序遍历结束索引
                         root_index_in_inorder + 1, // 右子树中序遍历起始索引
                         ino_end); // 右子树中序遍历结束索引

    中序遍历

    // 递归构建左子树
    root.left = setTree(inorder, 
                        ino_start, // 左子树中序遍历起始索引
                        root_index_in_inorder - 1, // 左子树中序遍历结束索引(排除根节点)
                        ...); // 省略其他参数
    
    // 递归构建右子树
    root.right = setTree(inorder, 
                         root_index_in_inorder + 1, // 右子树中序遍历起始索引
                         ino_end, // 右子树中序遍历结束索引
                         ...); // 省略其他参数

    后序遍历

    // 递归构建左子树
    root.left = setTree(postorder, 
                        inorder, 
                        post_start, // 左子树后序遍历起始索引
                        post_start + left_tree_len - 1, // 左子树后序遍历结束索引
                        ino_start, // 左子树中序遍历起始索引
                        root_index_in_inorder - 1); // 左子树中序遍历结束索引
    
    // 递归构建右子树
    root.right = setTree(postorder, 
                         inorder, 
                         post_start + left_tree_len, // 右子树后序遍历起始索引
                         post_end - 1, // 右子树后序遍历结束索引
                         root_index_in_inorder + 1, // 右子树中序遍历起始索引
                         ino_end); // 右子树中序遍历结束索引

    LeetCode150-填充每个节点的下一个右侧节点指针 II-LC117

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/5 19:02
     * @Email: lihh53453@hundsun.com
     * @Description:
     * 使用dfs递归的方式处理 
     * 1.在遍历的过程中 左节点的个数与层数都相同
     * 2.当不同时则是需要被连接的节点
     * 3.更新每一层的最新的位置节点位置
     * 4.递归处理
     * 5.递归结束 当节点为空时不处理 直接return
     */
    public class LC117 {
        private final List<TreeNodePoint> list = new ArrayList<>();
        public TreeNodePoint connect(TreeNodePoint root) {
            dfs(root,0);
            return root;
        }
        public void dfs(TreeNodePoint root,int depth){
            if(root == null) return;
            if(depth == list.size()){
                list.add(root);
            }else{
                list.get(depth).next = root;
                list.set(depth,root);
            }
            dfs(root.left,depth + 1);
            dfs(root.right,depth + 1);
        }
    }
    

    LeetCode150-二叉树展开为链表-LC114

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/6 9:08
     * @Email: lihh53453@hundsun.com
     * @Description: 二叉树展开为链表
     * 逻辑:找到左子树的最右节点 将右子树的头节点衔接到左子树的最右节点
     * cur 当前节点root
     * next root的左子树头节点
     * pre 从左子树头节点开始依次遍历到左子树的最右节点
     * pre.right = cur.right; 连接操作
     * cur进入链表 进入下一节点 cur = cur.right
     */
    public class LC114 {
        public void flatten(TreeNode root) {
            TreeNode cur = root;
            while(cur != null){
                if(cur.left != null){
                    TreeNode next = cur.left;
                    TreeNode pre = next;
                    while(pre.right != null){
                        pre = pre.right;
                    }
                    pre.right = cur.right;
                    cur.left = null;
                    cur.right = next;
                }
                cur = cur.right;
            }
        }
    }

    LeetCode150-路径总和-LC112

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/6 9:22
     * @Email: lihh53453@hundsun.com
     * @Description: 路径总和
     */
    public class LC112 {
        public boolean hasPathSum(TreeNode root, int targetSum) {
            if(root == null) return false;
            //if(root.val == targetSum) return true;
            //这里需要一个条件判断
            if(root.left == null && root.right == null){
                return targetSum == root.val;
            }
            return hasPathSum(root.left,targetSum - root.val) || hasPathSum(root.right,targetSum - root.val);
        }
    }

    LeetCode150-求根节点到叶节点数字之和-LC129

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/6 9:32
     * @Email: lihh53453@hundsun.com
     * @Description: 求根节点到叶节点的数字之和
     * 向下传递路径和 4 7 5 6 向7 传递47 但是返回475 + 476
     */
    public class LC129 {
        public int sumNumbers(TreeNode root) {
            return dfs(root,0);
        }
        private int dfs(TreeNode root,int sum){
            if(root == null) return 0;
            sum = sum * 10 + root.val;
            if(root.left == null && root.right == null){
                return sum;
            }else {
                return dfs(root.left,sum) + dfs(root.right,sum);
            }
        }
    }

    LeetCode150-二叉树中的最大路径和-LC124

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/6 9:58
     * @Email: lihh53453@hundsun.com
     * @Description: 二叉树中的最大路径和 
     * 解析如图
     */
    public class LC124 {
        int max_value = Integer.MIN_VALUE;
        public int maxPathSum(TreeNode root) {
            maxSum(root);
            return max_value;
        }
        public int maxSum(TreeNode root){
            if(root == null) return 0;
    
            int left = Math.max(maxSum(root.left),0);
            int right = Math.max(maxSum(root.right),0);
            int nowVal = left + right + root.val;
            max_value = Math.max(max_value,nowVal);
            return root.val + Math.max(left,right);
        }
    }

    LeetCode150-二叉树中的最大路径和-LC124

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/6 10:46
     * @Email: lihh53453@hundsun.com
     * @Description: 使用中序遍历构建二叉搜索树
     */
    public class LC173 {
        private List<Integer> list;
        private int index;
        public LC173(TreeNode root) {
            index = 0;
            list = new ArrayList<>();
            init_tree(root,list);
        }
    
        public int next() {
            return list.get(index++);
        }
    
        public boolean hasNext() {
            return index < list.size();
        }
        public void init_tree(TreeNode root,List<Integer> arr){
            if(root == null){
                return;
            }
            init_tree(root.left,arr);
            arr.add(root.val);
            init_tree(root.right,arr);
        }
    }
    

    LeetCode150-完全二叉树的节点个数-LC222

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/6 16:25
     * @Email: lihh53453@hundsun.com
     * @Description: 完全二叉树的节点个数
     * 递归计算即可
     */
    public class LC222 {
        public int countNodes(TreeNode root) {
            if(root == null) return 0;
            int left = countNodes(root.left);
            int right = countNodes(root.right);
            return left + right + 1;
        }
    }
    

    LeetCode150-二叉树的最近公共祖先-LC236

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/6 16:38
     * @Email: lihh53453@hundsun.com
     * @Description: 二叉树的最近公共祖先
     */
    public class LC236 {
        public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
            /*
              返回条件:1.当前节点为空
                      2.找到了p节点
                      3.找到了q节点
             */
            if(root == null || p == root || q == root) return root;
            TreeNode left = lowestCommonAncestor(root.left,p,q);
            TreeNode right = lowestCommonAncestor(root.right,p,q);
            if(left == null) return right; //如果左子树为空 那么两个节点都在右子树
            if(right == null) return left; //如果右子树为空 那么两个节点都在左子树
            return root; //如果左子树和右子树都拿到值了 那么此时这个节点就是要找的节点
        }
    }

    递归树总结:

    • 对于树的题一般采用dfs算法
    • 递归时确定递归的截至位置 例如if(root == null) return 0;
    • 与计算相关时 截止位置的返回值一般为return 0
    • 找到递归的逻辑

    LeetCode150-二叉树的右视图-LC199

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/6 17:33
     * @Email: lihh53453@hundsun.com
     * @Description: 二叉树的右视图
     */
    public class LC199 {
        public List<Integer> rightSideView(TreeNode root) {
            List<Integer> res = new ArrayList<>();
            if (root == null) return res;
            Queue<TreeNode> queue = new LinkedList<>();
            queue.offer(root);
            while(!queue.isEmpty()){
                int count = queue.size();
                for (int i = 0; i < count; i++) {
                    TreeNode node = queue.poll();
                    //                queue.offer(root.left);
                    //                queue.offer(root.right);
                    //这里要注意判断是否非空
                    if(node!=null){
                        if(node.left != null) queue.offer(node.left);
                        if(node.right != null) queue.offer(node.right);
                        if(i == count - 1) res.add(node.val); //唯一的操作就是在bfs的过程中将最后的节点加入list
                    }
                }
            }
            return res;
        }
    }

    LeetCode150-二叉树的层平均值-LC637

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/7 9:04
     * @Email: lihh53453@hundsun.com
     * @Description: 二叉树的层平均值
     */
    public class LC637 {
        public List<Double> averageOfLevels(TreeNode root) {
            List<Double> res = new ArrayList<>();
            Queue<TreeNode> queue = new LinkedList<>();
            int size;
            queue.offer(root);
            while(!queue.isEmpty()){
                size = queue.size();
                double levelSum = 0.0;
                for (int i = 0; i < size; i++) {
                    TreeNode node = queue.poll();
                    if(node != null){
                        levelSum += node.val;
                        if(node.left != null) queue.offer(node.left);
                        if(node.right != null) queue.offer(node.right);
                    }
                }
                res.add((levelSum / size));
            }
            return res;
        }
    }

    LeetCode150-二叉树的层序遍历-LC102

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/7 9:20
     * @Email: lihh53453@hundsun.com
     * @Description: 二叉树的层序遍历
     */
    public class LC102 {
        public List<List<Integer>> levelOrder(TreeNode root) {
            if(root == null) return new ArrayList<>();
            List<List<Integer>> res = new ArrayList<>();
            int size;
            Queue<TreeNode> queue = new LinkedList<>();
            queue.offer(root);
            while(!queue.isEmpty()){
                size = queue.size();
                List<Integer> temp = new ArrayList<>();
                for (int i = 0; i < size; i++) {
                    TreeNode node = queue.poll();
                    if(node!=null){
                        temp.add(node.val);
                        if(node.left != null) queue.offer(node.left);
                        if(node.right != null) queue.offer(node.right);
                    }
                }
                res.add(temp);
            }
            return res;
        }
    }
    

    层次树总结

    
    /**
     * 层次遍历模板代码
     */
    public List<List<Integer>> static bfs(TreeNode root){
        List<List<Integer>> res = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        List<Integer> list = new ArrayList<>();
        queue.offer(root);
        int size;
        ... 添加逻辑代码
        while(!queue.isEmpty()){
            size = queue.size();
            for(int i = 0;i < size;i++){
                TreeNode node = queue.poll();
                list.add(node.val);
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
                ... 添加逻辑代码
            }
            res.add(list);
        }
        return res;
    }

    LeetCode150-二叉搜索树的最小绝对差-LC530

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/7 14:18
     * @Email: lihh53453@hundsun.com
     * @Description: 二叉搜索树的最小绝对差
     */
    public class LC530 {
        int pre;
        int res;
        public int getMinimumDifference(TreeNode root) {
            res = Integer.MAX_VALUE;
            pre = -1;
            dfs(root);
            return res;
        }
        private void dfs(TreeNode root){
            if(root == null) return;
            dfs(root.left);
            if (pre != -1) {
                res = Math.min(res, root.val - pre);
            }
            pre = root.val; //保存上一节点值
            dfs(root.right);
        }
    }
    

    LeetCode150-二叉搜索树中第K小的元素-LC230

    LeetCode150-验证二叉搜索树-LC98

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/7 15:34
     * @Email: lihh53453@hundsun.com
     * @Description: 验证二叉搜索树
     */
    public class LC98 {
        long pre = Long.MIN_VALUE; //Integer.MIN_VALUE 和例子冲突 0x8000000000000000L为最小值
        boolean res;
        public boolean isValidBST(TreeNode root) {
            res = true;
            dfs(root);
            return res;
        }
        public void dfs(TreeNode root){
            if(root == null) return;
            if(!res) return; //检查res是否已经更新为false;
            dfs(root.left);
            if(root.val > pre){
                pre = root.val; //更新pre值
            }else{
                res = false;
            }
            dfs(root.right);
        }
    }

    二叉搜索树总结

    1.二叉搜索树的逻辑就是中序遍历

    public void dfs(TreeNode root){
        if(root == null) return;
            ... 可以在这里限制执行结束的操作 如2所述
        dfs(root.left);
        ... 中序遍历要执行的操作 例如 list.add(root.val);
        dfs(root.right);
    }

    2.可以设置一个全局变量作为结束当前遍历的标志
    例如因为找到了第K小的元素 此时–k == 0
    此时更新值 并且直接返回return即可 后面的操作直接忽略

    LeetCode150-岛屿数量-LC200

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/7 16:17
     * @Email: lihh53453@hundsun.com
     * @Description: 被围绕的区域
     */
    public class LC200 {
        int row;
        int col;
        public int numIslands(char[][] grid) {
            if(grid == null ||  grid.length == 0 || grid[0].length == 0) return 0;
            row = grid.length;
            col = grid[0].length;
            int res = 0;
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < col; j++) {
                    if(grid[i][j] == '1'){
                        res++;
                        dfs(grid,i,j);
                    }
                }
            }
            return res;
        }
        public void dfs(char[][] grid,int i,int j){
            if(i < 0 || j < 0 || i >= row || j >= col || grid[i][j] == '0'){
                return;
            }
            grid[i][j] = '0';
            dfs(grid,i + 1,j);
            dfs(grid,i - 1,j);
            dfs(grid,i,j + 1);
            dfs(grid,i,j - 1);
        }
    }
    

    LeetCode150-被环绕的区域-LC130

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/7 17:03
     * @Email: lihh53453@hundsun.com
     * @Description: 被环绕的区域
     */
    public class LC130 {
        int row;
        int col;
        public void solve(char[][] board) {
            if(board == null || board[0].length == 0) return;
            row = board.length;
            col = board[0].length;
            for (int i = 0; i < col; i++) {
                dfs(board,0,i);
                dfs(board,row - 1,i);
            }
            for (int i = 0; i < row; i++) {
                dfs(board,i,0);
                dfs(board,i,col - 1);
            }
            for (int i = 0; i < row; i++) {
                for (int j = 0; j < col; j++) {
                    if(board[i][j] == 'A'){
                        board[i][j] = 'O';
                    }else if(board[i][j] == 'O'){
                        board[i][j] = 'X';
                    }
                }
            }
    
        }
        public void dfs(char[][] board,int i,int j){
    //        if(i < 0|| j < 0 || i >= row || j >= col || board[i][j] == 'X'){
    //            return;
    //        }
            //board[i][j] == 'X' 这里没有对'A'进行排除 否则就会报错 死循环
            // 死循环 -> [['O','O'],['O','O']]
            if(i < 0|| j < 0 || i >= row || j >= col || board[i][j] != 'O'){
                return;
            }
            board[i][j] = 'A';
            dfs(board,i+1,j);
            dfs(board,i-1,j);
            dfs(board,i,j+1);
            dfs(board,i,j-1);
    
        }
    }
    

    LeetCode150-克隆图-LC130

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/8 10:34
     * @Email: lihh53453@hundsun.com
     * @Description: 克隆图
     * 保证图不会死循环的满足条件
     * 1.map存储已经访问的节点 在访问之前进行判断
     * 2.在递归之前就要将克隆节点保存到map中防止重复访问
     */
    public class LC133 {
        private final Map<MapNode,MapNode> map = new HashMap<>();
        public MapNode cloneGraph(MapNode node) {
            if(node == null) return null;
            if(map.containsKey(node)) return map.get(node);
            MapNode cacheNode = new MapNode(node.val,new ArrayList<>());
            map.put(node,cacheNode);
            for(MapNode mapNode:node.neighbors){
                cacheNode.neighbors.add(cloneGraph(mapNode));
            }
            return cacheNode;
        }
    }

    (待解决)LeetCode150-除法求值-LC399

    LeetCode150-课程表-LC207

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/8 14:18
     * @Email: lihh53453@hundsun.com
     * @Description: 课程表
     * 这一题就是判断有没有环 如果在dfs的过程中走到了自己 说明不满足拓扑排序
     * 构造拓扑排序 每一个节点对应一个状态 0未开始 1真在找节点 2节点访问完毕
     * 停止条件即judge为false时 全部return
     */
    public class LC207 {
        List<List<Integer>> keys;
        int[] visit;
        boolean judge = true;
        public boolean canFinish(int numCourses, int[][] prerequisites) {
            keys = new ArrayList<>();
            visit = new int[numCourses];
            for (int i = 0; i < numCourses; i++) {
                keys.add(new ArrayList<>());
            }
            for (int[] arr:prerequisites){
                keys.get(arr[1]).add(arr[0]);
            }
            for (int i = 0; i < numCourses && judge; i++) {
                if(visit[i] == 0){
                    dfs(i);
                }
            }
            return judge;
        }
    
        public void dfs(int num){
            visit[num] = 1;
            for (int v:keys.get(num)){
                if(visit[v] == 0){
                    dfs(v);
                    if(!judge){
                        return;
                    }
                }else if(visit[v] == 1){
                    judge = false;
                    return;
                }
            }
            visit[num] = 2;
        }
    }
    

    LeetCode150-课程表II-LC210

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/8 14:44
     * @Email: lihh53453@hundsun.com
     * @Description: 课程表II
     */
    public class LC210 {
        List<List<Integer>> keys; //每一个课程对应一个链表
        boolean flag = true; // 是否满足有向无环图
        int[] result; //模拟栈
        int[] visited; //当前坐标位置访问标记
        int index; //栈的位置坐标
        public int[] findOrder(int numCourses, int[][] prerequisites) {
            keys = new ArrayList<>();
            result = new int[numCourses];
            visited = new int[numCourses];
            index = numCourses - 1;
            for (int i = 0; i < numCourses; i++) {
                keys.add(new ArrayList<>());
            }
            for(int[] arr:prerequisites){
                keys.get(arr[0]).add(arr[1]); //注意这里的先后顺序 [0,1] 先走arr[0] 才能走arr[1];
            }
            for (int i = 0; i < numCourses && flag; i++) {
                if(visited[i] == 0){
                    dfs(i);
                }
            }
            if (!flag) {
                return new int[0];
            }
            return result;
        }
        public void dfs(int num){
            visited[num] = 1;
            for(int val:keys.get(num)){
                if(visited[val] == 0){
                    dfs(val);
                    if(!flag) return;
                }else if(visited[val] == 1){
                    flag = false;
                    return;
                }
            }
            visited[num] = 2;
            result[index--] = num;
        }
    }

    拓扑排序总结

    模板代码

    public class Sort {
        List<List<Integer>> keys; //每一个坐标都有一个可以走到的点,用一个链表维护
        int[] visit; //当前坐标位置访问状态 0未开始 1进行中 2结束
        boolean judge = true; //是否满足有向无环图
        public boolean canFinish(int numCourses, int[][] prerequisites) {
            keys = new ArrayList<>();
            visit = new int[numCourses];//初始化
            for (int i = 0; i < numCourses; i++) {
                keys.add(new ArrayList<>());//numCourses个坐标对应numCourses个链表
            }
            for (int[] arr:prerequisites){
                keys.get(arr[1]).add(arr[0]);//将当前点位可以到达的坐标添加到链表
            }
            for (int i = 0; i < numCourses && judge; i++) {
                if(visit[i] == 0){//当前坐标未开始 从当前节点进入
                    dfs(i);
                }
            }
            return judge;//返回是否满足拓扑排序
        }
    
        public void dfs(int num){
            visit[num] = 1;//将状态修改为进行中
            for (int v:keys.get(num)){
                if(visit[v] == 0){
                    dfs(v);
                    if(!judge){
                        return;//如果此时已经不满足拓扑排序规则直接return
                    }
                }else if(visit[v] == 1){//拓扑排序走到了自己 说明形成了环不满足规则将标志位改为false
                    judge = false;
                    return;
                }
            }
            visit[num] = 2;
        }
    }

    LeetCode150-蛇梯棋-LC909

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/8 16:29
     * @Email: lihh53453@hundsun.com
     * @Description: 蛇梯棋
     */
    public class LC909 {
        public int snakesAndLadders(int[][] board) {
            int n = board.length;
            boolean[] vis = new boolean[n*n + 1];
            Queue<int[]> queue = new LinkedList<>();
            queue.offer(new int[]{1,0});
            while(!queue.isEmpty()){
                int[] node = queue.poll();
                for(int i = 1;i <= 6;++i){
                    int nxt = node[0] + i;
                    if(nxt > n * n){
                        break;
                    }
                    int[] rc = change(nxt,n);
                    if(board[rc[0]][rc[1]] > 0){
                        nxt = board[rc[0]][rc[1]];
                    }
                    if(nxt == n * n){
                        //return rc[1]+1; 这里不是坐标位置加1 而是节点位置+1
                        return node[1] + 1;
                    }
                    if(!vis[nxt]){
                        vis[nxt] = true;
                        queue.offer(new int[]{nxt,node[1]+1});//同上
                    }
                }
            }
            return -1;
        }
    
        public int[] change(int next,int n) {
            int row = (next - 1) / n;
            int col = (next - 1) % n;
            if(row % 2 == 1){
                col = n - 1 - col;
            }
            return new int[]{n - 1 - row,col};
        }
    
        public static void main(String[] args) {
            int[][] matrix = {
                    {-1, -1, -1, -1, -1, -1},
                    {-1, -1, -1, -1, -1, -1},
                    {-1, -1, -1, -1, -1, -1},
                    {-1, 35, -1, -1, 13, -1},
                    {-1, -1, -1, -1, -1, -1},
                    {-1, 15, -1, -1, -1, -1}
            };
            LC909 lc909 = new LC909();
            lc909.snakesAndLadders(matrix);
        }
    }
    

    LeetCode150-最小基因变化-LC433

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/9 9:03
     * @Email: lihh53453@hundsun.com
     * @Description: 最小基因变化
     * 思路:
     * 1.将单词中每一个字符替换成dict的单词 添加到队列中
     * 2.存储在队列中时进行判断 visit是否遍历过 set集合是否包含目标数组
     * 3.满足以上条件step++
     * 4.当找到end单词时返回step 否则返回-1
     * 5.bfs条件:队列 弹出 队列长度遍历 
     */
    public class LC433 {
        public int minMutation(String startGene, String endGene, String[] bank) {
            Queue<String> queue = new ArrayDeque<>();
            Set<String> set = new HashSet<>(Arrays.asList(bank));
            Set<String> visited = new HashSet<>();
            Character[] dict = {'A','C','G','T'};
            if(startGene.equals(endGene)) return 0;
            if(!set.contains(endGene)) return -1;
            queue.add(startGene);
            visited.add(startGene);
            int step = 1;
            while (!queue.isEmpty()){
                int size = queue.size();
                for (int i = 0; i < size; i++) {
                    String word = queue.poll();
                    for (int j = 0; i < word.length(); i++) {
                        for (int k = 0; j < 4; j++) {
                            if(dict[k] != word.charAt(j)){
                                StringBuilder sb = new StringBuilder(word);
                                sb.setCharAt(j,dict[k]);
                                String v_string = sb.toString();
                                if(!visited.contains(v_string) && set.contains(v_string)){
                                    if(v_string.equals(endGene)){
                                        return step;
                                    }
                                    queue.offer(v_string);
                                    visited.add(v_string);
                                }
                            }
                        }
                    }
                }
                step++;
            }
            return -1;
        }
    }
    

    LeetCode150-单词接龙-LC127

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/9 9:44
     * @Email: lihh53453@hundsun.com
     * @Description: 单词接龙
     * 单词接龙 参数 start开始单词 end结束单词 限定符合的单词集合keys
     * 1.遍历单词集合set防止重复遍历
     * 2.queue队列保存下一遍历单词 创建队列 初始化队列 弹出节点 确定每一次队列的长度循环遍历
     * 3.确定判断条件
     *   3.1 不在keys中直接进入下一次循环
     *   3.2 visit已经访问的直接进入下一次循环
     */
    public class LC127 {
        public int ladderLength(String beginWord, String endWord, List<String> wordList) {
            Set<String> visit = new HashSet<>();
            Queue<String> queue = new ArrayDeque<>();
            Set<String> key = new HashSet<>(wordList);
            int step = 1;
            if(beginWord.equals(endWord) || !key.contains(endWord)) return 0;
            queue.offer(beginWord);
            visit.add(beginWord);
            while(!queue.isEmpty()){
                int size = queue.size();
                for (int i = 0; i < size; i++) {
                    String word = queue.poll();
                    assert word != null;
                    if(word.equals(endWord)) return step;
                    for (int j = 0; j < endWord.length(); j++) {
                        char[] charArray = word.toCharArray();
                        char temp = charArray[j];
                        for (char k = 'a'; k <= 'z'; k++) {
                            if(k == word.charAt(j)){
                                continue;
                            }
                            charArray[j] = k;
                            String v_string = new String(charArray);
                            if(!visit.contains(v_string) && key.contains(v_string)){
                                visit.add(v_string);
                                queue.offer(v_string);
                            }
                        }
                        charArray[j] = temp;
                    }
                }
                step++;
            }
            return 0;
        }
    }

    LeetCode150-实现前缀树-LC208

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/9 10:40
     * @Email: lihh53453@hundsun.com
     * @Description:
     */
    public class LC208 {
        private LC208[] children;
        private boolean isEnd;
        public LC208() {
            boolean isEnd = false;
            children = new LC208[26];
        }
    
        public void insert(String word) {
            LC208 trie = this;
            char[] charArray = word.toCharArray();
            for (int i = 0; i < charArray.length; i++) {
                char c = charArray[i];
                int index = c - 'a';
                if(trie.children[index] == null){
                    trie.children[index] = new LC208();
                }
                trie = trie.children[index];
            }
            trie.isEnd = true;
        }
    
        public boolean search(String word) {
            LC208 trie = searchPrefix(word);
            return null != trie && trie.isEnd;
        }
    
        public boolean startsWith(String prefix) {
            return searchPrefix(prefix) != null;
        }
        private LC208 searchPrefix(String prefix) {
            LC208 trie = this;
            for (int i = 0; i < prefix.length(); i++) {
                char c = prefix.charAt(i);
                int index = c - 'a';
                if(trie.children[index] == null){
                    return null;
                }
                trie = trie.children[index];
            }
            return trie;
        }
    }
    

    LeetCode150-单词拼写-LC221

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/9 11:13
     * @Email: lihh53453@hundsun.com
     * @Description: 单词拼写
     * 1.每一个节点维护一个节点数组和结束标志isEnd
     * 2.使用start坐标标志位 当与长度相同时 结束判断isEnd是否为true
     */
    public class LC221 {
    }
    class WordDictionary {
        private WordDictionary[] children;
        private boolean isEnd;
    
        public WordDictionary() {
            children = new WordDictionary[26];
        }
    
        public void addWord(String word) {
            WordDictionary node = this;
            char[] charArray = word.toCharArray();
            for (int i = 0; i < charArray.length; i++) {
                int index = charArray[i] - 'a';
                if (node.children[index] == null) {
                    node.children[index] = new WordDictionary();
                }
                node = node.children[index];
            }
            node.isEnd = true;
        }
    
        public boolean search(String word) {
            return search(this, word, 0);
        }
    
        private boolean search(WordDictionary node, String word, int start) {
            if (start == word.length()) {
                return node.isEnd;
            }
            char c = word.charAt(start);
            if (c != '.') {
                WordDictionary child = node.children[c - 'a'];
                return child != null && search(child, word, start + 1);
            } else {
                for (int j = 0; j < 26; j++) {
                    if (node.children[j] != null && search(node.children[j], word, start + 1)) {
                        return true;
                    }
                }
                return false;
            }
        }
    }
    

    LeetCode150-单词拼写II-LC212

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/9 14:35
     * @Email: lihh53453@hundsun.com
     * @Description: 字典树 + 回溯 + 递归dfs
     * 1.构建字典树 将每一个单词添加到字典树中
     * 2.遍历每一个棋子 遍历完棋子都需要回溯
     * 3.dfs的过程中会访问当前字典树上是否已经构建了单词 满足isEnd时就将其添加到结果集合中
     */
    public class LC212 {
        // 存储结果集
        private final Set<String> res = new HashSet<>();
        // 四个方向的移动:右,左,下,上
        private final int[][] directions = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    
        // 接收一个字符矩阵和单词数组,返回矩阵中能拼出的单词列表
        public List<String> findWords(char[][] board, String[] words) {
            Trie trie = new Trie();
            // 将所有单词插入字典树
            for (String word : words) {
                trie.insert(word);
            }
    
            // 遍历棋盘的每个格子,从每个格子开始深度优先搜索
            for (int i = 0; i < board.length; i++) {
                for (int j = 0; j < board[0].length; j++) {
                    dfs(board, trie.root, i, j, new StringBuilder());
                }
            }
    
            // 将结果集转换为列表并返回
            return new ArrayList<>(res);
        }
    
        // 深度优先搜索函数
        private void dfs(char[][] board, TrieNode node, int i, int j, StringBuilder word) {
            // 检查当前位置是否越界或已经被访问过
            if (i < 0 || j < 0 || i >= board.length || j >= board[0].length || node == null || board[i][j] == '*') {
                return;
            }
    
            // 当前字符
            char c = board[i][j];
            // 计算字符在字典树中的索引
            int index = c - 'a';
            // 获取字典树中对应的节点
            TrieNode nextNode = node.children[index];
            // 如果没有对应的节点,说明当前路径不包含任何单词
            if (nextNode == null) {
                return;
            }
    
            // 将当前字符添加到路径中
            word.append(c);
            // 如果当前节点是单词的结尾,将路径添加到结果集中
            if (nextNode.isEnd) {
                res.add(word.toString());
            }
    
            // 标记当前位置为已访问
            board[i][j] = '*';
            // 遍历四个方向
            for (int[] dir : directions) {
                int newI = i + dir[0];
                int newJ = j + dir[1];
                // 递归搜索下一个位置
                dfs(board, nextNode, newI, newJ, word);
            }
            // 恢复当前位置的字符
            board[i][j] = c;
            // 回溯,移除路径中的最后一个字符
            word.setLength(word.length() - 1);
        }
    }
    
    // 定义字典树节点
    class TrieNode {
        // 存储子节点的数组,长度为26,对应26个英文字母
        TrieNode[] children = new TrieNode[26];
        // 标记该节点是否是单词的结尾
        boolean isEnd;
    
        public TrieNode() {}
    }
    
    // 定义字典树
    class Trie {
        // 字典树的根节点
        public TrieNode root = new TrieNode();
    
        // 向字典树中插入单词
        public void insert(String word) {
            TrieNode node = root;
            // 遍历单词中的每个字符
            for (char c : word.toCharArray()) {
                // 计算字符在字典树中的索引
                int index = c - 'a';
                // 如果当前节点没有子节点,创建新的子节点
                if (node.children[index] == null) {
                    node.children[index] = new TrieNode();
                }
                // 移动到子节点
                node = node.children[index];
            }
            // 设置当前节点为单词的结尾
            node.isEnd = true;
        }
    }

    图论总结

    1.岛屿问题
    定义遍历时的出发点 有时时从四周 有时遍历每一个点
    维护一个visit数组将访问过的节点标记防止重复遍历死循环
    2.拓扑排序(有向无环图)

    public class Sort {
        List<List<Integer>> keys; //每一个坐标都有一个可以走到的点,用一个链表维护
        int[] visit; //当前坐标位置访问状态 0未开始 1进行中 2结束
        boolean judge = true; //是否满足有向无环图
        public boolean canFinish(int numCourses, int[][] prerequisites) {
            keys = new ArrayList<>();
            visit = new int[numCourses];//初始化
            for (int i = 0; i < numCourses; i++) {
                keys.add(new ArrayList<>());//numCourses个坐标对应numCourses个链表
            }
            for (int[] arr:prerequisites){
                keys.get(arr[1]).add(arr[0]);//将当前点位可以到达的坐标添加到链表
            }
            for (int i = 0; i < numCourses && judge; i++) {
                if(visit[i] == 0){//当前坐标未开始 从当前节点进入
                    dfs(i);
                }
            }
            return judge;//返回是否满足拓扑排序
        }
    
        public void dfs(int num){
            visit[num] = 1;//将状态修改为进行中
            for (int v:keys.get(num)){
                if(visit[v] == 0){
                    dfs(v);
                    if(!judge){
                        return;//如果此时已经不满足拓扑排序规则直接return
                    }
                }else if(visit[v] == 1){//拓扑排序走到了自己 说明形成了环不满足规则将标志位改为false
                    judge = false;
                    return;
                }
            }
            visit[num] = 2;
        }
    }

    3.前缀树

    定义前缀树

    class Trie{
        public Trie[] children;
        public boolean isEnd;
        public Trie(){
            children = new Trie[n] //n为需要的长度
            isEnd = false;
        }
        // 向字典树中插入单词
        public void insert(String word) {
            TrieNode node = this;
            // 遍历单词中的每个字符
            for (char c : word.toCharArray()) {
                // 计算字符在字典树中的索引
                int index = c - 'a';
                // 如果当前节点没有子节点,创建新的子节点
                if (node.children[index] == null) {
                    node.children[index] = new TrieNode();
                }
                // 移动到子节点
                node = node.children[index];
            }
            // 设置当前节点为单词的结尾
            node.isEnd = true;
        }
    }
  • LeetCode150-链表

    面试150题-链表

    📕文章题目选自LeetCode面试150题-链表
    😊该文章内容基于作者本人思考总结,如有错误欢迎指正
    🏠题单地址:https://leetcode.cn/studyplan/top-interview-150/

    链表

    LeetCode150-环形链表-LC141

    /**
     * @ProjectName: Mounts
     * @Description: 环形链表
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 12:47
     * @Email: lihh53453@hundsun.com
     * 处理环形问题只需要考虑使用快慢指针 当相遇的时候一定有环
     * 但一定要注意循环的结束位置
     */
    public class LC141 {
        public boolean hasCycle(Node head) {
            if (head == null || head.next == null) {
                return false;
            }
            Node fast = head.next;
            Node slow = head;
            while(fast != slow){
                if(fast == null || fast.next == null){
                    return false;
                }
                fast = fast.next.next;
                slow = slow.next;
            }
            return true;
        }
    }
    

    LeetCode150-合并两个有序链表-LC21

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 13:32
     * @Email: lihh53453@hundsun.com
     * @Description: 合并两个有序链表
     * list1 = list1.next; 保证节点能在l1上继续走
     * list2 = list2.next; 保证节点能在l2上继续走
     * 将两个值进行比较 决定下一个扩展的节点是哪一个
     * 最后必定有一个值剩余 将节点指向最后一个有值的节点即可
     */
    public class LC21 {
        public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
            ListNode temp = new ListNode();
            ListNode cur = temp;
            while(list1 != null && list2 != null){
                if(list1.val < list2.val){
                    cur.next = list1;
                    list1 = list1.next;
                }else{
                    cur.next = list2;
                    list2 = list2.next;
                }
                cur = cur.next;
            }
            if(list1 == null){
                cur.next = list2;
            }else {
                cur.next = list1;
            }
            return temp.next;
        }
    }

    LeetCode150-两数相加-LC2

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 13:32
     * @Email: lihh53453@hundsun.com
     * @Description: 合并两个有序链表
     * list1 = list1.next; 保证节点能在l1上继续走
     * list2 = list2.next; 保证节点能在l2上继续走
     * 将两个值进行比较 决定下一个扩展的节点是哪一个
     * 最后必定有一个值剩余 将节点指向最后一个有值的节点即可
     */
    public class LC21 {
        public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
            ListNode temp = new ListNode();
            ListNode cur = temp;
            while(list1 != null && list2 != null){
                if(list1.val < list2.val){
                    cur.next = list1;
                    list1 = list1.next;
                }else{
                    cur.next = list2;
                    list2 = list2.next;
                }
                cur = cur.next;
            }
            if(list1 == null){
                cur.next = list2;
            }else {
                cur.next = list1;
            }
            return temp.next;
        }
    }
    

    LeetCode150-随机链表的复制-LC138

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 14:04
     * @Email: lihh53453@hundsun.com
     * @Description: 随机链表的复制
     * 用一个map来保存当前节点和传入节点的关系 将其关联上
     * 如果map存在这个节点直接将其返回即可
     * 如果不存在则选择创建一个新的节点 并且向下拓展
     */
    public class LC138 {
        Map<Node,Node> cache = new HashMap<>();
        public Node copyRandomList(Node head) {
            if(head == null){
                return null;
            }
            if(!cache.containsKey(head)){
                Node new_node = new Node(head.val);
                cache.put(head,new_node);
                new_node.next = copyRandomList(head.next);
                new_node.random = copyRandomList(head.random);
            }
            return cache.get(head);
        }
    }
    

    LeetCode150-反转链表II-LC92

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 14:59
     * @Email: lihh53453@hundsun.com
     * @Description:
     * 1 2 3 4 5
     * 2->4
     */
    public class LC92 {
        public ListNode reverseBetween(ListNode head, int left, int right) {
            ListNode dummy = new ListNode(-1,head);
            ListNode start = dummy;
            for(int i = 0;i<left - 1;i++){
                start = start.next;
            }
            ListNode pre = null;
            ListNode cur = start.next;
            for(int i = 0;i < right - left;i++){// 2-4  -> 0-2 -> 2 循环两次
                ListNode next = cur.next;
                cur.next = pre;
                pre = cur;
                cur = next;
            }
            start.next.next = cur;
            start.next = pre;
            return dummy.next;
        }
    }

    LeetCode150-K 个一组翻转链表-LC25

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 15:25
     * @Email: lihh53453@hundsun.com
     * @Description: K 个一组翻转链表
     */
    public class LC25 {
        public ListNode reverseKGroup(ListNode head, int k) {
            ListNode dummy = new ListNode(-1);
            dummy.next = head;
            ListNode temp = dummy;
            int n = 0;
            while(temp.next != null){
                temp = temp.next;
                n++;
            }
            ListNode p = dummy;
            ListNode pre = null;
            ListNode cur = dummy.next;
            for(;n>=k;n-=k){
                for(int i = 0;i < k ;i++){
                    ListNode next = cur.next;
                    cur.next = pre;
                    pre = cur;
                    cur = next;
                }
                ListNode new_start = p.next;
                p.next.next = cur;
                p.next = pre;
                p = new_start;
            }
            return dummy.next;
        }
    }
    

    反转链表总结

    确定反转的四个变量

    • 反转链表节点 cur

    • 反转链表节点cur的前一个节点 pre 初始为null

    • 反转链表前一个节点 p 初始为cur->pre

    • 反转链表后的链表的下一个无关节点p.next

      反转逻辑

    • 暂存cur.next节点为nxt

    • 将当前节点cur指向pre

    • 更新precur

    • 更新curnext

      反转范围

      因为头节点也可能被操作 所以需要创造有个虚拟的头节点**dummy**

    反转链表I

    直接反转整个列表 循环条件为while(dummy.next != null)

    反转链表II
    1. 由于制定了范围 首先要确定起始位置 for(int i = 0;i < left - 1; i++) 这个循环一共会走left-1步
    2. 此时节点p位置确定 cur位置初始化为p.next, pre初始化为null
    3. 执行反转逻辑
    4. 连接链表
      • p.next(反转后的尾节点).next指向cur(此时cur位于反转链表后的链表的下一个无关节点)
      • p.next指向反转后的链表的头节点 -> p.next = pre(pre此时位于反转后的新头节点)
    K 个一组翻转链表
    1. 首先确定节点个数 从节点个数中以k个节点为单位 依次循环链表 for(;n>=k;n-=k)

    2. 在k个范围内初始化 以下三个值

      ListNode p = dummy;
      ListNode pre = null;
      ListNode cur = dummy.next;
    3. 此时节点p位置确定 cur位置初始化为p.next, pre初始化为null

    4. 执行反转逻辑

    5. 连接链表

      • 将p.next进行保存 因为在下一轮中p需要更新为新的起始位置 ListNode new_start = p.next
      • p.next(反转后的尾节点).next指向cur(此时cur位于反转链表后的链表的下一个无关节点)
      • p.next指向反转后的链表的头节点 -> p.next = pre(pre此时位于反转后的新头节点)
      • 更新p p.next = new_start

    LeetCode150-删除链表的倒数第 N 个结点-LC19

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 16:01
     * @Email: lihh53453@hundsun.com
     * @Description:
     * 使用快慢指针 先让快指针走n步
     * 然后两个节点同时出发
     * 当快指针走到最后一步的时候 此时两个节点只相隔n
     */
    public class LC19 {
        public ListNode removeNthFromEnd(ListNode head, int n) {
            ListNode dummy = new ListNode();
            dummy.next = head;
            ListNode fast = dummy;
            ListNode slow = dummy;
            while(n-->0){
                fast = fast.next;
            }
            while (fast.next != null){
                fast = fast.next;
                slow = slow.next;
            }
            slow.next = slow.next.next;
            return dummy.next;
        }
    }
    

    LeetCode150-删除排序链表中的重复元素II-LC82

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 16:09
     * @Email: lihh53453@hundsun.com
     * @Description:
     */
    public class LC82 {
        public ListNode deleteDuplicates(ListNode head) {
            ListNode dummy = new ListNode(-1);
            dummy.next = head;
            ListNode p = dummy;
            while(p.next != null && p.next.next != null){
                if(p.next.val == p.next.next.val){
                    int val = p.next.val;
                    while(p.next != null && p.next.val == val){//循环覆盖操作
                        p.next = p.next.next;//覆盖操作
                    }
                }else{
                    p = p.next;//跳过 不采取行动
                }
            }
            return dummy.next;
        }
    }

    LeetCode150-旋转链表-LC61

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 16:21
     * @Email: lihh53453@hundsun.com
     * @Description: 旋转链表
     * 解题思路: 首先确定链表长度 
     *          拼接链表为环状
     *          平移头节点位置
     *          返回新头节点
     */
    public class LC61 {
        public ListNode rotateRight(ListNode head, int k) {
            if(head == null || k == 0 || head.next == null) return head;
            ListNode temp = head;
            int index = 1;
            while(temp.next != null){
                temp = temp.next;
                index++;
            }
            int add = index - k % index;//计算出需要平移多少位置
            if(index == add){
                return head;
            }
            temp.next = head;
            while(add-->0){
                temp = temp.next;
            }
            ListNode new_head = temp.next;
            temp.next = null;
            return new_head;
        }
    }
    

    LeetCode150-分隔链表-LC86

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 16:34
     * @Email: lihh53453@hundsun.com
     * @Description: 分隔链表
     * 将小于和大于该节点的节点保存在链表中 再将两个链表相连即可
     */
    public class LC86 {
        public ListNode partition(ListNode head, int x) {
            ListNode small = new ListNode(0);
            ListNode small_head = small;
            ListNode biger = new ListNode(0);
            ListNode biger_head = biger;
            while(head != null){
                if(head.val < x){
                    small_head.next = head;
                    small_head = small_head.next;
                }else{
                    biger_head.next = head;
                    biger_head = biger_head.next;
                }
                head = head.next;
            }
            biger_head.next = null;
            small_head.next = biger.next;
            return small.next;
        }
    }

    LeetCode150-LRU缓存-LC146

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/4 16:55
     * @Email: lihh53453@hundsun.com
     * @Description: LRU 缓存
     */
    public class LC146 {
        private Map<Integer, LinkedNode> cache = new HashMap<Integer, LinkedNode>();
        private int size;
        private int capacity;
        private LinkedNode head, tail;
    
        public LC146(int capacity) {
            this.size = 0;
            this.capacity = capacity;
            // 使用伪头部和伪尾部节点
            head = new LinkedNode();
            tail = new LinkedNode();
            head.next = tail;
            tail.pre = head;
        }
    
        public int get(int key) {
            LinkedNode node = cache.get(key);
            if (node == null) {
                return -1;
            }
            // 如果 key 存在,先通过哈希表定位,再移到头部
            moveToHead(node);
            return node.value;
        }
    
        public void put(int key, int value) {
            LinkedNode node = cache.get(key);
            if (node == null) {
                // 如果 key 不存在,创建一个新的节点
                LinkedNode newNode = new LinkedNode(key, value);
                // 添加进哈希表
                cache.put(key, newNode);
                // 添加至双向链表的头部
                addToHead(newNode);
                ++size;
                if (size > capacity) {
                    // 如果超出容量,删除双向链表的尾部节点
                    LinkedNode tail = removeTail();
                    // 删除哈希表中对应的项
                    cache.remove(tail.key);
                    --size;
                }
            }
            else {
                // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
                node.value = value;
                moveToHead(node);
            }
        }
    
        private void addToHead(LinkedNode node) {
            node.pre = head;
            node.next = head.next;
            head.next.pre = node;
            head.next = node;
        }
    
        private void removeNode(LinkedNode node) {
            node.pre.next = node.next;
            node.next.pre = node.pre;
        }
    
        private void moveToHead(LinkedNode node) {
            removeNode(node);
            addToHead(node);
        }
    
        private LinkedNode removeTail() {
            LinkedNode res = tail.pre;
            removeNode(res);
            return res;
        }
    }
    
    class LinkedNode{
        public int key;
        public int value;
        public LinkedNode pre;
        public LinkedNode next;
        public LinkedNode(LinkedNode pre,LinkedNode next){
            this.pre = pre;
            this.next = next;
        }
        public LinkedNode(){}
        public LinkedNode(int _key, int _value) {key = _key; value = _value;}
    }
    

    总结

    1. 要注意链表的创建 指针指向和定义
    2. 成环时考虑拆解 快慢指针可以确定节点位置
    3. 反转链表的四个变量
  • LeetCode150-栈

    面试150题-栈

    📕文章题目选自LeetCode面试150题-栈
    😊该文章内容基于作者本人思考总结,如有错误欢迎指正
    🏠题单地址:https://leetcode.cn/studyplan/top-interview-150/

    LeetCode150-有效的括号-LC20

    /**
     * @ProjectName: Mounts
     * @Description: LeetCode150-有效的括号-LC20
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/3 16:01
     * @Email: lihh53453@hundsun.com
     */
    public class LC20 {
        public boolean isValid(String s) {
            Map<Character,Character> map = new HashMap<Character,Character>(){{
                put(']','[');
                put('}','{');
                put(')','(');
            }};
            Deque<Character> queue = new LinkedList<>();
            for(char c:s.toCharArray()){
                if(map.containsKey(c)){
                    if (queue.isEmpty() || queue.peek() != map.get(c)){
                        return false;
                    }
                    queue.pop();
                }else{
                    /*
                    [(]1722672830113
                    [[, (]1722672830113
                     */
                    queue.push(c); //添加到顶部
                    //queue.add(c); 添加到末尾
                    /*
                    [(]1722672780704
                    [(, []1722672780705
                    [[]1722672780705
                    []1722672780705
                     */
                }
                System.out.println(queue + String.valueOf(System.currentTimeMillis()));
            }
            return queue.isEmpty();
        }
    
        public static void main(String[] args) {
            LC20 lc20 = new LC20();
            lc20.isValid("([)]");
        }
    }

    LeetCode150-简化路径-LC71

    /**
     * @ProjectName: Mounts
     * @Description: LeetCode150-简化路径-LC71
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/3 16:41
     * @Email: lihh53453@hundsun.com
     */
    public class LC71 {
        public String simplifyPath(String path) {
            String[] split = path.split("/");
            Deque<String> queue = new LinkedList<>();
            for(String str:split){
                if("..".equals(str)){
                    if(!queue.isEmpty()){
                        queue.pollLast();
                    }
                }else if(!str.isEmpty() && !".".equals(str)){
                    queue.addLast(str);
                }
                /*
                这样的写法会导"/.."的情况 不满足条件"..".equals(str) && !queue.isEmpty() 而走到!str.isEmpty() && !".".equals(str)
                此时满足条件 那么queue就会添加".."
                 */
    //            if("..".equals(str) && !queue.isEmpty()){
    //                queue.pollLast();
    //            }else if(!str.isEmpty() && !".".equals(str)){
    //                queue.addLast(str);
    //            }
    
            }
            StringBuilder stringBuilder = new StringBuilder();
            if (queue.isEmpty()) {
                stringBuilder.append('/');
            } else {
                while (!queue.isEmpty()) {
                    stringBuilder.append('/');
                    stringBuilder.append(queue.pollFirst());
                }
            }
            return stringBuilder.toString();
        }
    
        public static void main(String[] args) {
            LC71 lc71 = new LC71();
            lc71.simplifyPath("/..");
        }
    }

    LeetCode150-最小栈-LC155

    /**
     * @ProjectName: Mounts
     * @Description: LeetCode150-最小栈-LC155
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/3 17:07
     * @Email: lihh53453@hundsun.com
     * 有关栈的简单实现
     */
    public class LC155 {
        Node root;
        public LC155() {
            this.root = new Node();
        }
    
        public void push(int val) {
            //当前节点保存root之前的状态 例如next和min 相当于备份
            //先备份node 在更新root
            Node node = new Node(val,root.next);
            node.min = root.min;
            root.min = Math.min(root.min,val);
            root.next = node;
        }
    
        public void pop() {
            Node cur = root.next;
            if (cur.val == root.min) {
                root.min = cur.min;
                //cur节点保证进来之前root的min值
            }
            root.next = cur.next;
            cur.next = null;
    
        }
    
        public int top() {
            return root.next.val;
        }
    
        public int getMin() {
            return root.min;
        }
    
        public static void main(String[] args) {
            LC155 minStack = new LC155();
            minStack.push(-2);
            minStack.push(0);
            minStack.push(-3);
            minStack.getMin();   //--> 返回 -3.
            minStack.pop();
            minStack.top();      //--> 返回 0.
            minStack.getMin();   //--> 返回 -2.
        }
    }
    
    class Node{
        Node next;
        int min;
        int val;
        public Node(Node next){
            this.next = next;
        }
        public Node(int val,Node next){
            this.val = val;
            this.next = next;
        }
        public Node(){
            this.min = Integer.MAX_VALUE;
        }
    }
    

    LeetCode150-逆波兰表达式求值-LC150

    /**
     * @ProjectName: Mounts
     * @Description: LC150-逆波兰表达式求值LC150
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/3 17:38
     * @Email: lihh53453@hundsun.com
     */
    public class LC150 {
    
        public int evalRPN(String[] tokens) {
            Set<String> set = new HashSet<>();
            set.add("*");
            set.add("-");
            set.add("+");
            set.add("/");
            Deque<Integer> queue = new LinkedList<>();
            for(String str:tokens){
                if(set.contains(str)){
                    int num2 = queue.pop();
                    int num1 = queue.pop();
                    switch (str){
                        case "+":
                            queue.push(num1 + num2);
                            break;
                        case "-":
                            queue.push(num1 - num2);
                            break;
                        case "*":
                            queue.push(num1 * num2);
                            break;
                        case "/":
                            queue.push(num1 / num2);
                            break;
                    }
                }else{
                    queue.push(Integer.parseInt(str));
                }
            }
            return queue.pop();
        }
    }
    

    LeetCode150-基本计算器-LC224

    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/3 19:17
     * @Email: lihh53453@hundsun.com
     * @Description:
     * 定义一个栈用来记录当前括号内的数字是否要变成负数
     * 例如-(1 + 2 + 3) --> -1 - 2 - 3
     * sign 的作用即是 保持同一个括号内的数字 去掉括号后的数字要转换
     * 并且该题目也有多位数相加 所以还要添加一个循环
     * (1 + 2 - (1 + 2))
     * 分析:
     *     1.遇到(括号 将当前sign值添加到队列
     *     2.遇到 数字1 res+=sign*1 此时sign为1
     *     3.遇到 + 保持最新的sign peek操作不会将值弹出只做检查
     *     4.遇到 数字2 res+=sign*2 此时sign为1
     *     5.遇到 - 反转此时的sign值 -ops.peek() 将此时的sign值反转
     *     6.遇到(括号 将当前sign值添加到队列 此时sign值为-1 意味着括号内的所有加减号都要反转
     *     7.遇到 数字1 res+=sign*1 此时sign为-1 所以是减操作
     *     8.遇到 数字2 res+=sign*1 此时sign为-1 所以是减操作
     *     9.遇到)括号 将当前sign值移除队列 最新的sign值不会对产生数字影响
     *     10.遇到)括号 将当前sign值移除队列 最新的sign值不会对产生数字影响
     */    
    public class LC224 {
        public int calculate(String s) {
            Deque<Integer> ops = new LinkedList<>();
            int sign = 1;
            ops.push(1);
            int index = 0;
            int res = 0;
            while(index < s.length()){
                if(s.charAt(index) == ' '){
                    index++;
                }else if(s.charAt(index) == '('){
                    ops.push(sign);
                    index++;
                }else if(s.charAt(index) == ')'){
                    ops.pop();
                    index++;
                }else if(s.charAt(index) == '+'){
                    sign = ops.peek();
                    index++;
                }else if(s.charAt(index) == '-'){
                    sign = -ops.peek();
                    index++;
                }else{
                    int num = 0;
                    while(index < s.length() && Character.isDigit(s.charAt(index))){
                        num = num * 10 + s.charAt(index) - '0';
                        index++;
                    }
                    res += sign * num;
                }
            }
            return res;
        }
    
        public static void main(String[] args) {
            String s = " 2-1 + 2 ";
            LC224 lc224 = new LC224();
            lc224.calculate(s);
        }
    }

    总结

    栈(JAVA)一律采用双端队列Deque
    实现:Deque queue = new LinkedList<>();
    poll():从双端队列的头部移除并返回元素。如果队列为空,则返回null
    pop(): 从双端队列的头部移除并返回元素,如果移除失败,直接抛出异常
    pollLast():从双端队列的尾部移除并返回元素。如果队列为空,则返回null
    peek():返回双端队列头部的元素但不移除它。如果队列为空,则返回null
    peekLast():返回双端队列尾部的元素但不移除它。如果队列为空,则返回null
    add():向双端队列尾部添加一个元素,插入成功返回true,插入失败抛异常
    push():向双端队列的头部插入一个元素,插入成功返回void,插入失败抛异常

  • LeetCode150-滑动窗口+哈希表

    面试150题-滑动窗口+哈希表

    📕文章题目选自LeetCode面试150题-滑动窗口+哈希表
    😊该文章内容基于作者本人思考总结,如有错误欢迎指正
    🏠题单地址:https://leetcode.cn/studyplan/top-interview-150/

    滑动窗口

    LeetCode150-长度最小的子数组-LC209

    /**
     * 题目:长度最小的子数组
     * 算法:滑动窗口
     * 解题思路:1.初始窗口大小为0 start end 都是从0开始
     *         2.while循环中end一直向右移动并且 当sum > target,start向右移动缩小窗口大小
     *         3.ans记录最小值
     */
    public class LC209 {
        public static void main(String[] args) {
            int[] nums = {2,3,1,2,4,3};
            int target = 7;
            System.out.println(minSubArrayLen(target,nums));
        }
        public static int minSubArrayLen(int target, int[] nums) {
            int len = nums.length;
            int start = 0;
            int end = 0;//end值不能设为1 否则无法添加第一个元素
            int sum = 0;
            int ans = Integer.MAX_VALUE;
            while(end < len){
                sum += nums[end];
                while(sum >= target){
                    ans = Math.min(ans,end - start + 1);
                    sum-=nums[start++];
                }
                end++;
            }
            return ans == Integer.MAX_VALUE ? 0 : ans;
        }
    }
    

    LeetCode150-无重复字符的最长子串-LC209

    class Solution {
        public int lengthOfLongestSubstring(String s) {
            if(s.length() == 1) return 1;
            int maxLength = 0;
            int right = -1;
            Set<Character> set = new HashSet<Character>();
            for(int left = 0; left < s.length() - 1;left++){
                if(left != 0){
                    set.remove(s.charAt(left - 1));
                }
                while(right + 1 < s.length() && !set.contains(s.charAt(right + 1))){
                    set.add(s.charAt(right + 1));
                    right++;
                }
                maxLength = Math.max(maxLength,right - left + 1);
            }
            return maxLength;
        }
    }

    LeetCode150-串联所有单词的子串-LC30

    package cn.dsxriiiii.LeetCode.LC150;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/16 16:32
     * @Email: lihh53453@hundsun.com
     * @Description: 串联所有单词的子串
     * 思路:滑动窗口
     *      1.确定滑动窗口起始位置left,right
     *      2.right不断右移,移动条件为right+单个单词长度不超过s.length()
     *      3.left不断右移,移动条件为
     *                              1.right右移产生的单词在字符串中不存在 此时需要将map清空 left = right count计数器 =0
     *                              2.right右移产生单个单词数超过了words中的数量 此时while循环会判断当前right右移产生单个单词数是否一直超过了words中的数量
     *                                true:count(用来计数判断的有效个数)--,left移动
     */
    public class LC30 {
        public static void main(String[] args) {
            String s = "foobarfoobarfoothe";
            String[] words = {"foo","bar","foo","the"};
            System.out.println(findSubstring(s,words));
        }
    
        public static List<Integer> findSubstring(String s,String[] words){
            Map<String,Integer> map = new HashMap<>();
            List<Integer> res = new ArrayList<>();
            if(s.isEmpty() || words.length == 0) return res;
            for(String word:words){
                if(!s.contains(word)) return res;
                map.put(word,map.getOrDefault(word,0) + 1);
            }
            int oneWordLength = words[0].length();
            int wordsLength = oneWordLength * words.length;
            if(wordsLength > s.length()) return res;
            for(int i = 0;i < words[0].length();i++){
                int left = i;
                int right = i;
                int count = 0;
                Map<String,Integer> subMap = new HashMap<>();
                while(right + oneWordLength  <= s.length()){
                    String newWordByRight = s.substring(right,right+oneWordLength);
                    right = right + oneWordLength;
                    if(!map.containsKey(newWordByRight)){
                        subMap.clear();
                        left = right;
                        count = 0;
                    }else{
                        subMap.put(newWordByRight,subMap.getOrDefault(newWordByRight,0) + 1);
                        count++;
                        while(subMap.getOrDefault(newWordByRight,0) > map.getOrDefault(newWordByRight,0)){
                            String newWordByLeft = s.substring(left,left + oneWordLength);
                            subMap.put(newWordByLeft,subMap.getOrDefault(newWordByLeft,0) - 1);
                            count--;
                            left = left + oneWordLength;
                        }
                        if(count == words.length){
                            res.add(left);
                        }
                    }
                }
            }
            return res;
        }
    
        public static List<Integer> solution2(String s, String[] words) {
            List<Integer> res = new ArrayList<>();
            Map<String, Integer> wordsMap = new HashMap<>();
            if (s.isEmpty() || words.length == 0) return res;
            for (String word: words) {
                // 主串s中没有这个单词,直接返回空
                if (!s.contains(word)) return res;
                // map中保存每个单词,和它出现的次数
                wordsMap.put(word, wordsMap.getOrDefault(word, 0) + 1);
            }
            // 每个单词的长度, 总长度
            int oneLen = words[0].length(), wordsLen = oneLen * words.length;
            // 主串s长度小于单词总和,返回空
            if (wordsLen > s.length()) return res;
            // 只讨论从0,1,..., oneLen-1 开始的子串情况,
            // 每次进行匹配的窗口大小为 wordsLen,每次后移一个单词长度,由左右窗口维持当前窗口位置
            for (int i = 0; i < oneLen; ++i) {
                // 左右窗口
                int left = i, right = i, count = 0;
                // 统计每个符合要求的word
                Map<String, Integer> subMap = new HashMap<>();
                // 右窗口不能超出主串长度
                while (right + oneLen <= s.length()) {
                    // 得到一个单词
                    String word = s.substring(right, right + oneLen);
                    // 有窗口右移
                    right += oneLen;
                    // words[]中没有这个单词,那么当前窗口肯定匹配失败,直接右移到这个单词后面
                    if (!wordsMap.containsKey(word)) {
                        left = right;
                        // 窗口内单词统计map清空,重新统计
                        subMap.clear();
                        // 符合要求的单词数清0
                        count = 0;
                    } else {
                        // 统计当前子串中这个单词出现的次数
                        subMap.put(word, subMap.getOrDefault(word, 0) + 1);
                        ++count;
                        // 如果这个单词出现的次数大于words[]中它对应的次数,又由于每次匹配和words长度相等的子串
                        // 如 ["foo","bar","foo","the"]  "| foobarfoobar| foothe"
                        // 第二个bar虽然是words[]中的单词,但是次数抄了,那么右移一个单词长度后 "|barfoobarfoo|the"
                        // bar还是不符合,所以直接从这个不符合的bar之后开始匹配,也就是将这个不符合的bar和它之前的单词(串)全移出去
                        while (subMap.getOrDefault(word, 0) > wordsMap.getOrDefault(word, 0)) {
                            // 从当前窗口字符统计map中删除从左窗口开始到数量超限的所有单词(次数减一)
                            String w = s.substring(left, left + oneLen);
                            subMap.put(w, subMap.getOrDefault(w, 0) - 1);
                            // 符合的单词数减一
                            --count;
                            // 左窗口位置右移
                            left += oneLen;
                        }
                        // 当前窗口字符串满足要求
                        if (count == words.length) res.add(left);
                    }
                }
            }
            return res;
        }
    }
    

    LeetCode150-最小覆盖子串-LC76

    package cn.dsxriiiii.LeetCode.LC150;
    
    /**
     * @ProjectName: Mounts
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/17 9:55
     * @Email: lihh53453@hundsun.com
     * @Description: 最小覆盖子串
     * 思路:滑动窗口
     *      1.维护两个数组保存128位(包含大小写)
     *      2.包含函数 判断s中所有的字符数量比t的所有字符数量多
     *      3.右移条件:走到s串的结尾
     *      4.左移条件:使用包含函数 不断左指针右移 并且在数组中减少字符--;
     *      5.此时左右指针维护的最小字串长度判断并且不断更新
     */
    public class LC76 {
        public static void main(String[] args) {
            String s = "aaaaaaaaaaaabbbbbcdd";
            String t = "abcdd";
            System.out.println(minWindow(s,t));
        }
        public static String minWindow(String s, String t) {
            char[] charArray = s.toCharArray();
            int nowLeft = -1;
            int nowRight = s.length();
            int left = 0;
            int[] arrT = new int[128];
            int[] arrS = new int[128];
            for(char c : t.toCharArray()){
                arrT[c]++;
            }
            for (int right = 0; right < s.length(); right++) {
                arrS[charArray[right]]++;
                while(isCover(arrS,arrT)){
                    if(right - left < nowRight - nowLeft){
                        nowRight = right;
                        nowLeft = left;
                    }
                    arrS[charArray[left++]]--;
                }
            }
            return nowLeft < 0 ? "" : s.substring(nowLeft,nowRight+1);
        }
    
        private static boolean isCover(int[] s, int[] t) {
            for(int i = 'A';i <= 'Z';i++){
                if(s[i] < t[i]){
                    return false;
                }
            }
            for(int i = 'a';i <= 'z';i++){
                if(s[i] < t[i]){
                    return false;
                }
            }
            return true;
        }
    }
    

    滑动窗口总结

    1.长度最小的子数组

    初始窗口大小为0 start end 都是从0开始
    while循环中end一直向右移动并且 当sum > targetstart向右移动缩小窗口大小
    ans记录最小值

    2,.无重复字符的最长子串

    遍历字符串,每次移动左指针时从集合中移除相应字符,并且将left-1的字符移除
    right从-1开始

    哈希表

    LeetCode150-赎金信-LC383

    /**
     * @ProjectName: Mounts
     * @Description: LC383 赎金信
     * @Author: DSXRIIIII
     * @CreateDate: 2024/5/24 17:02
     * @Email: lihh53453@hundsun.com
     *
     * 解题思路:字母数组++和--
     **/
    public class LC383 {
        public boolean canConstruct(String ransomNote, String magazine) {
            if(ransomNote.length() > magazine.length()){
                return false;
            }
            int[] charArr = new int[26];
            for(char a : magazine.toCharArray()){
                charArr[a -'a']++;
            }
            for(char b : ransomNote.toCharArray()){
                --charArr[b - 'a'];
                if(charArr[b - 'a'] < 0){
                    return false;
                }
            }
            return true;
        }
    }
    

    LeetCode150-同构字符串-LC205

    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @ProjectName: Mounts
     * @Description: LC205 同构字符串
     * @Author: DSXRIIIII
     * @CreateDate: 2024/5/24 17:12
     * @Email: lihh53453@hundsun.com
     *
     * 思路一:两个HashMap
     **/
    public class LC205 {
        public boolean isIsomorphic(String s, String t) {
            if (s.length() != t.length()) {
                return false;
            }
            Map<Character, Character> mapS = new HashMap<>();
            Map<Character, Character> mapT = new HashMap<>();
            for (int index = 0; index < s.length(); index++) {
                char charS = s.charAt(index);
                char charT = t.charAt(index);
                if (!mapS.containsKey(charS)) {
                    mapS.put(charS, charT);
                }
                if (!mapT.containsKey(charT)) {
                    mapT.put(charT, charS);
                }
                if (mapS.get(charS) != charT || mapT.get(charT) != charS) {
                    return false;
                }
            }
            return true;
        }
    
        public static void main(String[] args) {
            String s = "paper";
            String t = "title";
            LC205 lc205 = new LC205();
            System.out.println(lc205.isIsomorphic(s, t));
        }
    }
    

    LeetCode150-单词规律-LC290

    public class LC290 {
        public static boolean wordPattern(String pattern, String s) {
            String[] words = s.split(" ");
            Map<Character,String> map =new HashMap<>();
            Map<String,Character> map2 =new HashMap<>();
            for (int i = 0; i < pattern.length(); i++) {
                if(map.containsKey(pattern.charAt(i)) && !map.get(pattern.charAt(i)).equals(words[i])){
                    return false;
                }
                if(map2.containsKey(words[i]) && !map2.get(words[i]).equals(pattern.charAt(i))){
                    return false;
                }
                map.put(pattern.charAt(i),words[i]);
                map2.put(words[i],pattern.charAt(i));
            }
            return true;
    
        }
    
        public static void main(String[] args) {
            String pattern = "abba";
            String s = "dog cat cat dog";
            System.out.println(wordPattern(pattern, s));
    
        }
    }
    

    LeetCode150-有效的字母异位词-LC242

    /**
     * @ProjectName: Mounts
     * @Description: 数组++ -- 即可
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/5 17:18
     * @Email: lihh53453@hundsun.com
     **/
    public class LC242 {
        public boolean isAnagram(String s, String t) {
            if(s.length() != t.length()) return false;
            int[] c = new int[26];
            for (int i = 0; i < s.length(); i++) {
                c[s.charAt(i) - 'a']++;
                c[t.charAt(i) - 'a']--;
            }
            for (int i = 0; i < 26; i++) {
                if(c[i] != 0){
                    return false;
                }
            }
            return true;
        }
    }

    LeetCode150-字母异位词分组-LC49

    /**
     * 将str->char数组->打乱->String
     * 排序的String转换成key存储在map中 用来匹配 将匹配的结果放到list中
     * @ProjectName: Mounts
     * @Description: 字母异位词分组
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/5 17:28
     * @Email: lihh53453@hundsun.com
     **/
    public class LC49 {
        public List<List<String>> groupAnagrams(String[] strs) {
            Map<String,List<String>> map = new HashMap<>();
            for(String str:strs){
                char[] charArray = str.toCharArray();
                Arrays.sort(charArray);
                String key = new String(charArray);
                List<String> list = map.getOrDefault(key,new ArrayList<>());
                list.add(str);
                map.put(key,list);
            }
            return new ArrayList<List<String>>(map.values());
        }
    }
    

    LeetCode150-有效的数独-LC38

    /**
     * @ProjectName: Mounts
     * @Description: 有效的数独
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/18 13:40
     * @Email: lihh53453@hundsun.com
     */
    public class LC38 {
        public boolean isValidSudoku(char[][] board) {
            int[][] arr1 = new int[9][9];
            int[][] arr2 = new int[9][9];
            int[][][] arr3 = new int[3][3][9];
            for (int i = 0; i < 9; i++) {
                for (int j = 0; j < 9; j++) {
                    char c = board[i][j];
                    if (c != '.') {
                        int index = c - '0' - 1;
                        arr1[i][index]++;//保存列的个数
                        arr2[j][index]++;//保存行的个数
                        arr3[i / 3][j / 3][index]++;//保存单个3*3模块的个数
                        if (arr1[i][index] > 1 || arr2[j][index]++ > 1 || arr3[i / 3][j / 3][index] > 1) {
                            return false;
                        }
                    }
                }
            }
            return true;
        }
    }
    

    LeetCode150- 螺旋矩阵-LC38

    package cn.dsxriiiii.LeetCode.LC150;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @ProjectName: Mounts
     * @Description: 螺旋矩阵
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/18 14:00
     * @Email: lihh53453@hundsun.com
     *  方法与螺旋矩阵II类似
     */
    public class LC54 {
        public static void main(String[] args) {
            int[][] arr = {{2,5,8},{4,0,-1}};
            System.out.println(spiralOrder(arr));
        }
        public static List<Integer> spiralOrder(int[][] matrix) {
            List<Integer> res = new ArrayList<>();
            int up = 0;
            int down = matrix.length - 1;
            int left = 0;
            int right = matrix[0].length - 1;
            while(true){
                for(int i = left;i <= right;i++) res.add(matrix[up][i]);
                if(++up > down) break;
                for (int j = up; j <= down; j++) res.add(matrix[j][right]);
                if(--right < left) break;
                for (int m = right; m >= left ; m--) res.add(matrix[down][m]);
                if(--down < up) break;
                for (int n = down; n >= up; n--) res.add(matrix[n][left]);
                if(++left > right) break;
            }
            return res;
        }
    }
    

    LeetCode150- 旋转图像-LC48

    package cn.dsxriiiii.LeetCode.LC150;
    
    /**
     * @ProjectName: Mounts
     * @Description: 旋转图像
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/18 14:47
     * @Email: lihh53453@hundsun.com
     */
    public class LC48 {
        public void rotate(int[][] matrix) {
            int len = matrix.length;
            int[][] new_matrix = new int[len][len];
            for (int i = 0; i < len; i++) {
                for (int j = 0; j < len; j++) {
                    new_matrix[j][len - i - 1] = matrix[i][j];  //找到此处的数学规律即可
                }
            }
            for (int i = 0; i < len; i++) {
                System.arraycopy(new_matrix[i], 0, matrix[i], 0, len);
            }
        }
    }
    

    LeetCode150- 矩阵置零-LC73

    package cn.dsxriiiii.LeetCode.LC150;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @ProjectName: Mounts
     * @Description: 矩阵置零
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/18 14:53
     * @Email: lihh53453@hundsun.com
     */
    public class LC73 {
        public void setZeroes(int[][] matrix) {
            int m = matrix.length;
            int n = matrix[0].length;
            //boolean[][] arr = new boolean[len][len];
            boolean[] col = new boolean[n];
            boolean[] row = new boolean[m];
            //原本的想法是按照注释的那种 但是如果是对单行单列操作只需要一位数组就行了
            List<List<int[]>> list = new ArrayList<>();
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if(matrix[i][j] == 0){
                        col[j] = true;
                        row[i] = true;
                    }
                }
            }
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if(col[j] || row[i]){
                        matrix[i][j] = 0;
                    }
                }
            }
    
        }
    }
    

    LeetCode150- 字母异位词分组-LC49

    package cn.dsxriiiii.LeetCode.LC150;
    
    import java.util.*;
    
    /**
     * @ProjectName: Mounts
     * @Description: 字母异位词分组
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/5 17:28
     * @Email: lihh53453@hundsun.com
     * 思路:哈希表
     *      1.将每个字符串排列之后得到的key是一样的
     *      2.依次放入map中的list
     *      3.最后return new ArrayList<List<String>>(map.values());将map转化为ArrayList
     **/
    public class LC49 {
        public List<List<String>> groupAnagrams(String[] strs) {
            Map<String,List<String>> map = new HashMap<>();
            for(String str:strs){
                char[] charArray = str.toCharArray();
                Arrays.sort(charArray);
                String key = new String(charArray);
                List<String> list = map.getOrDefault(key,new ArrayList<>());
                list.add(str);
                map.put(key,list);
            }
            return new ArrayList<List<String>>(map.values());
        }
    }
    

    LeetCode150- 字母异位词分组-LC49

    package cn.dsxriiiii.LeetCode.LC150;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @ProjectName: Mounts
     * @Description:
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/19 14:11
     * @Email: lihh53453@hundsun.com
     * 思路:哈希表
     *      1.把值和index绑定在hashmap中
     *      2.注意匹配 两数之和即 target - 其中一数
     */
    public class LC1 {
        public int[] twoSum(int[] nums, int target) {
            Map<Integer,Integer> map = new HashMap<>();
            for(int i = 0; i < nums.length;i++){
                if(map.containsKey(target - nums[i])){
                    return new int[]{i,map.get(target - nums[i])};
                }
                map.put(nums[i],i);
            }
            return new int[0];
        }
    }
    

    LeetCode150- 两数之和-LC1

    package cn.dsxriiiii.LeetCode.LC150;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @ProjectName: Mounts
     * @Description: 两数之和
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/19 14:11
     * @Email: lihh53453@hundsun.com
     * 思路:哈希表
     *      1.把值和index绑定在hashmap中
     *      2.注意匹配 两数之和即 target - 其中一数
     */
    public class LC1 {
        public int[] twoSum(int[] nums, int target) {
            Map<Integer,Integer> map = new HashMap<>();
            for(int i = 0; i < nums.length;i++){
                if(map.containsKey(target - nums[i])){
                    return new int[]{i,map.get(target - nums[i])};
                }
                map.put(nums[i],i);
            }
            return new int[0];
        }
    }
    

    LeetCode150- 快乐数-LC202

    package cn.dsxriiiii.LeetCode.LC150;
    
    import java.util.HashSet;
    import java.util.Set;
    
    /**
     * @ProjectName: Mounts
     * @Description: 快乐数  1**2 + 9**2 = 82
     *                      8**2 + 2**2 = 68
     *                      6**2 + 8**2 = 100
     *                      1**2 + 0**2 + 0**2 = 1
     * @Author: DSXRIIIII
     * @CreateDate: 2024/6/19 14:39
     * @Email: lihh53453@hundsun.com
     * 技巧:快乐树会形成一个循环
     *      例如:4 最终会回到4
     *      最大的数999999999 也会回到810  最后811次循环就会回到起点
     * 思路:1.字符串分解 ? -> 会死循环
     *      2.怎么跳出死循环 ?
     *      3.哈希Set 把每个数都放到set中 当set有重复值则会退出循环
     *      4.快慢指针 当快指针指向慢指针说明此时走了一个循环周期  要判断这个循环是不是1引起的
     */
    public class LC202 {
    
        public static int GetNext(int n){
            int total = 0;
            while(n != 0){
                int num = n % 10;
                total += num * num;
                n = n / 10;
            }
            return total;
        }
    
        public boolean isHappyBySet(int n) {
            Set<Integer> set = new HashSet<>();
            while(n != 1 && !set.contains(n)){
                set.add(n);
                n = GetNext(n);
            }
            return n == 1;
        }
    
        public boolean isHappyByHash(int n) {
            int slow = n,fast = n;
            do{
                slow = GetNext(slow);
                fast = GetNext(fast);
                fast = GetNext(fast);
            }while(slow != fast);
    
            return slow == 1;
        }
    
        public static void main(String[] args) {
            //System.out.println(GetNext(12));
        }
    }
    

    LeetCode150- 存在重复元素 II-LC129

    /**
     * @ProjectName: Mounts
     * @Description: 存在重复元素 II
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/2 21:05
     * @Email: lihh53453@hundsun.com
     * 哈希表和滑动数组解决
     * 1.哈希表可以保存数据坐标值和数组值相互对应上
     * 2.看到固定距离就要想到滑动窗口 而滑动窗口要想到set集合 或者队列
     */
    public class LC219 {
        public boolean containsNearbyDuplicate_byMap(int[] nums, int k) {
            Map<Integer, Integer> map = new HashMap<>();
            for (int i = 0; i < nums.length; i++) {
                if(map.containsKey(nums[i]) && i - map.get(nums[i]) <= k){
                    return true;
                }
                map.put(nums[i],i);
            }
            return false;
        }
    
        /**
         * 假设为4 位置i = 3  5 - 4 - 1 只有比窗口大的时候才会有减的空间 因为i-k限定了
         * 0 1 2 3 4
         * 1 2 3 4 -> 3
         *   2 3 4 5
         * @param nums 初始数组
         * @param k 滑动窗口大小
         * @return boolean
         */
        public boolean containsNearbyDuplicate(int[] nums, int k) {
            int length = nums.length;
            Set<Integer> set = new HashSet<>();
            for (int i = 0; i < length; i++) {
                if(i > k){
                    set.remove(nums[i - k - 1]);
                }
                if(!set.add(nums[i])){
                    return true;
                }
            }
            return false;
        }
    }

    LeetCode150-最长连续序列-LC128

    /**
     * @ProjectName: Mounts
     * @Description:LeetCode150-最长连续序列-LC128
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/2 21:28
     * @Email: lihh53453@hundsun.com
     * 可以将set想象成一个桶 不断++取得新的数字
     */
    public class LC128 {
        public int longestConsecutive(int[] nums) {
            Set<Integer> set = new HashSet<>();
            //初始长度为0 因为要对空的数组判断
            int result = 0;
            for (int num : nums) {
                set.add(num);
            }
    //        for (int num : nums) {
    //            int locLen = 1;
    //            int currentNum = num;
    //            while (!set.contains(currentNum - 1)) {
    //                currentNum += 1;
    //                locLen += 1;
    //            }
    //            result = Math.max(locLen, result);
    //        }
    //        return result;
            for (int num : nums) {
                //上面的方法缺少对这个数是不是最底层的数的判断
                //因为题目要求连续那么只能是一直加1连续的数 那么-1判断的逻辑就不能少
                if(!set.contains(num - 1)){
                    //有数字 保底就是1
                    int locLen = 1;
                    int currentNum = num;
                    //不断往前走的逻辑 只要set有+1的数 就是连续并且满足条件的
                    while (set.contains(currentNum + 1)) {
                        currentNum += 1;
                        locLen += 1;
                    }
                    result = Math.max(locLen, result);
                }
            }
            return result;
        }
    }

    区间

    LeetCode150-汇总区间-LC228

    /**
     * @ProjectName: Mounts
     * @Description: 汇总区间-LC228
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/2 21:48
     * @Email: lihh53453@hundsun.com
     * 思路:记录初始位置值 并且不断index++即可
     * 要注意while循环条件下
     * 1.确保循环条件
     * 2.确保不要死循环
     */
    public class LC228 {
        public List<String> summaryRanges(int[] nums) {
            if(nums.length == 0) return new ArrayList<>();
            List<String> res = new ArrayList<>();
            int index = 0;
            while(index < nums.length){
                int start = index;
                index++;
    //            while(nums[index - 1] + 1 == nums[index]){
    //                index+=1;
    //            }
                //这里要添加限定的范围否则index会一直增加
                while(index < nums.length && nums[index - 1] + 1 == nums[index]){
                    index+=1;
                }
                int location = index - 1;
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.append(nums[start]);
                if(start<location){
                    stringBuilder.append("->");
                    stringBuilder.append(nums[location]);
                }
                res.add(stringBuilder.toString());
            }
            return res;
        }
    }

    LeetCode150-合并区间-LC56

    /**
     * @ProjectName: Mounts
     * @Description: 合并区间-LC56
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/3 14:02
     * @Email: lihh53453@hundsun.com
     * 将新的加入结果集
     * 并且与容器内的数组进行比对
     */
    public class LC56 {
        public int[][] merge(int[][] intervals) {
            if(intervals.length == 0) return new int[0][0];
            Arrays.sort(intervals, (o1, o2) -> o1[0] - o2[0]);
            List<int[]> list = new ArrayList<>();
            for(int[] arr:intervals){
                int left = arr[0],right = arr[1];
                if(list.isEmpty() || list.get(list.size() - 1)[1] < left){
                    list.add(new int[]{left,right});
                }else{
                    list.get(list.size() - 1)[1] = Math.max(right,list.get(list.size() - 1)[1]);
                }
            }
            return list.toArray(new int[list.size()][]);
        }
    
        public static void main(String[] args) {
            int [][] arr = {{1,2},{2,3}};
            List<int[]> arrayList = new ArrayList<>();
            arrayList.add(new int[]{1,2});
            arrayList.add(new int[]{2,3});
            /* 输出结果为
             * [[1, 2], [2, 3]]
             * [[I@3b07d329
             * [[1, 2], [2, 3]]
             */
            System.out.println(Arrays.deepToString(arr));
            System.out.println(arrayList.toArray(new int[arrayList.size()][]));
            System.out.println(Arrays.deepToString(arrayList.toArray(new int[arrayList.size()][])));
        }
    }
    

    LeetCode150-插入区间-LC57

    /**
     * @ProjectName: Mounts
     * @Description: 插入区间-LC57
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/3 14:41
     * @Email: lihh53453@hundsun.com
     * 这里选择最简单的插入然后排序的方法
     * 在排序的过程中也可以选择二分法将nlog(n) -> log(n)
     */
    public class LC57 {
        public int[][] insert(int[][] intervals, int[] newInterval) {
            int[][] arr = Arrays.copyOf(intervals, intervals.length + 1);
            arr[arr.length - 1] = new int[2];
            arr[arr.length - 1][0] = newInterval[0];
            arr[arr.length - 1][1] = newInterval[1];
            //Arrays.sort(arr,(num1,num2)->num1[0] - num2[0]);
            Arrays.sort(arr, Comparator.comparingInt(num -> num[0]));
            List<int[]> res = new ArrayList<>();
            for(int[] array:arr){
                int left = array[0],right = array[1];
                if(res.isEmpty() || res.get(res.size() - 1)[1] < left){
                    res.add(new int[]{left,right});
                }else {
                    res.get(res.size() - 1)[1] = Math.max(right,res.get(res.size() - 1)[1]);
                }
            }
            return res.toArray(new int[res.size()][]);
        }
    
        public static void main(String[] args) {
            int[][] arr1 = {{1,2},{2,3}};
            int[][] arr2 = Arrays.copyOf(arr1, arr1.length + 1);
            System.out.println(Arrays.deepToString(arr2));
        }
    }

    LeetCode150-用最少数量的箭引爆气球-LC452

    /**
     * @ProjectName: Mounts
     * @Description: LC452-用最少数量的箭引爆气球
     * @Author: DSXRIIIII
     * @CreateDate: 2024/8/3 15:04
     * @Email: lihh53453@hundsun.com
     * 这一题也可以为合并区间的一种写法 采用交集
     */
    public class LC452 {
        public int[][] findMinArrowShots(int[][] points) {
            Arrays.sort(points, Comparator.comparingInt(i -> i[1]));//并交集不同之处 确保不比对的值为最小
            List<int[]> res = new ArrayList<>();
            for(int[] arr: points){
                int left = arr[0],right = arr[1];
                if(res.isEmpty() || res.get(res.size() - 1)[1] < left){
                    res.add(new int[]{left,right});
                }else {
                    res.get(res.size() - 1)[0] = Math.min(res.get(res.size() - 1)[0],left);//并交集不同之处
                }
            }
            return res.toArray(new int[res.size()][]);
        }
    
        public static void main(String[] args) {
            int[][] arr = {{3,9},{7,12},{3,8},{6,8},{9,10},{2,9},{0,9},{3,9},{0,6},{2,8}};
            Arrays.sort(arr, Comparator.comparingInt(i -> i[1]));
            System.out.println(Arrays.deepToString(arr));
            LC452 lc452 = new LC452();
            System.out.println(Arrays.deepToString(lc452.findMinArrowShots(arr)));
        }
    }
    

    总结

    做区间题时需要考虑并交集的选择

    1. 首先为了方便插入数据,需要创建一个List数组
    2. 在根据不同并交集的选择 对一号位或者二号位排序
    3. 当数组列表为空或者右边界比新值小就添加到结果列表
    4. 否则更新左边界(交集,边界值取最小)/右边界(并集,边界值取最大)

      1.将列表转换成int[][]方法res.toArray(new int[res.size()][])
      2.排序方法Arrays.sort(points, Comparator.comparingInt(i -> i[1]));
      或者Arrays.sort(intervals, (o1, o2) -> o1[0] – o2[0])
      推荐使用Comparator

  • LeetCode150-双指针

    面试150题-双指针

    📕文章题目选自LeetCode面试150题-双指针部分
    😊该文章内容基于作者本人思考总结,如有错误欢迎指正
    🏠题单地址:https://leetcode.cn/studyplan/top-interview-150/

    双指针

    LeetCode150-验证回文串-LC125

    /**
     * 题目:验证回文串
     * 算法:双指针
     * 思路:双指针遇到不是字符或者数字就向前移动
     * 补充:Character方法 isLetterOrDigit() 判断字符是否是数字还是字母
     */
    public class LC125 {
        public boolean isPalindrome_function(String s) {
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i < s.length();i++){
                char c = s.charAt(i);
                if(Character.isLetterOrDigit(c)){
                    sb.append(Character.toLowerCase(c));
                }
            }
            StringBuilder sb2 = new StringBuilder(sb).reverse();
            return sb2.toString().contentEquals(sb);
        }
    
        public boolean isPalindrome(String s) {
            int left = 0;
            int right = s.length() - 1;
            while(left < right){
                while(left < right && !Character.isLetterOrDigit(s.charAt(left))){
                    left++;
                }
                while(left < right && !Character.isLetterOrDigit(s.charAt(right))){
                    right--;
                }
                if(left < right){
                    if(Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right))){
                        return false;
                    }
                    left++;
                    right--;
                }
            }
            return true;
        }
    }

    LeetCode150-判断子序列-LC392

    /**
     * 题目:判断子序列
     * 算法:双指针
     * 思路:双指针一一比对然后移动
     */
    public class LC392 {
        public static void main(String[] args) {
            String s = "abc", t = "ahbgdc";
            System.out.println(isSubsequence(s,t));
        }
        public static boolean isSubsequence(String s, String t) {
            if(s.length() == 0) return true;
            int index_s = 0;
            int index_t = 0;
            while(index_t != t.length()){
                if(s.charAt(index_s) == t.charAt(index_t)){
                    if(index_s == s.length() - 1){
                        return true;
                    }
                    index_t++;
                    index_s++;
                    continue;
                }
                index_t++;
            }
            return false;
        }
    }

    LeetCode150-两数之和-LC167

    /**
     * 题目:两数之和
     * 算法:双指针
     * 思路:双指针前后两方向移动
     */
    public class LC167 {
        public int[] twoSum(int[] numbers, int target) {
            int fast = numbers.length - 1;
            int slow = 0;
            while(slow < fast){
                int sum = numbers[slow] + numbers[fast];
                if(sum == target){
                    return new int[]{slow + 1,fast + 1};
                }else if(sum < target){
                    slow++;
                }else{
                    fast--;
                }
            }
            return new int[]{-1,-1};
        }
    }
    

    LeetCode150-盛最多水的容器-LC11

    /**
     * 题目:盛最多水的容器
     * 算法:双指针
     * 思路:双指针前后两方向移动,每次移动时记录此时面积并且保存最大的面积值
     * 技巧:移动时 容器的最大值取决于边界的最小值 area = min(left,right) * (right - left);
     *      无论长边怎么移动 都取决于短边的长度 所以移动时要移动短边
     */
    public class LC11 {
        public static void main(String[] args) {
            int[] a = {1,8,6,2,5,4,8,3,7};
            System.out.println(maxArea(a));
        }
        public static int maxArea(int[] height) {
            int left = 0;
            int right = height.length - 1;
            int res = 0;
            while(left < right){
                res = Math.max(res,Math.min(height[left],height[right]) * (right - left));
                if(height[left] < height[right]){
                    left++;
                }else{
                    right--;
                }
            }
            return res;
        }
    }

    LeetCode150-三数之和-LC15

    /**
     * 题目:三数之和
     * 算法:暴力剪枝 -> O(n**3) -> O(n**2)
     * 优化思路:1.锁定a,b = a + 1,c = len - 1;避免三重循环
     *         2.遇到相同值continue
     */
    public class LC15 {
        public List<List<Integer>> threeSum(int[] nums) {
            int len = nums.length;
            Arrays.sort(nums);
            //结果数组
            List<List<Integer>> res = new ArrayList<>();
            for(int a = 0; a < len;a++){
                int target = -nums[a];//确认目标值 锁定a;
                int c = len - 1;//每一次确定第三个值为末尾
                if(a > 0 && nums[a - 1] == nums[a]){
                    //a相等去重
                    continue;
                }
                for(int b = a + 1;b < len;b++){
                    //b的起点为a的下一位
                    if(b > a + 1 && nums[b - 1] == nums[b]){
                        //去重
                        continue;
                    }
                    while(b < c && nums[b] + nums[c] > target){
                        //如果比目标值大 c--使和减少
                        c--;
                    }
                    //当b追上c是此时没有结果值 退出当前循环
                    if(b == c){
                        break;
                    }
                    //a b c找到结果值时添加进入列表
                    if(nums[b] + nums[c] == target){
                        List<Integer> list = new ArrayList<>();
                        list.add(nums[a]);
                        list.add(nums[b]);
                        list.add(nums[c]);
                        res.add(list);
                    }
                }
            }
            return res;
        }
    }

    双指针总结

    1.验证回文串

    定义left指针和right指针左右开工 使用Character.toLowerCase(c)降低为小写 使用Character.isLetterOrDigit(s.charAt(left)判断是否为字母

    2.判断子序列

    从左向右一一遍历对比即可

    3.两数之和

    双指针秒了

    4.盛最多水的容器

    无论长边怎么移动 都取决于短边的长度 所以移动时要移动短边 并且记录每次水对应的最大值

    5.三数之和

    1.锁定a,b = a + 1,c = len – 1;避免三重循环
    2.遇到相同值continue

  • LeetCode-150-数组

    面试150题-数组部分

    📕文章题目选自LeetCode面试150题-数组部分
    😊该文章内容基于作者本人思考总结,如有错误欢迎指正
    🏠题单地址:https://leetcode.cn/studyplan/top-interview-150/

    数组/字符串

    LeetCode150-移除元素-LC27

    /**
     * @Author DSXRIIIII
     * @Project mounts
     * @Package cn.dsxriiiii.LeetCode.LC150
     * @Date 2024/8/20 9:59
     * @Description: 移除元素
     */
    public class LC27 {
        public int removeElement(int[] nums, int val) {
            int left = 0;
            int right = nums.length;
            while(left < right){
                if(nums[left] == val){
                    nums[left] = nums[right - 1];
                    right--;
                }else{
                    left++;
                }
            }
            return left;
        }
    }

    LeetCode150-删除有序数组中的重复项-LC26

    /**
     * @Author DSXRIIIII
     * @Project mounts
     * @Package cn.dsxriiiii.LeetCode
     * @Date 2024/8/20 10:08
     * @Description: 删除有序数组的重复项
     * 快指针匹配相等时会往后一直匹配 当匹配不相同的时候会将快指针的值给慢指针
     */
    public class LC26 {
        public int removeDuplicates(int[] nums) {
            int len = nums.length;
            if(len == 0) return 0;
            int slow = 1;
            int fast = 1;
            while(fast!=len){
                if(nums[fast] != nums[fast-1]){
                    nums[slow] = nums[fast];
                    slow++;
                }
                fast++;
            }
            return slow;
        }
    }

    LeetCode150-删除有序数组中的重复项II-LC80

    //题目要求将出现两次以上的数让其只出现过两次
    //原地操作(空间复杂度为O(1)) 采用覆盖操作咯  考虑快慢指针
    public static int removeDuplicates(int[] nums){
        int len = nums.length;
        if(len < 2) return 2;
        int fast = 2;
        int slow = 2;
        while(fast != len){
            if(nums[slow-2] !=nums[fast]){ //这个地方就限制了 数组元素只能出现两次
                //这里不是赋值给nums[slow-2]
                nums[slow++] = nums[fast];
            }
            fast++;
        }
        return slow; //翻新后的数组长度
        //println()直接打印
    }
    //如何理解快慢指针逻辑
    //1.快指针每一次循环都会向前++
    //2.慢指针只有当nums[slow - 2] != nums[fast] 此时slow处于位置重复元素长度为3的位置,此时进行快指针的覆盖
    //3.覆盖操作完成 快慢指针都会继续判断 
    //4.慢指针永远记录被修改的位置 快慢指针指向同一位置 也会进行赋值操作 并且原地不变

    LeetCode150-轮转数组-LC189

    //方法一 数组拷贝
    public void rotate(int[] nums,int k){
        int len = nums.length;
        int[] arr = new int[len];
        for(int i = 0; i < len;i++){
            arr[(i + k) % n] = nums[i];
            //使用取余操作进行覆盖
        }
        System.arraycopy(arr,0,nums,0,len);
        //调用拷贝函数 将新数组数据拷贝到旧数组
    }
    
    //方法二 使用翻转数组元素方式
    public void rotate(int nums[],int k){
        k = k % (nums.length);
        reverse(0,nums.length-1,k);
        reverse(0,k - 1,k);
        reverse(k,nums.length,k);
    }
    public static void reverse(int start,int end,int[] nums){
            while(start < end){ // 判断条件是 <
                int temp = nums[start];
                nums[start] = nums[end];
                nums[end] = temp;
                start+=1;
                end-=1; 
            }
        }
    }

    LeetCode150-买卖股票的最佳时机-LC189

    //暴力枚举每一项时间复杂度为O(n**2)
    //以下方法可以每一次判断当求到最小值时 ,保存最小值
    //往后的每次都按照当前值与最小值的差值 与 保存的最大值进行比较
    //最后求得最大的股票差值
    
    public int maxProfit(int nums[]){
        int minValue = Integer.MAX_VALUE;
        int maxValue = 0;
        for(int i = 0;i < nums.length;i++){
            if(nums[i] < minValue){
                minValue = nums[i];//保存最小值
            }else{
                if(nums[i] - minValue > maxValue){
                    maxValue = nums[i] - minValue;//计算差值
                }
            }
        }
        return maxValue;
    }

    LeetCode150-买卖股票的最佳时机II-LC189

    //动态规划思路
    //讲实话真的很难懂
    //假如股票的价格为{7,1,5,3,6,4}
    //还是很难理解 。。。。。
    public int maxProfit(int[] prices){
        int len = prices.length;
        if(len < 2) return 0;
        int[] cash = new int[len];
        int[] gupiao = new int[len];
        cash[0] = 0;
        gupiao[0] = -prices[i];
        for(int i = 1;i < len;i++){
            cash[i] = Math.max(cash[i-1],gupiao[i - 1] + prices[i]) //买股票和不买股票的最大值
            gupiao[i] = Math.max(gupiao[i - 1],cash[i- 1] - prices[i]);
        }
        return cash[len - 1];
    }
    
    //使用贪心算法 每次计算出差值进行比较 这样的方法比较通俗易懂 时间复杂度O(n) 空间复杂度O(1)
    public int maxProfit(int[] prices){
        int len = prices.length;
        if(len < 2) return 0;
        int res = 0;
        for(int i = 1;i < len;i++){
            int diff = prices[i] - prices[i - 1];
            if(diff > 0){
                res += diff;
            }
        }
        return res;
        }
    

    LeetCode150-跳跃游戏-LC55

    /**
     * 题目:跳跃游戏
     * 算法思路-1:贪心算法
     * 具体思路:遍历每一个位置,思考每一个位置可以到达的最远举例,前提条件是位置i可以到达
     */
    public class LC55 {
        public static void main(String[] args) {
            int[] nums = {3,2,1,0,4};
            System.out.println(canJump(nums));
        }
        public static boolean canJump(int[] nums) {
            int maxIndex = 0;
            for(int i = 0; i < nums.length;i++){
                int curIndex = i + nums[i];
                if(i <= maxIndex){   //这里必须要满足的条件 maxIndex记录的是要到达的最远距离
                    //如果 i > maxIndex 进行操作是无效的 因为永远到达不了这个位置
                    maxIndex = Math.max(curIndex,maxIndex);
                }
                if(maxIndex >= nums.length - 1){  //这里必须满足>=的条件 {0} 这样的情况也是可以到达的
                    return true;
                }
            }
            return false;
        }
    }

    LeetCode150-跳跃游戏II-LC45

    /**
     * 题目:跳跃游戏II
     * 算法思路-1:贪心算法
     * 具体思路:遍历每一个位置,思考每一个位置可以到达的最远距离
     * 当到达边界时此时 res++ 表示跳跃一次 我可以到达的最远距离赋值给边界
     * 边界不断更新 那么跳跃次数也会不断更新 知道最后边界值大于等于数组长度
     * 要注意遍历的范围[0,len - 1] 避免i走到数组末尾再一次+1
     */
    public class LC45 {
        public static int jump(int[] nums) {
            int len = nums.length;
            int maxIndex = 0;
            int Border = 0;
            int res = 0;
            for(int i = 0; i < len;i++){
                /*
                 * 此处i < len - 1
                 * 在遍历数组时,我们不访问最后一个元素,这是因为在访问最后一个元素之前,我们的边界一定大于等于最后一个位置,
                 * 否则就无法跳到最后一个位置了。如果访问最后一个元素,在边界正好为最后一个位置的情况下
                 * 我们会增加一次「不必要的跳跃次数」 如何理解呢
                 * 假如数组int[] nums = {2,3,1,1,4}; 此时i走到了最后的位置 i = 4;此时边界border = 4;
                 * 此时会再一次走到i == border判断条件 此时res++ 就多余了
                 */
                maxIndex = Math.max(maxIndex,i+nums[i]);
                if(i == Border){
                    Border = maxIndex;
                    res++;
                }
            }
            return res;
        }
    
        public static void main(String[] args) {
            int[] nums = {2,3,1,1,4};
            System.out.println(jump(nums));
        }
    }

    LeetCode150-H指数-LC274

    /**
     * 题目:H指数
     * 算法思路-1:贪心算法
     * 具体思路:对于数组的每一个值 判断大于其值的个数是否等于i
     */
    public int hIndex(int[] citations) {
            Arrays.sort(citations);
            int h = 0, i = citations.length - 1; 
            while (i >= 0 && citations[i] > h) {
                h++; 
                i--;
            }
            return h;
        }

    LeetCode150-O(1)时间插入删除获取随机元素-LC380

    /**
     * 题目:O(1)时间插入删除获取随机元素数
     * 算法思路-1:贪心算法
     * 具体思路:对于数组的每一个值 判断大于其值的个数是否等于i
     */
    class RandomizedSet {
        List<Integer> list;
        Map<Integer,Integer> map;
        Random random;
        public RandomizedSet() {
            list = new ArrayList<>();
            map = new HashMap<>();
            random = new Random();
        }
    
        public boolean insert(int val) {
            if(map.containsKey(val)){
                return false;
            }
            int index = list.size();
            list.add(val);
            map.put(val,index);
            return true;
        }
    
        public boolean remove(int val) {
            if(!map.containsKey(val)){
                return false;
            }
            int index = map.get(val);
            int lastnum = list.get(list.size()-1);
            list.set(index,lastnum);
            map.put(lastnum,index);
            map.remove(val);
            list.remove(list.size()-1);
            return true;
        }
    
        public int getRandom() {
            int randomIndex = random.nextInt(list.size());
            return list.get(randomIndex);
        }
    }
    
    /**
     * Your RandomizedSet object will be instantiated and called as such:
     * RandomizedSet obj = new RandomizedSet();
     * boolean param_1 = obj.insert(val);
     * boolean param_2 = obj.remove(val);
     * int param_3 = obj.getRandom();
     */

    LeetCode150-除自身以外数组的乘积-LC238

    /**
     * 题目:除自身以外数组的乘积
     * 算法思路:遍历
     * 具体思路:观察测试用例发现结果数组 保存的是除本身之外的左右所有值的乘积
     * 分为两次循环更新数组
     */
    public class LC238 {
        public int[] productExceptSelf(int[] nums) {
            int len = nums.length;
            int[] answer = new int[len];
            answer[0] = 1;
            for(int i = 1; i < len;i++){
                answer[i] = nums[i - 1] * answer[i - 1];
                //对于数组 从左往右遍历 answer保存当前坐标(不包括自己)的所有乘积
            }
            int right = 1;
            for(int i = len - 1;i >= 0;i--){
                answer[i] = answer[i] * right;
                //对于数组从右往左遍历 answer更新 right保存当前坐标(不包括当前坐标的值所有乘积
                right = right * nums[i]; //right实时更新
            }
            return answer;
        }
    }

    LeetCode150-加油站-LC134

    /**
     * 题目:加油站
     * 算法思路:遍历
     * 具体思路:1.循环数组实现 
     *         2.剪枝 更新i时 考虑index
     *         3.计算每次的油量
     */
    public class LC134 {
        public int canCompleteCircuit(int[] gas, int[] cost) {
            int len = gas.length;
            int i = 0;
            //思路
            while(i < len) {
                int gasCapacity = 0;
                int costCapacity = 0;
                int index = 0;//保存遍历长度 这个坐标可以表示走了index步 是否成功
                while(index < len){//index表示要走的步数 总共要走len步 走到len步就说明成功了
                    int cur = (index + i) % len;
                    gasCapacity += gas[cur];
                    costCapacity += cost[cur];
                    if(costCapacity > gasCapacity){ //不满足条件会走break退出while循环 下一步index也不会更新
                        break;
                    }
                    index++;
                }
                //长度为len满足条件返回坐标
                if(index == len){
                    return i;
                }else{
                    i = i + 1 + index;//优化手段 
                    //为什么不采用以下的方式
                    //i = i + 1;
                    //因为index已经走了index++自增步数 如果break退出证明index + i这个范围都不合适
                    //为什么不合适 如果下一步还是从i + 1开始走 走到 前一个index + i位置时还是不满足条件 所以选择剪枝
                    //在更新时将i = i + index + 1;此时从这个地方走可以节约很多时间
                }
            }
            return -1;
        }
    }

    LeetCode150-分发糖果-LC135

    /**
     * 题目:分发糖果
     * 算法思路:两次遍历
     * 具体思路:1.从左向右遍历 当i > i-1 数组更新++,否则arr[i] = 1;
     *         2.从右向左遍历 同上 记录right值是为了当不断递增时 res 可以 加上 比较中的最大值
     */
    public class LC135 {
        public static void main(String[] args) {
            int[] nums = {1,0,2};
            System.out.println(candy(nums));
    
        }
        public static int candy(int[] ratings) {
            int res = 0;
            int len = ratings.length;
            int[] arr = new int[len];
            for(int i = 0; i < ratings.length;i++){
                if(i > 0 && ratings[i] > ratings[i - 1]){
                    arr[i] = arr[i - 1] + 1;
                }else{
                    arr[i] = 1;
                }
            }
            int right = 0;
            for(int j = ratings.length - 1;j >= 0;j--){
                if(j < len - 1 && ratings[j] > ratings[j + 1]) {
                    right++;
                }else{
                    right = 1;
                }
                //res += arr[j]; 保存的right值是用来当左边值大于右边值时 right会right++
                //例如下面这个数组{1,3,5,3,2,1} 第三个人比左右两边都要高 但是 从右向左right == 4 arr[i] = 3 所以要取大值4
                res += Math.max(arr[j],right);
    
            }
            //System.out.println(Arrays.toString(arr));
            return res;
        }
    }
    

    LeetCode150-接雨水-LC42

    /**
     * 题目:接雨水
     * 算法思路:双指针
     * 具体思路:1.维护左右leftMax,rightMax记录左右的最大值
     *         2.如果 height[left] < height[right],则必有leftMax < rightMax或者leftMax > rightMax那么此时就可能会有雨水
     *         3.雨水值就为leftMax - height[left] 或者 rightMax - height[right]
     *         4.最后res用来保存雨水值 当左右left与right指针相遇时结束
     */
    public class LC42 {
        public int trap(int[] height) {
            int res = 0;
            int left = 0,right = height.length - 1;
            int leftMax = 0;
            int rightMax = 0;
            while(left < right){
                leftMax = Math.max(height[left],leftMax);
                rightMax = Math.max(height[right],rightMax);
                if(height[left] < height[right]){
                    res+=leftMax - height[left];
                    left++;
                }else{
                    res+=rightMax - height[right];
                    right--;
                }
            }
            return res;
        }
    }
    

    LeetCode150-罗马数字转整数-LC13

    import java.util.HashMap;
    import java.util.Map;
    /**
     * 题目:罗马数字转整数
     * 算法思路:遍历找特殊条件
     * 具体思路:1.列举特殊条件即可
     */
    public class LC13 {
        public static void main(String[] args) {
            String s = "LVIII";  //1000 + 900 + 90 + 4
            System.out.println(romanToInt(s));
        }
        public static int romanToInt(String s) {
            Map<Character,Integer> map = new HashMap<>();
            map.put('I',1);
            map.put('V',5);
            map.put('X',10);
            map.put('L',50);
            map.put('C',100);
            map.put('D',500);
            map.put('M',1000);
            int res = 0;
            res  += map.get(s.charAt(0));
            for(int i = 1;i < s.length();i++){
                if(
                        (s.charAt(i) == 'V' || s.charAt(i) == 'X') && s.charAt(i - 1) == 'I' ||
                        (s.charAt(i) == 'L' || s.charAt(i) == 'C') && s.charAt(i - 1) == 'X' ||
                        (s.charAt(i) == 'D' || s.charAt(i) == 'M') && s.charAt(i - 1) == 'C'
                        //记得加()维持条件
                ){
                    res -= map.get(s.charAt(i - 1));
                    res += map.get(s.charAt(i)) - map.get(s.charAt(i - 1));
                }else{
                    res  += map.get(s.charAt(i));
                }
    
            }
            return res;
        }
    }

    LeetCode150-整数转罗马数字-LC12

    /**
     * 题目:整数转罗马数字
     * 算法思路:遍历找特殊条件
     * 具体思路:1.列举特殊条件即可 使用StringBuilder拼接
     */
    public class LC12 {
        public static void main(String[] args) {
            System.out.println(intToRoman(58));
        }
        public static String intToRoman(int num) {
            int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
            String[] symbols = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < values.length; i++) {
                while(num >= values[i]){
                    num-=values[i];
                    sb.append(symbols[i]);
                }
                if(num == 0){
                    break;
                }
    
            }
            return sb.toString();
        }
    }
    

    LeetCode150-最后一个单词的长度-LC58

    /**
     * 题目:最后一个单词的长度
     * 算法思路:遍历
     * 具体思路:1.去掉前后的空格 trim()方法
     *         2.遍历到‘ ’处停止
     */
    public class LC58 {
        public static void main(String[] args) {
            String s = "a";
            System.out.println(lengthOfLastWord(s));
        }
        public static int lengthOfLastWord(String s) {
            s = s.trim();
            int res = 0;
            for (int i = s.length() - 1; i >= 0; i--) {
                if(s.charAt(i) != ' '){
                    res++;
                }else{
                    break;
                }
            }
            return res;
        }
    }
    

    LeetCode150-最长公共前缀-LC14

    /**
     * 题目:最长公共前缀
     * 算法思路:遍历
     * 具体思路:1.字符数组的第一个字符串为比较串
     *          2.横向比较法 两两比较
     *          3.纵向比较法 全部比较
     *          4.时间复杂度都为O(mn) 空间复杂度为O(1) 常数
     */
    public class LC14 {
        /**
         * 横向比较法 每一次取两个字符串比较 取得的相似值放到下一次比较中
         */
        public String longestCommonPrefix(String[] strs) {
            int len = strs.length;
            if(len == 0) return "";
            String SameString = strs[0];
            for(int i = 1; i < len;i++){
                SameString = getSameString(SameString,strs[i]); //返回得到两个字符串的最长序列 在进入到下一次循环中
    //            if(SameString == null){
    //                break;
    //            }
            }
            return SameString;
        }
    
        /**
         * 纵向比较法 将初始字符串的每一个字符与后续的所有字符串进行比较
         */
        public String longestCommonPrefix_2(String[] strs) {
            int len = strs[0].length();
            if(len == 0) return "";
            for(int i = 0; i < len;i++){
                char c = strs[0].charAt(i);
                for(int j = 1;j < strs.length;j++){
                    if(i == strs[j].length() || strs[j].charAt(i) != c){
                        //i == strs[j].length() 此时短的字符串走完了
                        //strs[j].charAt(i) != c 与当前字符比较不相等 i为公共最长长度
                        //i 记录公共长度
                        strs[0] = strs[0].substring(0,i);
                    }
                }
            }
            return strs[0];
        }
    
        private String getSameString(String sameString, String str) {
            int index = 0;
            int length = Math.max(sameString.length(),str.length());
            while(index < length && sameString.charAt(index) == str.charAt(index)){
                index++;
            }
            return sameString.substring(0,index);
        }
    
    }
    

    LeetCode150-反转字符串中的单词-LC151

    /**
     * 题目:反转字符串中的单词
     * 算法思路:调用方法(正则表达式)
     * 具体思路:1.字符数组的第一个字符串为比较串
     *          2.横向比较法 两两比较
     *          3.纵向比较法 全部比较
     *          4.时间复杂度都为O(mn) 空间复杂度为O(1) 常数
     * 补充:正则表达式:
     *          1.“_”  匹配任何单个字符
     *          2."*"  匹配0个或者多个在它面前的字符
     *          3."%"  匹配任意数字字符
     *          4."[]" 匹配方括号中的任意字符
     */
    public class LC151 {
        public static void main(String[] args) {
            String s = "the sky is blue";
            System.out.println(reverseWords_queue(s));
        }
        public static String reverseWords_function(String s) {
            s = s.trim();
            List<String> list = Arrays.asList(s.split("\\s+"));
            // "\\s+" 表示一个或者多个连续的空白字符串
            Collections.reverse(list);//翻转list中的所有单词
            System.out.println(list);//[blue, is, sky, the]
            return String.join(" ",list);
        }
    
        /**
         * 使用双端队列Deque在头部插入新数据
         */
        public static String reverseWords_queue(String s) {
            s = s.trim();
            Deque<String> queue = new LinkedList<>();
            StringBuilder word = new StringBuilder();
            int left = 0;
            int right = s.length() - 1;
            while(left <= right){ //从左向右遍历 遇到字符添加进入word 当遇到空白字符时添加进入队列并且重置word
                char c = s.charAt(left);
                if(word.length() != 0 && c == ' '){
                    queue.offerFirst(word.toString());
                    word.setLength(0);
                }else if(c != ' '){//"example   good a" 确保这样的方式也能通过 避免中间的多余空格
                    word.append(c);
                }
                left++;
            }
            queue.offerFirst(word.toString());
            return String.join(" ",queue);
        }
    }

    LeetCode150-Z字形变换-LC6

    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 题目:Z字形变换
     * 算法思路:StringBuilder拼接字符串
     * 具体思路:1.flag 用于调转方向 比如 1,2,3 -> 3,2,1 -> 1,2,3
     */
    public class LC6 {
        public String convert(String s, int numRows) {
            if (numRows < 2) return s;
            List<StringBuilder> res = new ArrayList<>();
            int index = 0,flag = 1;
            for(int i = 0; i < numRows;i++){
                res.add(new StringBuilder());
            }
            for(char c : s.toCharArray()){
                res.get(index).append(c);
                index+=flag;
                if(index == 0 || index == numRows - 1){
                    flag = - flag;
                }
            }
            StringBuilder sb = new StringBuilder();
            for (StringBuilder stringBuilder : res){
                sb.append(stringBuilder.toString());
            }
            return sb.toString();
        }
    }

    LeetCode150-找出字符串中第一个匹配项的下标-LC28

    /**
     * 题目:找出字符串中第一个匹配项的下标
     * 算法:KMP
     * 思路  1.KMP算法计算出needle的具体数值
     *       2.利用next数组进行回退 当遇到字符不匹配时 
     *       3.j坐标回退 如果j == m说明此时已经匹配到了主串
     *       4.返回结果i - m + 1
     */
    public class LC28 {
        public static void main(String[] args) {
            String haystack = "mississippi", needle = "issip";
            System.out.println(strStr_kmp(haystack,needle));
        }
        public static int strStr_kmp(String haystack, String needle) {
            int j = 0;
            int[] next = new int[needle.length()];
            for(int i = 1;i < needle.length();i++){
                while(j > 0 && needle.charAt(i) != needle.charAt(j)){
                    j = next[j - 1];
                }
                if(needle.charAt(i) == needle.charAt(j)){
                    j++;
                }
                next[i] = j;
            }
            for(int i = 0,k = 0;i < haystack.length();i++){
                while(j > 0 && needle.charAt(j) != haystack.charAt(i)){
                    j = next[j - 1];
                }
                if(needle.charAt(j) == haystack.charAt(i)){
                    j++;
                }
                if(j == needle.length()){
                    return i - needle.length() + 1;
                }
            }
            return -1;
        }
    
        @Deprecated
        public static int strStr_index(String haystack, String needle) {
            int targetLen = needle.length();
            int index = 0;
            for(int i = 0;i < haystack.length();i++){
                if(haystack.charAt(i) == needle.charAt(index)){
                    index++;
                }else{
                    index = 0;
                }
                if(index == targetLen){
                    return i - targetLen + 1;
                }
            }
            return -1;
        }
    }

    数组字符串总结

    1.移除元素

    2.删除有序数组中的重复项

    双指针->if(nums[fast-1]!=nums[fast])

    3.删除有序数组中的重复项II

    双指针->if(nums[slow-2]!=nums[fast]) -> nums[slow] = nums[fast]

    4.轮转数组

    arr[(i+k)%n]=arr[i] -> System.arraycopy()

    5.买卖股票的最佳时机

    for循环-> 保存最小值pre -> 当前节点-pre不断保存最大ans

    6.买卖股票的最佳时机II

    贪心 保存每一次的差值

    7.跳跃游戏

    满足i <= maxIndex 才会更新最大坐标 如果不满足代表当前位置不可达 return true的条件 满足>=最后一位即可

    8.跳跃游戏II

    记录每一次的最大边界值 当到达最大边界值时 ans++ 并且更新最新的最大边界值 但是注意最后一个坐标可能会重复++ 修改区间[0,length-2]

    9.H指数

    len >= 0 && h < citations[len]条件下 寻找h的最大值 h++ len--

    10.O(1) 时间插入、删除和获取随机元素

    Set+List+Random

    11.除自身以外数组的乘积

    从左到右将结果数字相乘[1,len) 从右到左将结果数组相乘 [len-1,0]

    12.加油站

    记录坐标位置i 步数index 记录对应每一个坐标每一步所需要的开销和油量 坐标更替要用到(i+k)%n 开销大于油量break index走到len说明走到了原位返回即可 剪枝优化i=i+1+index [i,j]不行 [i+1,j]也不行

    13.分发糖果

    ans数组从左到右更新当前值 从右到左比对 起点为1 right不断++ res记录right值和ans[i] 谁最大添加谁

    14.接雨水

    双指针 [left,right]区间 记录最大的left_maxright_max 使用最大的max值减去当前值即可获取水池的大小 保证水池是最小的 左右指针每次只移动一个

    15.罗马数字转整数

    罗马数字保存到map中 如果匹配到特殊组合将其从结果减去并添加特殊值

    16.整数转罗马数字

    values对应上字母symbols for循环对于每一位字符使用while循环不断减去 sb添加坐标位置字符

    17.最后一个单词的长度

    trim()去掉首尾空格 匹配或者split()

    18.最长公共前缀

    两两比较放到下一次比较中

    19.反转字符串中的单词

    双端队列

    20.Z字形变换

    反转flag 对应多个StringBuilder stringBuilder的坐标位置根据index变化

    21.找出字符串中第一个匹配项的坐标

    KMP算法的简单应用
    KMP模版

    int[] next = new int[haystack.length()];
    // i 从 1 开始 0号坐标没有前后缀
    for(int i = 1,j = 0; i < haystack.length();i++){
        while(j > 0 && haystack.charAt(i) != needle.charAt(j)){
            j = next[j - 1];
        }
        if(haystack.charAt(i) == needle.charAt(j)){
            j++;
        }
        next[i] = j;
    }
  • LeetCode-SQL50

    SQL-50

    📕文章题目选自LeetCode力扣基础SQL50题
    😊该文章内容基于作者本人思考总结,如有错误欢迎指正
    🏠题单地址:https://leetcode.cn/studyplan/sql-free-50/

    查询

    可回收且低脂的产品-where and 语句的使用

    where and 语句的使用

    select product_id from Products
    where low_fats = "Y" and recyclable = "Y";

    584.寻找用户推荐人-不等于(<>)和判断为NULL(is null)的使用

    不等于(<>)和判断为NULL(is null)的使用

    select name from customer
    where referee_id <> 2 or referee_id is NULL;

    595.大的国家-大于等于(>=)的使用

    大于等于(>=)的使用

    select name,population,area
    from World
    where World.population >= 25000000 or World.area >= 3000000;

    1148.文章浏览I-去重(distinct)的使用

    去重(distinct)的使用

    select DISTINCT author_id as id
    from Views 
    where author_id = viewer_id
    order by id;

    1683.无效的推文-CHAR_LENGTH

    求单列字符长度的使用

    select tweet_id
    from tweets
    where CHAR_LENGTH(content) > 15;

    连接

    1378. 使用唯一标识码替换员工ID-左连接

    select unique_id,name 
    from Employees left join EmployeeUNI
    on Employees.id = EmployeeUNI.id;

    1068. 产品销售分析 I-左连接

    select product_name,year,price
    from Sales left join Product
    on Sales.product_id = Product.product_id;

    1581. 进店却未进行过交易的顾客-左连接+GROUP

    select customer_id,count(customer_id) as count_no_trans
    from Visits left join Transactions
    on Visits.visit_id = Transactions.visit_id
    where transaction_id is NULL
    Group by customer_id;

    197. 上升的温度-datediff+cross join连接

    • CROSS JOIN 会产生两个表的笛卡尔积,即第一个表的每一行与第二个表的每一行进行组合。
    • 例如,如果表 A 有 m 行,表 B 有 n 行,那么 CROSS JOIN 连接后的结果将有 m×n 行。
      select a.id
      from Weather a join Weather b
      on a.Temperature > b.Temperature
      and datediff(a.recordDate, b.recordDate) =1;

      1661. 每台机器的进程平均运行时间-CASE+小数ROUND+COUNT+SUM

      除数:SUM(CASE WHEN activity_type = ‘end’ THEN timestamp ELSE -timestamp END)
      被除数:COUNT(DISTINCT(process_id))
      小数点:ROUND(x,?)
      条件判断:CASE WHEN activity_type = ‘end’ THEN timestamp ELSE -timestamp END

      select machine_id,
      ROUND(SUM(CASE WHEN activity_type = 'end' THEN timestamp ELSE -timestamp END)/
      COUNT(DISTINCT(process_id)),3) as processing_time
      from activity
      group by machine_id;

    577.员工奖金-左右连接+NULL判断

    select name,b.bonus
    from Bonus as b right join Employee as e 
    on b.empId = e.empId
    where b.bonus IS NULL or b.bonus < 1000;

    1280. 学生们参加各科测试的次数-join连接+子查询

    先找出每一个学生对应的参与每一个科目的次数再左右连接

    SELECT student_id, subject_name, COUNT(*) AS attended_exams
    FROM Examinations
    GROUP BY student_id, subject_name
    SELECT 
        s.student_id, s.student_name, sub.subject_name, IFNULL(grouped.attended_exams, 0) AS attended_exams
    FROM 
        Students s
    CROSS JOIN 
        Subjects sub
    LEFT JOIN (
        SELECT student_id, subject_name, COUNT(*) AS attended_exams
        FROM Examinations
        GROUP BY student_id, subject_name
    ) grouped 
    ON s.student_id = grouped.student_id AND sub.subject_name = grouped.subject_name
    ORDER BY s.student_id, sub.subject_name;
    

    570. 至少有5名直接下属的经理-HAVING

    • WHERE 子句在分组之前执行,用于筛选原始数据集中的行。
    • HAVING 子句在分组之后执行,用于筛选分组后的结果集。
      select Manager.Name as Name
      from
      Employee as Manager join Employee as Report
      on Manager.Id = Report.ManagerId
      group by Manager.Id
      having count(Report.Id) >= 5

      1934. 确认率-AVG+ROUND+左右连接+IFNULL替换

      语句拆分ROUND(IFNULL(AVG(c.action=’confirmed’),0),2)
      AVG(c.action='confirmed')计算满足条件c.action='confirmed'的平均值。这里实际上是判断action列的值是否等于'confirmed',如果等于则视为满足条件,在计算平均值时会将满足条件的行视为1,不满足条件的行视为0,然后求这些值的平均值。
      IFNULL(...)表示如果前面的平均值计算结果为NULL,则将其替换为0。这是为了处理当没有满足条件的行时,避免出现NULL结果。

      select s.user_id,
      ROUND(IFNULL(AVG(c.action='confirmed'),0),2) as confirmation_rate
      from Signups as s left join Confirmations c
      on s.user_id = c.user_id
      group by s.user_id;

    聚合函数

    620. 有趣的电影-MOD

    select * from cinema
    where description <> 'boring' and mod(id, 2) = 1
    order by rating desc;

    1251.❗平均售价-聚合函数+左右连接+BETWEEN

    SELECT
        product_id,
        IFNULL(Round(SUM(sales) / SUM(units), 2), 0) AS average_price
    FROM (
        SELECT
            Prices.product_id AS product_id,
            Prices.price * UnitsSold.units AS sales,
            UnitsSold.units AS units
        FROM Prices 
        LEFT JOIN UnitsSold ON Prices.product_id = UnitsSold.product_id
        AND (UnitsSold.purchase_date BETWEEN Prices.start_date AND Prices.end_date)
    ) T
    GROUP BY product_id

    1075.项目员工I-AVG

    select project_id,ROUND(AVG(experience_years),2) as average_years 
    from Project as p left join Employee as e
    on p.employee_id = e.employee_id
    group by project_id;

    1211. 查询结果的质量和占比-AVG+COUNT+SUM+ROUND+IF+/

    SELECT 
        query_name, 
        ROUND(AVG(rating/position), 2) quality,
        ROUND(SUM(IF(rating < 3, 1, 0)) * 100 / COUNT(*), 2) poor_query_percentage
    FROM Queries
    Where query_name IS NOT NULL
    GROUP BY query_name

    1633. 各赛事的用户注册率-ROUND+SUM+COUNT

    select contest_id,ROUND(COUNT(u.user_id) * 100/(select count(*) from users),2) as percentage
    from Users as u left join Register as r
    on u.user_id = r.user_id
    where contest_id IS NOT NULL
    group by contest_id
    order by percentage desc,contest_id asc;
    

    1193. ❗每月交易 I-COUNT+SUM+IF+GROUP+DATE_FORMAT

    select 
    DATE_FORMAT(trans_date, '%Y-%m') as month,
    country,
    count(*) as trans_count,
    COUNT(IF(state = 'approved', 1, NULL)) as approved_count,
    SUM(amount) as trans_total_amount,
    SUM(IF(state = 'approved', amount, 0)) as approved_total_amount
    from Transactions
    group by month,country;

    1174. 即时食物配送 II-MIN+SUM+子查询

    SUM(order_date = customer_pref_delivery_date) 即可查询到满足条件的数量
    MIN找到最小日期

    select ROUND(
        SUM(order_date = customer_pref_delivery_date) * 100 /
        count(*),
        2) as immediate_percentage
    from Delivery
    where (customer_id, order_date) in (
        select customer_id, min(order_date)
        from delivery
        group by customer_id
    )

    550. 游戏玩法分析 IV-SUM+COUNT+IFNULL+DATE_ADD+MIN

    1.先将日期添加为新的一天 创建新表
    2.将旧表的结束日期与新表比对 找到符合条件的一天
    3.IFNULL去掉NULL
    4.数学除法

    select IFNULL(round(count(distinct(Result.player_id)) / count(distinct(Activity.player_id)), 2), 0) as fraction
    from (
      select Activity.player_id as player_id
      from (
        select player_id, DATE_ADD(MIN(event_date), INTERVAL 1 DAY) as second_date
        from Activity
        group by player_id
      ) as Expected, Activity
      where Activity.event_date = Expected.second_date and Activity.player_id = Expected.player_id
    ) as Result, Activity

    排序和分组

    2356. ❗每位教师所教授的科目种类的数量-COUNT+DISTINCT

    select teacher_id,COUNT(DISTINCT subject_id) as cnt
    from teacher
    group by teacher_id

    1141. 查询近30天活跃用户数-DATEDIFF+COUNT

    SELECT activity_date AS day, count(DISTINCT user_id) AS active_users
    FROM Activity
    WHERE DATEDIFF("2019-07-27",activity_date) BETWEEN 0 AND 29
    GROUP BY activity_date

    1084. ❗销售分析III-BETWEENAND

    分组后的数据进行比对
    or null表示如果前面的条件不满足或者结果为NULL,也会被考虑在统计范围内。这样的写法可能是为了确保即使某些行的sale_dateNULL也能被计入总数中

    # 在这个时间内售出的商品数量等于总商品数量
    select p.product_id,p.product_name
    from Product as p left join Sales as s
    on p.product_id = s.product_id
    group by p.product_id
    having count(sale_date between '2019-01-01' and '2019-03-31' or null) = count(*)

    596. 超过 5 名学生的课-HAVING

    select class from Courses
    group by class
    having count(class) >= 5
    ;

    1729. 求关注者的数量-GROUPBY

    select user_id,count(user_id) as followers_count
    from Followers
    group by user_id
    order by user_id;

    619. 只出现一次的最大数字-GROUPBY+MAX

    select max(num) as num
    from (select num
        from MyNumbers
        group by num
        having count(num) = 1) as t
    ;

    1045. 买下所有产品的客户-GROUPBY+COUNT

    select customer_id
    from Customer
    where product_key IN (SELECT product_key FROM Product)
    GROUP BY customer_id
    HAVING COUNT(DISTINCT product_key) = (SELECT COUNT(*) FROM Product);

    高级查询和连接

    对同一列数据进行筛选和计算即需要用到自链接

    1731. ❗❗每位经理的下属员工数量-聚合函数+自连接

    select a.employee_id as 'employee_id',a.name as 'name',count(b.employee_id) as 'reports_count',round(avg(b.age), 0) as 'average_age'
    from Employees a join Employees as b
    on a.employee_id = b.reports_to
    group by a.employee_id
    order by a.employee_id

    1789. 员工的直属部门-UNION

    select employee_id,department_id
    from Employee
    group by employee_id
    having count(Employee.employee_id) = 1
    UNION
    select  employee_id, department_id
    FROM
        Employee
    WHERE
        primary_flag = 'Y' ;

    610. 判断三角形-CASE END+条件判断

    select x,y,z,
    case when x + y > z AND x + z > y AND y + z > x THEN 'Yes'
    else 'No' end as 'triangle'
    from triangle;

    180. 连续出现的数字-DISTINCT

    SELECT *
    FROM
        Logs l1,
        Logs l2,
        Logs l3
    WHERE
        l1.Id = l2.Id - 1
        AND l2.Id = l3.Id - 1
        AND l1.Num = l2.Num
        AND l2.Num = l3.Num
    ;

    1164. ❗❗指定日期的产品价格-连接+IFNULL+条件判断

    select p1.product_id, ifnull(p2.new_price, 10) as price
    from (
        select distinct product_id
        from products
    ) as p1 -- 所有的产品
    left join (
        select product_id, new_price 
        from products
        where (product_id, change_date) in (
            select product_id, max(change_date)
            from products
            where change_date <= '2019-08-16'
            group by product_id
        )
    ) as p2 -- 在 2019-08-16 之前有过修改的产品和最新的价格
    on p1.product_id = p2.product_id

    1204. 最后一个能进入巴士的人-GROUPBY+HAVING

    select a.person_name
    from Queue a, Queue b
    where a.turn >= b.turn 
    group by a.person_id having sum(b.weight) <= 1000
    order by a.turn desc
    limit 1;

    1907. 按分类统计薪水-UNION+CASE WHEN END

    SELECT 
        'Low Salary' AS category,
        SUM(CASE WHEN income < 20000 THEN 1 ELSE 0 END) AS accounts_count
    FROM 
        Accounts
    
    UNION
    SELECT  
        'Average Salary' category,
        SUM(CASE WHEN income >= 20000 AND income <= 50000 THEN 1 ELSE 0 END) 
        AS accounts_count
    FROM 
        Accounts
    
    UNION
    SELECT 
        'High Salary' category,
        SUM(CASE WHEN income > 50000 THEN 1 ELSE 0 END) AS accounts_count
    FROM 
        Accounts
    

    子查询

    1978. 上级经理已离职的公司员工-左连接

    select e1.employee_id 
    from Employees e1 left join Employees e2
    on e1.manager_id = e2.employee_id 
    where e1.salary < 30000 and e1.manager_id is not null and e2.employee_id is null
    order by e1.employee_id

    626. ❗换座位-CASEWHEN逻辑判断

    select (
        case 
        when id%2=0 then id-1
        when id%2=1 and id < (select max(id) from Seat) then id + 1
        else id
        end
    )as id ,student from Seat
    order by id;

    1341. 电影评分-左右连接

    (SELECT u.name AS results
    FROM Users u
    LEFT JOIN MovieRating mr ON u.user_id = mr.user_id
    GROUP BY u.user_id
    ORDER BY COUNT(*) DESC, u.name ASC
    LIMIT 1)
    
    UNION ALL
    
    (SELECT m.title AS results
    FROM Movies m
    LEFT JOIN MovieRating mr ON m.movie_id = mr.movie_id AND YEAR(mr.created_at) = 2020 AND MONTH(mr.created_at) = 2
    GROUP BY mr.movie_id
    ORDER BY AVG(mr.rating) DESC, m.title
    LIMIT 1);

    1321. ❗❗餐馆营业额变化增长-窗口函数

    [你要的操作] OVER ( PARTITION BY  <用于分组的列名>
                        ORDER BY <按序叠加的列名> 
                        ROWS <窗口滑动的数据范围> )
    当前行 - current row
    之前的行 - preceding
    之后的行 - following
    无界限 - unbounded
    表示从前面的起点 - unbounded preceding
    表示到后面的终点 - unbounded following

    求出窗口的大小

    SUM(amount) OVER ( ORDER BY visited_on ROWS 6 PRECEDING ) AS sum_amount
    -- 求得出的窗口的平均值
    SELECT DISTINCT visited_on,
           sum_amount AS amount, 
           ROUND(sum_amount/7, 2) AS average_amount
    FROM (
        SELECT visited_on, 
           SUM(amount) OVER ( ORDER BY visited_on ROWS 6 PRECEDING ) AS sum_amount
        FROM (
            -- 求出每日消费的当量
            SELECT visited_on, 
                SUM(amount) AS amount
            FROM Customer
            GROUP BY visited_on
             ) TT
         ) LL
        --  求出六天之内的差值
    WHERE DATEDIFF(visited_on, (SELECT MIN(visited_on) FROM Customer)) >= 6

    602. 好友申请 II :谁有最多的好友-UNIONALL

    • UNION ALL:同样用于合并多个 SELECT 语句的结果集,但不会去除重复行。
    • UNION:用于合并两个或多个 SELECT 语句的结果集,并去除重复行。
      select t1.ids as id,count(*) as num
      from (
      select requester_id as ids from RequestAccepted 
      union all
      select accepter_id as ids from RequestAccepted
      )as t1
      group by id
      order by num desc
      limit 1;

      585. 2016年的投资-CONCAT+HAVING

      SELECT
      round(SUM(insurance.TIV_2016),2) AS tiv_2016
      FROM
      insurance
      WHERE
      insurance.TIV_2015 IN
      (
        SELECT
          TIV_2015
        FROM
          insurance
        GROUP BY TIV_2015
        HAVING COUNT(*) > 1
      )
      AND CONCAT(LAT, LON) IN
      (
        SELECT
          CONCAT(LAT, LON)
        FROM
          insurance
        GROUP BY LAT , LON
        HAVING COUNT(*) = 1
      )
      ;

      185. ❓部门工资前三高的所有员工

      SELECT
      d.Name AS 'Department', e1.Name AS 'Employee', e1.Salary
      FROM
      Employee e1
          JOIN
      Department d ON e1.DepartmentId = d.Id
      WHERE
      3 > (SELECT
              COUNT(DISTINCT e2.Salary)
          FROM
              Employee e2
          WHERE
              e2.Salary > e1.Salary
                  AND e1.DepartmentId = e2.DepartmentId
          )
      ;

    高级字符串函数 / 正则表达式 / 子句

    1667. 修复表中的名字-LEFT+RIGHT+CONCAT+UPPER+LOWER

    select user_id,concat(upper(left(name,1)),lower(right(name,length(name)-1))) as name
    from users
    order by user_id;

    1527. 患某种疾病的患者

    正则表达式
    ^:表示一个字符串或行的开头
    [a-z]:表示一个字符范围,匹配从 a 到 z 的任何字符。
    [0-9]:表示一个字符范围,匹配从 0 到 9 的任何字符。
    [a-zA-Z]:这个变量匹配从 a 到 z 或 A 到 Z 的任何字符。请注意,你可以在方括号内指定的字符范围的数量没有限制,您可以添加想要匹配的其他字符或范围。
    [^a-z]:这个变量匹配不在 a 到 z 范围内的任何字符。请注意,字符 ^ 用来否定字符范围,它在方括号内的含义与它的方括号外表示开始的含义不同。
    [a-z]*:表示一个字符范围,匹配从 a 到 z 的任何字符 0 次或多次。
    [a-z]+:表示一个字符范围,匹配从 a 到 z 的任何字符 1 次或多次。
    .:匹配任意一个字符。
    \.:表示句点字符。请注意,反斜杠用于转义句点字符,因为句点字符在正则表达式中具有特殊含义。还要注意,在许多语言中,你需要转义反斜杠本身,因此需要使用\\.
    $:表示一个字符串或行的结尾。

    select patient_id,patient_name,conditions
    from Patients
    where conditions regexp '\\bDIAB1.*';
    “\b” 是单词边界,表示匹配的位置必须是在一个单词的开头或结尾处。
    “DIAB1” 是固定的字符序列,会严格匹配这几个字符。
    “.*” 表示匹配任意数量的除换行符之外的任何字符。

    196. 删除重复的电子邮箱-DELETE

    delete p1 from Person p1,
    Person p2
    where p1.Email = p2.Email AND p1.ID > p2.ID;

    176. 第二高的薪水-OFFSET

    使用limit 1 offset 1跳过第一行取第二行

    select (
        select distinct Salary
        from employee
        order by salary desc
        limit 1 offset 1
    ) as SecondHighestSalary

    1484. 按日期分组销售产品-SEPARATOR+GROUP_CONCAT

    GROUP_CONCAT是一个聚合函数,它将分组中的某一列的值连接成一个字符串。
    SEPARATORGROUP_CONCAT函数的一个参数,用于指定连接字符串时的分隔符。

    SELECT 
        sell_date,
        count(distinct(product)) as num_sold,
        GROUP_CONCAT(DISTINCT product ORDER BY product SEPARATOR ',') AS products
    FROM 
        Activities
    GROUP BY 
        sell_date
    ORDER BY 
        sell_date ASC

    1327. 列出指定时间段内所有的下单产品-GROUPBY

    select product_name,sum(unit) as unit
    from Products as p right join Orders as o
    on p.product_id = o.product_id
    where month(order_date) = 2 and year(order_date) = 2020
    group by product_name
    having sum(unit) >= 100

    1517. 查找拥有有效邮箱的用户

    SELECT user_id, name, mail
    FROM Users
    WHERE mail REGEXP '^[a-zA-Z][a-zA-Z0-9_.-]*\\@leetcode\\.com$';

    ^[a-zA-Z] 表示开头取一个字符
    [a-zA-Z0-9_.-]* 指包含a-zA-Z0-9_.-的0个或者多个字符
    \\@ 表示转义字符@
    \\. 表示转义字符'.'

  • BeanFactory

    BeanFactory

    介绍

    • 👋 Hi, I’m @DSXRIIIII
    • 👀 该项目旨在帮助初学者对Bean注入方式的思考和理解👑
    • ⚡ git后记得修改mq的地址和账户密码
    • 📫 1870066109@qq.com
    • 😄 个人博客请访问 DSXRIIIII个人博客

    在项目构建中 当我们使用模板方法模式时 我们需要创建一个规范化接口 定义方法的行为准则
    此时我们使用注解@Service或者@Component注解时 可能会出现一些问题

    设计代码

    //定义接口
    /**
     * @ProjectName: `Bean`Map
     * @Description: 定义行为规范
     * @Author: DSXRIIIII
     * @CreateDate: 2024/7/19 17:09
     * @Email: lihh53453@hundsun.com
     */
    public interface IService {
        public void doLogin();
        public void doChange();
    }
    
    //定义实现类
    @Service
    public class UserService implements IService {
        @Override
        public void doLogin() {
            System.out.println("调用用户doLogin方法( •̀ ω •́ )✧");
        }
    
        @Override
        public void doChange() {
            System.out.println("调用用户doChange方法ヾ(≧ ▽ ≦)ゝ");
        }
    }
    
    @Service
    public class CustomerService implements IService {
        @Override
        public void doLogin() {
            System.out.println("调用消费者doLogin方法( •̀ ω •́ )✧");
        }
    
        @Override
        public void doChange() {
            System.out.println("调用消费者doChange方法ヾ(≧ ▽ ≦)ゝ");
        }
    }
    
    //`Bean`注入自动调用
    @Component
    public class ServiceInvoke {
        @Resource
        private IService service;
    
        @`Bean`
        public void invoke_service(){
            service.doChange();
            service.doLogin();
        }
    }
    

    此时会报错:
    :::danger
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘serviceInvoke’: Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘cn.dsxriiiii.middleware.protocol.IService’ available: expected single matching bean but found 2: customerService,userService
    :::
    说明此时IOC找到了两个Bean 但是不知道使用的是哪一个

    方案一

    使用**@Primary**注解:可以将其中一个bean标记为@Primary,这样当存在多个候选bean时,Spring将优先选择被标记为@Primary的bean进行自动装配。

    //添加注解
    @Primary
    @Service
    public class UserService implements IService {
        @Override
        public void doLogin() {
            System.out.println("调用UserService用户doLogin方法( •̀ ω •́ )✧");
        }
    
        @Override
        public void doChange() {
            System.out.println("调用UserService用户doChange方法ヾ(≧ ▽ ≦)ゝ");
        }
    }
    //此时启动项目时 
    打印日志如下
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::               (v2.7.12)
    
    2024-07-19 17:27:49.344  INFO 26980 --- [           main] cn.dsxriiiii.middleware.Application      : Starting Application using Java 1.8.0_401 on DESKTOP-50PSSHU with PID 26980 (D:\JAVAProject\HaHa\`Bean`Map\target\classes started by hspcadmin in D:\JAVAProject\HaHa\`Bean`Map)
    2024-07-19 17:27:49.347  INFO 26980 --- [           main] cn.dsxriiiii.middleware.Application      : No active profile set, falling back to 1 default profile: "default"
    2024-07-19 17:27:49.873  INFO 26980 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
    2024-07-19 17:27:49.878  INFO 26980 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
    2024-07-19 17:27:49.878  INFO 26980 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.75]
    2024-07-19 17:27:50.034  INFO 26980 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
    2024-07-19 17:27:50.034  INFO 26980 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 649 ms
    调用UserService用户doChange方法ヾ(≧ ▽ ≦)ゝ
    调用UserService用户doLogin方法( •̀ ω •́ )✧
    2024-07-19 17:27:50.306  INFO 26980 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
    2024-07-19 17:27:50.314  INFO 26980 --- [           main] cn.dsxriiiii.middleware.Application      : Started Application in 1.23 seconds (JVM running for 2.085)
    

    方案二

    使用**@Qualifier**注解:如果不想或不能使用@Primary,可以在自动装配的地方使用@Qualifier注解来指定需要注入的具体bean的名称。

    //注入的时候添加注解
    @Resource
    @Qualifier("customerService")
    private IService service;
    //输出日志
      .   ____          _            __ _ _
      /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
      ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
      =========|_|==============|___/=/_/_/_/
      :: Spring Boot ::               (v2.7.12)
    
      2024-07-19 17:42:02.941  INFO 25308 --- [           main] cn.dsxriiiii.middleware.Application      : Starting Application using Java 1.8.0_401 on DESKTOP-50PSSHU with PID 25308 (D:\JAVAProject\HaHa\`Bean`Map\target\classes started by hspcadmin in D:\JAVAProject\HaHa\`Bean`Map)
      2024-07-19 17:42:02.946  INFO 25308 --- [           main] cn.dsxriiiii.middleware.Application      : No active profile set, falling back to 1 default profile: "default"
      2024-07-19 17:42:03.769  INFO 25308 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
      2024-07-19 17:42:03.775  INFO 25308 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
      2024-07-19 17:42:03.776  INFO 25308 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.75]
      2024-07-19 17:42:03.939  INFO 25308 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
      2024-07-19 17:42:03.939  INFO 25308 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 912 ms
      调用CustomerService消费者doChange方法ヾ(≧ ▽ ≦)ゝ
      调用CustomerService消费者doLogin方法( •̀ ω •́ )✧
      2024-07-19 17:42:04.319  INFO 25308 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
      2024-07-19 17:42:04.329  INFO 25308 --- [           main] cn.dsxriiiii.middleware.Application      : Started Application in 1.768 seconds (JVM running for 2.738)
    

    方案三

    使用构造方法注入:当存在多个相同类型的Bean需要注入时,可以使用构造方法注入,并将这些Bean作为参数传递。Spring框架会自动识别并注入所有匹配的Bean,你可以通过在构造方法参数上使用@Qualifier注解来区分它们。此外,Spring允许将所有匹配的Bean自动装配到一个Map集合中,其中Map的键是Bean的名称,值是Bean实例本身

    //创建Factory工厂类
    //定义调用方法
    public interface IServiceFactory {
        void process();
    }
    
    //在实现类上 我们需要定义一个Map Spring框架可以自动注入
    @Service
    public class ServiceFactory implements IServiceFactory {
    
        private final Map<String, IService> ServiceGroup;
    
        public ServiceFactory(Map<String, IService> ServiceNodeGroup) {
            this.ServiceGroup = ServiceNodeGroup;
        }
    
        @Override
        public void process() {
            ServiceGroup.get("userService").doLogin();
            ServiceGroup.get("customerService").doLogin();
        }
    }
    日志如下
    .   ____          _            __ _ _
    /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
    \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
    '  |____| .__|_| |_|_| |_\__, | / / / /
    =========|_|==============|___/=/_/_/_/
    :: Spring Boot ::               (v2.7.12)
    
    2024-07-19 18:08:06.972  INFO 20964 --- [           main] cn.dsxriiiii.middleware.Application      : Starting Application using Java 1.8.0_401 on DESKTOP-50PSSHU with PID 20964 (D:\JAVAProject\HaHa\`Bean`Map\target\classes started by hspcadmin in D:\JAVAProject\HaHa\`Bean`Map)
    2024-07-19 18:08:06.976  INFO 20964 --- [           main] cn.dsxriiiii.middleware.Application      : No active profile set, falling back to 1 default profile: "default"
    2024-07-19 18:08:07.731  INFO 20964 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
    2024-07-19 18:08:07.738  INFO 20964 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
    2024-07-19 18:08:07.738  INFO 20964 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.75]
    2024-07-19 18:08:07.879  INFO 20964 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
    2024-07-19 18:08:07.879  INFO 20964 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 846 ms
    调用UserService用户doLogin方法( •̀ ω •́ )✧
    调用CustomerService消费者doLogin方法( •̀ ω •́ )✧
    2024-07-19 18:08:08.173  INFO 20964 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
    2024-07-19 18:08:08.182  INFO 20964 --- [           main] cn.dsxriiiii.middleware.Application      : Started Application in 1.61 seconds (JVM running for 2.81)
    
  • RabbitMQ操作使用

    介绍

    消息模型

    • Publisher:生产者,不再发送消息到队列中,而是发给交换机
    • Exchange:交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。
    • Queue:消息队列也与以前一样,接收消息、缓存消息。不过队列一定要与交换机绑定。
    • Consumer:消费者,与以前一样,订阅队列,没有变化

    交换机类型

    • Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机
    • Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列
    • Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符
    • Headers:头匹配,基于MQ的消息头匹配,用的较少。

      创建项目

      1.引入MQ依赖 创建项目

      <dependencies>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-amqp</artifactId>
      </dependency>
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      </dependency>
      <dependency>
      <groupId>org.springframework.amqp</groupId>
      <artifactId>spring-rabbit-test</artifactId>
      <scope>test</scope>
      </dependency>
      </dependencies>

    2.定义消费者和生产者

    2.1发送方

    生产者用来发送消息 消费者用来接受消息
    发送方可以发送到任意交换机 如TOPICFANOUTDIRECT
    具体实现到Test包下调用查看

    import cn.dsxriiiii.publisher.BaseEvent;
    import cn.dsxriiiii.publisher.publish.EventPublish;
    import com.alibaba.fastjson2.JSONObject;
    import jakarta.annotation.Resource;
    import org.junit.jupiter.api.Test;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    /**
     * @ProjectName: rabbit-demo
     * @Description:
     * @Author: DSXRIIIII
     * @CreateDate: 2024/7/19 13:40
     * @Email: lihh53453@hundsun.com
     */
    @SpringBootTest
    public class DirectExchangeTest {
    
        private final Logger logger = LoggerFactory.getLogger(DirectExchangeTest.class);
    
        @Resource
        private AwardEvent awardEvent;
    
        @Resource
        private EventPublish eventPublish;
    
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        @Test
        public void send_award_message(){
            for (int i = 0; i < 10; i++) {
                BaseEvent.BaseEventMessage<Long> longBaseEventMessage = awardEvent.buildBaseEventMessage((long) i);
                eventPublish.publish(awardEvent.topic(),longBaseEventMessage);
                logger.info("send_award_message 发送mq消息成功 for循环节点:{},发送消息为:{}",i, JSONObject.toJSONString(longBaseEventMessage));
            }
        }
    
        @Test
        public void testExchangeDirect_award(){
            String exchangeName = "dome.direct";
            String message = "hello,direct_award,Key为{award}要接受到/(ㄒoㄒ)/~~";
            rabbitTemplate.convertAndSend(exchangeName,"award",message);
        }
    
        @Test
        public void testExchangeDirect_rebate(){
            String exchangeName = "dome.direct";
            String message = "hello,direct_award,Key为{rebate}要接受到/(ㄒoㄒ)/~~";
            rabbitTemplate.convertAndSend(exchangeName,"rebate",message);
        }
    
        @Test
        public void testExchangeTopic_all(){
            String exchangeName = "dome.topic";
            String message = "hello,我是topic,dome下的都要接收到 (⊙o⊙)!";
            rabbitTemplate.convertAndSend(exchangeName,"dome.#",message);
        }
    
        @Test
        public void testExchangeTopic_rebate(){
            String exchangeName = "dome.topic";
            String message = "hello,我是topic,rebate前的都要接收到ヽ(✿゚▽゚)ノ";
            rabbitTemplate.convertAndSend(exchangeName,"#.rebate",message);
        }
    
        @Test
        public void testExchangeFanout(){
            String exchangeName = "dome.fanout";
            String message = "我是广播 都要接收到 (╯▔皿▔)╯";
            rabbitTemplate.convertAndSend(exchangeName,"",message);
        }
    }
    
    2.1.1定义消息模板

    定义抽象方法BaseEvent
    抽象出不同领域下对应不同MQ发送方

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    import java.util.Date;
    
    /**
     * @ProjectName: rabbit-demo
     * @Description:
     * @Author: DSXRIIIII
     * @CreateDate: 2024/7/19 9:18
     * @Email: lihh53453@hundsun.com
     */
    @Data
    public abstract class BaseEvent<T> {
    
        public abstract BaseEventMessage<T> buildBaseEventMessage();
    
        public abstract String topic();
    
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        @Builder
        public static class BaseEventMessage<T>{
            private String id;
            private Date timm;
            private T data;
        }
    }

    直接选择继承即可

    @Component
    public class AwardEvent extends BaseEvent<Long>{
    
        @Value("${mq.server.Award}")
        private String topic;
    
        @Override
        public BaseEventMessage<Long> buildBaseEventMessage(Long data) {
            return BaseEventMessage.<Long>builder()
                    .id(RandomStringUtils.randomNumeric(11))
                    .time(new Date())
                    .data(data)
                    .build();
        }
    
        @Override
        public String topic() {
            return topic;
        }
    
    }

    如果自定义消息体 使用泛型即可

    import cn.dsxriiiii.publisher.BaseEvent;
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.apache.commons.lang.RandomStringUtils;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    
    /**
     * @ProjectName: rabbit-demo
     * @Description:
     * @Author: DSXRIIIII
     * @CreateDate: 2024/7/19 9:44
     * @Email: lihh53453@hundsun.com
     */
    @Component
    public class RebateEvent extends BaseEvent<RebateEvent.RebateMessage> {
    
        @Value("${mq.server.rebate}")
        private String topic;
    
        @Override
        public BaseEventMessage<RebateMessage> buildBaseEventMessage(RebateMessage data) {
            return BaseEventMessage.<RebateMessage>builder()
                    .id(RandomStringUtils.random(11))
                    .time(new Date())
                    .data(data)
                    .build();
        }
    
        @Override
        public String topic() {
            return topic;
        }
    
        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        @Builder
        public static class RebateMessage{
            private String id;
            private String info;
        }
    }
    2.1.2项目中定义接收消息方法
    import cn.dsxriiiii.publisher.BaseEvent;
    import com.alibaba.fastjson.JSON;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.rabbit.core.RabbitTemplate;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * @ProjectName: rabbit-demo
     * @Description:
     * @Author: DSXRIIIII
     * @CreateDate: 2024/7/19 10:25
     * @Email: lihh53453@hundsun.com
     */
    @Component
    public class EventPublish {
    
        private final Logger logger = LoggerFactory.getLogger(EventPublish.class);
    
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        public void publish(String topic, BaseEvent.BaseEventMessage<?> eventMessage){
            try{
                String messageJson = JSON.toJSONString(eventMessage);
                rabbitTemplate.convertAndSend(topic,messageJson);
                logger.info("发送MQ消息 topic:{} message:{}", topic, messageJson);
            }catch (Exception e){
                logger.error("发送MQ消息 topic:{} message:{}", topic, JSON.toJSONString(eventMessage),e);
                throw e;
            }
        }
    
        public void publish(String topic, String eventMessageJSON){
            try {
                rabbitTemplate.convertAndSend(topic, eventMessageJSON);
                logger.info("发送MQ JSON数据 topic:{} message:{}", topic, eventMessageJSON);
            } catch (Exception e) {
                logger.error("发送MQ JSON数据失败 topic:{} message:{}", topic, eventMessageJSON, e);
                throw e;
            }
        }
    
    }
    

    2.2接收方

    2.2.1 FANOUT交换机
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.core.ExchangeTypes;
    import org.springframework.amqp.rabbit.annotation.Exchange;
    import org.springframework.amqp.rabbit.annotation.Queue;
    import org.springframework.amqp.rabbit.annotation.QueueBinding;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    /**
     * @ProjectName: rabbit-demo
     * @Description:
     * @Author: DSXRIIIII
     * @CreateDate: 2024/7/19 14:12
     * @Email: lihh53453@hundsun.com
     */
    @Component
    public class FanoutListener {
    
        private static final Logger log = LoggerFactory.getLogger(FanoutListener.class);
    
        /**
         *  fanout交换机匹配 
         *  与fanout交换机绑定的消息队列都可以收到消息
         * @param msg 传递的消息
         */
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue(name = "${mq.listener.fanout.award}"),
                exchange = @Exchange(name = "dome.fanout",type = ExchangeTypes.FANOUT)
        ))
        public void direct_award(String msg){
            log.info("FANOUT 广播 mq.fanout.award队列接收到消息{}",msg);
        }
    
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue(name = "${mq.listener.fanout.rebate}"),
                exchange = @Exchange(name = "dome.fanout",type = ExchangeTypes.FANOUT)
        ))
        public void direct_rebate(String msg){
            log.info("FANOUT 广播 mq.fanout.rebate队列接收到消息{}",msg);
        }
    }
    
    2.2.2 DIRECT交换机
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.rabbit.annotation.Exchange;
    import org.springframework.amqp.rabbit.annotation.Queue;
    import org.springframework.amqp.rabbit.annotation.QueueBinding;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    /**
     * @ProjectName: rabbit-demo
     * @Description:
     * @Author: DSXRIIIII
     * @CreateDate: 2024/7/19 13:16
     * @Email: lihh53453@hundsun.com
     */
    @Component
    public class DirectListener {
    
        private static final Logger log = LoggerFactory.getLogger(DirectListener.class);
    
        /**
         *  topic交换机匹配 
         *  根据routingKey 匹配消息队列 
         * @param msg 传递的消息
         */
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue(name = "${mq.listener.direct.award}"),
                exchange = @Exchange(name = "dome.direct"),
                key = "award"
        ))
        public void direct_award(String msg){
            log.info("DIRECT 广播 mq.direct.award队列接收到消息{}",msg);
        }
    
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue(name = "${mq.listener.direct.rebate}"),
                exchange = @Exchange(name = "dome.direct"),
                key = "rebate"
        ))
        public void direct_rebate(String msg){
            log.info("DIRECT 广播 mq.direct.rebate队列接收到消息{}",msg);
        }
    }
    
    2.2.3 TOPIC交换机
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.amqp.core.ExchangeTypes;
    import org.springframework.amqp.rabbit.annotation.Exchange;
    import org.springframework.amqp.rabbit.annotation.Queue;
    import org.springframework.amqp.rabbit.annotation.QueueBinding;
    import org.springframework.amqp.rabbit.annotation.RabbitListener;
    import org.springframework.stereotype.Component;
    
    /**
     * @ProjectName: rabbit-demo
     * @Description:
     * @Author: DSXRIIIII
     * @CreateDate: 2024/7/19 13:58
     * @Email: lihh53453@hundsun.com
     */
    @Component
    public class TopicListener {
    
        private static final Logger log = LoggerFactory.getLogger(TopicListener.class);
    
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue(name = "${mq.listener.topic.award}"),
                exchange = @Exchange(name = "dome.topic", type = ExchangeTypes.TOPIC),
                key = "dome.#"
        ))
        public void topic_award(String msg){
            log.info("TOPIC 广播 mq.topic.award队列接收到消息{}",msg);
        }
    
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue(name = "${mq.listener.topic.rebate}"),
                exchange = @Exchange(name = "dome.topic",type = ExchangeTypes.TOPIC),
                key = "#.rebate"
        ))
        public void topic_rebate(String msg){
            log.info("TOPIC 广播 mq.topic.rebate队列 ·队列接收到消息{}",msg);
        }
    }

    2.3消息转换器

    将传入的消息对象转化为JSON
    实现代码如下:

    @Bean
    public MessageConverter messageConverter(){
        // 1.定义消息转换器
        Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter();
        // 2.配置自动创建消息id,用于识别不同消息,也可以在业务中基于ID判断是否是重复消息
        jackson2JsonMessageConverter.setCreateMessageIds(true);
        return jackson2JsonMessageConverter;
    }

    publisherconsumer两个服务中都引入依赖:

    <dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.9.10</version>
    </dependency>

    注意,如果项目中引入了spring-boot-starter-web依赖,则无需再次引入Jackson依赖。

    2.4使用@BEAN注解定义交换机类型以及绑定队列代码

    import org.springframework.amqp.core.*;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    /**
     * @ProjectName: rabbit-demo
     * @Description: 非注解形式绑定队列 防止Bean冲突 这里注释了注解
     * @Author: DSXRIIIII
     * @CreateDate: 2024/5/23 10:08
     * @Email: lihh53453@hundsun.com
     **/
    @Configuration
    public class DirectConfig {
    
        @Value("${mq.listener.direct.award}")
        private String queue_award;
    
        @Value("${mq.listener.direct.rebate}")
        private String queue_rebate;
    
        @Bean
        public DirectExchange directExchange(){
            return ExchangeBuilder.directExchange("dome.direct").build();
        }
    
    //    @Bean
    //    public TopicExchange topicExchange(){
    //        return ExchangeBuilder.topicExchange("dome.topic").build();
    //    }
    
        //@Bean
        public Queue queue_award(){
            return new Queue(queue_award);
        }
    
        //@Bean
        public Queue queue_rebate(){
            return new Queue(queue_rebate);
        }
    
        //@Bean
        public Binding QueueAwardBindingDirect(Queue queue_award, DirectExchange directExchange){
            return BindingBuilder.bind(queue_award).to(directExchange).with("award");
        }
    
        //@Bean
        public Binding QueueRebateBindingDirect(Queue queue_rebate, DirectExchange directExchange){
            return BindingBuilder.bind(queue_rebate).to(directExchange).with("rebate");
        }
    
    }

    2.5实现结果

    1.启动消费者和生产者o( ̄▽ ̄)ブ
    运行Application方法 注意修改mq地址和账户密码⚠
    2.调用Test方法发送消息,接收方打印日志

    
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::                (v3.2.5)
    
    2024-07-19T15:55:27.573+08:00  INFO 31892 --- [           main] c.d.customer.CustomerApplication         : Starting CustomerApplication using Java 17.0.11 with PID 31892 (D:\JAVAProject\Learning\rabbit-demo\customer\target\classes started by hspcadmin in D:\JAVAProject\Learning\rabbit-demo)
    2024-07-19T15:55:27.576+08:00  INFO 31892 --- [           main] c.d.customer.CustomerApplication         : No active profile set, falling back to 1 default profile: "default"
    2024-07-19T15:55:28.355+08:00  INFO 31892 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
    2024-07-19T15:55:28.364+08:00  INFO 31892 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
    2024-07-19T15:55:28.364+08:00  INFO 31892 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.20]
    2024-07-19T15:55:28.407+08:00  INFO 31892 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
    2024-07-19T15:55:28.408+08:00  INFO 31892 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 775 ms
    2024-07-19T15:55:28.899+08:00  INFO 31892 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path ''
    2024-07-19T15:55:28.901+08:00  INFO 31892 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [47.113.228.37:5672]
    2024-07-19T15:55:29.047+08:00  INFO 31892 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#4a9860:0/SimpleConnection@1c046c92 [delegate=amqp://dsxriiiii@47.113.228.37:5672/, localPort=62435]
    2024-07-19T15:55:30.544+08:00  INFO 31892 --- [           main] c.d.customer.CustomerApplication         : Started CustomerApplication in 3.355 seconds (process running for 3.897)
    2024-07-19T15:55:44.415+08:00  INFO 31892 --- [ntContainer#2-1] c.d.customer.exchange.FanoutListener     : FANOUT 广播 mq.fanout.rebate队列接收到消息我是广播 都要接收到 (╯▔皿▔)╯
    2024-07-19T15:55:44.415+08:00  INFO 31892 --- [ntContainer#3-1] c.d.customer.exchange.FanoutListener     : FANOUT 广播 mq.fanout.award队列接收到消息我是广播 都要接收到 (╯▔皿▔)╯
    2024-07-19T15:55:44.425+08:00  INFO 31892 --- [ntContainer#5-1] c.d.customer.exchange.TopicListener      : TOPIC 广播 mq.topic.rebate队列 ·队列接收到消息hello,我是topic,rebate前的都要接收到ヽ(✿゚▽゚)ノ
    2024-07-19T15:55:44.430+08:00  INFO 31892 --- [ntContainer#0-1] c.d.customer.exchange.DirectListener     : DIRECT 广播 mq.direct.rebate队列接收到消息hello,direct_award,Key为{rebate}要接受到/(ㄒoㄒ)/~~
    2024-07-19T15:55:44.672+08:00  INFO 31892 --- [ntContainer#4-1] c.d.customer.exchange.TopicListener      : TOPIC 广播 mq.topic.award队列接收到消息hello,我是topic,dome下的都要接收到 (⊙o⊙)!
    2024-07-19T15:55:44.673+08:00  INFO 31892 --- [ntContainer#5-1] c.d.customer.exchange.TopicListener      : TOPIC 广播 mq.topic.rebate队列 ·队列接收到消息hello,我是topic,dome下的都要接收到 (⊙o⊙)!
    2024-07-19T15:55:44.678+08:00  INFO 31892 --- [ntContainer#1-1] c.d.customer.exchange.DirectListener     : DIRECT 广播 mq.direct.award队列接收到消息hello,direct_award,Key为{award}要接受到/(ㄒoㄒ)/~~