목차
[코드 구현은 최적해에 대한 코드만 구현합니다. 로그마이저는 방법 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("成功!!!");
}
}