求最大子列和问题

方法1:暴力计算法

i 表示子列开始索引

j 表示子列结束索引

k(i<k<j) 辅助计算 i~j之间子列和

public int method1(int[] arr) {
        int maxSum = 0;
        for (int i = 0; i < arr.length; i++) {
            for (int j = i; j < arr.length; j++) {
                int tempMax = 0;
                for (int k = i; k <= j; k++) {
                    tempMax += arr[k];
                }
                if (tempMax > maxSum) {
                    maxSum = tempMax;
                }
            }
        }
        return maxSum;
    }

三层for循环,时间复杂度 T(N) = n^3

方法2:暴力破解优化

因为方法一每次都是从 i 加到 j ,而 j 每次只往后扫描变化一个,所以直接将 k 循环省略在 j 循环中计算

public int method2(int[] arr) {
        int maxSum = 0;
        for (int i = 0; i < arr.length; i++) {
            int tempSum = 0;
            for (int j = i; j <= arr.length; j++) {
                tempSum += arr[j];
         
if (tempSum > maxSum) {
            maxSum = tempSum;
          }
        }
     }
     return maxSum;
}

两层for循环,T(N) = n^2

方法3:分治策略,将问题划分为更小的问题,解决后再返回来求最优解

    /**
     * 为了与方法1和2有同样的调用接口
     * @param arr
     * @return
     */
    public int method3(int[] arr) {
        return divideAndConquer(arr, 0, arr.length - 1);
    }

    /**
     * 将数组划分为两部分,先计算左半部分最大子列和,再计算又半部分最大子列和,再从分界线向左向右分别扫描获取最大子列和取得跨界最大子列和
     *
     * @param arr
     * @param left
     * @param right
     * @return
     */
    private int divideAndConquer(int[] arr, int left, int right) {
        /**
         * 如果索引相等,表示已经划分该部分为最小问题,元素个数为1
         * 若该元素大于0,则对计算下一步运算有帮助,返回原值
         * 若该元素小于0,则无论向左向右加都会减小相邻子列的和,所以舍弃该元素,返回0
         */
        if (left == right) {
            if (arr[left] > 0) {
                return arr[left];
            } else {
                return 0;
            }
        }

        /* 下面是"分"的过程 */
        int mid = (left + right) / 2;
        /* 求最大左子列和 */
        int maxLeftSum = divideAndConquer(arr, left, mid);
        /* 求最大右子列和 */
        int maxRightSum = divideAndConquer(arr, mid + 1, right);

        /* 求跨界最大子列和 */
        int maxLeftBorderSum = 0;
        int maxRightBorderSum = 0;
        int leftBorderSum = 0;
        int rightBorderSum = 0;

        /* 从中线向左扫描 */
        for (int i = mid; i >= left; i--) {
            leftBorderSum += arr[i];
            if (leftBorderSum > maxLeftBorderSum) {
                maxLeftBorderSum = leftBorderSum;
            }
        }

        /* 从中线向右扫描 */
        for (int i = mid + 1; i < right; i++) {
            rightBorderSum += arr[i];
            if (rightBorderSum > maxRightBorderSum) {
                maxRightBorderSum = rightBorderSum;
            }
        }
        
        /* 返回该分段中最左,最右,跨界三者中最大数作为该分段最大子列和 */
        return max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);
    }

    /**
     * 求三整数最大值
     * 计算顺序
     * a > b ? (a > c ? a : c) : (b > c ? b : c)
     * @param a
     * @param b
     * @param c
     * @return
     */
    private int max3(int a, int b, int c) {
        return a > b ? a > c ? a : c : b > c ? b : c;
    }

T(N) = T(N/2) + cN

求解得时间复杂度 T(N) = nlogn

方法4:在线处理方法,扫描一个元素解决当前阶段最大子列和

/**
     * 在线处理
     *
     * @param arr
     */
    public int method4(int[] arr) {
        int maxSeqSum = 0;
        int tempMax = 0;
        for (int i = 0; i < arr.length; i++) {
            tempMax += arr[i]; // 向右累加
            if (tempMax > maxSeqSum) {
                maxSeqSum = tempMax; // 发现更大的和则更新当前结果
            } else if (tempMax < 0) { // 如果当前子列和为负
                tempMax = 0; // 则不可能使后面的部分和增大,抛弃之
            }
        }
        return maxSeqSum;
    }

每个元素最少都要扫描一遍,时间复杂度T(N) = n

 

运行代码

