2023算法

算法

常用算法

进阶算法

动态规划

1. 基础知识

1.1 4大尝试模型
> 1)从左往右的尝试模型
> 2)范围上的尝试模型
> 3)多样本位置全对应的尝试模型
> 4)寻找业务限制的尝试模型 

2. 经典题型

问题1:机器人走路
问题描述

假设有排成一行的N个位置,记为1~N,N 一定大于或等于 2
开始时机器人在其中的M位置上(M 一定是 1~N 中的一个)
如果机器人来到1位置,那么下一步只能往右来到2位置;
如果机器人来到N位置,那么下一步只能往左来到 N-1 位置;
如果机器人来到中间位置,那么下一步可以往左走或者往右走;
规定机器人必须走 K 步,最终能来到P位置(P也是1~N中的一个)的方法有多少种
给定四个参数 N、M、K、P,返回方法数。

解题思路:范围尝试模型
1. 准备一个指针,表示机器人当前位置。
2. 根据机器人当前的位置,判断并确定机器人下一步能走到的位置。(左走或者右走)
3. 一直重复第 2 步,直到剩余步数为零。
4. 判断是否是目标地址。如果是+1,否则+0。
代码逻辑
public class ClassicRobotWalk {


    @Test
    public void test01() {
        System.out.println(Solution01.getAllCount(5, 2, 4, 6));
        System.out.println(Solution02.getAllCount(5, 2, 4, 6));

    }

    /**
     * 暴力递归版本
     */
    private static class Solution01 {

        /**
         * @param n 总共的位置
         * @param m 起始位置
         * @param p 目标位置
         * @param k 需要走 K 步
         * @return 符合要求的方案数量
         */
        public static int getAllCount(int n, int m, int p, int k) {
            if (n < 2) {
                return 0;
            }
            return f(n, m, k, p);
        }

        private static int f(int n, int cur, int rest, int target) {
            if (rest == 0) {
                return cur == target ? 1 : 0;
            }
            if (cur < 1 || cur > n) {
                return 0;
            }
            // 向左走
            int p1 = f(n, cur - 1, rest - 1, target);
            // 向右走
            int p2 = f(n, cur + 1, rest - 1, target);
            return p1 + p2;
        }

    }

    /**
     * 动态规划版本
     */
    private static class Solution02 {

        /**
         * @param n 总共的位置
         * @param m 起始位置
         * @param p 目标位置
         * @param k 需要走 K 步
         * @return 符合要求的方案数量
         */
        public static int getAllCount(int n, int m, int p, int k) {
            if (n < 2) {
                return 0;
            }
            int[][] dp = new int[k + 1][n + 1];
            dp[0][p] = 1;
            for (int cur = 1; cur <= k; cur++) {
                dp[cur][1] = dp[cur - 1][2];
                dp[cur][n] = dp[cur - 1][n - 1];
                for (int i = 2; i < n; i++) {
                    dp[cur][i] = dp[cur - 1][i - 1] + dp[cur - 1][i + 1];
                }
            }
            return dp[k][m];
        }
    }

}
总结
1. 根据指针所指向的位置,判断机器人能走哪些位置。 (拓展:马走日,有8中走法,就是一种范围尝试模型)
问题2:两人拿卡牌
问题描述

给定一个整型数组arr,代表数值不同的纸牌排成一条线
玩家A和玩家B依次拿走每张纸牌
规定玩家A先拿,玩家B后拿
但是每个玩家每次只能拿走最左或最右的纸牌
玩家A和玩家B都绝顶聪明
请返回最后获胜者的分数。

解题思路:从左往右尝试模型

代码逻辑


总结

问题3:最长回文子序列长度
问题描述

给定一个字符串str,返回这个字符串的最长回文子序列长度
比如 : str = “a12b3c43def2ghi1kpm”
最长回文子序列是“1234321”或者“123c321”,返回长度7

解题思路:从左往右尝试模型
第一步:准备两个指针(左右指针) 第二步:左右指针开始移动,并分析存在的情况。 第三步:存在四种情况: 1. 要左要右 2. 不要左不要要右 3. 不要左要右 4. 要左不要右 第四步:取这四种情况的最大值,并返回
第五步:跳转至第二步执行,直到左右指针相碰...
代码逻辑

leetcode:最长回文序列(可中断,即去除中间部分字符)

/**
 * 最长回文序列(可中断,即去除中间部分字符)
 * https://leetcode.com/problems/longest-palindromic-subsequence/
 *
 * @author zzt
 * @since 2021/5/18 22:46
 */
public class ClassicPalindromeStrMaxLen {

    public static void main(String[] args) {
        String s = "aaaaaabbbaaaaaaa";
        System.out.println(Solution01.getStrPalindromeMaxLen1(s));
        System.out.println(Solution02.getStrPalindromeMaxLen1(s));
        System.out.println(Solution03.getStrPalindromeMaxLen1(s));
    }

    /**
     * 暴力递归
     */
    private static class Solution01 {
        /**
         * @param s 求的字符串
         * @return 最长回文子序列长度
         */
        public static int getStrPalindromeMaxLen1(String s) {
            if (s == null || s.length() == 0) {
                return 0;
            }
            char[] cs = s.toCharArray();
            return process(cs, 0, cs.length - 1);
        }

        private static int process(char[] cs, int l, int r) {
            if (l == r) {
                return 1;
            }
            if (l == r - 1) {
                return cs[l] == cs[r] ? 2 : 1;
            }
            int p1 = process(cs, l + 1, r - 1);
            int p2 = process(cs, l, r - 1);
            int p3 = process(cs, l + 1, r);
            int p4 = (cs[l] == cs[r] ? 2 : 0)
                    + process(cs, l + 1, r - 1);
            return Math.max(Math.max(p1, p2), Math.max(p3, p4));
        }
    }

    /**
     * 动态规划
     */
    private static class Solution02 {
        /**
         * @param s 求的字符串
         * @return 最长回文子序列长度
         */
        public static int getStrPalindromeMaxLen1(String s) {
            if (s == null || s.length() == 0) {
                return 0;
            }
            char[] cs = s.toCharArray();
            int n = cs.length;
            int[][] dp = new int[n][n];
            dp[n - 1][n - 1] = 1;
            for (int i = 0; i < n - 1; i++) {
                dp[i][i] = 1;
                dp[i][i + 1] = cs[i] == cs[i + 1] ? 2 : 1;
            }
            for (int l = n - 3; l >= 0; l--) {
                for (int r = l + 2; r < n; r++) {
                    int p1 = dp[l + 1][r - 1];
                    int p2 = dp[l][r - 1];
                    int p3 = dp[l + 1][r];
                    int p4 = (cs[l] == cs[r] ? 2 : 0) + dp[l + 1][r - 1];
                    dp[l][r] = Math.max(Math.max(p1, p2), Math.max(p3, p4));
                }
            }
            return dp[0][n - 1];
        }
    }

