ARTS_WEEK17

二叉树、极客专栏笔记、写作技巧

Posted by CDz on March 29, 2020

这段话时间,更多的精力放在了LeetCode活动每日一题上,计划的算法练习项就缓慢了许多。并且本身二叉树的练习也就非常多,所以一个二叉树的练习项,大概已经做了有三周的时间了。

慕课视频上的多线程视频,自从去公司上班之后,就基本上没有看了,这方面的笔记也暂时搁置了。

这周主要看了一部记录篇《富翁谷底求翻身》,看完之后确实很热血,甚至感觉我领悟到了东西一样。但是在知乎里看到问答才知道这是一个真人秀综艺,剧本性质当然非常大。我想中间的很多思维还是非常值得学习的。

还有这周开始试验,自己的专栏学习计划。

  • 每篇文章看完之后笔记
    • 如何笔记?
    • 写出这篇文章讲了什么,能与我的什么知识结合(这个是刚刚想到的之前没有做过)
    • 看完这篇文章后的疑问

因为确实很多时候看得快看的多,并没有太多的用处,甚至一周下来,什么都不记得了。

下周继续优化学习方法:

音频+文章笔记

  • 音频是自己先听一遍(整个课程)
    • 挺一般一下可以听好几节课
    • 这样对看的文章前后联系会更紧密
  • 笔记法与此前一样
  • 看着文稿再听一遍

如果主攻的话,其实可以一门课,在一周的时间快速过一遍。问题全部先存疑。看文稿的时候就会更加仔细,因为是带着问题去看文稿了。

1. although

1.1. 每日一练

1.1.1. 卡牌分组

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CDz
 * Date: 2020-03-27
 * Time: 下午2:51
 * 914. 卡牌分组
 * 给定一副牌,每张牌上都写着一个整数。
 * <p>
 * 此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组:
 * <p>
 * 每组都有 X 张牌。
 * 组内所有的牌上都写着相同的整数。
 * 仅当你可选的 X >= 2 时返回 true。
 * <p>
 * <p>
 * <p>
 * 示例 1:
 * <p>
 * 输入:[1,2,3,4,4,3,2,1]
 * 输出:true
 * 解释:可行的分组是 [1,1],[2,2],[3,3],[4,4]
 * 示例 2:
 * <p>
 * 输入:[1,1,1,2,2,2,3,3]
 * 输出:false
 * 解释:没有满足要求的分组。
 * 示例 3:
 * <p>
 * 输入:[1]
 * 输出:false
 * 解释:没有满足要求的分组。
 * 示例 4:
 * <p>
 * 输入:[1,1]
 * 输出:true
 * 解释:可行的分组是 [1,1]
 * 示例 5:
 * <p>
 * 输入:[1,1,2,2,2,2]
 * 输出:true
 * 解释:可行的分组是 [1,1],[2,2],[2,2]
 * <p>
 * 提示:
 * <p>
 * 1 <= deck.length <= 10000
 * 0 <= deck[i] < 10000
 */

这题关键的是找到所有相同数字的count的最大公约数。

/**
     * 关键点 求解最大公约数
     *
     * @param deck
     * @return
     */
    public boolean hasGroupsSizeX(int[] deck) {

        int[] countor = new int[10000];

        for (int i : deck) {
            countor[i]++;
        }

        int x = countor[deck[0]];

        for (int i = 0; i < countor.length; i++) {
            int count = countor[i];
            if (count == 1) return false;

            if (count >= 2) {
                x = gcd(x, count);
                if (x == 1) return false;
            }
        }
        return true;
    }

    public boolean hasGroupsSizeX1(int[] deck) {

        int[] countor = new int[10000];

        for (int i : deck) {
            countor[i]++;
        }

        int x = countor[deck[0]];

        System.out.println(Arrays.stream(countor).reduce(this::gcd).getAsInt());
        //每两个数字都 gcd一下,所以只要最终结果 大于等2就说明有解
        return Arrays.stream(countor).reduce(this::gcd).getAsInt() >= 2;

    }


    private int gcd(int x, int y) {

        if (y == 0) return x;

        return gcd(y, x % y);
    }

    public static void main(String[] args) {
        Week17EHasGroupsSizeX914 groupsSizeX = new Week17EHasGroupsSizeX914();
        groupsSizeX.hasGroupsSizeX1(new int[]{2,3,4,4,3,2,1});
    }

