剑指offer(三)

前面咱们已经刷过《剑指offer》的一些题目了
剑指offer(一)
剑指offer(二)
今天的题目主要使用一些高级的算法,好比动态规划、回溯法来解决主要有如下知识点:算法

  • 矩阵中的路径(回溯法)
  • 剪绳子(动态规划和贪心算法)
  • 连续子数组的最大和)

矩阵中的路径(回溯法)

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串全部字符的路径。路径能够从矩阵中任意一格开始,每一步能够在矩阵中间向左、右、上、下移动一格。若是一条路径通过了矩阵的某一格,那么该路径不能再次进入该格子。
例如:在下面的3*4的矩阵中包含一条字符串”bcced”的路径。但矩阵中不包含字符串“abcb”的路径,由于字符串的第一个字符b占据了矩阵中的第一行第二格子以后,路径不能再次进入这个格子。
a b c e
s f c s
a d e e

解题思路

首先,在矩阵中任选一个格子做为路径的起点。假设矩阵中某个格子的字符为c,而且这个格子将对应路径上的第i个字符。若是路径上的第i个字符正好是c,那么往相邻的格子寻找路径上的第i+1个字符。除在矩阵边界上的格子以外,其余格子都有4个相邻的格子。
因为路径不能重复进入矩阵的格子,还须要定义和字符矩阵大小同样的布尔值矩阵,用来标识路径是否已经进入每一个格子。
当矩阵中坐标为(row,col)的格子和路径字符串中下标为pathLength的字符同样时,从4个相邻的格子(row,col-1),(row-1,col),(row,col+1)以及(row+1,col)中去定位路径字符串中下标为pathLength+1的字符。
若是4个相邻的格子都没有匹配字符串中下标为pathLength+1的字符,代表当前路径字符串中下标为pathLength的字符在矩阵中的定位不正确,咱们须要回到前一个字符(pathLength-1),而后从新定位。
一直重复这个过程,直到路径字符串上全部字符都在矩阵中找到合适的位置。segmentfault

代码实现

public class Test {

    /**
     * @param matrix 输入矩阵
     * @param rows   矩阵行数
     * @param cols   矩阵列数
     * @param str    要搜索的字符串
     * @return 是否找到 true是,false否
     */ 

    public static boolean hasPath(char[] matrix, int rows, int cols, char[] str) {

        //输入判断
        if (matrix == null || rows < 1 || cols < 1 || str == null)
            return false;

        //visited:访问标记数组——用来标识路径是否已经进入过格子 false表示没有
        boolean[] visited = new boolean[rows * cols];
        for (int i = 0; i < visited.length; i++) {
            visited[i] = false;
        }

        //pathLength:记录字符串下标
        int pathLength = 0;

        //开始检索
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                if (hasPathCore(matrix, rows, cols, row, col, str, pathLength, visited)) {
                    return true;
                }

            }
        }
        return false;
    }
    
    /**
     * 回溯搜索算法
     * @param matrix     输入矩阵
     * @param rows       矩阵行数
     * @param cols       矩阵列数
     * @param str        要搜索的字符串
     * @param visited    访问标记数组
     * @param row        当前处理的行号
     * @param col        当前处理的列号
     * @param pathLength 已经处理的str中字符个数
     * @return 是否找到 true是,false否
     */ 
    public static boolean hasPathCore(char[] matrix, int rows, int cols, int row, int col,
                                      char[] str, int pathLength, boolean[] visited) {

        //匹配成功
        if (pathLength == str.length)
            return true;

        boolean hasPath = false;

        //判断位置是否合法
        if (row >= 0 && row < rows && col >= 0 && col < cols
                && matrix[row * cols + col] == str[pathLength] && !visited[row * cols + col]) {

            pathLength++;
            visited[row * cols + col] = true;

            //按左上右下回溯
            hasPath = hasPathCore(matrix, rows, cols, row, col - 1, str, pathLength, visited)
                    || hasPathCore(matrix, rows, cols, row - 1, col, str, pathLength, visited)
                    || hasPathCore(matrix, rows, cols, row, col + 1, str, pathLength, visited)
                    || hasPathCore(matrix, rows, cols, row + 1, col, str, pathLength, visited);

            //上下左右都没法匹配到字符则从新回到前一个字符
            if (!hasPath) {
                pathLength--;
                visited[row * cols + col] = false;
            }
        }
        return hasPath;
    }

    public static void main(String[] args) {
        System.out.println(
                hasPath("ABCESFCSADEE".toCharArray(), 3, 4,"ABCCED".toCharArray())
        );
    }
}
输出:
true

剪绳子(动态规划和贪心算法)

给你一根长度为n的绳子,请把绳子剪成m段 (m和n都是整数,n>1而且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0] k[1]...*k[m]可能的最大乘积是多少?
例如,当绳子的长度为8时,咱们把它剪成长度分别为2,3,3的三段,此时获得的最大乘积是18。

解题思路

  • 动态规划法
首先定义函数f(n)为把长度为n的绳子剪成若干段后各段长度乘积的最大值。在剪第一刀时,咱们有n-1种选择,也就是说第一段绳子的可能长度分别为1,2,3.....,n-1。所以 f(n)=max(f(i)*f(n-i)),其中0<i<n。当绳子的长度为2的时候,只能剪成长度为1的两段,因此f(2) = 1,当n = 3时,容易得出f(3) = 2。
  • 贪心算法