    /**
     * 动态规划(优化)
     */
    private static class Solution03 {
        /**
         * @param s 求的字符串
         * @return 最长回文子序列长度
         */
        public static int getStrPalindromeMaxLen1(String s) {
            if (s == null || s.length() == 0) {
                return 0;
            }
            char[] cs = s.toCharArray();
            int n = cs.length;
            int[][] dp = new int[n][n];
            dp[n - 1][n - 1] = 1;
            for (int i = 0; i < n - 1; i++) {
                dp[i][i] = 1;
                dp[i][i + 1] = cs[i] == cs[i + 1] ? 2 : 1;
            }
            for (int l = n - 3; l >= 0; l--) {
                for (int r = l + 2; r < n; r++) {
                    int p2 = dp[l][r - 1];
                    int p3 = dp[l + 1][r];
                    int p4 = (cs[l] == cs[r] ? 2 : 0) + dp[l + 1][r - 1];
                    dp[l][r] = Math.max(p2, Math.max(p3, p4));
                }
            }
            return dp[0][n - 1];
        }
    }

}

代码链接

总结

解题思路:从左往右尝试模型

代码逻辑


总结

问题4:0-1背包问题
问题描述

给定两个长度都为N的数组weights和values,
weights[i]和values[i]分别代表 i号物品的重量和价值。
给定一个正数bag,表示一个载重bag的袋子,
你装的物品不能超过这个重量。
返回你能装下最多的价值是多少?

问题升级:

达到 target 价值的方案有多少种?

解题思路:从左往右尝试模型
> 1. 准备两个指针,一个用于指定数组,另一个用于指定当前背包的容量大小
> 2. 
代码逻辑
public class ClassicBackpack0Or1 {

    @Test
    public void test01() {
        int[] weights = {3, 2, 4, 7, 4, 1, 2, 2, 4};
        int[] values = {5, 6, 3, 19, 8, 12, 32, 1, 4};
        int bag = 18;
        System.out.println(Solution01.getMaxValue(weights, values, bag));
        System.out.println(Solution02.getMaxValue(weights, values, bag));
    }

    /**
     * 暴力求解
     */
    private static class Solution01 {

        public static int getMaxValue(int[] w, int[] v, int bag) {
            return process(w, v, 0, bag);
        }

        private static int process(int[] w, int[] v, int i, int curBag) {
            if (i == w.length) {
                return 0;
            }
            int p1 = process(w, v, i + 1, curBag);
            int p2 = 0;
            if (w[i] <= curBag) {
                p2 = v[i] + process(w, v, i + 1, curBag - w[i]);
            }
            return Math.max(p1, p2);
        }
    }


    /**
     * 动态规划
     */
    private static class Solution02 {

        public static int getMaxValue(int[] w, int[] v, int bag) {

            int len = w.length;
            int k = bag + 1;
            int[][] dp = new int[len + 1][k];

            for (int i = len - 1; i >= 0; i--) {
                for (int curBag = 0; curBag < k; curBag++) {
                    int p1 = dp[i + 1][curBag];
                    int p2 = 0;
                    if (w[i] <= curBag) {
                        p2 = v[i] + dp[i + 1][curBag - w[i]];
                    }
                    dp[i][curBag] = Math.max(p1, p2);
                }
            }
            return dp[0][bag];
        }

    }

}

代码链接

总结
先写好暴力递归,然后找到位置依赖。。。
问题5:马走日
问题描述

请同学们自行搜索或者想象一个象棋的棋盘,
然后把整个棋盘放入第一象限,棋盘的最左下角是(0,0)位置
那么整个棋盘就是横坐标上9条线、纵坐标上10条线的区域
给你三个 参数 x,y,k
返回“马”从(0,0)位置出发,必须走k步
最后落在(x,y)上的方法数有多少种?

解题思路:范围尝试模型
> 1. 准备两个指针,表示马所在的位置(x,y)
> 2. 根据马所在的位置(x,y),得到下一步能够到达的位置(8种走法,期间要判断是否走出了棋盘)
> 3. 判断是否达到预期效果,如果没有达到,则重复第2步,直到达到预期(预期:走完k步;或者走出了棋盘)
> 4. 将所有的情况相加得到最终的结果

代码逻辑
public class ClassicHorseWalkingWay {

    public static void main(String[] args) {
        System.out.println(Solution01.getAllWayCount(4, 2, 4));
        System.out.println(Solution02.getAllWayCount(4, 2, 4));
    }

    /**
     * 暴力递归
     */
    private static class Solution01 {
        /**
         * @param x 目标坐标x
         * @param y 目标坐标y
         * @param k 必须要走的步数
         * @return 总的方法数
         */
        public static int getAllWayCount(int x, int y, int k) {
            return f(x, y, 0, 0, k);
        }

        private static int f(int x, int y, int i, int j, int res) {
            if (i < 0 || i > 10 || j < 0 || j > 9) {
                return 0;
            }
            if (res == 0) {
                return x == i && y == j ? 1 : 0;
            }
            int way = f(x, y, i - 1, j - 2, res - 1);
            way += f(x, y, i - 1, j + 2, res - 1);
            way += f(x, y, i + 1, j - 2, res - 1);
            way += f(x, y, i + 1, j + 2, res - 1);
            way += f(x, y, i - 2, j - 1, res - 1);
            way += f(x, y, i + 2, j + 1, res - 1);
            way += f(x, y, i - 2, j + 1, res - 1);
            way += f(x, y, i + 2, j - 1, res - 1);
            return way;
        }

    }