1.1.2. 按摩师

/**
 * 面试题 17.16. 按摩师
 * 一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。
 * <p>
 * 注意:本题相对原题稍作改动
 * <p>
 * <p>
 * <p>
 * 示例 1:
 * <p>
 * 输入: [1,2,3,1]
 * 输出: 4
 * 解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。
 * 示例 2:
 * <p>
 * 输入: [2,7,9,3,1]
 * 输出: 12
 * 解释: 选择 1 号预约、 3 号预约和 5 号预约,总时长 = 2 + 9 + 1 = 12。
 * 示例 3:
 * <p>
 * 输入: [2,1,4,5,3,1,1,3]
 * 输出: 12
 * 解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。
 */

简单的动态规划,关键点,当前考虑值的两种状态:选与不选。

/**
     * 动态规划
     * @param nums
     * @return
     */
    public int massage(int[] nums) {
        return dp(nums, nums.length - 1);
    }

    //递归的方式
    private int dp(int[] nums, int n) {
        if (n < 0) {
            return 0;
        }
        return Math.max(dp(nums, n - 2) + nums[n], dp(nums, n - 1));
    }

    //使用数组缓存
    public int massage1(int[] nums) {

        int n = nums.length;
        if (n == 0) return 0;
        if (n == 1) return nums[0];

        int[] dp = new int[n];

        //前两个的最优解
        dp[0] = nums[0];
        dp[1] = Math.max(nums[1], nums[0]);

        for (int i = 2; i < n; i++) {
            dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
        }

        return dp[n - 1];
    }

    //只缓存两个数字
    public int massage2(int[] nums) {

        int n = nums.length;
        if (n == 0) return 0;
        if (n == 1) return nums[0];

        int a = 0, b = 0;

        for (int i = 0; i < n; i++) {
            int c = Math.max(b, nums[i] + a);
            a = b;
            b = c;
        }

        return b;
    }

1.1.3. 链表的中间结点

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CDz
 * Date: 2020-03-23
 * Time: 上午10:45
 *
 * 876. 链表的中间结点
 * 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。
 *
 * 如果有两个中间结点,则返回第二个中间结点。
 *
 *
 *
 * 示例 1:
 *
 * 输入:[1,2,3,4,5]
 * 输出:此列表中的结点 3 (序列化形式:[3,4,5])
 * 返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。
 * 注意,我们返回了一个 ListNode 类型的对象 ans,这样:
 * ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
 * 示例 2:
 *
 * 输入:[1,2,3,4,5,6]
 * 输出:此列表中的结点 4 (序列化形式:[4,5,6])
 * 由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
 *
 *
 * 提示:
 *
 * 给定链表的结点数介于 1 和 100 之间。
 */

经典的双指针。