根据数学计算,当n>=5时,2(n-2)>n,3(n-3)>n,这就是说, 将绳子剪成2和(n-2)或者剪成3和(n-3)时,乘积大于不剪的乘积,所以须要把绳子剪成2或者3。而且3(n-3)>=2(n-2),也就是说,当n>=5时,应该剪尽可能多的3,可使最后的乘积最大。
对于长度是n的绳子,咱们能够剪出n/3个3,剩余长度是1或者2,若是余数是1,就能够把1和最后一个3合并成4,那么4剪出两个2获得的乘积是4,比1*3大,所以这种状况下,须要将3的个数减小1,变成两个2;若是余数是2,那么无需作修改。
能够获得最大的乘积是: 3^timesOf3 * 2^timesOf

代码实现

  • 动态规划法
public class Test14 {

   /**
    * 动态规划法
    * @param length 绳子长度
    * @return   乘积最大值
    */
    public static int maxAfterCutRope1(int length) {

        if (length < 2) return 0;
        if (length == 2) return 1;
        if (length == 3) return 2;

        //products[i]表示长度为i的绳子的乘积最大值
        //products[一、二、3]是特例
        int[] products = new int[length + 1];
        products[0] = 0;
        products[1] = 1;
        products[2] = 2;
        products[3] = 3;

        int max = 0;
        // 为何动态规划从4开始?
        // 由于当length<=3时,绳子不剪的长度>=剪后长度乘积最大值
        // 当length>=4时,绳子不剪的长度<=剪后长度乘积最大值
        for (int i = 4; i <= length; i++) {
            max = 0;
            //求得全部的f(j)*f(i-j)并比较它们获得乘积最大值
            for(int j=1;j<=i/2;j++){
                int product=products[j]*products[i-j];
                if (product>max){
                    max=product;
                }
                products[i]=max;
            }
        }
        max=products[length];
        return max;
    }

我我的以为动态规划在笔试中常常遇到,Leetcode上有一道求子数组的最大乘积和这道很相似,对这道题来讲最重要的是理解为何动态规划要从4开始,注释已经写得很清楚了就很少说了!数组

  • 贪心算法
/**
    * 贪心算法
    * @param length 绳子长度
    * @return   乘积最大值
    */
    public static int maxProductAfterCutRope2(int length){

        if (length < 2) return 0;
        if (length == 2) return 1;
        if (length == 3) return 2;

        //长度为3的绳子段
        int timesOf3=length/3;

        //当剩下绳子长度为4时,再也不去剪长度为3的绳子段
        //更好的方法是把绳子剪成长度为2的两段,由于2*2>1*3
        if (length-timesOf3*3==1)
            timesOf3-=1;

        int timesOf2=(length-timesOf3*3)/2;

        return (int) (Math.pow(3, timesOf3)*Math.pow(2, timesOf2));
    }

    public static void main(String[] args) {

        System.out.println(Test14.maxAfterCutRope1(11));
        System.out.println(Test14.maxProductAfterCutRope2(11));
    }
}

连续子数组的最大和

输入一个整型数组,数组里有正数也有负数。数组中一个或连续的多个整数组成一个子数组。
求全部子数组的和的最大值。要求时间复杂度为O(n)。
例如输入的数组为{1, -2, 3, 10, -4, 7, 2, -5},和最大的子数组为{3, 10, -4, 7, 2},所以输出为该子数组的和18 。

解题思路

解法一:分析数组的规律
初始化和为0。第一步加上第一个数字1, 此时和为1。接下来第二步加上数字-2,和就变成了-1。第三步刷上数字3。
咱们注意到因为此前累计的和是-1,小于0,那若是用-1加上3 ,获得的和是2 ,比3自己还小。
也就是说从第一个数字开始的子数组的和会小于从第三个数字开始的子数组的和。
所以咱们不用考虑从第一个数字开始的子数组,以前累计的和也被抛弃。函数

解法二: 应用动态归划法
若是用函数f(i)表示以第i个数字结尾的子数组的最大和
当i = 0或f(i-1) <= 0时,f(i) = a[i];
当i > 0且f(i-1) > 0时,f(i) = f(i-1) + a[i];
这个公式的意义:当以第 i-1 个数字结尾的子数组中全部数字的和小于0时,若是把这个负数与第 i 个数累加,获得的结果比第 i 个数字自己还要小,因此这种状况下以第i个数字结尾的子数组就是第 i 个数字自己。若是以第 i-1 个数字结尾的子数组中全部数字的和大于0 ,
与第 i 个数字累加就获得以第 i 个数字结尾的子数组中全部数字的和。设计

代码实现

解法一:分析数组的规律code

public static int findGreatestSumOfSubArray1(int[] a) {

    if (a == null || a.length <= 0)
        throw new RuntimeException("不合法的输入");
        
     int curSum = 0;
    int greatestSum = 0x80000000;//int最小值
    for (int i = 0; i < a.length; i++) {
        //若是前面累加和<0,抛弃累加和从当前数字从新累加
        if (curSum <= 0) {
            curSum = a[i];
        } else {
            curSum += a[i];
        }
        //找出最大值
        if (curSum > greatestSum) {
            greatestSum = curSum;
        }
    }
    return greatestSum;
}

解法二: 应用动态归划法字符串

public static int findGreatestSumOfSubArray2(int[] a) {

    if (a == null || a.length == 0) {
        throw new RuntimeException("不合法的输入");
    }

    int sum = a[0];//局部最大值
    int max = a[0];//全局最大值
    
    for (int i = 1; i < a.length; i++) {
        sum = Math.max(sum + a[i], a[i]);
        if (sum >= max) {
            max = sum;
        }
    }
    return max;
}
相关文章
相关标签/搜索