算法【java进阶笔记八】

目录

树的遍历

题一:如何给二叉树的所有节点数值加一?

题二:Leetcode 100 如何判断两颗二叉树是否相同?

题三:判断一棵树是否是排序二叉树

题四:序列化、反序列化一个二叉树

动态规划

举例:斐波那序列

题一:凑零钱问题


算法

树的遍历

题一:如何给二叉树的所有节点数值加一?

换个说法就是遍历每个节点,然后每个节点值 + 1;

实现方式就是树的遍历:【前、中、后序遍历】

    public void traverseHelper(MyNode node){
        if (node==null){
            return;
        }
        //前序遍历
        traverseHelper(node.left);
        //中序遍历
        traverseHelper(node.right);
        //后续遍历
    }

所以题一的解为:

    public void NodePlus(TreeNode node) {
        if (node == null) {
            return;
        }
        //node.value = node.value + 1;
        NodePlus(node.left);
        //node.value = node.value + 1;
        NodePlus(node.right);
        node.value = node.value + 1;
    }

题二:Leetcode 100 如何判断两颗二叉树是否相同?

这题的思路还是树的遍历,只是在遍历的基础上添加了判断的条件。对比一二题可以总结出树遍历的方法:递归

题二的解为:

    public static boolean isSameBST(TreeNode one, TreeNode two) {
        if (one == null && two == null) {
            return true;
        }
​
        if (one == null || two == null) {
            return false;
        }
​
        if (one.value != two.value) {
            return false;
        }
​
        // 上面是判断条件,下面两条递归语句就是树的遍历的核心
        boolean s1 = isSameBST(one.left, two.left);
        boolean s2 = isSameBST(one.right, two.right);
        return s1 && s2;
    }
​
    // 测试类
    public static void main(String[] args) {
​
        TreeNode node1 = new TreeNode(2);
        node1.left = new TreeNode(1);
        node1.right = new TreeNode(2);
​
        TreeNode node2 = new TreeNode(2);
        node2.left = new TreeNode(1);
        node2.right = new TreeNode(3);
​
        System.out.println(isSameBST(node1, node2));
    }

题三:判断一棵树是否是排序二叉树

LeetCode:98. 验证二叉搜索树

力扣

有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。

  • 节点的右子树只包含 大于 当前节点的数。

  • 所有左子树和右子树自身必须也是二叉搜索树。

    思路:树的遍历,遍历每个节点时带上父节点的值,所以需要 bstHelper 方法提供父节点的信息。

    public static boolean isBST(TreeNode treeNode) {
        return bstHelper(treeNode, null, null);
    }
​
    private static boolean bstHelper(TreeNode currentNode, TreeNode maxNode, TreeNode minNode) {
​
        if (currentNode == null) {
            return true;
        }
​
        if (maxNode != null && currentNode.value => maxNode.value) {
            return false;
        }
        if (minNode!=null &&currentNode.value <= minNode.value) {
            return false;
        }
​
        boolean b1 = bstHelper(currentNode.left, currentNode, minNode);
        boolean b2 = bstHelper(currentNode.right, maxNode, currentNode);
​
        return b1 && b2;
    }
​
    // 测试类
    public static void main(String[] args) {
        TreeNode node = new TreeNode(2);
        node.left = new TreeNode(1);
        node.right = new TreeNode(4);
        System.out.println(isBST(node));
    }

题四:序列化、反序列化一个二叉树

剑指 Offer 37. 序列化二叉树

力扣

实现一个二叉树可以被序列化为一个字符串,并且可以将这个字符串反序列化为原始的树结构。

关键:不能只通过一种排序算法确定树的结构,确定树的结构需要两种遍历才可以【前中、前后】

结论:序列化的时候加上 空子树的标志

    private static LinkedList<Integer> list = new LinkedList<>();
    
    // 序列化:遍历每个节点值存到 linkedList 里
    public static void serialize(TreeNode node){
        if (node==null){
            list.add(-1);
            return;
        }
        list.add(node.value);
        serialize(node.left);
        serialize(node.right);
    }
​
    // 反序列化:从 linkedList 里取出值还原成树
    public static TreeNode deSerialize(LinkedList<Integer> deList){
        Integer first = deList.removeFirst();
        if (first==-1){
            return null;
        }
        TreeNode node = new TreeNode(first);
        node.left = deSerialize(deList);
        node.right = deSerialize(deList);
        return node;
    }