public ListNode middleNode(ListNode head) {

        //双指针方式
        ListNode slow = head;
        ListNode fast = head;

        while (fast!=null&&fast.next!=null){
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

1.1.4. 车的可用捕获量

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CDz
 * Date: 2020-03-26
 * Time: 上午10:45
 * <p>
 * <p>
 * 999. 车的可用捕获量
 * 在一个 8 x 8 的棋盘上,有一个白色车(rook)。也可能有空方块,白色的象(bishop)和黑色的卒(pawn)。它们分别以字符 “R”,“.”,“B” 和 “p” 给出。大写字符表示白棋,小写字符表示黑棋。
 * <p>
 * 车按国际象棋中的规则移动:它选择四个基本方向中的一个(北,东,西和南),然后朝那个方向移动,直到它选择停止、到达棋盘的边缘或移动到同一方格来捕获该方格上颜色相反的卒。另外,车不能与其他友方(白色)象进入同一个方格。
 * <p>
 * 返回车能够在一次移动中捕获到的卒的数量。
 * <p>
 * <p>
 * 示例 1:
 * <p>
 * <p>
 * <p>
 * 输入:[[".",".",".",".",".",".",".","."],[".",".",".","p",".",".",".","."],[".",".",".","R",".",".",".","p"],[".",".",".",".",".",".",".","."],[".",".",".",".",".",".",".","."],[".",".",".","p",".",".",".","."],[".",".",".",".",".",".",".","."],[".",".",".",".",".",".",".","."]]
 * 输出:3
 * 解释:
 * 在本例中,车能够捕获所有的卒。
 * 示例 2:
 * <p>
 * <p>
 * <p>
 * 输入:[[".",".",".",".",".",".",".","."],[".","p","p","p","p","p",".","."],[".","p","p","B","p","p",".","."],[".","p","B","R","B","p",".","."],[".","p","p","B","p","p",".","."],[".","p","p","p","p","p",".","."],[".",".",".",".",".",".",".","."],[".",".",".",".",".",".",".","."]]
 * 输出:0
 * 解释:
 * 象阻止了车捕获任何卒。
 * 示例 3:
 * <p>
 * <p>
 * <p>
 * 输入:[[".",".",".",".",".",".",".","."],[".",".",".","p",".",".",".","."],[".",".",".","p",".",".",".","."],["p","p",".","R",".","p","B","."],[".",".",".",".",".",".",".","."],[".",".",".","B",".",".",".","."],[".",".",".","p",".",".",".","."],[".",".",".",".",".",".",".","."]]
 * 输出:3
 * 解释:
 * 车可以捕获位置 b5,d6 和 f5 的卒。
 * <p>
 * <p>
 * 提示:
 * <p>
 * board.length == board[i].length == 8
 * board[i][j] 可以是 'R','.','B' 或 'p'
 * 只有一个格子上存在 board[i][j] == 'R'
 */

题目不算难,但是注意代码的整洁度。

public int numRookCaptures(char[][] board) {
        int n = board.length;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                char c = board[i][j];
                //找到车
                if (c == 'R') {
                    int up = j, down = j;
                    int left = i, right = i;

                    int count = 0;

                    //向上查找
                    while (up >= 0) {
                        char upNum = board[i][up];
                        if (upNum == 'p') {
                            count++;
                            break;
                        } else if (upNum == '.' || upNum == c) {
                            up--;

                        } else {
                            break;
                        }
                    }
                    //向下查找
                    while (down < n) {
                        char downNum = board[i][down];
                        if (downNum == 'p') {
                            count++;
                            break;
                        } else if (downNum == '.' || downNum == c) {
                            down++;
                        } else {
                            break;
                        }

                    }
                    //向左查找
                    while (left >= 0) {
                        char leftNum = board[left][j];
                        if (leftNum == 'p') {
                            count++;
                            break;
                        } else if (leftNum == '.' || leftNum == c) {
                            left--;

                        } else {
                            break;
                        }
                    }

                    //向右查找
                    while (right < n) {
                        char rightNum = board[right][j];
                        if (rightNum == 'p') {
                            count++;
                            break;
                        } else if (rightNum == '.' || rightNum == c) {
                            right++;
                        } else {
                            break;
                        }
                    }
                    return count;
                }
            }

        }

        return 0;
    }

抽象整理:

    public int numRookCaptures1(char[][] board) {

        int n = board.length;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {

                if (board[i][j] == 'R') {
                    int res = 0;
                    for (int[] direction : directions) {
                        if (burnout(board, i, j, direction)) {
                            res++;
                        }
                    }
                    return res;
                }
            }
        }


        return 0;
    }

    private boolean burnout(char[][] board, int x, int y, int[] direction) {
        int i = x;
        int j = y;

        while (isArea(i, j)) {
            char c = board[i][j];
            if (c == 'B') break;

            if (c == 'P') return true;

            i += direction[0];
            j += direction[1];
        }
        return false;
    }

    private boolean isArea(int i, int j) {
        return i >= 0 && i < 8 && j >= 0 && j < 8;
    }

