zoukankan      html  css  js  c++  java
  • 算法笔记

    1、逆序想到 stack ; 比如 445. 两数相加 II,当然,可以用 stack 也是可以用 List,list 有序,因此也是可以当作 stack 用;

    2、要求达到 O(n log n)O(nlogn) 的时间复杂度和 O(1)O(1) 的空间复杂度,时间复杂度是 O(n log n)O(nlogn) 的排序算法包括归并排序、堆排序和快速排序(快速排序的最差时间复杂度是 O(n^2)O(n2)),其中最适合链表的排序算法是归并排序。

    3、对于链表,有时候需要把头结点加入循环,可以在头结点前面在家一个节点,这样就可以用于判断了;

    4、链表常用的操作逻辑,用 stack, 数组保存节点组成新的队列;用一个新节点来连接原来的节点,这样,即使对原有节点改变了,还是可以返回头结点。熟悉链表的反转,合并等逻辑。

    5、关于二叉树,二叉树一般采用递归,一般是递归到最后一个的时候,在不断往前,因此你要做好最好一个判断到底是返回什么,比如深度的求解等等。

    class Solution {
        public int maxDepth(TreeNode root) {
            if (root == null) {
                return 0;
            } else {
                int leftHeight = maxDepth(root.left);
                int rightHeight = maxDepth(root.right);
                return Math.max(leftHeight, rightHeight) + 1;  // 这里加1,不断递归,数值就变大了
            }
        }
    }

     6、二叉树的递归调用其实就是存在一个隐藏的stack。只是大家平时熟悉了递归,没有去细想内部的逻辑。所以如果不采用递归方式来实现,就是得采用 stack 来维护这个顺序,但是要注意先入后出这个点。

    7、如果给你一个数组 num1 足够空间,前部分已经存在值了,让你把另一个 num2 的值填到 num1 中。这种要么新建一个数组,填完后再挪到 num1 中,另一种是从合并后的长度开始填,这样确保不会覆盖 num1 前面已有的值。可参考 88. 合并两个有序数组

    8、两个字符串找不同,对于字符串可以采用求和,位运算,以及一个数组来记录各个字母出现的个数,以此来找到不同。参考:389. 找不同

     1 // 求和
     2 class Solution {
     3     public char findTheDifference(String s, String t) {
     4         int as = 0, at = 0;
     5         for (int i = 0; i < s.length(); ++i) {
     6             as += s.charAt(i);
     7         }
     8         for (int i = 0; i < t.length(); ++i) {
     9             at += t.charAt(i);
    10         }
    11         return (char) (at - as);
    12     }
    13 }
    14 
    15 // 计数
    16 class Solution {
    17     public char findTheDifference(String s, String t) {
    18         int[] cnt = new int[26];
    19         for (int i = 0; i < s.length(); ++i) {
    20             char ch = s.charAt(i);
    21             cnt[ch - 'a']++;
    22         }
    23         for (int i = 0; i < t.length(); ++i) {
    24             char ch = t.charAt(i);
    25             cnt[ch - 'a']--;
    26             if (cnt[ch - 'a'] < 0) {
    27                 return ch;
    28             }
    29         }
    30         return ' ';
    31     }
    32 }
    33 
    34 // 位运算
    35 class Solution {
    36     public char findTheDifference(String s, String t) {
    37         int ret = 0;
    38         for (int i = 0; i < s.length(); ++i) {
    39             ret ^= s.charAt(i);
    40         }
    41         for (int i = 0; i < t.length(); ++i) {
    42             ret ^= t.charAt(i);
    43         }
    44         return (char) ret;
    45     }
    46 }
    两个字符串找不同

    9、翻转二叉树,这道题目遇到过好几遍,虽然知道翻转,但是就不知道如何写比较好:226. 翻转二叉树

    class Solution {
        public TreeNode invertTree(TreeNode root) {
            if (root == null) {
                return null;
            }
            // 如果此时左右节点已经为 null 了,那么就会执行后面的交换逻辑,然后递归就会回到父节点那一层
            TreeNode left = invertTree(root.left);
            TreeNode right = invertTree(root.right);
            root.left = right;
            root.right = left;
            return root;
        }
    }
        

     10、关于递归。我感觉自己对递归还是没有理解透彻。

    对于509. 斐波那契数,我可以很轻松写下 如下代码,并不会觉得有啥问题。

    class Solution {
        public int fib(int n) {
            if (n==1 || n==2) return 1;
            return fib(n-1)+ fib(n-2);
        }
    }

    同样的,对于129. 求根节点到叶节点数字之和,也是递归,但是对于二叉树的递归我确实很忐忑,总是觉得自己写的代码会有啥问题,具体代码如下:

    class Solution {
        public int sumNumbers(TreeNode root) {
            return dfs(root, 0);
        }
    
        public int dfs(TreeNode root, int prevSum) {
            if (root == null) {
                return 0;
            }
            int sum = prevSum * 10 + root.val;
            if (root.left == null && root.right == null) {
                return sum;
            } else {
                // 既然你知道是递归,同时你也把递归的分支搞清楚了,只有左右两个分支,因此你就写下就是了
                return dfs(root.left, sum) + dfs(root.right, sum);
            }
        }
    }
        

     还有就是我在想,是不是应该按照第一题一样,先把递归的状态搞清楚,也就是当前状态和前面几个状态是啥关系,有几个分支。都理清楚了可能就好写了。然后把递归的结束条件分析清楚就好,结束条件放在前面,递归逻辑放在后面,只有没有结束才是可以进入递归的。

    10、对于二分法要时刻关注只有两个元素的情况,比如:[1,2],[2,1]。这时候 middle = left。这时候注意 left 和 right 之间的关系。

    11、在做加法和乘法的点时候要考虑会不会存在溢出的情况,一旦溢出,结果就不一样了。可以考虑乘法变成除法,加法变成减法,以此逃过溢出。

    12、在做加法乘法的时候,int 很容易溢出,这时候可以考虑使用 long 类型,比如在用 while 做 10 的阶乘的时候,很容易就突然超了 int 的值,一般循环个 9 -10 次就差不多了 。 400. 第 N 位数字

    13、如何理解动态规划。有时候很多题目我们知道要用动态规划,可是总会想这么做到底对不对,会不会错过什么的。其实就是要理解动态规划的无后向性。比如 1696. 跳跃游戏 VI,其实它就是只有 k 种选择。如果我们将到某个位置 pos 的前 k 种情况都列举出来了,那么到 pos 位置只能从 k 种里面去选最大或者最小的。在往前考虑没有意义了,最多只有 k 步。同时对于后面的点位 last,last 位置前的 k 位其实都已经计算出来了,选出最优的结果就好。

    记住每一个点都有自己的一个最优解。对于 pos 位置,就是前 k 个位置的最优解一起放出来进行比较,在选择最优的。

            // 总共有nums 个元素
            for (int i = 1; i < nums.length; i++) {
                    // 只有k个选择
                    for (int j = Math.max(0, i - k); j < i; j++) {
                            // 比较这 k 个的大小
                            dp[i] = Math.max(dp[i], dp[j]);
                    }
                    // 选择最优的,然后再加上当前位置,就是当前 i 的最优解
                    dp[i] += nums[i];
            }
    
    // 这段代码有个问题就是,每次 K 个元素的比较其实会有重复的,相当于是一个移动窗口,窗口大小不变,里面的元素相互比较。因此,对于优化,可以从这里考虑           

     14、关于动态规划,我觉得你还是得理解 dp 表示的含义,这个含义一般就是题目要求的结果,但是可能是解释上还是有点区别的。然后接着去理解每一个[][]索引所代表的含义。然后再逐步优化。121. 买卖股票的最佳时机,参考解答:暴力解法、动态规划(Java)。还有比如这题 392. 判断子序列 返回的是布尔值,那么你要如何去定义 dp 的含义呢?参考解答。这些都是值得学习的地方。

    附算法系列文章

    滑动窗口算法基本原理与实践

    广度优先搜索原理与实践

    深度优先搜索原理与实践

    双指针算法基本原理和实践

    分治算法基本原理和实践

    动态规划算法原理与实践

    算法笔记

    树林美丽、幽暗而深邃,但我有诺言尚待实现,还要奔行百里方可沉睡。 -- 罗伯特·弗罗斯特
  • 相关阅读:
    oracle如何查询哪个表数据量大
    SecureRandom生成随机数超慢 导致tomcat启动时间过长的解决办法
    smartctl----硬盘状态监控
    Oracle数据库的状态查询
    jdbc连接数据库使用sid和service_name的区别
    V$INSTANCE 字段说明
    V$PROCESS和V$SESSION,以及使用这两个视图能做什么
    NetOps Defined
    POI 海量数据
    HTML5 CSS3 诱人的实例: 3D立方体旋转动画
  • 原文地址:https://www.cnblogs.com/huansky/p/14947981.html
Copyright © 2011-2022 走看看