    /**
     * 动态规划
     */
    private static class Solution02 {
        /**
         * @param x 坐标x
         * @param y 坐标y
         * @param k 必须要走的步数
         * @return 总的方法数
         */
        public static int getAllWayCount(int x, int y, int k) {
            int[][][] dp = new int[k + 1][10][9];
            dp[0][x][y] = 1;
            for (int res = 1; res <= k; res++) {
                for (int i = 0; i < 10; i++) {
                    for (int j = 0; j < 9; j++) {
                        dp[res][i][j] = pick(dp, i + 1, j + 2, res)
                                + pick(dp, i - 1, j + 2, res)
                                + pick(dp, i + 1, j - 2, res)
                                + pick(dp, i - 1, j - 2, res)
                                + pick(dp, i - 2, j + 1, res)
                                + pick(dp, i + 2, j - 1, res)
                                + pick(dp, i - 2, j - 1, res)
                                + pick(dp, i + 2, j + 1, res);
                    }
                }
            }
            return dp[k][0][0];
        }

        private static int pick(int[][][] dp, int i, int j, int res) {
            if (i < 0 || i > 9 || j < 0 || j > 8) {
                return 0;
            }
            return dp[res - 1][i][j];
        }

    }

}

代码链接

总结

问题6:求最短路径

暴力递归->动态规划4:22分钟

问题描述

给定一个二维数组matrix,一个人必须从左上角出发,最后到达右下角
沿途只可以向下或者向右走,沿途的数字都累加就是距离累加和
返回最小距离累加和

解题思路:范围尝试模型

leetCode原题

> 1. 准备两个指针,表示人所在的位置(x,y)
> 2. 根据人所在的位置(x,y),得到下一步能够到达的位置(向下或者向右)
> 3. 判断是否达到预期效果,如果没有达到,则重复第2步,直到达到预期(预期:走完k步;或者走出了棋盘)
> 4. 将所有的情况获取最小值,并返回最终的结果
代码逻辑
public class ClassicMinPath {

    public static void main(String[] args) {
        int[][] matrix = {{1, 3, 1}, {1, 5, 1}, {4, 2, 1}};
        System.out.println(Solution01.getMinPath(matrix));
        System.out.println(Solution02.getMinPath(matrix));
    }

    /**
     * 暴力递归
     */
    private static class Solution01 {
        public static int getMinPath(int[][] matrix) {
            return f(matrix, 0, 0);
        }

        private static int f(int[][] matrix, int i, int j) {
            if (i == matrix.length - 1 && j == matrix[0].length - 1) {
                return matrix[i][j];
            }
            if (i == matrix.length || j == matrix[0].length) {
                return Integer.MAX_VALUE;
            }
            int val = matrix[i][j];
            int p1 = f(matrix, i + 1, j);
            int p2 = f(matrix, i, j + 1);
            return val + Math.min(p1, p2);
        }
    }

    /**
     * 动态规划
     */
    private static class Solution02 {

        public static int getMinPath(int[][] matrix) {
            int y = matrix.length, x = matrix[0].length;
            int[][] dp = new int[y][x];
            int endY = y - 1;
            int endX = x - 1;
            dp[endY][endX] = matrix[endY][endX];
            for (int i = endX - 1; i >= 0; i--) {
                dp[endY][i] = dp[endY][i + 1] + matrix[endY][i];
            }
            for (int i = endY - 1; i >= 0; i--) {
                dp[i][endX] = dp[i + 1][endX] + matrix[i][endX];
            }
            for (int i = endY - 1; i >= 0; i--) {
                for (int j = endX - 1; j >= 0; j--) {
                    dp[i][j] = matrix[i][j] + Math.min(dp[i][j + 1], dp[i + 1][j]);
                }
            }
            return dp[0][0];
        }

    }

}

代码链接

总结

问题7:货币问题(目标值,第21节ppt)

暴力->动态规划4:42:47

问题描述

arr是货币数组,其中的值都是正数。再给定一个正数aim。
每个值都认为是一张货币,
即便是值相同的货币也认为每一张都是不同的,
返回组成aim的方法数
例如:arr = {1,1,1},aim = 2
第0个和第1个能组成2,第1个和第2个能组成2,第0个和第2个能组成2
一共就3种方法,所以返回3

问题扩展1:

arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim。
每个值都认为是一种面值,且认为张数是无限的。
返回组成aim的方法数
例如:arr = {1,2},aim = 4
方法如下:1+1+1+1、1+1+2、2+2
一共就3种方法,所以返回3

问题扩展2:

arr是货币数组,其中的值都是正数。再给定一个正数aim。
每个值都认为是一张货币,
认为值相同的货币没有任何不同,
返回组成aim的方法数
例如:arr = {1,2,1,1,2,1,2},aim = 4
方法:1+1+1+1、1+1+2、2+2
一共就3种方法,所以返回3

解题思路:从左往右尝试模型
> 1. 问题的理解:从左往右尝试(要或不要)。并判断是否达到了目标值
> 2. 准备好两个指针,index 指向货币数组,rest 代表还差下多少(离目标值)。
> 3. 直到`index`指针==货币数组长度 或者 `rest`=0为止。并计算过程中所有的方法数之和
代码逻辑
public class ClassicCurrency {

    @Test
    public void test01() {
        int[] arr = {1, 1, 1};
        int target = 2;
        System.out.println(Solution01.getAllCount(arr, target));
        System.out.println(Solution02.getAllCount(arr, target));
    }

    /**
     * 暴力递归
     */
    private static class Solution01 {

        /**
         * @param arr    货币数组
         * @param target 目标值
         * @return 所有=目标值 方案数量
         */
        public static int getAllCount(int[] arr, int target) {
            return f(arr, 0, target);
        }

        private static int f(int[] arr, int index, int rest) {
            if (rest == 0) {
                return 1;
            }
            if (index == arr.length) {
                return 0;
            }
            int p1 = f(arr, index + 1, rest);
            int p2 = 0;
            if (arr[index] <= rest) {
                p2 = f(arr, index + 1, rest - arr[index]);
            }
            return p1 + p2;
        }

    }

    /**
     * 动态规划
     */
    private static class Solution02 {

        /**
         * @param arr    货币数组
         * @param target 目标值
         * @return 所有=目标值 方案数量
         */
        public static int getAllCount(int[] arr, int target) {

            int[][] dp = new int[arr.length + 1][target + 1];
            for (int i = 0; i <= arr.length; i++) {
                dp[i][0] = 1;
            }
            for (int index = arr.length - 1; index >= 0; index--) {
                for (int rest = 0; rest <= target; rest++) {
                    int p1 = f(arr, index + 1, rest);
                    int p2 = 0;
                    if (arr[index] <= rest) {
                        p2 = f(arr, index + 1, rest - arr[index]);
                    }
                    dp[index][rest] = p1 + p2;
                }
            }
            return dp[0][target];
        }