1.1.5. 三维形体的表面积

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CDz
 * Create: 2020-03-25 08:57
 * <p>
 * 892. 三维形体的表面积
 * 在 N * N 的网格上,我们放置一些 1 * 1 * 1  的立方体。
 * <p>
 * 每个值 v = grid[i][j] 表示 v 个正方体叠放在对应单元格 (i, j) 上。
 * <p>
 * 请你返回最终形体的表面积。
 * <p>
 * <p>
 * <p>
 * 示例 1:
 * <p>
 * 输入:[[2]]
 * 输出:10
 * 示例 2:
 * <p>
 * 输入:[[1,2],[3,4]]
 * 输出:34
 * 示例 3:
 * <p>
 * 输入:[[1,0],[0,2]]
 * 输出:16
 * 示例 4:
 * <p>
 * 输入:[[1,1,1],[1,0,1],[1,1,1]]
 * 输出:32
 * 示例 5:
 * <p>
 * 输入:[[2,2,2],[2,1,2],[2,2,2]]
 * 输出:46
 * <p>
 * <p>
 * 提示:
 * <p>
 * 1 <= N <= 50
 * 0 <= grid[i][j] <= 50
 **/

题意需要理解,确实看了很长时间才理解题意。

先想象一个n*n的二维坐标系。

int x = grid[i][j]可以理解为,在n*n坐标系上(i,j)位置,凸起x高度

public int surfaceArea(int[][] grid) {
        //题目 : n*n的底,n为grid的length
        int area = 0;
        int n = grid.length;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                int high = grid[i][j];
                if (high > 0) {
                    area += high * 4 + 2;
                    area -= i > 0 ? Math.min(high, grid[i - 1][j]) * 2 : 0;
                    area -= j > 0 ? Math.min(high, grid[i][j - 1]) * 2 : 0;
                }
            }
        }
        return area;
    }

1.1.6. 地图分析

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CDz
 * Create: 2020-03-29 10:40
 * 1162. 地图分析
 * 你现在手里有一份大小为 N x N 的『地图』(网格) grid,上面的每个『区域』(单元格)都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地,你知道距离陆地区域最远的海洋区域是是哪一个吗?请返回该海洋区域到离它最近的陆地区域的距离。
 * <p>
 * 我们这里说的距离是『曼哈顿距离』( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个区域之间的距离是 |x0 - x1| + |y0 - y1| 。
 * <p>
 * 如果我们的地图上只有陆地或者海洋,请返回 -1。
 * <p>
 * <p>
 * <p>
 * 示例 1:
 * <p>
 * <p>
 * <p>
 * 输入:[[1,0,1],[0,0,0],[1,0,1]]
 * 输出:2
 * 解释:
 * 海洋区域 (1, 1) 和所有陆地区域之间的距离都达到最大,最大距离为 2。
 * 示例 2:
 * <p>
 * <p>
 * <p>
 * 输入:[[1,0,0],[0,0,0],[0,0,0]]
 * 输出:4
 * 解释:
 * 海洋区域 (2, 2) 和所有陆地区域之间的距离都达到最大,最大距离为 4。
 * <p>
 * <p>
 * 提示:
 * <p>
 * 1 <= grid.length == grid[0].length <= 100
 * grid[i][j] 不是 0 就是 1
 **/

多点BFS,关键是要做到,已经计算过的点,不能再进行计算。


    public int maxDistance1(int[][] grid) {
        //多点BFS
        //但是要设置,遍历过得不能再遍历
        int n = grid.length;

        LinkedList<int[]> queue = new LinkedList<>();

        //将所有陆地加入队列
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == 1) {
                    queue.addLast(new int[]{i, j});
                }
            }
        }
        //设置上下左右 陆地被包围的是个点
        int[] dx = new int[]{1, -1, 0, 0};
        int[] dy = new int[]{0, 0, 1, -1};

        int[] print = null;
        boolean hashOcean = false;
        //BFS遍历
        while (!queue.isEmpty()) {

            print = queue.poll();
            int x = print[0];
            int y = print[1];

            for (int i = 0; i < 4; i++) {
                int newx = x+dx[i];
                int newy = y+dy[i];

                //grid[newx][newy]!=0
                // 表示如果下一个节点 有值就说明被标记过
                if (newx<0||newx>=n||newy<0||newy>=n||grid[newx][newy]!=0){
                    continue;
                }
                hashOcean = true;
                grid[newx][newy] = grid[x][y]+1;
                queue.addLast(new int[]{newx,newy});
            }
        }
        if (!hashOcean||print==null)return -1;
        //-1是因为 第一个陆地是1 所以需要减去
        return grid[print[0]][print[1]]-1;
    }

