高级进阶班1——补充(利用平凡解优化流程、通过记录结构找到可能性划分的边界情况、动态规划填表的斜率优化技巧、上中位数结构)

目录

【案例1】

【题目描述】

【思路解析】

【代码实现】

【案例2】

【题目描述】 

【题目描述】

【代码实现】

 【案例3】

扫描二维码关注公众号,回复: 16806473 查看本文章

【题目描述】

【思路解析】

【代码实现】

【案例4】

【题目描述】

【思路解析1】

【思路解析2】

【思路解析3】

【代码实现 仅实现最优解法的代码   对数器使用的是方法1】


【案例1】

【题目描述】

【思路解析】

先通过遍历数组得到整个数组的最小值和最大值,将【最小值,最大值】这个闭区间划分为n+1个小区间,然后整个数组的n个数字一定会分布在这n+1个小区间中,可能某一个区间会含有多个数,但是这些数中的相邻差值一定小于一个区间的长度,然后因为是n+1个小区间,就一定会有个别区间中没有数字,这些空区间左右的两边相邻数字差一定会大于一个区间的长度。(即这n+1个小区间,因为有空区间,所以给出了一个平凡解(区间长度),所以我们不需要再考虑一定小于平凡解的解,所以我们不必求解相同区间的相邻数字的解)。我们只用记录每个区间的最小值和最大值,然后在不同区间中求解。

【代码实现】

/**
 * @ProjectName: study3
 * @FileName: Ex1
 * @author:HWJ
 * @Data: 2023/9/16 15:57
 * 利用平凡解优化的技巧
 */
public class Ex1 {
    public static void main(String[] args) {

    }

    public static int getMaxDeviation(int[] arr){
        if (arr.length < 2) {
            return -1; // 只有一个数字的数组,无法得到两个相邻数的差
        }
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        int len = arr.length;
        for (int i = 0; i < len; i++) {
            max = Math.max(max, arr[i]);
            min = Math.min(min, arr[i]);
        }
        if (max == min){ // 满足此条件表示整个数组为常数数组。
            return 0;
        }
        boolean[] hasNum = new boolean[len + 1];
        int[] maxs = new int[len + 1];
        int[] mins = new int[len + 1];
        int index = 0;
        for (int i = 0; i < len; i++) {
            index = getIndex(len + 1, arr[i], min, max);
            maxs[index] = hasNum[index] ? Math.max(maxs[index], arr[i]) : arr[i];
            mins[index] = hasNum[index] ? Math.min(mins[index], arr[i]) : arr[i];
            hasNum[index] = true;
        }
        int lastMax = maxs[0];
        int res = Integer.MIN_VALUE;
        for (int i = 1; i < len + 1; i++) {
            if(hasNum[i]){
                res = Math.max(mins[i] - lastMax, res);
                lastMax = maxs[i];
            }
        }
        return res;
    }

    public static int getIndex(int len, int num, int min, int max){
        return (int) (num - min) * len / (max - min);
    }
}

【案例2】

【题目描述】 

 给出n个数字a1,……an,问如何将这个数组划分为多个部分,使得这个划分中异或和等于0的部分尽可能多。问最多是多少。

【题目描述】

对于任何一个i位置上的数字,只有两种可能性,(1)它在最优化分情况下能使从k位置到i位置的异或和等于0 ,(2)它在最优划分情况下不能使某一个部分的异或和等于0。所以我们需要解决如果i位置能使最优划分情况下从k位置到i位置的异或和等于0,我们怎么找到这个最近的k位置。我们可以在遍历时记录从0-i位置的异或和,异或和为m,则上一次达到m的位置是k-1。利用这个记录结构我们可以找到那些部分能够使异或和为0,然后再满足不重复的条件下,得到最优划分情况。

【代码实现】

import java.util.HashMap;

/**
 * @ProjectName: study3
 * @FileName: Ex2
 * @author:HWJ
 * @Data: 2023/9/16 16:31
 */
public class Ex2 {
    public static void main(String[] args) {
        int[] arr= {3,2,1,4,0,4,0,3,2,1};
        System.out.println(getBestDivision(arr));
    }

    public static int getBestDivision(int[] arr){
        int[] division = new int[arr.length];
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, -1); // 初始化记录表
        int xor = 0;
        for (int i = 0; i < arr.length; i++) {
            xor ^= arr[i];
            if (map.containsKey(xor)){
                int pre = map.get(xor);
                division[i] = pre == -1 ? 1 : Math.max(division[pre] + 1, division[i - 1]);
            }
            map.put(xor, i);
        }
        return division[arr.length - 1];
    }
}

 【案例3】

【题目描述】

【思路解析】

这道题可以看作使用普通币完成a面值有x种方法,纪念币完成m-a面值有y种方法。然后对于这样使用普通币和纪念币完成m面值的方法为 x*y。所以我们使用两个动态规划分别得到普通币完成0……m面值的方法数,纪念币完成0……m面值的方法数。