        private static int f(int[] arr, int index, int rest) {
            if (rest == 0) {
                return 1;
            }
            if (index == arr.length) {
                return 0;
            }
            int p1 = f(arr, index + 1, rest);
            int p2 = 0;
            if (arr[index] <= rest) {
                p2 = f(arr, index + 1, rest - arr[index]);
            }
            return p1 + p2;
        }

    }

    /**
     * 问题扩展1:【暴力递归】
     * arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim。
     * 每个值都认为是一种面值,且认为张数是无限的。
     * 返回组成aim的方法数
     * 例如:arr = {1,2},aim = 4
     * 方法如下:1+1+1+1、1+1+2、2+2
     * 一共就3种方法,所以返回3
     */
    private static class Solution11 {
        /**
         * @param arr    货币数组
         * @param target 目标值
         * @return 所有=目标值 方案数量
         */
        public static int getAllCount(int[] arr, int target) {
            return process(arr, 0, target);
        }

        private static int process(int[] arr, int index, int rest) {
            if (index == arr.length) {
                return rest == 0 ? 1 : 0;
            }
            int way = 0;
            for (int i = 0; (i * arr[index]) <= rest; i++) {
                way += process(arr, index + 1, rest - (i * arr[index]));
            }
            return way;
        }

    }

    /**
     * 问题扩展1:动态规划
     */
    private static class Solution12 {
        /**
         * @param arr    货币数组
         * @param target 目标值
         * @return 所有=目标值 方案数量
         */
        public static int getAllCount(int[] arr, int target) {
            int[][] dp = new int[arr.length + 1][target + 1];
            dp[arr.length][0] = 1;
            for (int index = arr.length - 1; index >= 0; index--) {
                for (int rest = 0; rest <= target; rest++) {
                    int way = 0;
                    for (int i = 0; (i * arr[index]) <= rest; i++) {
                        way += dp[index + 1][rest - (i * arr[index])];
                    }
                    dp[index][rest] = way;
                }
            }
            return dp[0][target];
        }
    }

    /**
     * 问题扩展2:【暴力递归】
     * arr是货币数组,其中的值都是正数。再给定一个正数aim。
     * 每个值都认为是一张货币,
     * 认为值相同的货币没有任何不同,
     * 返回组成aim的方法数
     * 例如:arr = {1,2,1,1,2,1,2},aim = 4
     * 方法:1+1+1+1、1+1+2、2+2
     * 一共就3种方法,所以返回3
     */
    private static class Solution21 {
        /**
         * @param arr    货币数组
         * @param target 目标值
         * @return 所有=目标值 方案数量
         */
        public static int getAllCount(int[] arr, int target) {

            Map<Integer, Integer> coinCountMap = new HashMap<>();
            int maxVal = 0;
            for (int a : arr) {
                if (!coinCountMap.containsKey(a)) {
                    coinCountMap.put(a, 1);
                    maxVal = Math.max(maxVal, a);
                } else {
                    coinCountMap.put(a, coinCountMap.get(a) + 1);
                }
            }
            int[] coinsCount = new int[maxVal + 1];
            int[] coins = new int[coinCountMap.size()];
            AtomicInteger i = new AtomicInteger(0);
            coinCountMap.forEach((k, v) -> {
                coinsCount[k] = v;
                coins[i.getAndIncrement()] = k;
            });
            return process(coins, coinsCount, 0, target);
        }

        private static int process(int[] coins, int[] coinsCount, int index, int rest) {
            if (index == coins.length) {
                return rest == 0 ? 1 : 0;
            }
            int way = 0;
            for (int i = 0; (i * coins[index]) <= rest && coinsCount[coins[index]] >= i; i++) {
                way += process(coins, coinsCount, index + 1, rest - (i * coins[index]));
            }
            return way;
        }

    }

    /**
     * 问题扩展2:动态规划
     */
    private static class Solution22 {
        /**
         * @param arr    货币数组
         * @param target 目标值
         * @return 所有=目标值 方案数量
         */
        public static int getAllCount(int[] arr, int target) {
            Map<Integer, Integer> coinCountMap = new HashMap<>();
            int maxVal = 0;
            for (int a : arr) {
                if (!coinCountMap.containsKey(a)) {
                    coinCountMap.put(a, 1);
                    maxVal = Math.max(maxVal, a);
                } else {
                    coinCountMap.put(a, coinCountMap.get(a) + 1);
                }
            }
            int[] coinsCount = new int[maxVal + 1];
            int[] coins = new int[coinCountMap.size()];
            AtomicInteger a = new AtomicInteger(0);
            coinCountMap.forEach((k, v) -> {
                coinsCount[k] = v;
                coins[a.getAndIncrement()] = k;
            });
            int[][] dp = new int[coins.length + 1][target + 1];
            dp[coins.length][0] = 1;
            for (int index = coins.length - 1; index >= 0; index--) {
                for (int rest = 0; rest <= target; rest++) {
                    int way = 0;
                    for (int i = 0
                         ; (i * coins[index]) <= rest && coinsCount[coins[index]] >= i
                            ; i++) {
                        way += dp[index + 1][rest - (i * coins[index])];
                    }
                    dp[index][rest] = way;
                }
            }
            return dp[0][target];
        }
    }
}
总结

问题8:咖啡机(京东笔试题,难)
问题描述

给定一个数组arr,arr[i]代表第i号咖啡机泡一杯咖啡的时间
给定一个正数N,表示N个人等着咖啡机泡咖啡,每台咖啡机只能轮流泡咖啡
只有一台咖啡机,一次只能洗一个杯子,时间耗费a,洗完才能洗下一杯
每个咖啡杯也可以自己挥发干净,时间耗费b,咖啡杯可以并行挥发
假设所有人拿到咖啡之后立刻喝干净,
返回从开始等到所有咖啡机变干净的最短时间
三个参数:int[] arr、int N,int a、int b

解题思路:寻找业务限制的尝试模型
> 1. 
代码逻辑

总结

问题9:砍怪兽(概率问题)
问题描述

给定3个参数,N,M,K
怪兽有N滴血,等着英雄来砍自己
英雄每一次打击,都会让怪兽流失[0~M]的血量
到底流失多少?每一次在[0~M]上等概率的获得一个值
求K次打击之后,英雄把怪兽砍死的概率

解题思路:(多样本位置全对应的尝试模型)
> 1. 
代码逻辑

总结

问题10:醉汉走路(概率问题)
问题描述