1.1.7. 单词的压缩编码

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CDz
 * Create: 2020-03-28 11:56
 * 820. 单词的压缩编码
 * 给定一个单词列表,我们将这个列表编码成一个索引字符串 S 与一个索引列表 A。
 * <p>
 * 例如,如果这个列表是 ["time", "me", "bell"],我们就可以将其表示为 S = "time#bell#" 和 indexes = [0, 2, 5]。
 * <p>
 * 对于每一个索引,我们可以通过从字符串 S 中索引的位置开始读取字符串,直到 "#" 结束,来恢复我们之前的单词列表。
 * <p>
 * 那么成功对给定单词列表进行编码的最小字符串长度是多少呢?
 * <p>
 * <p>
 * <p>
 * 示例:
 * <p>
 * 输入: words = ["time", "me", "bell"]
 * 输出: 10
 * 说明: S = "time#bell#" , indexes = [0, 2, 5] 。
 * <p>
 * <p>
 * 提示:
 * <p>
 * 1 <= words.length <= 2000
 * 1 <= words[i].length <= 7
 * 每个单词都是小写字母 。
 **/

trie数问题,理解trie能树才是关键。

public int minimumLengthEncoding(String[] words) {
        Trie trie = new Trie();
        int count = 0;

        //先要插入较长的
        Arrays.sort(words,(o1,o2)->o2.length()-o1.length());

        for (String word : words) {
            count+=trie.insert(word);
        }
        return count;
    }


    class Trie{
        private TrieNode root;
        boolean isNew = true;

        public Trie() {
            root = new TrieNode();
        }

        public int insert(String word){

            TrieNode curr = root;

            isNew = false;
            char[] chars = word.toCharArray();

            //倒着插入
            for (int i = chars.length-1; i >= 0; i--) {
                char aChar = chars[i];
                if (curr.children[aChar - 'a']==null){
                    isNew = true;
                    //是新单词
                    curr.children[aChar - 'a'] = new TrieNode();
                }
                curr = curr.children[aChar - 'a'];
            }
            return isNew?word.length()+1:0;
        }
    }

    class TrieNode{
        char val;
        TrieNode[] children = new TrieNode[26];
    }

1.2. 二叉树

1.2.1. 二叉树中第二小的节点

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CDz
 * Date: 2020-03-26
 * Time: 下午5:00
 * 671. 二叉树中第二小的节点
 * 给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么这个节点的值不大于它的子节点的值。
 *
 * 给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。
 *
 * 示例 1:
 *
 * 输入:
 *     2
 *    / \
 *   2   5
 *      / \
 *     5   7
 *
 * 输出: 5
 * 说明: 最小的值是 2 ,第二小的值是 5 。
 * 示例 2:
 *
 * 输入:
 *     2
 *    / \
 *   2   2
 *
 * 输出: -1
 * 说明: 最小的值是 2, 但是不存在第二小的值。
 *
 */

方法一使用BFS遍历,加入所有数字,找出第二小值。

Set<Integer> queue = new TreeSet<>(Integer::compareTo);

    /**
     * 方法一 使用set储存
     * @param root
     * @return
     */
    public int findSecondMinimumValue(TreeNode root) {
        dfs(root);
        int i = 0;
        int res = -1;
        for (Integer integer : queue) {
            if (i==1){
                res= integer;
            }
            i++;
        }
        return res;
    }

    private void dfs(TreeNode root) {
        if (root==null)return;
        dfs(root.left);
        dfs(root.right);
        queue.add(root.val);
    }

方法二,找到