​
    // 测试类
    public static void main(String[] args) {
        // 使用平衡二叉树(上一篇文章中有代码实现)
        AVLTree tree = new AVLTree();
        tree.insert(new TreeNode(2));
        tree.insert(new TreeNode(1));
        tree.insert(new TreeNode(3));
        tree.insert(new TreeNode(6));
        tree.insert(new TreeNode(10));
​
        serialize(tree.getRoot());
        TreeNode node = deSerialize(list);
        tree.traverse(node); //这里使用中序遍历,所以输出结果为1 2 3 6 10
    }

动态规划

应用场景:一般都是试求最值的问题

三要素:重叠子问题、最优子结构、状态转移方程

核心:先穷举出所有情况,然后优化问题,数学归纳

举例:斐波那序列

/**
     * 斐波那契数列
     *
     * 定义:从第3项开始,每一项都等于前两项之和
     */
    public static int simpleFib(int n) {
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return 1;
        }
        /**
         *  此方法(暴力递归)会出现: 重叠子问题
         *  例如 n = 4 时,要算出 n = 3 和 n = 2 的值
         *      n = 3 时,要算出 n = 2 和 n = 1 的值
         *      这里面 n = 2 值的问题重复了
         */
        return simpleFib(n - 1) + simpleFib(n - 2);
    }

【重叠子问题】:

 

暴力递归 算法的时间复杂度:子问题个数 * 解决一个子问题所需要的时间 O(2^n)

对上述重叠子问题进行优化:存储子问题结果

    public static int cacheFib(int n) {
        // 将子问题结果存储到 HashMap 里面
        Map<Integer, Integer> cache = new HashMap<>();
        return fibHelper(n, cache);
    }
​
    private static int fibHelper(int n, Map<Integer, Integer> cache) {
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return 1;
        }
        Integer val = cache.get(n);
        if (val != null) {
            return val;
        }
        int i = fibHelper(n - 1, cache) + fibHelper(n - 2, cache);
        cache.put(n, i);
        return i;
    }

经过优化之后,时间复杂度退回到常数级别 O(1)

还有一个问题,什么是状态转移方程?

【状态转移方程】:将 f(n) 想成一个状态,则此状态是通过 n-1 的状态与 n-2 的状态转移得到了。

simpleFib(n) = simpleFib(n - 1) + simpleFib(n - 2);

题一:凑零钱问题

LeetCode:322. 零钱兑换

力扣

题意:给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

例:

count = 11;coins{ 1, 2, 5 }

找规律:

coin = 1coin = 2coin = 5
11个(1*1)
22个(1*2)1个(2 * 1)
33个(1*3)2个(2 * 1+1 * 1)
44个(1*4)2个(2 * 2)
55个(1 * 5)2个(2 * 2 + 1 * 1)1个(5 * 1)
66个(1 * 6)3个(2 * 3)2个(5 * 1 + 1 * 1)
77个(1*7)4个(2 * 3 + 1 * 1)2个(5 * 1 + 2 * 1)
88个(1*8)4个(2 * 4)3个(5 * 1 + 2 * 1 + 1 * 1)
99个(1*9)5个(2 * 4 + 1 * 1)3个(5 * 1 + 2 * 2)
1010个(1*10)5个(2 * 5)2个(5 * 2)
1111个(1*11)6个(2 * 5 + 1 * 1)3个(5 * 2 + 1 * 1)

要想求出金额为 11 的最少硬币数,只需求出金额为 10 的最少硬币数(2)然后再加 1 = 3

同理要想求金额为 8 的最小硬币数,只需求出金额为 7 的最小硬币数(2)然后再加 1 = 3

【最优子结构】:向上面这种:子问题之间状态独立,且有最优解的结构

    public static int coinsChange(int[] coins, int totalMoney) {
        // 动态规划都有:边界问题
        if (coins.length == 0) {
            return -1;
        }
        // 数组用于存储每个 count 的最小硬币数
        int[] memo = new int[totalMoney + 1];
        memo[0] = 0;
​
        // 第一重循环为 count 总金额循环
        for (int i = 1; i <= totalMoney; i++) {
            int min = Integer.MAX_VALUE;
            // 第二重循环为 coin 硬币面值循环
            for (int j = 0; j < coins.length; j++) {
                int k = i - coins[j];
                if (k >= 0 && memo[k] < min) {
                    // 要想求出金额为 11 的最少硬币数,
                    // 只需求出金额为 10 的最少硬币数然后再 + 1 
                    min = memo[k] + 1;
                }
            }
            memo[i] = min;
        }
        return memo[totalMoney] == Integer.MAX_VALUE ? -1 : memo[totalMoney];
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值