算法学习(31)- 两人抽卡问题最大分数问题

/**
 * @version 0.1
 * @since 2021/10/23
 * @author Void Bug
 *
 *
 * <br>问题描述<br>
 * 有一排正数,玩家A和玩家B都可以看到。
 * 每位玩家在拿走数字的时候,都只能从最左和最右的数中选择一个。
 * 玩家A先拿,玩家B再拿,两人交替拿走所有的数字,
 * 两人都力争自己拿到的数的总和比对方多。请返回最后获胜者的分数。
 * 例如:
 * 5,2,3,4
 * 玩家A先拿,当前他只能拿走5或者4。
 * 如果玩家A拿走5,那么剩下2,3,4。轮到玩家B,此时玩家B可以选择2或4中的一个,…
 * 如果玩家A拿走4,那么剩下5,2,3。轮到玩家B,此时玩家B可以选择5或3中的一个,…
 */
public class Problem_03_CardsInLine {
    
    
    /**
     * win1():由于存在AB玩家都可以看到数字的大小,所以每次都在做一个决策。先开始的玩家势必会拿最大数,而第二玩家势必只能在第一个玩家拿到最大的前提下,自己去拿剩下数字中的最大者。所以用递归就可以很清晰的表达。
     * 针对谁先拿必然是先拿最大者,而后者就是拿最次大者。依序就是 first->second->first—>second …
     * a.其中 当i == j 对于first来说 可以拿到arr[i]
     * b.i == j 对于second来说 拿不到arr[i] 因为first具有优先权。
     * @param array 传入的 array 数值
     * @return 返回我最后获胜者最大分数
     */
    public static int win1(int[] array) {
    
    
        if (array == null || array.length == 0) {
    
    
            return 0;
        }
        return Math.max(first(array, 0, array.length - 1), second(array, 0, array.length - 1));
    }

    /** 先手暴力递归得到从 0 ~ n-1的最大值
     * 先手时的最大获胜者最大分数
     * @param array 传入的 array 数值
     * @param leftIndex 左下标
     * @param rightIndex 右下标
     * @return 先手暴力递归得到从 0 ~ n-1的最大值
     */
    private static int first(int[] array, int leftIndex, int rightIndex) {
    
    
        if (leftIndex == rightIndex) {
    
    
            return array[leftIndex];
        }
        return Math.max(array[leftIndex] + second(array, leftIndex + 1, rightIndex), array[rightIndex] + second(array, leftIndex, rightIndex - 1));
    }

    /** 后手暴力递归得到从 0 ~ n-1的最大值
     * 后手时的最大获胜者最大分数
     * @param array 传入的 array 数值
     * @param leftIndex 左下标
     * @param rightIndex 右下标
     * @return 后手暴力递归得到从 0 ~ n-1的最大值
     */
    private static int second(int[] array, int leftIndex, int rightIndex) {
    
    
        if (leftIndex == rightIndex) {
    
    
            return 0;
        }
        return Math.min(first(array, leftIndex + 1, rightIndex), first(array, leftIndex, rightIndex - 1));
    }

    /**
     * win2()思路
     * win1中递归会存在大量的重复计算。因此我们可以改成dp。dp主要是基于已知的条件来对下一项进行选择。而first玩家的决策,会影响到second玩家,
     * 因此。需要二维dp来解决这个问题。first[][] 记录first玩家的决策路径,second[][]记录second玩家的决策路径。i记录行号,j记录列。i的值不可能大于j 所以左下三角是没有数据的。
     * @param array 传入的 array 数值
     * @return 返回我最后获胜者最大分数
     */
    public static int win2(int[] array) {
    
    
        if (array == null || array.length == 0) {
    
    
            return 0;
        }
        int[][] first = new int[array.length][array.length];
        int[][] second = new int[array.length][array.length];
        for (int j = 0; j < array.length; j++) {
    
    
            first[j][j] = array[j];
            for (int i = j - 1; i >= 0; i--) {
    
    
                first[i][j] = Math.max(array[i] + second[i + 1][j], array[j] + second[i][j - 1]);
                second[i][j] = Math.min(first[i + 1][j], first[i][j - 1]);
            }
        }
        return Math.max(first[0][array.length - 1], second[0][array.length - 1]);
    }

