고전적인 동적 프로그래밍 : 가장 큰 하위 배열 문제

최대 하위 배열 문제  는 위에서 언급 한  고전적인 동적 프로그래밍 과 매우 유사합니다. 가장 오래 증가하는 하위 시퀀스 의 루틴은 매우 유사하여 특별한 종류의 동적 프로그래밍 문제를 나타냅니다.

62eaa70490a93418de98f67455620a19.jpeg

사고 분석

사실,이 질문을 처음 보았을 때 가장 먼저 생각한 것은 슬라이딩 윈도우 알고리즘 이었습니다. 왜냐하면 이전에 말했듯이 슬라이딩 윈도우 알고리즘은 하위 문자열 / 주 바레이 문제를 구체적으로 다루기 때문입니다.

그러나 약간의 분석 결과 배열의 숫자가 음수가 될 수 있기 때문에 슬라이딩 윈도우 알고리즘을이 문제에 사용할 수 없음을 발견했습니다 .

슬라이딩 윈도우 알고리즘은 두 개의 포인터로 구성된 창으로 전체 배열 / 하위 문자열을 스캔하는 것에 지나지 않지만, 핵심은 창을 확대하기 위해 오른쪽 포인터를 움직일 때와 창을 줄이기 위해 왼쪽 포인터를 움직일 때를 명확하게 알아야한다는 것입니다.

이 질문에 대해 생각해보세요. 창이 확장되면 음수가 나오고 창의 값이 증가하거나 감소 할 수 있습니다.이 경우 왼쪽 창을 축소해야 할시기를 모르면 찾을 수 없습니다. "최대 하위 배열 합계"가 출력됩니다.

이 문제를 해결하려면 동적 프로그래밍 기술이 필요하지만 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]가장 큰 서브 어레이의 합에 :

66aa78aa0290be2300c79ac3ba0d9369.jpeg

그래서이 경우 그림, 수학적 귀납법을 사용하여 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 상태 전이 방정식을 작성합니다.

_____________


추천

출처blog.51cto.com/15064450/2570978