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

2020-08-24  本文已影响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个节点的有限集。

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

树的遍历

(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;
    }
}
上一篇下一篇

猜你喜欢

热点阅读