public class SearchMaxQueueSum {
    public static void main(String[] args) {
        int[] arr = new int[]{-1, 3, -2, 4, -6, 1, 6, -1};
        SearchMaxQueueSum searchMaxQueueSum = new SearchMaxQueueSum();
        int seqSumFromMethod1 = searchMaxQueueSum.method1(arr);
        int seqSumFromMethod2 = searchMaxQueueSum.method3(arr);
        int seqSumFromMethod3 = searchMaxQueueSum.method3(arr);
        int seqSumFromMethod4 = searchMaxQueueSum.method4(arr);

        System.out.println("method1: "+seqSumFromMethod1);
        System.out.println("method2: "+seqSumFromMethod2);
        System.out.println("method3: "+seqSumFromMethod3);
        System.out.println("method4: "+seqSumFromMethod4);

    }

    public int method1(int[] arr) {
        int maxSum = 0;
        for (int i = 0; i < arr.length; i++) {
            for (int j = i; j < arr.length; j++) {
                int tempMax = 0;
                for (int k = i; k <= j; k++) {
                    tempMax += arr[k];
                }
                if (tempMax > maxSum) {
                    maxSum = tempMax;
                }
            }
        }
        return maxSum;
    }

    public int method2(int[] arr) {
        int maxSum = 0;
        for (int i = 0; i < arr.length; i++) {
            int tempSum = 0;
            for (int j = i; j <= arr.length; j++) {
                tempSum += arr[j];
                if (tempSum > maxSum) {
                    maxSum = tempSum;
                }
            }

        }
        return maxSum;
    }

    /**
     * 为了与方法1和2有同样的调用接口
     * @param arr
     * @return
     */
    public int method3(int[] arr) {
        return divideAndConquer(arr, 0, arr.length - 1);
    }

    /**
     * 将数组划分为两部分,先计算左半部分最大子列和,再计算又半部分最大子列和,再从分界线向左向右分别扫描获取最大子列和取得跨界最大子列和
     *
     * @param arr
     * @param left
     * @param right
     * @return
     */
    private int divideAndConquer(int[] arr, int left, int right) {
        /**
         * 如果索引相等,表示已经划分该部分为最小问题,元素个数为1
         * 若该元素大于0,则对计算下一步运算有帮助,返回原值
         * 若该元素小于0,则无论向左向右加都会减小相邻子列的和,所以舍弃该元素,返回0
         */
        if (left == right) {
            if (arr[left] > 0) {
                return arr[left];
            } else {
                return 0;
            }
        }

        /* 下面是"分"的过程 */
        int mid = (left + right) / 2;
        /* 求最大左子列和 */
        int maxLeftSum = divideAndConquer(arr, left, mid);
        /* 求最大右子列和 */
        int maxRightSum = divideAndConquer(arr, mid + 1, right);

        /* 求跨界最大子列和 */
        int maxLeftBorderSum = 0;
        int maxRightBorderSum = 0;
        int leftBorderSum = 0;
        int rightBorderSum = 0;

        /* 从中线向左扫描 */
        for (int i = mid; i >= left; i--) {
            leftBorderSum += arr[i];
            if (leftBorderSum > maxLeftBorderSum) {
                maxLeftBorderSum = leftBorderSum;
            }
        }

        /* 从中线向右扫描 */
        for (int i = mid + 1; i < right; i++) {
            rightBorderSum += arr[i];
            if (rightBorderSum > maxRightBorderSum) {
                maxRightBorderSum = rightBorderSum;
            }
        }

        /* 返回该分段中最左,最右,跨界三者中最大数作为该分段最大子列和 */
        return max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);
    }

    /**
     * 求三整数最大值
     * 计算顺序
     * a > b ? (a > c ? a : c) : (b > c ? b : c)
     * @param a
     * @param b
     * @param c
     * @return
     */
    private int max3(int a, int b, int c) {
        return a > b ? a > c ? a : c : b > c ? b : c;
    }

    /**
     * 在线处理
     *
     * @param arr
     */
    public int method4(int[] arr) {
        int maxSeqSum = 0;
        int tempMax = 0;
        for (int i = 0; i < arr.length; i++) {
            tempMax += arr[i]; // 向右累加
            if (tempMax > maxSeqSum) {
                maxSeqSum = tempMax; // 发现更大的和则更新当前结果
            } else if (tempMax < 0) { // 如果当前子列和为负
                tempMax = 0; // 则不可能使后面的部分和增大,抛弃之
            }
        }
        return maxSeqSum;
    }

}

运行结果

 

posted @ 2019-08-23 20:46  Star灬木子李  阅读(290)  评论(0编辑  收藏  举报