• Chapter nine Dynamic Programming(动态规划)


    1.triangle(数字三角形)

    给定一个数字三角形,找到从顶部到底部的最小路径和。每一步可以移动到下面一行的相邻数字上。如果你只用额外空间复杂度O(n)的条件下完成可以获得加分,其中n是数字三角形的总行数。

    自顶向下的解法:

    public class Solution {
        /**
         * @param triangle: a list of lists of integers.
         * @return: An integer, minimum path sum.
         */
        public int minimumTotal(int[][] triangle) {
            // write your code here
            if (triangle == null || triangle.length == 0) {
                return -1;
            }
            if (triangle[0] == null || triangle[0].length == 0) {
                return -1;
            }
            int n = triangle.length;
            int[][] f = new int[n][n];
            //初始化起点
            f[0][0] = triangle[0][0];
            //初始化三角形的最左边和最右边
            for (int i = 1; i < n; i++) {
                f[i][0] = f[i - 1][0] + triangle[i][0];
                f[i][i] = f[i - 1][i - 1] + triangle[i][i];
            }
            //从上往下循环递归求解
            for (int i = 1; i < n; i++) {
                for (int j = 1; j < i; j++) {
                    f[i][j] = Math.min(f[i - 1][j], f[i - 1][j - 1]) + triangle[i][j];
                }
            }
            //找到最后一层的最小值返回
            int best = f[n - 1][0];
            for (int i = 1; i < n; i++) {
                best = Math.min(best, f[n - 1][i]);
            }
            return best;
        }
    }
    View Code

    注意:f[i][j]表示从i,j出发走到最后一层的最小路径长度,首先初始化起点和三角形的最左边与最右边,然后自顶向下循环递归求解f[i][j]=Math.min(f[i-1][j],f[i-1][j-1]+triangle[i][j],最后返回最下一层的最小值为最终结果。

    自底向上的解法:

    public class Solution {
        /**
         * @param triangle: a list of lists of integers.
         * @return: An integer, minimum path sum.
         */
        public int minimumTotal(int[][] triangle) {
            // write your code here
            if (triangle == null || triangle.length == 0) {
                return -1;
            }
            if (triangle[0] == null || triangle[0].length == 0) {
                return -1;
            }
            int n = triangle.length;
            //f[i][j]表示从i,j出发走到最后一层的最小路径长度
            int[][] f = new int[n][n];
            //初始化,最后一层先有值
            for (int i = 0; i < n; i++) {
                f[n - 1][i] = triangle[n - 1][i];
            }
            //循环递归求解
            for (int i = n - 2; i >= 0; i--) {
                for (int j = 0; j <= i; j++) {
                    f[i][j] = Math.min(f[i + 1][j], f[i + 1][j + 1]) + triangle[i][j];
                }
            }
            //返回结果
            return f[0][0];
        }
    }
    View Code

    注意:f[i][j]表示从i,j出发走到最后一层的最小路径长度,首先初始化最后一层先有值,然后自底向上循环递归求解f[i][j]=Math.min(f[i+1][j],f[i+1][j+1]+triangle[i][j],最后返回起点f[0][0]为最终结果。

    记忆化搜索的解法:

    public class Solution {
        /**
         * @param triangle: a list of lists of integers.
         * @return: An integer, minimum path sum.
         */
        private int n;
        private int[][] minSum;
        private int[][] triangle;
        private int search(int x, int y) {
            if (x >= n) {
                return 0;
            }
            //该点已经计算过,直接返回即可
            if (minSum[x][y] != Integer.MAX_VALUE) {
                return minSum[x][y];
            }
            minSum[x][y] = Math.min(search(x + 1, y), search(x + 1, y + 1)) + triangle[x][y];
            return minSum[x][y];
        }
        public int minimumTotal(int[][] triangle) {
            // write your code here
            if (triangle == null || triangle.length == 0) {
                return -1;
            }
            if (triangle[0] == null || triangle[0].length == 0) {
                return -1;
            }
            this.n = triangle.length;
            this.triangle = triangle;
            this.minSum = new int[n][n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    minSum[i][j] = Integer.MAX_VALUE;
                }
            }
            return search(0, 0);
        }
    }
    View Code

    注意:minSum[n][n]存储从起点到(x,y)处的最小和,初始都为Integer.MAX_VALUE表示没有计算过,在寻找的过程中如果minSum[x][y]!=Integer.MAX_VALUE表示该点已经计算过,直接返回即可。

    坐标型动态规划

    • state:f[x] 表示我从起点走到坐标x…… f[x][y] 表示我从起点走到坐标x,y……
    • function: 研究走到x,y这个点之前的一步
    • initialize: 起点
    • answer: 终点

    2.minimum-path-sum(最小路径和)

    给定一个只含非负整数的m*n网格,找到一条从左上角到右下角的可以使数字和最小的路径。你在同一时间只能向下或者向右移动一步。

    public class Solution {
        /**
         * @param grid: a list of lists of integers.
         * @return: An integer, minimizes the sum of all numbers along its path
         */
        public int minPathSum(int[][] grid) {
            // write your code here
            if (grid == null || grid.length == 0 || grid[0].length == 0) {
                return 0;
            }
            int M = grid.length;
            int N = grid[0].length;
            int[][] f = new int[M][N];
            //初始化起点
            f[0][0] = grid[0][0];
            //初始化第一行和第一列
            for (int i = 1; i < M; i++) {
                f[i][0] = f[i - 1][0] + grid[i][0];
            }
            for (int i = 1; i < N; i++) {
                f[0][i] = f[0][i - 1] + grid[0][i];
            }
            //循环递归求解
            for (int i = 1; i < M; i++) {
                for (int j = 1; j < N; j++) {
                    f[i][j] = Math.min(f[i - 1][j], f[i][j - 1]) + grid[i][j];
                }
            }
            //返回结果:右下角的点
            return f[M - 1][N - 1];
        }
    }
    View Code

     注意:f[x][y] 表示从起点走到坐标x,y的最小和,首先初始化起点、第一行和第一列,然后循环递归求解f[i][j]=Math.min(f[i-1][j],f[i][j-1])+grid[i][j],最后返回f[M-1][N-1]为最终结果。

    3.unique-paths(不同的路径)

    有一个机器人位于一个 m × n 个网格左上角。机器人每一时刻只能向下或者向右移动一步。机器人试图达到网格的右下角。问有多少条不同的路径?n和m均不超过100。

    public class Solution {
        /**
         * @param n, m: positive integer (1 <= n ,m <= 100)
         * @return an integer
         */
        public int uniquePaths(int m, int n) {
            // write your code here
            if (m == 0 || n == 0) {
                return 1;
            }
            int[][] f = new int[m][n];
            for (int i = 0; i < m; i++) {
                f[i][0] = 1;
            }
            for (int i = 0; i < n; i++) {
                f[0][i] = 1;
            }
            for (int i = 1; i < m; i++) {
                for (int j = 1; j < n; j++) {
                    f[i][j] = f[i - 1][j] + f[i][j - 1];
                }
            }
            return f[m - 1][n - 1];
        }
    }
    View Code

    注意: f[x][y] 表示从起点走到坐标x,y的路径数。初始化第一行和第一列都为1,然后循环递归求解f[i][j]=f[i-1][j]+f[i][j-1](从坐标i-1,j或i,j-1到坐标i,j都只有一种路径,所以f[i][j]=f[i-1][j]+f[i][j-1]),最后返回f[m-1][n-1]为最终结果。

    4.unique-paths-ii(不同的路径II)

    “不同的路径”的跟进问题:现在考虑网格中有障碍物,那样将会有多少条不同的路径?网格中的障碍和空位置分别用 1 和 0 来表示。m 和 n 均不超过100。

    public class Solution {
        /**
         * @param obstacleGrid: A list of lists of integers
         * @return: An integer
         */
        public int uniquePathsWithObstacles(int[][] obstacleGrid) {
            // write your code here
            if (obstacleGrid == null || obstacleGrid.length == 0 || obstacleGrid[0].length == 0) {
                return 0;
            }
            int n = obstacleGrid.length;
            int m = obstacleGrid[0].length;
            int[][] sum = new int[n][m];
            for (int i = 0; i < n; i++) {
                if (obstacleGrid[i][0] != 1) {
                    sum[i][0] = 1;
                } else {
                    break;
                }
            }
            for (int i = 0; i < m; i++) {
                if (obstacleGrid[0][i] != 1) {
                    sum[0][i] = 1; 
                } else {
                    break;
                }
            }
            for (int i = 1; i < n; i++) {
                for (int j = 1; j < m; j++) {
                    if (obstacleGrid[i][j] != 1) {
                        sum[i][j] = sum[i - 1][j] + sum[i][j - 1];
                    } else {
                        sum[i][j] = 0;
                    }
                }
            }
            return sum[n - 1][m - 1];
        }
    }
    View Code

    注意:跟第3题相比,在初始化第一行和第一列时,如果遇到障碍就直接结束(break),因为后续节点都无法到达;在循环递归求解的时候,如果遇到障碍就将该位置处的路径和赋值为0。

    5.climbing-stairs(爬楼梯)

    假设你正在爬楼梯,需要n步你才能到达顶部。但每次你只能爬一步或者两步,你能有多少种不同的方法爬到楼顶部?

    public class Solution {
        /**
         * @param n: An integer
         * @return: An integer
         */
        public int climbStairs(int n) {
            // write your code here
            if (n <= 1){
                return 1;
            }
            int[] f = new int[n + 1];
            f[0] = 1;
            f[1] = 1;
            for (int i = 2; i < n + 1; i++) {
                f[i] = f[i - 1] + f[i - 2];
            }
            return f[n];
        }
    }
    View Code

    注意:长度为n+1的数组f[]表示爬0阶到n阶台阶的不同方法数。初始化f[0]=f[1]=1,然后循环递归求解f[i]=f[i-1]+f[i-2](i-1到i走一步;i-2到i走两步),最后返回f[n]。

    6.climbing-stairs-ii(爬楼梯II)

    一个小孩爬一个 n 层台阶的楼梯。他可以每次跳 1 步, 2 步 或者 3 步。实现一个方法来统计总共有多少种不同的方式爬到最顶层的台阶。

    public class Solution {
        /**
         * @param n an integer
         * @return an integer
         */
        public int climbStairs2(int n) {
            // Write your code here
            if (n <= 1){
                return 1;
            }
            int[] f = new int[n + 1];
            f[0] = 1;
            f[1] = 1;
            f[2] = 2;
            for (int i = 3; i < n + 1; i++) {
                f[i] = f[i - 1] + f[i - 2] + f[i - 3];
            }
            return f[n];
        }
    }
    View Code

    注意:注意:长度为n+1的数组f[]表示爬0阶到n阶台阶的不同方法数。初始化f[0]=f[1]=1,f[2]=2,然后循环递归求解f[i]=f[i-1]+f[i-2]+f[i-3](i-1到i走一步;i-2到i走两步;i-3到i走三步),最后返回f[n]。

    7.jump-game(跳跃游戏)

    给出一个非负整数数组,你最初定位在数组的第一个位置。数组中的每个元素代表你在那个位置可以跳跃的最大长度。判断你是否能到达数组的最后一个位置。

    这个问题有两个方法,一个是贪心(最优解法)和 动态规划贪心方法时间复杂度为O(n)动态规划方法的时间复杂度为为O(n^2)

    动态规划解法:

    public class Solution {
        /**
         * @param A: A list of integers
         * @return: The boolean answer
         */
        public boolean canJump(int[] A) {
            // wirte your code here
            if (A == null || A.length == 0) {
                return false;
            }
            int len = A.length;
            boolean[] dp = new boolean[len];
            dp[0] = true;
            for (int i = 1; i < len; i++) {
                dp[i] = false;
                for (int j = 0; j < i; j++) {
                    if (dp[j] && A[j] + j >= i) {
                        dp[i] = true;
                        break;
                    }
                }
            }
            return dp[len - 1];
        }
    }
    View Code

    注意:boolean型数组dp[n]记录每个位置是否可达,初始化dp[0]=true(可达)。每到一个点i,初始化dp[i]=false(不可达),扫描i之前的所有点,如果之前某点j本身可达(dp[j]=true),并且(&&)j与i点可达(A[j]+j>=i),表示点i是可达的。返回DP数组的最后一个值dp[n]即可。

    贪心解法:

    public class Solution {
        public boolean canJump(int[] A) {
            // think it as merging n intervals
            if (A == null || A.length == 0) {
                return false;
            }
            int farthest = A[0];
            for (int i = 1; i < A.length; i++) {
                if (i <= farthest && A[i] + i >= farthest) {
                    farthest = A[i] + i;
                }
            }
            return farthest >= A.length - 1;
        }
    }
    View Code

    8.jump-game-ii(跳跃游戏II)

    给出一个非负整数数组,你最初定位在数组的第一个位置。数组中的每个元素代表你在那个位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。

    public class Solution {
        /**
         * @param A: A list of lists of integers
         * @return: An integer
         */
        public int jump(int[] A) {
            // write your code here
            if (A == null || A.length == 0) {
                return 0;
            }
            int len = A.length;
            int[] steps = new int[len];
            steps[0] = 0;
            for (int i = 1; i < len; i++) {
                steps[i] = Integer.MAX_VALUE;
            }
            for (int i = 1; i < len; i++) {
                for (int j = 0; j < i; j++) {
                    if (steps[j] != Integer.MAX_VALUE && A[j] + j >= i) {
                        steps[i] = Math.min(steps[i], steps[j] + 1);
                    }
                }
            }
            return steps[len - 1];
        }
    }
    View Code

    注意:steps[]数组记录跳到某个位置的最小跳跃次数,初始化steps[0]=0,steps[1~length-1]=Integer.MAX_VALUE。每到一个点i,扫描i之前的所有点,如果之前某点j本身可达(steps[j] != Integer.MAX_VALUE),并且(&&)j与i点可达(A[j]+j>=i),表示点i是可达的,求当前steps[i]的最少跳跃次数(steps[i] = Math.min(steps[i], steps[j] + 1))。最后返回steps数组的最后一个值steps[length-1]即可。

    接龙型动态规划

    9.longest-increasing-subsequence(最长上升【严格递增】子序列)【必须会】

    给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。最长上升子序列的定义:最长上升子序列问题是在一个无序的给定序列中找到一个尽可能长的由低到高排列的子序列,这种子序列不一定是连续的或者唯一的。

    public class Solution {
        /**
         * @param nums: The integer array
         * @return: The length of LIS (longest increasing subsequence)
         */
        public int longestIncreasingSubsequence(int[] nums) {
            // write your code here
            if (nums == null || nums.length == 0) {
                return 0;
            }
            int n = nums.length;
            int[] f = new int[n];
            int max = 0;
            for (int i = 0; i < n; i++) {
                f[i] = 1;
            }
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < i; j++) {
                    if (nums[j] < nums[i]) {
                        f[i] = Math.max(f[i], f[j] + 1);
                    }
                }
                max = Math.max(f[i], max);
            }
            return max;
        }
    }
    View Code

    注意:将n个数看作n个木桩,目的是从某个木桩出发,从前向后,从低往高,看做最多能踩多少个木桩。f[i]数组用来表示(从任意某个木桩)跳到第i个木桩,最多踩过多少根木桩,初始化f[0...n-1]=1。每到一个位置i,扫描i之前的所有点,如果之前某位置j的值比位置i的值小 (nums[j] < nums[i]),求当前f[i]的最多踩过木桩数(f[i] = Math.max(f[i], f[j] + 1)),在位置i之前的点扫描完之后,求max=Math.max(f[i],max).最后返回max值即可。

    10.perfect-squares(完美平方)

    给一个正整数 n, 找到若干个完全平方数(比如1, 4, 9, ... )使得他们的和等于 n。你需要让平方数的个数最少。

    public class Solution {
        /**
         * @param n a positive integer
         * @return an integer
         */
        public int numSquares(int n) {
            // Write your code here
            int[] f = new int[n + 1];
            for (int i = 0; i < n + 1; i++) {
                f[i] = Integer.MAX_VALUE;
            }
            //Arrays.fill(f, Integer.MAX_VALUE);
            //将所有平方数的结果置1
            for (int i = 0; i * i <= n; i++) {
                f[i * i] = 1;
            }
            //从小到大找任意数i
            for (int i = 0; i <= n; i++) {
                //从小到大找平方数b*b
                for (int j = 0; i + j * j <= n; j++) {
                    f[i + j * j] = Math.min(f[i] + 1, f[i + j * j]);
                }
            }
            return f[n];
        }
    }
    View Code

    注意:如果一个数X可以表示为一个任意数a加上一个平方数b*b,也就是x=a+b*b,那么能组成这个数x最少的平方数个数就是能组成a最小的平方数个数加1(因为b*b已经是平方数了)。

    数组f[i]用来表示和等于i的最小平方数个数,初始化f[0...n]=Integer.MAX_VALUE。对于每个整数i,如果i*I≤n,f[i*i]=1。从小到大找任意数a,从小到大找平方数b*b,f[a+b*b]=Math.min(f[a]+1,f[a+b*b])。最后返回f[n]即为最终结果。

    11.largest-divisible-subset(最大可除尽子集)

    给定一组不同的正整数,找到最大子集,使得该子集中的每个元素对(Si,Sj)满足:Si%Sj = 0或Sj%Si = 0。如果有多个解决方案,返回任何子集都可以。

    public class Solution {
        /**
         * @param nums a set of distinct positive integers
         * @return the largest subset 
         */
        public List<Integer> largestDivisibleSubset(int[] nums) {
            // Write your code here
            int n = nums.length;
            List<Integer> ans = new ArrayList<Integer>();
            if (n == 0) {
                return ans;
            }
            Arrays.sort(nums);
            int[] dp = new int[n];
            int[] pre = new int[n];
            //求dp[i]及相应的pre[i]
            for (int i = 0; i < n; i++) {
                dp[i] = 1;
                pre[i] = i;
                for (int j = 0; j < i; j++) {
                    if (nums[i] % nums[j] == 0 && dp[i] < dp[j] + 1) {
                        dp[i] = dp[j] + 1;
                        pre[i] = j;
                    }
                }
            }
            //求可整除子集的最大长度curMax及子集中最后一个元素的位置idx
            int curMax = 0;
            int idx = 0;
            for (int i = 0; i < n; i++) {
                if (dp[i] > curMax) {
                    curMax = dp[i];
                    idx = i;
                }
            }
            //把最后一个元素放入结果集
            ans.add(nums[idx]);
            //利用idx和pre数组找到可整除子集中的所有元素,当idx==pre[idx]时说明已找全
            while (idx != pre[idx]) {
                idx = pre[idx];
                ans.add(nums[idx]);
            }
            //翻转成从小到大的顺序
            Collections.reverse(ans);
            return ans;
        }
    }
    View Code

    注意:考虑较小数对较大数取余一定为0,那么问题就变成了看较大数能不能整除这个较小数。先对数组进行排序,然后每次就只要看后面的数字能否整除前面的数字。

    dp[i]表示从位置0到i(所有位置)的最大可整除子集,要找出0~i-1之间的所有能够整除nums[i]的num[j](nums[i]%nums[j]=0),那么dp[i]=Math.max(dp[i],dp[j]+1),j在[0,i-1]之间。pre[i]表示以i结尾的最大可整除子集中,i的前一个数的索引。curMax是所有dp[i]的最大值,即当前最大子集长度,idx是对应长度的子集的最后一个元素的索引。

    12.knight-shortest-path-ii(骑士最短路径II)

    给一个骑士在棋盘n * m(二进制矩阵,0为空,1为障碍)。骑士初始位置是(0,0),他要达到位置(n-1,m-1),骑士只能从左到右。找到目的地位置的最短路径,返回路线的长度。如果骑士无法到达,返回-1。如果骑士在(x,y),他可以一步到达以下位置:(x + 1,y + 2)(x-1,y + 2)(x + 2,y + 1)(x-2,y + 1)

    public class Solution {
        /**
         * @param grid a chessboard included 0 and 1
         * @return the shortest path
         */
        public int shortestPath2(boolean[][] grid) {
            // Write your code here
            int n = grid.length;
            int m = grid[0].length;
            if (n == 0 || m == 0) {
                return -1;
            }
            int[][] f = new int[n][m];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    f[i][j] = Integer.MAX_VALUE;
                }
            }
            f[0][0] = 0;
            
            for (int j = 1; j < m; j++) {
                for (int i = 0; i < n; i++) {
                    if (!grid[i][j]) {
                        if (i >= 1 && j >= 2 && f[i - 1][j - 2] != Integer.MAX_VALUE) {
                            f[i][j] = Math.min(f[i][j], f[i - 1][j - 2] + 1);
                        }
                        if (i + 1 < n && j >= 2 && f[i + 1][j - 2] != Integer.MAX_VALUE) {
                            f[i][j] = Math.min(f[i][j], f[i + 1][j - 2] + 1);
                        }
                        if (i >= 2 && j >= 1 && f[i - 2][j - 1] != Integer.MAX_VALUE) {
                            f[i][j] = Math.min(f[i][j], f[i - 2][j - 1] + 1);
                        }
                        if (i + 2 < n && j >= 1 && f[i + 2][j - 1] != Integer.MAX_VALUE) {
                            f[i][j] = Math.min(f[i][j], f[i + 2][j - 1] + 1);
                        }
                    }
                }
            }
            if (f[n - 1][m - 1] == Integer.MAX_VALUE) {
                return -1;
            }
            return f[n - 1][m - 1];
        }
    }
    View Code

    注意:f[i][j]用来表示从起点到该位置的最小路线长度, 初始化f[0][0]=0,其余位置都为Integer.MAX_VALUE。由于骑士只能从左到右,所以要固定列坐标搜索行坐标。

    for (int j = 1; j < m; j++) {    for (int i = 0; i < n; i++) {...}   } 如果grid[i][j]不为障碍,则在grid[i][j]不越界且骑士到达位置(i,j)的上一个位置可达(!=Integer.MAX_VALUE)的情况下,f[i][j]=Math.min(f[i][j],上一个位置处的F值+1)。 

    13.drop-eggs(扔鸡蛋)

    有n层楼的建筑物。如果一个鸡蛋从第k层或以上掉落,就会破裂。如果它从k下面的任何楼层掉落,都不会破。给你两个鸡蛋,找到k,同时最小化最坏情况下扔的次数。返回最坏情况下扔的次数。这两只鸡蛋一模一样,不碎的话可以扔无数次。对于n = 10,找到k的天真的方式是从第一层,第二层,... 第k层扔鸡蛋。但是在这种最坏的情况下(k = 10),你必须扔10次。请注意,你有两个鸡蛋,所以你可以在4层,7层和9层扔鸡蛋,在最差的情况下(例如,k = 9)扔4次。要声明为long数据类型。

    public class Solution {
        /**
         * @param n an integer
         * @return an integer
         */
        public int dropEggs(int n) {
            // Write your code here
            long ans = 0;
            for (int i = 1;; i++) {
                ans += (long) i;
                if (ans >= (long) n) {
                    return i;
                }
            }
        }
    }
    View Code

    注意:因为只有两个鸡蛋,所以第一个鸡蛋应该是按一定的间距扔,比如10楼,20楼,30楼等,比如10楼和20楼没碎,30楼碎了,那么第二个鸡蛋就要做线性搜索,分别尝试21楼,22楼,23楼等,直到鸡蛋碎了就能找到临界点。假设n=100,那么我们来看下列两种情况:

    1. 假如临界点是9楼,那么鸡蛋1在第一次扔10楼碎掉,然后鸡蛋2依次遍历1到9楼,则总共需要扔10次。

    2. 假如临界点是100楼,那么鸡蛋1需要扔10次,到100楼时碎掉,然后鸡蛋2依次遍历91楼到100楼,总共需要扔19次。

    所以上述方法的最坏情况是19次,那么有没有更少的方法呢,上面那个方法每多扔一次鸡蛋1,鸡蛋2的线性搜索次数最多还是10次,那么最坏情况肯定会增加,所以我们需要让每多扔一次鸡蛋1,鸡蛋2的线性搜索最坏情况减少1,这样能够保持整体最坏情况的平衡,那么我们假设鸡蛋1第一次在第X层扔,然后向上X-1层扔一次,然后向上X-2层扔,以此类推直到100层,那么我们通过下面的公式求出X:X + (X-1) + (X-2) + ... + 1 = 100 -> X = 14所以我们先到14楼,然后27楼,然后39楼,以此类推,最坏情况需要扔14次。

    所以为保持整体最坏情况的平衡,假设鸡蛋1第一次在第X层扔,然后向上X-1层扔,然后向上X-2层扔,以此类推直到n层,x+(x-1)+(x-2)+...+1=n(总楼层数),求出x即可。

    14.drop-eggs-ii(扔鸡蛋II)

    有n层楼的建筑物。如果一个鸡蛋从第k层或以上掉落,就会破裂。如果它从k下面的任何楼层掉落,都不会破。给你m个鸡蛋,找到k,同时最小化最坏情况下扔的次数。返回最坏情况下扔的次数。这两只鸡蛋一模一样,不碎的话可以扔无数次。

    public class Solution {
        /**
         * @param m the number of eggs
         * @param n the umber of floors
         * @return the number of drops in the worst case
         */
        public int dropEggs2(int m, int n) {
            // Write your code here
            int[][] dp = new int[m + 1][n + 1];
            for (int i = 1; i <= m; ++i) {
                dp[i][1] = 1;
                dp[i][0] = 0;
            }
            for (int j = 1; j <= n; ++j)
                dp[1][j] = j;
    
            for (int i = 2; i <= m; ++i) {
                for (int j = 2; j <= n; ++j) {
                    dp[i][j] = Integer.MAX_VALUE;
                    for (int k = 1; k <= j; ++k) {
                        dp[i][j] = Math.min(dp[i][j],
                            1 + Math.max(dp[i - 1][k - 1], dp[i][j - k]));
                    }
                }
            }
            return dp[m][n];
    
    }
    View Code

    15.russian-doll-envelopes(俄罗斯套娃信封)【hard】

    您有一些宽度和高度作为一对整数(w,h)给出的信封给出。只有当一个信封的宽度和高度都大于另一个信封的宽度和高度时,一个信封可以适合另一个信封。俄罗斯套娃(把一个放在其他的)的最大信封数量是多少? 

    public class Solution {
        /**
         * @param envelopes a number of envelopes with widths and heights
         * @return the maximum number of envelopes
         */
        public int maxEnvelopes(int[][] envelopes) {
            // Write your code here
            if (envelopes == null || envelopes.length == 0
                || envelopes[0] == null || envelopes[0].length != 2) {
                return 0;
            }
            Arrays.sort(envelopes, new Comparator<int[]>(){
                public int compare(int[] arr1, int[] arr2){
                    if (arr1[0] == arr2[0]) {
                        return arr2[1] - arr1[1];
                    } else {
                        return arr1[0] - arr2[0];
                    }
                }
            });
            int [] dp = new int [envelopes.length];
            int len = 0;
            for (int[] envelope : envelopes) {
                int index = Arrays.binarySearch(dp, 0, len, envelope[1]);
                if (index < 0) {
                    index = -index - 1;
                }
                dp[index] = envelope[1];
                if (index == len) {
                    len++;
                }
            }
            return len;
        }
    }
    View Code

    16.frog-jump(青蛙过河)【hard】

    一只青蛙正在过河。河流分为x个单位,每个单位可能有也可能没有石头。青蛙可以跳上石头,但不能跳进水里。按照升序排序列出石头的位置(单位),确定青蛙是否能够通过登陆在最后一块石头上过河。最初,青蛙在第一块石头上,假设第一跳必须是1个单位。如果青蛙的上一跳是k个单位,则其下一个跳转必须是k-1,k或k + 1个单位。请注意,青蛙只能向前移动。石头数量≥2,<1100。每个石头的位置将是非负整数<2 ^ 31。第一石头的位置总是0。

    public class Solution {
        /**
         * @param stones a list of stones' positions in sorted ascending order
         * @return true if the frog is able to cross the river or false
         */
        public boolean canCross(int[] stones) {
            // Write your code here
            HashMap<Integer, HashSet<Integer>> dp =
                new HashMap<Integer, HashSet<Integer>>(stones.length);
            for (int i = 0; i < stones.length; i++) {
                dp.put(stones[i], new HashSet<Integer>());
            }
            dp.get(0).add(0);
            for (int i = 0; i < stones.length - 1; ++i) {
                int stone = stones[i];
                for (int k : dp.get(stone)) {
                    // k - 1
                    if (k - 1 > 0 && dp.containsKey(stone + k - 1)) {
                        dp.get(stone + k - 1).add(k - 1);
                    }
                    // k
                    if (dp.containsKey(stone + k)) {
                        dp.get(stone + k).add(k);
                    }
                    // k + 1
                    if (dp.containsKey(stone + k + 1)) {
                        dp.get(stone + k + 1).add(k + 1);
                    }
                }
            }
            return !dp.get(stones[stones.length - 1]).isEmpty();
        }
    }
    View Code
  • 相关阅读:
    [原创] 扩展jquery-treegrid插件, 实现勾选功能和全删按钮.
    [原创]多版本Java环境变量的配置
    [转]Redmine 配置163邮箱
    [转] --- Error: “A field or property with the name was not found on the selected data source” get only on server
    服务器控件中使用<%#...>, JS和html控件中使用<%=...>
    【字源大挪移—读书笔记】 第三部分:字尾
    【字源大挪移—读书笔记】 第二部分:字根
    使用WebClient 或者 HttpWebRequest均报:"The Remote name can't be solved"
    【字源大挪移—读书笔记】 第一部分:字首
    【英语魔法俱乐部——读书笔记】 3 高级句型-简化从句&倒装句(Reduced Clauses、Inverted Sentences) 【完结】
  • 原文地址:https://www.cnblogs.com/struggleli/p/6934287.html
Copyright © 2020-2023  润新知