美文网首页
算法训练营-第二周-哈希树堆图

算法训练营-第二周-哈希树堆图

作者: 我是阿喵酱 | 来源:发表于2020-08-24 08:07 被阅读0次

    一.哈希表

    定义

    键值映射关系

    <img src="https://mmbiz.qpic.cn/mmbiz_png/Z6bicxIx5naLDQzfJtARmicCd4F6ASYliaibastwh0eUlfWtHFU7I7zSmtzRfDgg25fic8eCx7QZkrTibs8yKjCAQ1oQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="img" style="zoom:33%;" />

    时间复杂度

    写入 O(1)

    读取 O(1)

    扩容 O(n)

    哈希函数

    把key转成index寻找值

    index = HashCode ("001121") % Array.length = 7
    

    <img src="https://mmbiz.qpic.cn/mmbiz_png/Z6bicxIx5naLDQzfJtARmicCd4F6ASYliaib9ywjXKqGDwR8iaNia8BHhPcYt6rwMI5L2EyAMTIql226icxIGicicV1Kdpw/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="img" style="zoom:33%;" />

    实战题目

    242. 有效的字母异位词

    /*
    
    
        1.暴力 sort 遍历比较 
            时间复杂度 O(nlogn)
            空间复杂度 O(1)
        2.哈希表 比较字母出现的频次
            或者26个大小的数组,一边自增,一边自减。所有的都为0则相等。
            时间复杂度O(n)
            空间复杂度O(26)
    */
    public class ValidAnagram {
    
        /**
         * 解法1 排序后比较
         * 
         * 分别将两个字符串排序,然后比较
         * 
         * 时间复杂度:O(nlogn)
         * 空间复杂度:O(1)
         */
        public boolean isAnagram(String s, String t){
            if (s.length() != t.length())  return false;
            char[] str1 = s.toCharArray();
            char[] str2 = t.toCharArray();
            Arrays.sort(str1);
            Arrays.sort(str2);
            return Arrays.equals(str1, str2);
        }
    
        /**
         * 解法2 计数器
         * 
         * 用计数器统计每个字符出现的次数
         * 
         * 时间复杂度:O(n)
         * 空间复杂度:O(1)
         */
        public static boolean isAnagram2(String s, String t){
            if (s.length() != t.length()) return false;
            int[] table = new int[26];
            for (int i = 0; i < s.length(); i++) {
                //当前字符 - a,a = 97
                table[s.charAt(i) - 'a']++;
            }
            for (int i = 0; i < s.length(); i++) {
                table[t.charAt(i) - 'a']--;
                //减 1 之后出现次数小于就说明就在 s 中从未出现的字符
                if (table[t.charAt(i) - 'a'] < 0) return false;
            }
            return true;
        }
    }
    

    49. 字母异位词分组

    /*
        方法一:
            用HashMap接收最后的结果,
            对原数组字符串进行遍历,转成字符数组,排序,转成字符串
            key为排序后的字符串,value为原来的元素。
            
            返回HashMap.values。
                时间复杂度 O(NKlogK) N是字符串数组个数 K为字符串最大长度
                空间复杂度 O(NK) 开辟的ans哈希表
        方法二:建议课后背诵
            和方法一一个意思。只是用26个数组去接。没什么意义。只是时间复杂度小。不用排序。
            时间复杂度 O(N * (K > 26 ? K : 26)) N是字符串数组个数 K为字符串最大长度
            空间复杂度 O(NK)
    */
    /*
    class Solution {
        public List<List<String>> groupAnagrams(String[] strs) {
            if (strs.length == 0) return new ArrayList();
            Map<String, List> ans = new HashMap<String, List>();
            for (String s : strs) {
                char[] ca = s.toCharArray(); // 每个元素变成字符串数组
                Arrays.sort(ca); // 数组排序
                String key = String.valueOf(ca); // 转成字符串作为Key。key: 排序后的数组  value:字母异位词的集合
                if (!ans.containsKey(key)) ans.put(key, new ArrayList()); // 结果里没有这个key,就放入key,值为数组
                ans.get(key).add(s); // 数组里加原来这个元素
            }
            return new ArrayList(ans.values());
        }
    }
    */
    class Solution {
        public List<List<String>> groupAnagrams(String[] strs) {
            if (strs.length == 0) return new ArrayList();
            Map<String, List> ans = new HashMap<String, List>();
            int[] count = new int[26];
            for (String s : strs) {
                Arrays.fill(count, 0); // 数组每个值赋0
                for (char c : s.toCharArray()) count[c - 'a']++; // 字母对应位自增
                StringBuilder sb = new StringBuilder("");
                for (int i = 0; i < 26; i++) {
                    sb.append('#');
                    sb.append(count[i]); // #2#1#0#0
                }
                String key = sb.toString(); // 转成字符串作为密钥
                if (!ans.containsKey(key)) ans.put(key, new ArrayList()); // 相同key,放一个数组里
                ans.get(key).add(s);
            }
            return new ArrayList(ans.values());
        }
    }
    

    1. 两数之和

    /*
        找出数组中两个数字满足目标值的两个数字
            方法一:暴力迭代
                i -> 0, len - 2
                    j -> i+1, len - 1
                        nums[i] + nums[j] == target
            时间复杂度O(n^2)
            空间复杂度O(n)
            方法二: 利用哈希表,两次迭代
                第一次,将所有数字放入哈希表中,
                第二次,遍历哈希表,查找目标值-当前值的结果是否在哈希中
            时间复杂度O(n * 2)
            空间复杂度O(n)    
            方法三:利用哈希表,一次迭代
                其实只需要一次,先判断目标值-当前值的结果是否在哈希中,
                    不存在,在把值放进去。
            时间复杂度:O(n)
            空间复杂度:O(n)
    
    
    */
    class Solution {
        public int[] twoSum(int[] nums, int target) {
            Map<Integer, Integer> map = new HashMap<>();
            for (int i = 0; i < nums.length; i++) {
                int complement = target - nums[i];
                if (map.containsKey(complement)) {
                    return new int[] { map.get(complement), i };
                }
                map.put(nums[i], i);
            }
            throw new IllegalArgumentException("No two sum solution");
        }
    }
    

    二.树

    定义

    树是n个节点的有限集。

    在非空树中,有如下特点:

    • 有且一个根节点

    • 当n>1时,其余节点可分为m个互不相交的有限集,每一个集合本身又是一个树,并称为根的子树。

    树的遍历

    (1)深度优先

    前序:根左右

    <img src="https://mmbiz.qpic.cn/mmbiz_png/Z6bicxIx5naLDQzfJtARmicCd4F6ASYliaib40oOC9T5IzJjSPAcrhUDicVG9HdKGVEwySEC3EvX1NqdricI68Arqm6g/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="img" style="zoom:33%;" />

    中序:左根右

    <img src="https://mmbiz.qpic.cn/mmbiz_png/Z6bicxIx5naLDQzfJtARmicCd4F6ASYliaibnS3Lgz89n4peOQL0dpOibjibhyUtqdKbMFNtwn4DTfOO7fFRcqhWH1Zg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="img" style="zoom:33%;" />

    后序:左右根

    <img src="https://mmbiz.qpic.cn/mmbiz_png/Z6bicxIx5naLDQzfJtARmicCd4F6ASYliaibM9ibtl8CeLLhDykSFKJAwsljsBOCMqAKVNr4FMO1UC8RUrXYyd9Nb4Q/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="img" style="zoom:33%;" />

    实现方式:递归或栈。

    (2)广度优先

    层序:一层一层遍历。

    <img src="https://mmbiz.qpic.cn/mmbiz_png/Z6bicxIx5naLDQzfJtARmicCd4F6ASYliaib81wdmp22Bz3N1Ju6TtB2oiaIgpDFdBrTCXYic3czK5mEfTxcoA59gY1w/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1" alt="img" style="zoom:33%;" />

    实现方式:队列。

    三.二叉树

    二叉树

    二叉,顾名思义,这种树的每个节点最多有2个孩子节点。注意,这里是最多有2个,也可能只有1个,或者没有孩子节点。

    满二叉树?

    一个二叉树的所有非叶子节点都存在左右孩子,并且所有叶子节点都在同一层级上,那么这个树就是满二叉树。

    完全二叉树

    对一个有n个节点的二叉树,按层级顺序编号,则所有节点的编号为从1到n。如果这个树所有节点和同样深度的满二叉树的编号为从1到n的节点位置相同,则这个二叉树为完全二叉树。

    倒数第二层满,最后一层全靠左

    img

    二叉查找树

    定义

    二叉查找树在二叉树的基础上增加了以下几个条件:

    • 如果左子树不为空,则左子树上所有节点的值均小于根节点的值。

    • 如果右子树不为空,则右子树上所有节点的值均大于根节点的值。

    • 左、右子树也都是二叉查找树。

    <img src="https://hexo-1257630696.cos.ap-singapore.myqcloud.com/img/640" alt="img" style="zoom: 33%;" />

    作用

    • 查找==》二分查找。
    • 排序==》中序遍历。

    实现方式

    • 链表。
    • 数组:对于稀疏二叉树来说,数组表示法是非常浪费空间的。

    实战题目

    589. N叉树的前序遍历

    /*
    // Definition for a Node.
    class Node {
        public int val;
        public List<Node> children;
    
        public Node() {}
    
        public Node(int _val) {
            val = _val;
        }
    
        public Node(int _val, List<Node> _children) {
            val = _val;
            children = _children;
        }
    };
    */
    
    class Solution {
        public List<Integer> preorder(Node root) {
            List<Integer> res = new ArrayList<Integer>();
            Stack<Node> stack = new Stack<Node>();
            if(root == null)
                return res;
            stack.push(root);
            while(!stack.isEmpty())
            {
                Node node = stack.pop();
                res.add (node.val);
                for(int i =  node.children.size()-1;i>= 0;i--)
                {
                    stack.add(node.children.get(i));
                }  
            }
            return res;
        }
    }
    

    590. N叉树的后序遍历

    /*
    // Definition for a Node.
    class Node {
        public int val;
        public List<Node> children;
    
        public Node() {}
    
        public Node(int _val) {
            val = _val;
        }
    
        public Node(int _val, List<Node> _children) {
            val = _val;
            children = _children;
        }
    };
    */
    //迭代实现:直接反转先序结果即可
    class Solution {
        public List<Integer> postorder(Node root) {
            List<Integer> res_pre = new ArrayList<>();
            if(root == null)
                return res_pre;
            Stack<Node> s = new Stack<>();
            s.push(root);
            while(!s.isEmpty()){
                Node n = s.pop();
                res_pre.add(n.val);
                for(Node node : n.children)
                    s.push(node);
            }
            Collections.reverse(res_pre);
            return res_pre;
        }
    }
    

    429. N叉树的层序遍历

    /*
    // Definition for a Node.
    class Node {
        public int val;
        public List<Node> children;
    
        public Node() {}
    
        public Node(int _val) {
            val = _val;
        }
    
        public Node(int _val, List<Node> _children) {
            val = _val;
            children = _children;
        }
    };
    */
    
    class Solution {
    
        List<List<Integer>> res = new ArrayList<>();
    
        public List<List<Integer>> levelOrder(Node root) {
            helper(root, 1);
            return res;
        }
    
        public void helper(Node root, int level) {
            if (root == null) return;
            if (res.size() < level) res.add(new ArrayList<Integer>());
            res.get(level - 1).add(root.val);
            for (Node child : root.children) 
                helper(child, level + 1);
        }
    }
    

    104. 二叉树的最大深度

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    class Solution {
        public int maxDepth(TreeNode root) {
            return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
        }
    }
    

    112. 路径总和

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    class Solution {
        public boolean hasPathSum(TreeNode root, int sum) {
            if (root == null) return false;
            if (root.left == null && root.right == null) return root.val == sum;
            return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
        }
    }
    

    94. 二叉树的中序遍历

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     * int val;
     * TreeNode left;
     * TreeNode right;
     * TreeNode(int x) { val = x; }
     * }
     * [1,null,2,3]
     * 二叉树的中序遍历。题目说不让用递归。这题只能用栈。
            中序遍历:左-根-右
     *      方法一:递归
     *          时间复杂度 O(n)
     *          空间复杂度 O(n)
     *      方法二:栈的遍历。建议背诵。
                指针不为空,栈不为空,就不结束。
                指针不为空,就一直找左儿子,放左儿子
                直到左儿子没了,就开始弹。放入结果中。然后指右儿子。
     *          时间复杂度 O(n)
     *          空间复杂度 O(n)
            方法三:莫里斯遍历
     */
     public class Solution {
        public List<Integer> inorderTraversal(TreeNode root) {
            // 结果链表
            List<Integer> res = new ArrayList<>();
            // 栈:工具人角色
            Stack<TreeNode> stack = new Stack<>();
            // curr 当前指针,从根开始
            TreeNode curr = root;
            // 指针不为Null且栈非空
            while (curr != null || !stack.isEmpty()) {
                // 放的步骤:指针不为null。将左边的都放先进去。当没有左儿子了,就开始弹。
                while (curr != null) {
                    // 把指针放进去
                    stack.push(curr);
                    // 指向左子树
                    curr = curr.left;
                }
                // 弹出栈顶值,值放入结果链表。指针指向右儿子。
                curr = stack.pop();
                res.add(curr.val);
                curr = curr.right;
            }
            return res;
        }
    }
    /*
    class Solution {
        public List<Integer> inorderTraversal(TreeNode root) {
            List<Integer> res = new ArrayList<>();
            helper(root, res);
            return res;
        }
    
    
        public void helper(TreeNode root, List<Integer> res) {
            // 根不为空
            if (root != null) {
                // 左子树不为空
                if (root.left != null) {
                    // 就放左子树
                    helper(root.left, res);
                }
                // 然后放中间数
                res.add(root.val);
                // 右子树不为空,就放右子树
                if (root.right != null) {
                    helper(root.right, res);
                }
            }
        }
    }*/
    

    22. 括号生成

    /*
        生成所有n个有效括号
            输入:n = 3
            输出:[
                   "((()))",
                   "(()())",
                   "(())()",
                   "()(())",
                   "()()()"
                 ]
     */
    
    
     // 递归
    // public class Solution {
    
    
    //     ArrayList<String> res = new ArrayList<>();
    
    
    //     public List<String> generateParenthesis(int n) {
    //         // 初始条件
    //         generate(0, 0, n, "");
    //         // 结束条件
    //         return res;
    //     }
    
    
    //     // 处理
    //     private void generate(int leftCnt, int rightCnt, int n, String s) {
    //         // 下钻
    //         // 如果左、右括号等于n个,将值放入结果中
    //         if (leftCnt == n && rightCnt == n) {
    //             res.add(s);
    //             return;
    //         }
    //         // 只要左括号没有达到n个,就可以加
    //         if (leftCnt < n) generate(leftCnt + 1, rightCnt, n, s + "(");
    //         // 右括号数量如果小于左括号,就可以加
    //         if (rightCnt < leftCnt) generate(leftCnt, rightCnt + 1, n, s + ")");
    //         // 清除全局临时变量
    //     }
    
    
    //     public static void main(String[] args) {
    //         System.out.println(new Solution().generateParenthesis(3));
    //     }
    // }
    
    
    // 递归。确保括号有效性。只要要求左括号先行于右括号。
    class Solution {
        List<String> res = new ArrayList<>();
        public List<String> generateParenthesis(int n) {
            dfs(n, n, "");
            return res;
        }
    
    
        private void dfs(int left, int right, String curStr) {
            if (left == 0 && right == 0) { // 左右括号都不剩余了,递归终止
                res.add(curStr);
                return;
            }
            // 第一次会进去。第二次会有两个分叉
            if (left > 0) { // 如果左括号还剩余的话,可以拼接左括号
                dfs(left - 1, right, curStr + "(");
            }
            // 第一次是进不去的,因为左右是相等的。第二次进入后,左右等号数量又相等了。
            if (right > left) { // 如果右括号剩余多于左括号剩余的话,可以拼接右括号
                dfs(left, right - 1, curStr + ")");
            }
        }
    }
    

    98. 验证二叉搜索树

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
    
    
    
        方法一:根据定义,左子树必须小于当前节点
                        右子树必须大于当前节点
            时间复杂度 O(n)
            空间复杂度 O(n)
        方法二:中序遍历是递增的,用临时变量去接,每次都比较
     */
    public class Solution {
        public boolean isValidBST(TreeNode root) {
            return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
        }
        // 判断是否是有效二叉搜索树,左子树小于当前节点,右子树大于当前节点
        public boolean isValidBST(TreeNode root, long minVal, long maxVal) {
            // null符合要求
            if (root == null) return true;
            if (root.val >= maxVal || root.val <= minVal) return false;
            // 左边这个:有效的是左节点与最大值。右边这个:有效的是右节点与最小值。
            return isValidBST(root.left, minVal, root.val) && isValidBST(root.right, root.val, maxVal);
        }
    }
    
    
     // 通过中序遍历来验证
    class Solution {
        long pre = Long.MIN_VALUE;
        public boolean isValidBST(TreeNode root) {
            if (root == null) {
                return true;
            }
            // 访问左子树
            if (!isValidBST(root.left)) {
                return false;
            }
            // 访问当前节点:如果当前节点小于等于中序遍历的前一个节点,说明不满足BST,返回 false;否则继续遍历。
            if (root.val <= pre) {
                return false;
            }
            pre = root.val;
            // 访问右子树
            return isValidBST(root.right);
        }
    }
    

    104. 二叉树的最大深度

    public class MaximumDepthOfBinaryTree {
    
        /**
         * 解法1 递归
         * 
         * 递归循环做的事情:先求左子树深度,再求右子树深度,然后取两者最大,再加上自身一层的深度
         * 
         * 时间复杂度: O(n)
         * 空间复杂度: 最坏时树完全不平衡 O(n),最好时树完全平衡 O(logn)
         * @param root:根节点
         * @return 树的最大深度
         */
        public int maxDepth(TreeNode root) {
            //递归终止条件:叶子节点无左右子节点
            if (root == null)
                return  0;
            else {
                //左子树深度
                int leftHeight = maxDepth(root.left);
                //右子树深度
                int rightHeight = maxDepth(root.right);
                // + 1 表示每次递归结束,求深度的时候,要把自身所在一层深度加上
                return Math.max(leftHeight, rightHeight) + 1;
            }
        }
    }
    
    class Solution {
        public int maxDepth(TreeNode root) {
            return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
        }
    }
    

    105. 从前序与中序遍历序列构造二叉树

    public class ConstructBinaryTreeFromPreorderAndInorderTraversal {
    
    
        /**
         * 解法1 递归
         *
         * 前序便利的第一个元素一定是树的根,然后这个根将中序遍历分成左右两颗子树,再通过移动指针递归两个子树,不停给左右子节点赋值,最后完成树的构建
         *
         * @param preOrder: 前序遍历
         * @param inOrder: 中序遍历
         * @return 构建好树返回根节点
         */
        public TreeNode buildTree(int[] preOrder, int[] inOrder){
            Map<Integer, Integer> map = new HashMap<Integer, Integer>();
            for (int i = 0; i < inOrder.length; i++)
                map.put(inOrder[i], i);
            return build(preOrder, 0, preOrder.length - 1, inOrder, 0, inOrder.length - 1, map);
        }
    
        /**
         * @param pre : 前序遍历 preOrder
         * @param ps  : preOrder start
         * @param pe  : preOrder end
         * @param in  : 中序遍历 inOrder
         * @param is  : inOrder start
         * @param ie  : inOrder end
         * @param map : inOrder map - key: 中序遍历数组元素 value: 元素数组下表
         * @return 构建完成返回根节点
         */
        private TreeNode build(int[] pre, int ps, int pe, int[] in, int is, int ie, Map<Integer, Integer> map) {
            if (ps > pe || is > ie) return null;
            //前序遍历的第一个元素一定是树的根
            TreeNode root = new TreeNode(pre[ps]);
    
            //找到这个根在中序遍历中的位置,它将中序遍历分成了左右两颗子树
            int rootIndex = map.get(root.val);
    
            //距离 = 根在中序遍历中的位置 - 左子节点的位置
            int distance = rootIndex - is;
    
            /**
             * ps + 1        : 前序遍历中 - 左子树的开始节点
             * ps + distance : 前序遍历中 - 左子树的结束节点
             * is            : 中序遍历中 - 左子树的开始节点
             * rootIndex - 1 : 中序遍历中 - 左子树的结束节点
             */
            root.left = build(pre, ps + 1, ps + distance, in, is, rootIndex - 1, map);
            /**
             * ps + distance + 1 : 前序遍历中 - 右子树的开始节点
             * pe                : 前序遍历中 - 右子树的结束节点
             * rootIndex + 1     : 中序遍历中 - 右子树的开始节点
             * ie                : 中序遍历中 - 右子树的结束节点
             */
            root.right = build(pre, ps + distance + 1, pe, in, rootIndex + 1, ie, map);
            return root;
        }
    }
    

    111. 二叉树的最小深度

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    public class MinimumDepthOfBinaryTree {
    
        /**
         * 解法1 递归
         * 
         * 取左子树的深度和右子树的深度两者最小为树的最小深度
         * 
         * 时间复杂度:O(n)
         * 空间复杂度:最坏 O(n),最好 O(logn)
         * 
         * @param root: 根节点
         * @return 返回二叉树的最小深度
         */
        public int minDepth(TreeNode root) {
            if (root == null) {
                return 0;                   
            }
            if (root.left != null && root.right!= null) 
                return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
            else 
                //如果左子节点为空或者右子节点为空,那么 root 有一个叶子节点也算是一层,所以取 max,再加上当前节点所在的一层
                //如果左右子节点都为空,那么返回节点所在的一层
                return Math.max(minDepth(root.left), minDepth(root.right)) + 1;
        }
    }
    

    相关文章

      网友评论

          本文标题:算法训练营-第二周-哈希树堆图

          本文链接:https://www.haomeiwen.com/subject/fcugjktx.html