Computer >> 컴퓨터 >  >> 프로그래밍 >> 프로그래밍

2D 행렬에서 최대 합 직사각형 찾기

문제 개요

정수로 구성된 2차원 행렬이 주어졌을 때, 원소들의 합이 최대가 되는 직사각형(경우에 따라 정사각형) 부분 행렬을 찾는 것이 목표입니다.

이 알고리즘의 핵심 아이디어는 왼쪽 열과 오른쪽 열을 고정하는 것에서 출발합니다. 두 열을 고정한 상태에서 각 행마다 왼쪽 열부터 오른쪽 열까지의 원소 합을 계산하여 임시 배열(temp)에 저장합니다. 그다음 이 1차원 배열에 카데인 알고리즘(Kadane's Algorithm)을 적용하면 최대 합을 가지는 연속 구간, 즉 위쪽 행(top)과 아래쪽 행(bottom)의 위치를 구할 수 있습니다. 고정해 둔 좌우 열과 카데인 알고리즘이 찾아낸 상하 행이 만나면 최대 합 직사각형이 완성됩니다.

입력 및 출력

입력:
정수 행렬
 1  2 -1 -4 -20
-8 -3  4  2   1
 3  8  10 1   3
-4 -1   1 7  -6

출력:
부분 행렬의 좌상단 좌표, 우하단 좌표, 그리고 부분 행렬의 총합
(Top, Left) (1, 1)
(Bottom, Right) (3, 3)
The max sum is: 29
2D 행렬에서 최대 합 직사각형 찾기

알고리즘

kadaneAlgorithm(array, start, end, n)

입력: 각 행의 합이 담긴 배열, 시작·끝 위치를 저장할 참조 변수, 원소 개수 n

출력: 최대 합과 함께 시작·끝 위치를 결정

시작
   sum := 0, maxSum := -∞
   end := -1
   tempStart := 0

   배열의 각 원소 i에 대해 반복
      sum := sum + array[i]
      만약 sum < 0 이면
         sum := 0
         tempStart := i + 1
      아니고 sum > maxSum 이면
         maxSum := sum
         start := tempStart
         end := i
   반복 끝

   만약 end ≠ -1 이면
      maxSum 반환

   // 배열의 모든 원소가 음수인 경우 처리
   maxSum := array[0], start := 0, end := 0

   배열의 1번째부터 n-1번째 원소 i에 대해 반복
      만약 array[i] > maxSum 이면
         maxSum := array[i]
         start := i, end := i
   반복 끝

   maxSum 반환

maxSumRect(Matrix)

입력: 주어진 행렬

출력: 직사각형의 최대 합

시작
   maxSum := -∞
   행렬의 행 수와 같은 크기의 임시 배열 temp 정의

   left := 0 부터 열의 개수까지 반복
      temp 배열을 0으로 초기화
      right := left 부터 열의 개수 - 1 까지 반복
         각 행 i에 대해
            temp[i] := matrix[i][right]

         sum := kadaneAlgorithm(temp, start, end, 행의 개수)
         만약 sum > maxSum 이면
            maxSum := sum
            endLeft := left
            endRight := right
            endTop := start
            endBottom := end
      반복 끝
   반복 끝

   좌상단·우하단 좌표와 maxSum 출력

C++ 구현 예제

#include<iostream>
#define ROW 4
#define COL 5
using namespace std;

int M[ROW][COL] = {
   {1, 2, -1, -4, -20},
   {-8, -3, 4, 2, 1},
   {3, 8, 10, 1, 3},
   {-4, -1, 1, 7, -6}
  };

// 최대 합과 시작·끝 위치를 찾는 카데인 알고리즘
int kadaneAlgo(int arr[], int &start, int &end, int n) {
   int sum = 0, maxSum = INT_MIN;

   end = -1;   // 처음에는 선택된 위치가 없음

   int tempStart = 0;   // 0부터 시작

   for (int i = 0; i < n; i++) {
      sum += arr[i];
      if (sum < 0) {   // 누적 합이 음수가 되면 버리고 새로 시작
         sum = 0;
         tempStart = i+1;
      }else if (sum > maxSum) {   // 최대 합 갱신 시 시작·끝 인덱스 업데이트
         maxSum = sum;
         start = tempStart;
         end = i;
      }
   }

   if (end != -1)
      return maxSum;

   // 배열의 모든 원소가 음수인 경우: 가장 큰 단일 원소 선택
   maxSum = arr[0];
   start = end = 0;

   for (int i = 1; i < n; i++) {
      if (arr[i] > maxSum) {
         maxSum = arr[i];
         start = end = i;
      }
   }
   return maxSum;
}

void maxSumRect() {
   int maxSum = INT_MIN, endLeft, endRight, endTop, endBottom;

   int left, right;
   int temp[ROW], sum, start, end;

   for (left = 0; left < COL; left++) {
      for(int i = 0; i<ROW; i++)   // temp를 0으로 초기화
         temp[i] = 0;

      for (right = left; right < COL; ++right) {
         for (int i = 0; i < ROW; ++i)   // 각 행마다 열 구간의 합을 누적
            temp[i] += M[i][right];
         sum = kadaneAlgo(temp, start, end, ROW);   // (top,left)~(bottom,right) 직사각형의 합 계산

         if (sum > maxSum) {   // 최대값 갱신 시 네 꼭짓점 좌표 저장
            maxSum = sum;
            endLeft = left;
            endRight = right;
            endTop = start;
            endBottom = end;
         }
      }
   }

   cout << "(Top, Left) ("<<endTop<<", "<<endLeft<<")"<<endl;
   cout << "(Bottom, Right) ("<<endBottom<<", "<<endRight<<")"<<endl;
   cout << "The max sum is: "<< maxSum;
}

int main() {
   maxSumRect();
}

실행 결과

(Top, Left) (1, 1)
(Bottom, Right) (3, 3)
The max sum is: 29

시간 복잡도

왼쪽 열과 오른쪽 열의 모든 조합을 살펴보는 데 O(C²)의 시간이 걸리며, 각 조합마다 카데인 알고리즘이 O(R) 시간에 동작합니다. 따라서 전체 시간 복잡도는 O(R × C²)입니다(단, R은 행의 수, C는 열의 수). 모든 가능한 직사각형을 일일이 더해 보는 브루트 포스 방식(O(R²C²))보다 훨씬 효율적이라는 점이 이 알고리즘의 큰 장점입니다.

또한 카데인 알고리즘 내부에는 배열의 모든 원소가 음수인 경우를 처리하는 로직이 포함되어 있어, 그런 입력에서도 가장 큰 단일 원소를 올바르게 반환합니다.