给定5个参数,N,M,row,col,k
表示在NM的区域上,醉汉Bob初始在(row,col)位置
Bob一共要迈出k步,且每步都会等概率向上下左右四个方向走一个单位
任何时候Bob只要离开N
M的区域,就直接死亡
返回k步之后,Bob还在N*M的区域的概率

解题思路:寻找业务限制的尝试模型
> 1. 
代码逻辑

总结

问题11:货币问题+目标值(最值问题)
问题描述

arr是面值数组,其中的值都是正数且没有重复。再给定一个正数aim。
每个值都认为是一种面值,且认为张数是无限的。
返回组成aim的最少货币数

难点:斜率优化:优化模型中的枚举行为

解题思路:从左往右的尝试模型 + 枚举行为
  1. 各种货币面值*张数 = aim
  2. 准备那个两个指针:指针index表示面值数组的索引;rest表示需要的目标价值;并枚举出所有可能的张数。
  3. 开始从左往右尝试,并枚举出所有的张数可能(总共的面值不能超过需要的最大值)
代码逻辑
public class ClassicCurrency3 {

    @Test
    public void test01() {
        int[] arr = {1, 2, 3};
        int aim = 6;
        System.out.println(Solution01.getMinCurrencyCount(arr, aim));
    }

    private static class Solution01 {

        public static int getMinCurrencyCount(int[] arr, int aim) {
//            return dp(arr, 0, aim);
            int n = arr.length;
            int[][] dp = new int[n + 1][aim + 1];
            for (int i = 1; i <= aim; i++) {
                dp[n][i] = Integer.MAX_VALUE;
            }
            for (int index = n - 1; index >= 0; index--) {
                for (int rest = 0; rest <= aim; rest++) {
                    int count = Integer.MAX_VALUE;
                    for (int i = 0; i * arr[index] <= rest; i++) {
                        int next = dp[index + 1][rest - i * arr[index]];
                        if (next != Integer.MAX_VALUE) {
                            count = Math.min(count, i + next);
                        }
                    }
                    dp[index][rest] = count;
                }
            }
            return dp[0][aim];
        }

        private static int dp(int[] arr, int index, int rest) {
            if (index == arr.length) {
                return rest == 0 ? 0 : -1;
            }
            int count = -1;
            for (int i = 0; i * arr[index] <= rest; i++) {
                int next = dp(arr, index + 1, rest - i * arr[index]);
                if (next != -1) {
                    count = Math.min(count, i + next);
                }
            }
            return count;
        }

    }
}
总结

问题12:货币问题+目标值+逆向分裂
问题描述

给定一个正数n,求n的裂开方法数,
规定:后面的数不能比前面的数小
比如4的裂开方法有:
1+1+1+1、1+1+2、1+3、2+2、4
5种,所以返回5

优化:有枚举行为,可以使用斜率优化

解题思路:寻找业务限制的尝试模型
1. 
代码逻辑
public class ClassicCurrency4 {

    // 正确性测试
    @Test
    public void test00() {
        int count = 100000;
        List<Integer> testData = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            testData.add(DataGenerator.getRandomVal(0, 20));
        }

        for (Integer data : testData) {
            int a1 = Solution01.getAimFromArr(data);
            int a2 = Solution01.dp1(data);
            int a3 = Solution01.dp2(data);
            if (a1 != a2 || a1 != a3) {
                System.err.println("程序出错了!!!");
                System.out.println("测试数据:" + data);
                System.out.printf("测试结果:%d - %d - %d", a1, a2, a3);
                break;
            }
        }
        System.out.println("程序测试完成!!!");
    }

    /**
     * 性能测试
     * 结论:目标值 `aim` 不大的情况下,暴力递归更有优势
     * 但在目标值比较大时(比如>20),动态规划优势就明显了,斜率优化更加明显
     */
    @Test
    @SuppressWarnings("all")
    public void test01() throws IOException {
        int count = 1;
        List<Integer> testData = new ArrayList<>(count);
        for (int i = 0; i < count; i++) {
            testData.add(DataGenerator.getRandomVal(500, 1000));
        }

        CyclicBarrier barrier = new CyclicBarrier(3);
        CountDownLatch latch = new CountDownLatch(3);
        Thread t1 = new Thread(() -> {
            try {
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            long start = System.currentTimeMillis();
            for (Integer aim : testData) {
                Solution01.getAimFromArr(aim);
            }
            long end = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "执行的时间:" + (end - start));
            latch.countDown();
        }, "暴力递归");
        Thread t2 = new Thread(() -> {
            try {
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            long start = System.currentTimeMillis();
            for (Integer aim : testData) {
                Solution01.dp1(aim);
            }
            long end = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "执行的时间:" + (end - start));
            latch.countDown();
        }, "动态规划");
        Thread t3 = new Thread(() -> {
            try {
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
            long start = System.currentTimeMillis();
            for (Integer aim : testData) {
                Solution01.dp2(aim);
            }
            long end = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "执行的时间:" + (end - start));
            latch.countDown();

        }, "动态规划+斜率优化");
        t1.start();
        t2.start();
        t3.start();
        latch.await();
        System.out.println("程序执行完毕!!!");

    }

    private static class Solution01 {
        public static int getAimFromArr(int aim) {
            return process(1, aim);
//            return dp1(aim);
//            return dp2(aim);
        }

        private static int process(int index, int rest) {
            if (rest == 0) {
                return 1;
            }
            if (index > rest) {
                return 0;
            }
            int ans = 0;
            for (int count = index; count <= rest; count++) {
                ans += process(count, rest - count);
            }
            return ans;
        }

        private static int dp1(int aim) {
            int n = aim + 1;
            int[][] dp = new int[n][n];
            dp[aim][0] = 1;
            dp[aim][aim] = 1;
            for (int index = aim - 1; index > 0; index--) {
                dp[index][0] = 1;
                dp[index][index] = 1;
                for (int rest = 1; rest <= aim; rest++) {
                    int ans = 0;
                    for (int count = index; count <= rest; count++) {
                        ans += dp[count][rest - count];
                    }
                    dp[index][rest] = ans;
                }
            }
            return dp[1][aim];
        }

        // 动态规划 + 斜率优化
        private static int dp2(int aim) {
            int n = aim + 1;
            int[][] dp = new int[n][n];
            dp[aim][0] = 1;
            dp[aim][aim] = 1;
            for (int index = aim - 1; index > 0; index--) {
                dp[index][0] = 1;
                dp[index][index] = 1;
                for (int rest = index; rest <= aim; rest++) {
                    int ans = dp[index + 1][rest] + dp[index][rest - index];
                    dp[index][rest] = ans;
                }
            }
            return dp[1][aim];
        }

    }

}
总结