对于普通币的动态规划可以进行斜率优化。

1 0 0 1 0
1
1

对于上面的表格可以做是普通币动态规划需要填充的表格,对于上面表格dp[i, j]表示使用0……i种,完成 j 面值的方法数。假设有三种货币分别为3,2,1,需要完成的面值为4。第一列根据定义表示为,使用 i种货币完成0面值的方法数,即不使用币,均为1。第一行表示为使用3这个货币,完成 j面值的方法数,然后第二行表示为使用 2和3这两个货币,完成 j面值的方法数,对于这一行任一j位置,它依赖与dp[i-1][j]  dp[i-1][j - 3]   dp[i-1][j - 6].......对于j - 3依赖dp[i-1][j - 3]   dp[i-1][j - 6].......所以我们对于任一j位置可以优化为dp[i][j] =  dp[i-1][j ] + dp[i][j - 3] .

【代码实现】

/**
 * @ProjectName: study3
 * @FileName: Ex3
 * @author:HWJ
 * @Data: 2023/9/16 17:09
 */
public class Ex3 {
    public static void main(String[] args) {
        int[] arr1 = {2,3,4};
        int[] arr2 = {2,3,1};
        System.out.println(dpWays(arr1, arr2, 8));
    }

    public static int dpWays(int[] arr1, int[] arr2, int m){
        int[][] dp1 = new int[arr1.length][m + 1];
        int[][] dp2 = new int[arr2.length][m + 1];
        for (int i = 0; i < arr1.length; i++) {
            dp1[i][0] = 1;
        }
        for (int i = arr1[0]; i < m + 1; i += arr1[0]) {
            dp1[0][i] = 1;
        }
        for (int i = 1; i < arr1.length; i++) {
            for (int j = 1; j < m + 1; j++) {
                dp1[i][j] = dp1[i - 1][j] + ((j - arr1[i]) >= 0 ? dp1[i][j - arr1[i]] : 0);
            }
        }

        for (int i = 0; i < arr2.length; i++) {
            dp2[i][0] = 1;
        }
        dp2[0][arr2[0]] = 1;
        for (int i = 1; i < arr2.length; i++) {
            for (int j = 1; j < m + 1; j++) {
                dp2[i][j] = dp2[i - 1][j] + ((j - arr2[i]) >= 0 ? dp2[i - 1][j - arr2[i]] : 0);
            }
        }
        int ans = 0;
        for (int i = 0; i < m + 1; i++) {
            ans += dp1[arr1.length - 1][i] * dp2[arr2.length - 1][m - i];
        }
        return ans;
    }
}

【案例4】

【题目描述】

【思路解析1】

利用两个指针进行外排序,时间复杂度为O(K)。最没有营养的解法。

【思路解析2】

因为是两个排好序的数组,那一定可以通过二分查找。

有两种情况第k位的数在A中,第k位的数在B中。

每次在A数组进行二分找到一个数,然后通过这个数在B数组进行二分查找他应该在那个位置。这样我就知道这个数在A排第a + 1位,在B排第b + 1位,那么他在总数中排a+b+1位,通过a+b+1和k的大小关系,我们可以决定下一次二分查找的策略。如果最后在A中找到了,就返回这个数,否则,在B中进行一次这样的查找过程。时间复杂度为O(logM*logN)。

【思路解析3】

上中位数的定义,因为是两个等长度的数组的上中位数,则总长度一定为偶数,则假设总长度为4,a b c d。处于中间的有两个数b 和 c,我们认为b是上中位数,c是下中位数。

我们定义一个函数,他能返回两个数组中的上中位数,要求两个数组等长度。

现在开始解析这个函数的实现方法。这里给出奇数的分析方法,偶数更简单,可自行实现。

a b c d e
1 2 3 4 5

每次分别找到这两个数组的中间的那个数字(a.lenth - 1)/2,如果c==3,则返回他们的数值,他们一定是两个数组的中位数,否则不妨设c>3,那我们现在来考虑有那些数可能成为上中位数,标蓝的就是可能成为上中位数的可能性。但是这样划分的话,我们就没有办法进行递归了,因为我们要求的是两个数组一定要等长,所以我们单独验证3是否为上中位数,排除后用a b 4 5进行递归。递归的上中位数就是总体的中位数。

好了现在我们有了这个函数后,我们进行总体可能性的划分。

长数组的长度为 n,短数组的长度为m

(1)k <=m。

我们直接选取长数组的前k个元素和短数组的前k个元素进行求解上中位数,返回值就是第k位的数。

(2) m < k <= n

当m < k <= n时我们一定能筛选出某些数字一定不能作为第k位,然后删除它们,剩下的两个数组可能不是等长的(长数组会多一个),但是我们可以单独验证个别数来使两个数组变为等长的,然后用这两个剩下的数组进行求解上中位数。

(3)k > n