/**
     * 方法二
     * 拿出左边最小的与
     * 右边最小的
     * <p>
     * 然后找出两个里面大的那一个,如果相同就返回-1
     *
     * @param root
     * @return
     */
    public int findSecondMinimumValue1(TreeNode root) {

        return myfun(root, root.val);
    }

    /**
     * 1. left right的最小值也 > root.val 说明root.val是最小的值
     * 第二小就是 left right 中最小的值
     * 2. 其他情况 因为只有三个数 只需要取 left 与 right最大值
     *
     * @param root 当前检查的node
     * @param val root的val
     * @return
     */
    private int myfun(TreeNode root, int val) {

        if (root == null) return -1;

        //返回大于 val的
        if (root.val > val) {
            return root.val;
        }

        int l = myfun(root.left, val);
        int r = myfun(root.right, val);

        if (l > val && r > val) {
            return Math.min(l, r);
        }
        return Math.max(l, r);
    }

1.2.2. 打家劫舍

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CDz
 * Date: 2020-03-25
 * Time: 下午4:01
 *
 * 198. 打家劫舍
 * 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
 *
 * 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
 *
 * 示例 1:
 *
 * 输入: [1,2,3,1]
 * 输出: 4
 * 解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
 *      偷窃到的最高金额 = 1 + 3 = 4 。
 * 示例 2:
 *
 * 输入: [2,7,9,3,1]
 * 输出: 12
 * 解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
 *      偷窃到的最高金额 = 2 + 9 + 1 = 12 。
 */

动态规划递推公司:fun(n) = max(fun(n-1),fun(n-2)+arr[n])

public int rob(int[] nums) {

        if (nums.length==0)return 0;
        if (nums.length==1)return nums[0];
        int a = 0,b = 0;

        for (int i = 0; i < nums.length; i++) {
            int c = Math.max(nums[i]+a,b);
            a= b;
            b=c;
        }
        return b;
    }

1.2.3. 打家劫舍 III

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: CDz
 * Date: 2020-03-19
 * Time: 下午3:50
 * <p>
 * 337. 打家劫舍 III
 * 在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
 * <p>
 * 计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
 * <p>
 * 示例 1:
 * <p>
 * 输入: [3,2,3,null,3,null,1]
 * <p>
 * 3
 * / \
 * 2   3
 * \   \
 * 3   1
 * <p>
 * 输出: 7
 * 解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.
 * 示例 2:
 * <p>
 * 输入: [3,4,5,1,3,null,1]
 * <p>
 * 3
 * / \
 * 4   5
 * / \   \
 * 1   3   1
 * <p>
 * 输出: 9
 * 解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
 */
/**
     * 递归方式,动态规划
     * @param root
     * @return
     */
    public int rob(TreeNode root) {
        if (root==null)return 0;
        int nextCount = root.val;
        if (root.left!=null){
            nextCount +=(rob(root.left.left)+rob(root.left.right));
        }
        if (root.right!=null){
            nextCount +=(rob(root.right.left)+rob(root.right.right));
        }
        return Math.max(nextCount,rob(root.left)+rob(root.right));
    }


    private Map<TreeNode,Integer> cache = new HashMap<>();
    /**
     * 使用map 缓存
     * @param root
     * @return
     */
    public int rob1(TreeNode root) {
        if (root==null)return 0;

        if (cache.containsKey(root)){
            return cache.get(root);
        }

        int nextCount = root.val;
        if (root.left!=null){
            nextCount +=(rob(root.left.left)+rob(root.left.right));
        }
        if (root.right!=null){
            nextCount +=(rob(root.right.left)+rob(root.right.right));
        }
        int max = Math.max(nextCount, rob(root.left) + rob(root.right));
        cache.put(root,max);
        return max;
    }

    //只记录两个状态
    public int rob2(TreeNode root) {
        int[] res = robInternal(root);
        return Math.max(res[0],res[1]);
    }

    /**
     * 0 表示不选
     * 1 表示选中
     * @param root
     * @return
     */
    private int[] robInternal(TreeNode root) {
        if (root==null)return new int[2];
        int[] res = new int[2];
        int[] left = robInternal(root.left);
        int[] right = robInternal(root.right);

        res[0] = Math.max(left[0],left[1])+Math.max(right[0],right[1]);
        res[1] = root.val + left[0] + right[0];
        return res;
    }