问题13:醉汉走路(概率问题)
问题描述

给定5个参数,N,M,row,col,k
表示在NM的区域上,醉汉Bob初始在(row,col)位置
Bob一共要迈出k步,且每步都会等概率向上下左右四个方向走一个单位
任何时候Bob只要离开N
M的区域,就直接死亡
返回k步之后,Bob还在N*M的区域的概率

解题思路:寻找业务限制的尝试模型
> 1. 
代码逻辑

总结

问题14:数组尽可能平分(值)
问题描述

给定一个正数数组arr,
请把arr中所有的数分成两个集合,尽量让两个集合的累加和接近
返回:最接近的情况下,较小集合的累加和

解题思路:寻找业务限制的尝试模型
> 1. 
代码逻辑

总结

问题15:数组尽可能平分(数量+值)
问题描述

给定一个正数数组arr,请把arr中所有的数分成两个集合
如果arr长度为偶数,两个集合包含数的个数要一样多
如果arr长度为奇数,两个集合包含数的个数必须只差一个
请尽量让两个集合的累加和接近
返回:最接近的情况下,较小集合的累加和

解题思路:寻找业务限制的尝试模型
> 1. 
代码逻辑

总结

问题16:N皇后问题
问题描述

N皇后问题是指在N*N的棋盘上要摆N个皇后,
要求任何两个皇后不同行、不同列, 也不在同一条斜线上
给定一个整数n,返回n皇后的摆法有多少种。n=1,返回1
n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0
n=8,返回92

解题思路:寻找业务限制的尝试模型
> 1. 
代码逻辑

总结

问题16:最长回文子串(字符串)

5. 最长回文子串

实现逻辑

class Solution {
    public String longestPalindrome(String s) {
        if (s.length() <= 1) {
            return s;
        }
        char[] chars = s.toCharArray();
        int len = chars.length;
        boolean[][] dp = new boolean[len][len];

        for (int i = 0; i < len; i++) {
            dp[i][i] = true;
        }
        int maxLen = 1;
        int begin = 0;
        // 对角线
        for (int l = len - 2; l >= 0; l--) {
            for (int r = l + 1; r < len; r++) {
                if (chars[r] == chars[l]) {
                    if (r - l < 3) {
                        dp[l][r] = true;
                    } else {
                        dp[l][r] = dp[l + 1][r - 1];
                    }
                }
                int temp;
                if (dp[l][r] && (temp = r - l + 1) > maxLen) {
                    maxLen = temp;
                    begin = l;
                }
            }
        }
        return s.substring(begin, begin + maxLen);
    }
}

3. 动态规划优化

常见优化模型
  1. 斜率优化
  2. 空间压缩优化
  3. 四边形不等式
  4. 其他优化技巧
经典案例

4. LeetCode原题

问题1:664. 奇怪的打印机(难)

664. 奇怪的打印机

5. 专题

5.1. 4种尝试模型
5.1.1 从左往右的尝试模型

问题1:0-1 背包问题

5.1.2 范围尝试模型
5.1.3 多样本位置全对应的尝试模型
5.1.4 寻找业务限制的尝试模型
5.2. 最值问题
5.3. 字符串问题
5.4. 目标值问题
5.4.1 升级版背包问题(阿里21春招)
5.4.2 货币问题 + 目标值(求最值)
5.5. 概率问题
5.5.1 砍怪兽(概率问题)
5.5.2 醉汉走路(概率问题)
5.6. 枚举行为问题
5.6.1 货币问题 + 目标值(求最值)[+逆袭分解]
5.6. 目标值问题

滑动窗口

1. 基础知识

1.1 滑动窗口是什么?
滑动窗口是一种想象出来的数据结构:
滑动窗口有左边界L和有边界R
在数组或者字符串或者一个序列上,记为S,窗口就是S[L..R]这一部分
L往右滑意味着一个样本出了窗口,R往右滑意味着一个样本进了窗口
L和R都只能往右滑
1.2 滑动内最大值和最小值的更新结构
窗口不管L还是R滑动之后,都会让窗口呈现新状况,
如何能够更快的得到窗口当前状况下的最大值和最小值?
最好平均下来复杂度能做到O(1)
利用单调双端队列!
1.3 窗口内所有数之和(<=sum)的问题
1. 给定一个数组 `arr`、`sum`值;
2. 求区间 `(l, r)` 内之和,不大于`sum`值。

2. 经典题型

问题1:指定大小内的最大值

问题描述

假设一个固定大小为W的窗口,依次划过arr,
返回每一次滑出状况的最大值
例如,arr = [4,3,5,4,3,3,6,7], W = 3
返回:[5,5,5,4,6,7]

问题分析

1. 创建并初始化滑动窗口(大小为 w)。
2. 声明左右指针:`l`、`r`。
3. 同时移动左右指针(都+1),然后淘汰掉最小的值

解题代码

public class Classic_01_MaxVal{
    
    public int[] getAllMax(int[] arr, int w){
        if (arr.length<w){
            return null;
        }
        int[] res = new int[arr.legnth-w];
        LinkedList<Integer> win = new LinkedList<>();
        for(int i = 0; i < w; i++) {
            while(!win.isEmpty&&arr[i]<arr[win.peekLast()]){
                win.pollLast();
            }
            win.addLast(i);
        }
        int index = 0;
        res[index++] = arr[win.peekFirst()];
        for(int i = w; i < arr.length; i++) {
            while(!win.isEmpty&&arr[i]<arr[win.peekLast()]){
                win.pollLast();
            }
            win.addLast(i);
            
            if(win.peekFirst() <= i-w) {
                win.pollFirst();
            }

            res[index++] = arr[win.peekFirst()];
        }  


        return res;
    }
}
问题2:区间内的最大值和最小值问题

问题描述

给定一个整型数组arr,和一个整数num
某个arr中的子数组sub,如果想达标,必须满足:
sub中最大值 – sub中最小值 <= num,
返回arr中达标子数组的数量

问题分析

  1. 问题中需要用到窗口的最大值和最小值。
  2. 准备两个窗口,一个最大值窗口,一个最小值窗口

解题代码