当k>n时我们一定能筛选出某些数字一定不能作为第k位,然后删除它们,剩下的两个数组应该是等长的,然后用这两个剩下的数组进行求解下中位数。但是我们的函数是求上中位数的,所以我们对这两个等长的数组的第一个元素进行检验,然后再对剩下的两个数组求上中位数。

因为每次取出的数来递归的长度不会超过m,则时间复杂度为O(logM).

【代码实现 仅实现最优解法的代码   对数器使用的是方法1】

package AdvancedPromotion2;

import java.util.Arrays;
import java.util.Random;

/**
 * @ProjectName: study3
 * @FileName: Ex4
 * @author:HWJ
 * @Data: 2023/9/17 15:23
 */
public class Ex4 {
    public static void main(String[] args) {
        Comparator();
    }

    public static int getKDigitNum(int[] arr1, int[] arr2, int k){ // 这里保证k合法, 即在调用函数之前,验证k的合法性。
        int n = Math.max(arr1.length, arr2.length);
        int m = Math.min(arr1.length, arr2.length);
        int[] longs = n == arr1.length ? arr1 : arr2;
        int[] shorts = n == arr1.length ? arr2 : arr1;
        if (k <= m){
            return upperMedian(shorts, longs, 0, k - 1, 0, k - 1);
        } else if (k <= n) {
            int l = k - m;
            if (longs[l - 1] > shorts[m - 1]){
                return longs[l - 1];
            }else {
                return upperMedian(shorts, longs, 0, m - 1, l, k - 1);
            }
        } else {
           int s = k - n;
           int l = k - m;
           if (longs[l - 1] >= shorts[m - 1]){
               return longs[l - 1];
           } else if (shorts[s - 1] >= longs[n - 1]) {
               return shorts[s - 1];
           }else {
               return upperMedian(shorts, longs, s, m - 1, l , n - 1);
           }
        }
    }

    // 求上中位数的函数
    public static int upperMedian(int[] arr1, int[] arr2, int s1, int e1, int s2, int e2) {
        if (s1 == e1) { // 当两个数组只有一个数时,进入baseCase
            return Math.min(arr1[s1], arr2[s2]);
        }
        int mid1 = (e1 - s1) / 2 + s1;
        int mid2 = (e2 - s2) / 2 + s2;
        if (arr1[mid1] == arr2[mid2]) {
            return arr1[mid1];
        } else if (arr1[mid1] > arr2[mid2]) {
            if ((e1 - s1) % 2 != 0) { // 数组长度为偶数情况下
                return upperMedian(arr1, arr2, s1, mid1, mid2 + 1, e2);
            } else { // 数组长度为奇数情况下
                if (arr2[mid2] >= arr1[mid1 - 1]) {
                    return arr2[mid2];
                } else {
                    return upperMedian(arr1, arr2, s1, mid1 - 1, mid2 + 1, e2);
                }
            }

        } else {
            if ((e1 - s1) % 2 != 0) { // 数组长度为偶数情况下
                return upperMedian(arr1, arr2, mid1 + 1, e1, s2, mid2);
            } else { // 数组长度为奇数情况下
                if (arr1[mid1] >= arr2[mid2 - 1]) {
                    return arr1[mid1];
                } else {
                    return upperMedian(arr1, arr2, mid1 + 1, e1, s2, mid2 - 1);
                }
            }
        }
    }

    public static int compare(int[] arr1, int[] arr2, int k){
        int p1 = 0;
        int p2 = 0;
        int ans = 0;
        while (p1 + p2 < k){
            if (p1 != arr1.length && p2 != arr2.length){
                if (arr1[p1] < arr2[p2]){
                    ans = arr1[p1++];

                }else {
                    ans = arr2[p2++];
                }
            }else {
                if (p1 == arr1.length){
                    ans = arr2[p2++];
                }else {
                    ans = arr1[p1++];
                }
            }

        }
        return ans;
    }

    public static void Comparator(){
        Random random = new Random();
        int times = 50000;
        int size = 1000;
        int max = 10000;
        for (int i = 0; i < times; i++) {
            int[] arr1 = new int[random.nextInt(size) + 100];
            int[] arr2 = new int[random.nextInt(size) + 100];
            for (int j = 0; j < arr1.length; j++) {
                arr1[j] = random.nextInt(max);
            }
            for (int j = 0; j < arr2.length; j++) {
                arr2[j] = random.nextInt(max);
            }
            int k = random.nextInt(arr1.length + arr2.length) + 1;
            Arrays.sort(arr1);
            Arrays.sort(arr2);
            int ans1 = getKDigitNum(arr1, arr2, k);
            int ans2 = compare(arr1, arr2, k);
            if (ans1 != ans2){
                System.out.println("失败!!!");
                System.out.println(ans1 + " " + ans2 + "  " + k);
                System.out.println(Arrays.toString(arr1));
                System.out.println(" ------------------ ");
                System.out.println(Arrays.toString(arr2));
                break;
            }
        }
        System.out.println("成功!!!");
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_73936404/article/details/132897175