2. review

3. tip

3.1. 练习写作技巧

  • 每日知乎热搜问题回答

仔细想想确实是一个非常好的策略,刻意练习的关键是反馈。在公众号其他地方进行写作,反馈相当有限。

知乎热搜问题,完美解决反馈的难题。

  • 热搜肯定有很多人看
  • 每天回答的浏览量,可以给自己一个非常好的反馈,尝试不同的写作方式,找到最佳最适合自己的

3.2. 线上CPU占用率过高排查流程

  • 使用top查找占用资源过多的Java PID
  • 通过ps H -eo pid,tid,%cpu grep 进程ID 定位那个线程过高
  • jstack 进程id
    • 找出Java线程信息
    • 第二步找到的 线程ID16进制转换
      • 找到对象线程信息
      • 定位代码行数

4. share

4.1. 《富翁谷底求翻身》看懂的地方

  • 先赚够生存必须的金额,才有精力去做事情(创业)
  • 想找到买家!其他的自然会水到渠成
  • 学会聊天,和你的了解你的上级简历,越详细越好。
  • 识人能力
  • 市场分析能力
  • 谈判能力
  • 找最专业的人做最专业的事
  • 强有力的分析能力,得到大家的信任,加上自身的自信(来自自己的分析能力),只有让别人相信你能成功,所有人才会帮助你成功
  • 分工,具体的的分工
  • 激励员工:如何给员工最大的激励,又不至于压垮他
  • 读心术,知道他在想什么,然后针对性的使用策略

4.2. 收集接收新知识是愉快的,运用确实痛苦的

结合我这几年学习知识,看书与看专栏的经验来看。更多的还是停留在一个非常初级的阶段,甚至可以说是在知识吸毒,其实与打游戏没有太大的差别。于是有了这段感悟。

学习新知识是愉快的

运用实践新知识是痛苦的

比如如何阅读一本书 比如模型思考

4.3. 极客学习笔记

4.3.1. Java基础

4.3.1.1. 使用了并发工具类库,线程安全就高枕无忧了吗?
  • 并发容器的错误用法
  • springboot web项目中ThreadLocal前后值不相同
    • 请求是交给Tomcat来执行的,在Tomcat中是多线程执行,其中使用到了线程池导致threadLocal值不同
    • Tomcat连接器部分——线程池使用
    • 解决办法:每次使用完ThreadLocal后声明式删除
  • ConcurrentHashMap只保证提供的方法,读写线程安全
    • 多个操作合并在一起,不保证原子性
    • 诸如 size、isEmpty 和 containsValue 等聚合方法,不保证原子性,可能拿到的是中间状态,不能作为流程控制变量
    • putAll 也是聚合方法,不能保证原子性
    • 提供聚合原子性操作:
      • computeIfAbsent
      • computeIfPresent
      • 这样的方法才具有原子性操作
      • ConcurrentHashMap的错误使用
  • 没有认清并发工具的使用场景,因而导致性能问题
    • CopyOnWriteArrayList
    • 只适用于并发下读多写少的情况
    • 错误的场景下导致性能极具下降
  • StopWatch测试类,可以测试代码执行效率
  • 总结
    • 认真阅读Oracle JDK文档,去深刻的理解使用的并发类
    • 并发代码需要,并发测试
4.3.1.2. 代码加锁:不要让“锁”事成为烦心事
  • 主要讲加锁的注意事项
  • 案例一(多个原子操作,加在一起不是原子操作)
    • add/compare方法内部一个循环加,一个循环比较相同的a/b两个值
      • a/b volatile修饰
      • Add/compare方法不同线程执行
    • a++
    • a<b
  • 案例二(加锁前要清楚锁和被保护的对象是不是一个层面的)
    • 静态字段属于类,类级别的锁才能保护;而非静态字段属于类实例,实例级别的锁就可以保护
  • 案例三(细粒度加锁)
    • ArrayList多线程操作
      • 只在操作list时加锁
  • 如果精细化考虑了锁应用范围后,性能还无法满足需求的话,我们就要考虑另一个维度的粒度问题了,即:区分读写场景以及资源的访问冲突,考虑使用悲观方式的锁还是乐观方式的锁。
  • 案例四(死锁)
    • 订单库存扣除