private static class Solution02 {
    public static int getMaxDiscrepancy(int[] arr, int num) {
        int count = 0;
        LinkedList<Integer> max = new LinkedList<>();
        LinkedList<Integer> min = new LinkedList<>();
        int r = 0;
        for (int l = 0; l < arr.length; l++) {
            while (r < arr.length) {
                while (!max.isEmpty() && arr[r] > arr[max.peekLast()]) {
                    max.pollLast();
                }
                max.addLast(r);
                while (!min.isEmpty() && arr[r] < arr[min.peekLast()]) {
                    min.pollLast();
                }
                min.addLast(r);
                if (arr[max.peekFirst()] - arr[min.peekFirst()] <= num) {
                    r++;
                } else {
                    break;
                }
            }
            count += r - l;
            // 淘汰过期数据
            if (max.peekFirst() == l) {
                max.pollFirst();
            }
            if (min.peekFirst() == l) {
                min.pollFirst();
            }
        }
        return count;
    }
}
问题3:货币分裂问题

问题描述

arr是货币数组,其中的值都是正数。再给定一个正数aim。
每个值都认为是一张货币,
返回组成aim的最少货币数
注意:因为是求最少货币数,所以每一张货币认为是相同或者不同就不重要了

问题分析

  1. 问题中需要用到窗口的最大值和最小值。
  2. 准备两个窗口,一个最大值窗口,一个最小值窗口

解题代码

总结

问题4:加油站问题

问题描述

加油站问题

问题分析 & 解题思路

1. 声明一个`2n+1`长度的前缀和数组,并求出所有的前缀之和(`gas[i]-cost[i]`),
2. 声明一个滑动窗口(前缀和数组最小值),并求出范围为`0 ~ n`的滑动窗口
    注意:判断特殊值(右边最初是 0)
3. 然后求出范围为`n+1 ~ 2n+1`内的前缀和数组值,`目标值 = 最小值-当前值`(注意有淘汰过期数据)

解题代码


总结

3. LeetCode原题

问题1:加油站问题

加油站问题

单调栈(ppt-25节)

基础知识

案例分析

一种特别设计的栈结构,为了解决如下的问题:
给定一个可能含有重复值的数组arri位置的数一定存在如下两个信息
1)arr[i]的左侧离i最近并且小于(或者大于)arr[i]的数在哪?
2)arr[i]的右侧离i最近并且小于(或者大于)arr[i]的数在哪?
如果想得到arr中所有位置的两个信息,怎么能让得到信息的过程尽量快。
那么到底怎么设计呢?

基本思想
(求最近小于的数为例)

1. 准备一个指针`index`,表示数组的索引位置,还有一个栈结构(记录`索引位置`)
(如果有重复值,可以考虑用数组解决,但不一定都要,根据实际情况而定)。
2. 将所有比栈顶**大**的值放入栈中,反之将栈顶索引位置(这是目标位置),两侧索引就是最近的小于`index`位置的值(如果左侧没有则为`-1`)。
3. 直到`index`遍历结束,然后再依次弹出栈顶中的索引位置。

难点(重点):

  1. 目标索引位置左右两边的含义
  2. 目标索引位置已经最近的小于的数之间的数字又代表什么含义

经典题型

题目一:单调栈的实现

代码实现
无重复值版本

public class Demo {
    /**
     * @param arr 求解数组
     * @return [      lSmall    rSmall
     *              0:     -1       3
     *              1:     0        4
     *          ]
     */
    public static int[][] monotonicStack(int[] arr) {
        int[][] rest = new int[arr.length][2];
        Stack<Integer> stackIndex = new Stack<>();
        for (int large = 0; large < arr.length; large++) {
            while (!stackIndex.isEmpty()
                    && arr[stackIndex.peek()] > arr[large]) {
                Integer targetIndex = stackIndex.pop();
                int small = stackIndex.isEmpty() ? -1 : stackIndex.peek();
                rest[targetIndex][0] = small;
                rest[targetIndex][1] = large;
            }
            stackIndex.push(large);
        }
        while (!stackIndex.isEmpty()) {
            int targetIndex = stackIndex.pop();
            int small = stackIndex.isEmpty() ? -1 : stackIndex.peek();
            rest[targetIndex][0] = small;
            rest[targetIndex][1] = -1;
        }
        return rest;
    }
}

有重复值版本

public class Demo{
    /**
     * @param arr 求解数组
     * @return [      small    large
     * 0:     -1       3
     * 1:     0        4
     * ]
     */
    public static int[][] monotonicStack(int[] arr) {
        int[][] rest = new int[arr.length][2];
        Stack<List<Integer>> stack = new Stack<>();
        for (int large = 0; large < arr.length; large++) {
            while (!stack.isEmpty() && arr[large] < arr[stack.peek().get(0)]) {
                List<Integer> targetIndexList = stack.pop();
                int smallIndex = stack.isEmpty() ? -1 : stack.peek().get(0);
                for (Integer targetIndex : targetIndexList) {
                    rest[targetIndex][0] = smallIndex;
                    rest[targetIndex][1] = large;
                }
            }
            if (stack.isEmpty() || arr[stack.peek().get(0)] != arr[large]) {
                LinkedList<Integer> list = new LinkedList<>();
                list.addLast(large);
                stack.push(list);
            } else {
                List<Integer> peek = stack.peek();
                peek.add(large);
            }
        }
    
        while (!stack.isEmpty()) {
            List<Integer> targetIndexList = stack.pop();
            int smallIndex = stack.isEmpty() ? -1 : stack.peek().get(0);
            for (Integer targetIndex : targetIndexList) {
                rest[targetIndex][0] = smallIndex;
                rest[targetIndex][1] = -1;
            }
        }
    
        return rest;
    }
}
题目二:窗口内最值问题

问题描述

给定一个只包含正数的数组arr,arr中任何一个子数组sub
一定都可以算出(sub累加和 )* (sub中的最小值)是什么
那么所有子数组中,这个值最大是多少?

问题分析

1. 找到问题核心:sub数组*内部最小值(两边取小的单调栈)
2. 理解单调栈中的取值特点(两边取小,中间就是较大值)
public class Classic_02_SubSumAndMin {

    public static void main(String[] args) {
        int[] arr = {3, 2, 4, 1, 5, 8, 7, 9};
        System.out.println(7 * ( 7 + 8 + 9));
        System.out.println(max1(arr));
        System.out.println(getMax(arr));
    }

