최대 하위 배열 문제 는 위에서 언급 한 고전적인 동적 프로그래밍 과 매우 유사합니다. 가장 오래 증가하는 하위 시퀀스 의 루틴은 매우 유사하여 특별한 종류의 동적 프로그래밍 문제를 나타냅니다.
사고 분석
사실,이 질문을 처음 보았을 때 가장 먼저 생각한 것은 슬라이딩 윈도우 알고리즘 이었습니다. 왜냐하면 이전에 말했듯이 슬라이딩 윈도우 알고리즘은 하위 문자열 / 주 바레이 문제를 구체적으로 다루기 때문입니다.
그러나 약간의 분석 결과 배열의 숫자가 음수가 될 수 있기 때문에 슬라이딩 윈도우 알고리즘을이 문제에 사용할 수 없음을 발견했습니다 .
슬라이딩 윈도우 알고리즘은 두 개의 포인터로 구성된 창으로 전체 배열 / 하위 문자열을 스캔하는 것에 지나지 않지만, 핵심은 창을 확대하기 위해 오른쪽 포인터를 움직일 때와 창을 줄이기 위해 왼쪽 포인터를 움직일 때를 명확하게 알아야한다는 것입니다.
이 질문에 대해 생각해보세요. 창이 확장되면 음수가 나오고 창의 값이 증가하거나 감소 할 수 있습니다.이 경우 왼쪽 창을 축소해야 할시기를 모르면 찾을 수 없습니다. "최대 하위 배열 합계"가 출력됩니다.
이 문제를 해결하려면 동적 프로그래밍 기술이 필요하지만 dp
배열의 정의는 특별합니다. 기존의 동적 프로그래밍 아이디어에 따르면 dp
배열 은 일반적으로 다음과 같이 정의됩니다 .
nums[0..i]
에서 "가장 큰 하위 배열 합계"입니다dp[i]
.
이와 같이 정의 된 경우 전체 nums
배열의 "가장 큰 하위 배열 합계"는 dp[n-1]
입니다. 상태 전이 방정식을 찾는 방법은 무엇입니까? 수학적 귀납법에 따르면, 우리가 그것을 안다면 dp[i-1]
어떻게 유도 dp[i]
할 수 있을까요?
추신 : 100 개 이상의 원본 기사를 신중하게 작성했으며 전원 버튼에 대한 200 개의 질문을 직접 손으로 직접 브러시로 작성했습니다. 모든 질문 은 지속적으로 업데이트되는 labuladong의 알고리즘 치트 시트에 게시되어 있습니다. 내 기사의 순서대로 질문 을 모으고 브러싱하고 다양한 알고리즘 루틴을 마스터 한 다음 질문의 바다에 던지는 것이 좋습니다.
아래 그림에 도시 된 바와 같이, 우리에 따른 dp
정의 의 배열 지금 ,, dp[i] = 5
동일한 nums[0..i]
가장 큰 서브 어레이의 합에 :
그래서이 경우 그림, 수학적 귀납법을 사용하여 dp[i]
시작할 dp[i+1]
수 있습니까?
실제로 하위 배열이 연속적이어야하므로 작동하지 않습니다. 현재 dp
배열 정의 에 따르면 nums[0..i]
가장 큰 하위 배열 nums[i+1]
이 서로 인접 해 있다는 보장 이 없으므로 dp[i]
추론 할 방법이 없습니다 dp[i+1]
.
따라서 우리 dp
가 이런 식으로 배열을 정의한다고 말하는 것은 올바르지 않으며 적절한 상태 전이 방정식을 얻을 수 없습니다. 이러한 유형의 하위 배열 문제 dp
에 대해 배열의 의미 를 재정의해야합니다 .
받는 사람 nums[i]
으로의 끝 "가장 큰 하위 배열,"dp[i]
.
이 정의 nums
에서 전체 배열의 "최대 하위 배열 합계" 를 얻으려면 직접 반환 할 수 없지만 dp[n-1]
전체 dp
배열 을 순회해야합니다 .
int res = Integer.MIN_VALUE; for (int i = 0; i <n; i ++) { res = Math.max (res, dp [i]); } return res;
상태 전이 관계를 찾기 위해 여전히 수학적 귀납법을 사용합니다. 우리가 이미 그것을 계산했다고 가정하면 dp[i-1]
어떻게 유도 dp[i]
할까요?
수행 될 수있다. dp[i]
두 개의 "선택"이되어 어느 하나의 확대와 서브 어레이를 형성하기 이전에 인접 서브 어레이에 연결]. 또는 이전 서브 어레이와 연결하여 자신의 길을 형성하고, 서브 어레이로서 작용하지.
선택하는 방법? "maximum sub-array sum"이 필요하기 때문에 물론 더 큰 결과를 가진 것을 선택하십시오.
// 자체 방식이거나 이전 하위 배열과 병합됩니다. dp [i] = Math.max (nums [i], nums [i] + dp [i-1]);
요약하면 상태 전이 방정식을 작성했으며 솔루션을 직접 작성할 수 있습니다.
int maxSubArray (int [] nums) { int n = nums.length; if (n == 0) return 0; int [] dp = new int [n]; // 기본 사례 // 첫 번째 요소 앞에 자식이 없습니다. 배열 dp [0] = nums [0]; // (int i = 1; i <n; i ++) { dp [i] = Math.max (nums [i], nums [i] + dp [i-1]); } // nums의 가장 큰 하위 배열 가져 오기 int res = Integer.MIN_VALUE; for (int i = 0; i <n; i ++) { res = Math.max (res, dp [i]) ; } 반환 res; }
위 솔루션의 시간 복잡도는 O (N)이고 공간 복잡도도 O (N)입니다. 폭력적인 솔루션은 이미 매우 훌륭하지만 상태 에만 관련되어 있다는 점에 유의하십시오dp[i]
dp[i-1]
. 그러면 공간 복잡도를 줄이기 위해 "상태 압축"을 수행 할 수 있습니다. 줄이다:
int maxSubArray (int [] nums) { int n = nums.length; (n == 0)이면 0을 반환합니다. // 기본 케이스 int dp_0 = nums [0]; int dp_1 = 0, res = dp_0; for (int i = 1; i <n; i ++) { // dp [i] = max (nums [i], nums [i] + dp [i-1]) dp_1 = Math.max (nums [i] , nums [i] + dp_0); dp_0 = dp_1; // 顺便 计算 最大 的 结果 res = Math.max (res, dp_1); } return res; }
최종 요약
동적 프로그래밍이 상태 전이 방정식을 더 많은 형이상학으로 밀어 붙이는 것은 사실이지만, 대부분은 여전히 따라야 할 몇 가지 규칙이 있습니다.
오늘날이 길은 "가장 큰 하위 배열"과 "가장 긴 증가하는 하위 시퀀스" dp
라는 배열의 정의 와 매우 유사합니다 . " nums[i]
가장 큰 하위 배열 및 / 가장 긴 증가 시퀀스 를 종료하는 것 dp[i]
"입니다. 접촉 을 정의 dp[i+1]
하고 dp[i]
설정 하는 유일한 방법이기 때문에 수학적 귀납법을 사용하여 x 상태 전이 방정식을 작성합니다.
_____________