4.3.2. MySQL

4.3.2.1. 全局锁和表锁 :给表加个字段怎么有这么多阻碍?
  • 全局锁
    • 场景:对账或者是备份时
    • FTWRL:MySQL 提供了一个加全局读锁的方法,命令是 Flush tables with read lock (FTWRL)
    • 当 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。
      • single-transaction 方法只适用于所有的表使用事务引擎的库。
    • set global readonly=true不推荐
      • 一是,在有些系统中,readonly 的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改 global 变量的方式影响面更大,我不建议你使用。
      • 二是,在异常处理机制上有差异。如果执行 FTWRL 命令之后由于客户端发生异常断开,那么 MySQL 会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为 readonly 之后,如果客户端发生异常,则数据库就会一直保持 readonly 状态,这样会导致整个库长时间处于不可写状态,风险较高。
  • 表级锁
    • 表锁的语法是 lock tables … read/write。
    • MDL(metadata lock)隐形添加
      • MDL 不需要显式使用,在访问一个表的时候会被自动加上。MDL 的作用是,保证读写的正确性。
      • 读不互斥
      • 写互斥同一时间只能有一个线程写
      • 类似于Java 中的ReadWriteLock
      • 案例:线上小表进行修改表结构DDL时,拖垮整个服务器
      • 如何正确的安全的加字段?
        • kill 长事务
          • information_schema 库的 innodb_trx 表中
        • MariaDB 已经合并了 AliSQL 的这个功能,所以这两个开源分支目前都支持 DDL NOWAIT/WAIT n 这个语法。

4.3.3. 架构

4.3.3.1. 高性能负载均衡:算法
  • 根据负载均衡达到的目的可分四类
    • 任务平分类:负载均衡系统收到任务平均分配给服务器进行处理,可以是绝对的平均,也可以是按比例的“加权”平均
    • 负载均衡类:突出负载的均衡,根据当前系统压力
      • CPU负载
      • IO负载
      • 网卡吞吐量
    • 性能最优类:根据服务器响应时间来任务分配
    • Hash类:根据某些关键信息进行Hash运算,同一个放在同一服务器上处理
      • 源地址hash
      • 目标地址hash
      • session ID hash
      • 用户ID hash
  • 轮询
    • 优点简单
    • 缺点不关心服务器状态
      • 如果有一个服务器死循环CPU过高,还是会发送任务
  • 加权轮询
    • 根据不通服务器的配置差异,不同的权重设置
  • 负载最低优先
    • 不同任务类型、不同业务场景、选择不同指标
      • LVS是4层负载,以连接数来判断服务器状态
      • Nginx是7层负载,以HTTP请求数来判断服务器状态
    • 自己开发,还需要注意,考虑选择的指标
      • IO密集型
      • CPU密集型
    • 复杂性问题:
      • 连接数优先算法
        • 要求:每个请求都会发送给服务器进行处理
        • 不适合:固定连接池方式
      • CPU负载
        • 收集信息的方式
          • 收集时间长度
            • 1分钟长度与15分钟长度效果不同,并不一定15分钟就好
          • 收集动作消耗性能
      • 代码复杂度
        • 轮询可能10行代码
        • 负载可能1000行
          • 并且需要多方面调试
  • 性能最优类
    • 站在客户端进行分配任务
    • 谁的性能此时最好,分配给谁
    • 高复杂度
      • 收集分析过程中,本身收集就会消耗性能
      • 为了减少性能消耗
        • 设置合适的采样率
          • 越大性能消耗越大
        • 合适的周期
          • 多久采样一次
  • Hash类
    • 源地址Hash
      • 适合存在事务、会话业务
      • 网上银行:生成临时会话信息
    • ID Hash
      • 根据某个ID的业务分配服务器处理
      • session ID也可以解决网上银行的例子