고급 고급1급 - 보충(프로세스 최적화를 위한 사소한 솔루션 사용, 레코드 구조를 통한 가능성 분할 경계 조건 찾기, 동적 프로그래밍 테이블 채우기를 위한 경사 최적화 기술, 상위 중앙값 구조)

목차

【사례 1】

[제목 설명]

[아이디어 분석]

【암호】

【사례 2】

[제목 설명] 

[제목 설명]

【암호】

 【사례 3】

[제목 설명]

[아이디어 분석]

【암호】

【사례 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이 주어지면 이 배열을 여러 부분으로 나누어 가능한 한 많은 부분에서 XOR 합이 0이 되도록 하는 방법을 물어보세요. 최대 개수가 얼마인지 물어보세요.

[제목 설명]

i 위치에 있는 임의의 숫자에 대해 가능성은 두 가지뿐입니다. (1) 최적의 나누기 사례에서 k 위치에서 i 위치까지의 XOR 합을 0과 동일하게 만들 수 있습니다. (2) 다음에서 XOR 합을 만들 수 있습니다. 최적나눗셈의 경우 k 위치에서 i 위치까지의 값은 0이 됩니다. 특정 부분의 XOR 합을 0으로 만들 수는 없습니다. 따라서 최적의 분할 하에서 i 위치가 k 위치에서 i 위치까지의 XOR 합을 0과 동일하게 만들 수 있는지, 가장 가까운 k 위치를 어떻게 찾을 수 있는지 해결해야 합니다. 순회 중에 0-i 위치에서 XOR 합을 기록할 수 있습니다. XOR 합은 m이고, m에 도달하는 마지막 위치는 k-1입니다. 이 레코드 구조를 이용하면 XOR 합을 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가지 방법이 있고, ma의 액면가를 완성하기 위해 기념 주화를 사용하는 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]

상위 중앙값의 정의는 동일한 길이의 두 배열의 상위 중앙값이므로 총 길이는 짝수여야 하며 총 길이는 abcd 4라고 가정합니다. 중앙에는 b와 c라는 두 개의 숫자가 있습니다. b는 상위 중앙값이고 c는 하위 중앙값이라고 생각합니다.

두 배열의 길이가 동일해야 하는 두 배열의 상위 중앙값을 반환할 수 있는 함수를 정의합니다.

이제 이 함수의 구현을 분석해 보겠습니다. 홀수에 대한 분석 방법은 다음과 같습니다. 짝수는 더 간단하고 직접 구현할 수 있습니다.

이자형
1 2 4 5

매번 두 배열의 중간에 있는 숫자(a.lenth - 1)/2를 찾습니다. c==3이면 해당 값을 반환합니다. 이 값은 두 배열의 중앙값이어야 합니다. 그렇지 않으면 다음과 같이 설정하는 것이 좋습니다. c >3이면 이제 어떤 숫자가 상위 중앙값이 될 수 있는지 생각해 보겠습니다. 파란색으로 표시된 숫자는 상위 중앙값이 될 가능성을 나타냅니다. 하지만 이렇게 나누어지면 두 배열의 길이가 같아야 하기 때문에 재귀를 수행할 방법이 없으므로 3이 상위 중앙값인지 별도로 확인하고 이를 제외한 후 ab 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