    public static int max1(int[] arr) {
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++) {
            for (int j = i; j < arr.length; j++) {
                int minNum = Integer.MAX_VALUE;
                int sum = 0;
                for (int k = i; k <= j; k++) {
                    sum += arr[k];
                    minNum = Math.min(minNum, arr[k]);
                }
                max = Math.max(max, minNum * sum);
            }
        }
        return max;
    }

    public static int getMax(int[] arr) {
        int[] sums = getSums(arr);
        return monotonic(arr, sums);
    }

    private static int monotonic(int[] arr, int[] sums) {
        int size = arr.length;
        Stack<Integer> stack = new Stack<>();
        int max = 0;
        for (int i = 0; i < arr.length; i++) {
            while (!stack.isEmpty() && arr[i] <= arr[stack.peek()]) {
                Integer large = stack.pop();
                max = Math.max(max, (stack.isEmpty() ? sums[i - 1] : (sums[i - 1] - sums[stack.peek()])) * arr[large]);
            }
            stack.push(i);
        }
        while (!stack.isEmpty()) {
            int j = stack.pop();
            max = Math.max(max, (stack.isEmpty() ? sums[size - 1] : (sums[size - 1] - sums[stack.peek()])) * arr[j]);
        }
        return max;
    }

    private static int[] getSums(int[] arr) {
        int[] sums = new int[arr.length];
        sums[0] = arr[0];
        for (int i = 1; i < arr.length; i++) {
            sums[i] = arr[i] + sums[i - 1];
        }
        return sums;
    }

}
题目三:柱状图中最大的矩形

问题描述

84. 柱状图中最大的矩形

给定一个非负数组arr,代表直方图
返回直方图的最大长方形面积

问题分析(木桶原理)

1. 本质:区间最小值的高度 * 宽度。问题在于`最小值的高度`求解
2. 单调栈中(两边取小),可以完美的解决此类问题。
题目四:最大矩形

85. 最大矩形

问题分析

1. 求`子数组最小值之和`子数组最小值之和问题,可以转化为求单调栈(两边最近小于的栈结构)
2. 求arr[i]左边,离arr[i]最近,<=arr[i],位置在x
3. 求arr[i]右边,离arr[i]最近,< arr[i],的数,位置在y
4. 两边距离 `i` 位置的个数就是`(i-x)*(y-i)`,
    所有以`i`位置作为最小值的子数组,值之和就是`(i-x)*(y-i)*arr[i]`
题目五:

1504. 统计全 1 子矩形

题目六:子数组的最小值之和

907. 子数组的最小值之和

问题分析

1. 求`子数组最小值之和`子数组最小值之和问题,可以转化为求单调栈(两边最近小于的栈结构)
2. 求arr[i]左边,离arr[i]最近,<=arr[i],位置在x
3. 求arr[i]右边,离arr[i]最近,< arr[i],的数,位置在y
4. 两边距离 `i` 位置的个数就是`(i-x)*(y-i)`,
    所有以`i`位置作为最小值的子数组,值之和就是`(i-x)*(y-i)*arr[i]`

代码逻辑

LeetCoded原题训练

专题训练

数列矩阵

基础知识

经典题型

1. 斐波那契数列

问题描述

问题分析

逻辑代码

总结

2. 爬楼梯

LeetCode原题-70. 爬楼梯

3. 牛生牛问题

问题描述:

第一年农场有1只成熟的母牛A,往后的每年:
1)每一只成熟的母牛都会生一只母牛
2)每一只新出生的母牛都在出生的第三年成熟
3)每一只母牛永远不会死
返回N年后牛的数量

问题分析 & 解题思路

解题思路:F(n) = F(N-1) + F(N-3)

KMP算法

基础知识

使用解题范围:

  1. 字符串s中是否包含 连续子字符串m

问题分析:

  1. 常见思路:每个字符依次进行判断(思路很简单,不多叙述)。
  2. 如果存在字符串s=aaaaaaaaaaaaaab,并判断是否存在子字符串m=aaaaaab
    这样的话,用普通的方法进行求解,时间复杂度太高。
  3. 上述的问题,在于a字符重复很多,无效判断太多了。

常见解题思路:

  1. 处理子字符串m,并得到其中的重复情况。

经典题型

问题1:KMP经典实现

问题描述

给定两个字符串smatch
s是否存在match子串,并返回第一个字符的位置

解题思路

1. 创建好 `match` 的 `next` 数组(记录上一个匹配的记录)

代码逻辑

class Kmp{
    public static int getIndexOf(String s1, String s2) {
        if (s1 == null || s2 == null || s2.length() < 1 || s1.length() < s2.length()) {
            return -1;
        }
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();
        int x = 0;
        int y = 0;
        // O(M) m <= n
        int[] next = getNextArray(str2);
        // O(N)
        while (x < str1.length && y < str2.length) {
            if (str1[x] == str2[y]) {
                x++;
                y++;
            } else if (next[y] == -1) { // y == 0
                x++;
            } else {
                y = next[y];
            }
        }
        return y == str2.length ? x - y : -1;
    }

    public static int[] getNextArray(char[] str2) {
        if (str2.length == 1) {
            return new int[] { -1 };
        }
        int[] next = new int[str2.length];
        next[0] = -1;
        next[1] = 0;
        int i = 2; // 目前在哪个位置上求next数组的值
        int cn = 0; // 当前是哪个位置的值再和i-1位置的字符比较
        while (i < next.length) {
            if (str2[i - 1] == str2[cn]) { // 配成功的时候
                next[i++] = ++cn;
            } else if (cn > 0) {
                cn = next[cn];
            } else {
                next[i++] = 0;  //此时的 cn 屹然是0(第一个位置,所以必然是 1 )
            }
        }
        return next;
    }
}
问题2:子树相等

问题描述

给定两棵二叉树的头节点head1和head2
想知道head1中是否有某个子树的结构和head2完全一样

解题思路


代码逻辑

总结

算法优化

LeetCoded原题训练

专题训练

线段树 && IndexTree

基础知识

线段树

解决的问题

  1. 区间相同操作
例如:
1. 区间内`3~200`之内的数字全部`+6`
2. 区间内`7~565`之内的数字全部更新为`7`。
3. 区间内`3~990`之内的数字累加和是多少?
(上述所有的操作,都是O(n)级别的 )
IndexTree

解决的问题

1. 前缀数之和,问题的基础上,更改某个位置的值,求`某区间范围内之和`?

经典题型

1. 线段树结构

问题描述

问题分析

逻辑代码


总结

2. IndexTree结构

问题描述

问题分析

逻辑代码


总结

LeetCoded原题训练

专题训练

数据结构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值