    /**
     * win3的思路是借鉴win1 是基于win1来说的,win1中需要计算first和second的两个值。如果这样想 只计算一个值value,总数sum-value 只需要比较两者最大值就可以了。
     * @param array 传入的 array 数值
     * @return 返回我最后获胜者最大分数
     */
    public static int win3(int[] array) {
    
    
        if (array == null || array.length == 0) {
    
    
            return 0;
        }
        int scores = getMaxSum(array, 0, array.length - 1);
        return Math.max(getSumValue(array) - scores, scores);
    }

    /**
     * 返回 Array 的 总和
     * @param array 传入的 array 数值
     * @return 返回 Array 的 总和
     */
    private static int getSumValue(int[] array){
    
    
        int sum = 0;
        for (int j : array) {
    
    
            sum += j;
        }
        return sum;
    }

    /** 先手 动态规划递归 得到从 0 ~ n-1的最大值
     * 先手时的最大获胜者最大分数
     * @param array 传入的 array 数值
     * @param leftIndex 左下标
     * @param rightIndex 右下标
     * @return 先手暴力递归得到从 0 ~ n-1的最大值
     */
    private static int getMaxSum(int[] array, int leftIndex, int rightIndex) {
    
    
        if (leftIndex == rightIndex) {
    
    
            return array[leftIndex];
        }
        if (leftIndex + 1 == rightIndex) {
    
    
            return Math.max(array[leftIndex], array[rightIndex]);
        }
        return Math.max(
                array[leftIndex] + Math.min(getMaxSum(array, leftIndex + 2, rightIndex), getMaxSum(array, leftIndex + 1, rightIndex - 1)),
                array[rightIndex] + Math.min(getMaxSum(array, leftIndex + 1, rightIndex - 1), getMaxSum(array, leftIndex, rightIndex - 2))
        );
    }

    /**
     * win4的思路是借鉴win3的方法,及将 win3 改成动态规划的方法解决问题
     * @param array 传入的 array 数值
     * @return 返回我最后获胜者最大分数
     */
    public static int win4(int[] array) {
    
    
        if (array == null || array.length == 0) {
    
    
            return 0;
        }
        if (array.length == 1) {
    
    
            return array[0];
        }
        if (array.length == 2) {
    
    
            return Math.max(array[0], array[1]);
        }

        // 初始化对角线及其对角线往上的那一条线的值, array[n][n] 这个除外,如果 array[n][n] 这个在这里赋值的话,则 dp[n][n + 1] 就报错的,故单独赋值
        int[][] dp = new int[array.length][array.length];
        for (int i = 0; i < array.length - 1; i++) {
    
    
            dp[i][i] = array[i];
            dp[i][i + 1] = Math.max(array[i], array[i + 1]);
        }
        dp[array.length - 1][array.length - 1] = array[array.length - 1];

        for (int k = 2; k < array.length; k++) {
    
    
            for (int j = k; j < array.length; j++) {
    
    
                int i = j - k;
                dp[i][j] = Math.max(
                        array[i] + Math.min(dp[i + 2][j], dp[i + 1][j - 1]),
                        array[j] + Math.min(dp[i + 1][j - 1], dp[i][j - 2])
                );
            }
        }
        return Math.max(dp[0][array.length - 1], getSumValue(array) - dp[0][array.length - 1]);
    }

    /**
     * 数组的对数器:生成随机数组,不定长不定值正整型数组
     * @return 返回生成的数组
     */
    public static int[] getRandomArray() {
    
    
        int[] res = new int[(int) (Math.random() * 20) + 1];
        for (int i = 0; i < res.length; i++) {
    
    
            res[i] = (int) (Math.random() * 20) + 1;
        }
        return res;
    }

    /**
     * 主方法
     * @param args 输入的信息
     * 测试方法
     */
    public static void main(String[] args) {
    
    
        int testTime = 500;
        boolean err = false;
        for (int i = 0; i < testTime; i++) {
    
    
            int[] arr = getRandomArray();
            int r1 = win1(arr);
            int r2 = win2(arr);
            int r3 = win3(arr);
            int r4 = win4(arr);
            if (r1 != r2 || r1 != r3 || r1 != r4) {
    
    
                err = true;
            }
        }
        if (err) {
    
    
            System.out.println("3333333333");
        } else {
    
    
            System.out.println("6666666666");
        }
    }

}

猜你喜欢

转载自blog.csdn.net/qq_45205390/article/details/120951836