Sword는 Offer-2.1 배열을 나타냅니다.

정렬

배열은 가장 간단한 데이터 구조라고 할 수 있으며, 연속적인 메모리를 점유하며 데이터를 순서대로 저장합니다. 배열을 생성할 때 먼저 배열의 용량을 지정한 다음 크기에 따라 메모리를 할당해야 합니다. 배열에 숫자 하나만 저장하더라도 모든 데이터에 대해 메모리를 미리 할당해야 합니다. 따라서 어레이의 공간 효율성이 그다지 좋지 않고, 여유 공간이 충분히 활용되지 않는 경우가 많습니다.

배열의 메모리는 연속적이므로 첨자에 따라 모든 요소를 ​​O(1) 시간에 읽고 쓸 수 있으므로 시간 효율성이 매우 높습니다. 배열을 사용하면 배열의 시간 효율성이 높다는 장점을 바탕으로 간단한 해시 테이블을 구현할 수 있습니다. 배열의 첨자를 해시 테이블의 키 값(Key)으로 설정하고, 배열의 각 숫자를 해시 테이블로 설정합니다. 값(Value). 각 첨자와 배열의 첨자에 해당하는 숫자가 키-값 쌍을 형성합니다. 이러한 해시 테이블을 사용하면 O(1)에서 조회를 구현할 수 있으므로 많은 문제를 빠르고 효율적으로 해결할 수 있습니다.

낮은 배열 공간 효율성 문제를 해결하기 위해 사람들은 C++의 STL의 벡터와 같은 다양한 동적 배열을 설계하고 구현해 왔습니다. 낭비를 피하기 위해 먼저 어레이를 위한 더 작은 공간을 연 다음 어레이에 데이터를 추가합니다. 데이터의 개수가 배열의 용량을 초과하면 더 큰 공간을 재할당하고(STL 벡터가 용량을 확장할 때마다 새로운 용량은 이전 용량의 2배가 됨), 이전 데이터를 새 배열에 복사합니다. 메모리 낭비를 줄이기 위해 이전 메모리를 해제합니다. 하지만 어레이 용량을 확장할 때마다 추가 작업이 많아 시간 성능에 부정적인 영향을 미치므로 동적 어레이를 사용할 경우 어레이 용량을 변경하는 횟수를 최소화하도록 노력해야 합니다. 크기.

C/C++에서 배열과 포인터는 서로 관련되어 있지만 서로 다른 두 가지 개념입니다. 배열을 선언할 때 배열 이름은 배열의 첫 번째 요소를 가리키는 포인터이기도 합니다. 포인터를 사용하여 배열에 접근할 수 있습니다. 그러나 C/C++는 배열의 크기를 기록하지 않으므로 포인터를 사용하여 배열의 요소에 액세스할 때 프로그래머는 배열의 경계를 초과하지 않도록 해야 합니다. 배열과 포인터의 차이점을 이해하기 위해 예를 들어 보겠습니다. 다음 코드를 실행해 보세요. 출력은 무엇인가요?

int GetSize(int data[])
{
    
    
    return sizeof(data);
}

int main(int argc, char* argv[])
{
    
    
     int data[] = {
    
    1,2,3,4,5};
     int size1 = sizeof(data1);
     int* data2 = data1;
     int size2 = sizeof(data2);
     int size3 = GetSize(data1);
     printf("%d, %d, %d", size1, size2, size3);
}

대답은 "20,4,4"를 출력하는 것입니다. data1은 배열이고, sizeof(data1)은 배열의 크기를 찾는 것입니다. 이 배열에는 각각 4바이트를 차지하는 5개의 정수가 포함되어 있으므로 총 20바이트가 됩니다. data2는 포인터로 선언되었으며, data1 배열의 첫 번째 숫자를 가리키지만 그 본질은 여전히 ​​포인터입니다. 32비트 시스템에서 임의의 포인터에 대해 sizeof를 수행하면 결과는 항상 4입니다. C/C++에서 배열이 함수의 매개변수로 전달되면 배열은 자동으로 동일한 유형의 포인터로 변환됩니다. 따라서 GetSize 함수의 매개변수 데이터를 배열로 선언하더라도 포인터로 변질되어 size3의 결과는 여전히 4이다.

2차원 배열에서 검색

질문: 2차원 배열에서 각 행은 왼쪽에서 오른쪽으로 오름차순으로 정렬되고, 각 열은 위에서 아래로 오름차순으로 정렬됩니다. 함수를 완성하고, 이러한 2차원 배열과 정수를 입력하고, 배열에 정수가 포함되어 있는지 확인하세요.

예를 들어, 다음 2차원 배열은 각 행과 열에서 오름차순으로 정렬됩니다. 이 배열에서 숫자 7을 검색하면 true를 반환하고, 숫자 5를 검색하면 배열에 해당 숫자가 포함되어 있지 않기 때문에 false를 반환합니다.

1  2  8  9
2  4  9  12
4  7  10 13
6  8  11 15

이 문제를 분석할 때 많은 응시자는 2차원 배열을 직사각형으로 그린 ​​다음 배열에서 숫자를 선택하고 검색 과정을 세 가지 상황으로 분석합니다. 배열에서 선택한 숫자가 찾으려는 숫자와 정확히 같으면 검색 프로세스가 종료됩니다. 선택한 숫자가 찾으려는 숫자보다 작은 경우 배열 정렬 규칙에 따라 찾을 숫자는 현재 선택된 위치의 오른쪽 또는 아래에 있어야 합니다(그림 2.1 (a) 참조). 마찬가지로, 선택한 숫자가 찾으려는 숫자보다 크면 찾을 숫자는 현재 선택한 위치의 위나 왼쪽에 있어야 합니다(그림 2.1(b) 참조).
여기에 이미지 설명을 삽입하세요.
참고: 배열 중앙의 숫자(어두운 사각형)를 선택하고, 크기에 따라 찾고자 하는 숫자가 나타날 수 있는 영역(어두운 영역)을 판단하세요.

위 분석에서는 현재 선택된 위치를 기준으로 찾을 숫자가 두 영역에 나타날 수 있고, 두 영역이 겹치기 때문에 문제가 복잡해 보여 여기서 막히는 분들이 많을 텐데요.

복잡한 문제를 해결해야 할 때 매우 효과적인 방법은 특정 문제부터 시작하여 간단하고 구체적인 사례를 분석하여 보편적인 규칙을 찾으려고 노력하는 것입니다. 이 문제를 해결하기 위해 구체적인 예부터 시작하는 것이 좋습니다. 검색 과정을 단계별로 분석하기 위해 문제에 주어진 배열에서 숫자 7을 찾는 예를 들어보겠습니다.

앞서 문제가 발생한 이유는 찾고자 하는 숫자와 비교하기 위해 2차원 배열의 중간에 있는 숫자를 선택했는데, 이로 인해 다음에 찾아야 할 두 개의 영역이 겹치게 되었기 때문입니다. 우리가 찾고 있는 숫자와 비교하기 위해 배열의 한쪽 모서리에서 숫자를 선택하면 더 쉽지 않을까요?

먼저 배열의 오른쪽 상단에 있는 숫자 9를 선택합니다. 9는 7보다 크고 9는 열 4의 첫 번째(그리고 가장 작은) 숫자이므로 숫자 9와 같은 열에 7이 나타날 수 없습니다. 따라서 고려해야 할 영역에서 이 열을 제거하고 나머지 세 개의 열만 분석하면 됩니다(그림 2.2(a) 참조). 나머지 행렬에서 오른쪽 상단의 숫자는 8입니다. 마찬가지로 8은 7보다 크므로 8이 있는 열을 삭제할 수도 있습니다. 다음으로 남은 두 개의 열만 분석하면 됩니다(그림 2.2(b) 참조).

나머지 두 개의 열로 구성된 배열에서 숫자 2는 배열의 오른쪽 상단에 위치합니다. 2가 7보다 작으면 찾으려는 7이 2의 오른쪽에 있거나 2 아래에 있을 수 있습니다. 이전 단계에서 ⒉ 오른쪽에 있는 열이 제거되었음을 확인했습니다. 즉, 7은 2의 오른쪽에 나타날 수 없으므로 7은 2 아래에만 나타날 수 있습니다. 그래서 숫자 2가 위치한 행도 제거하고, 나머지 3행 2열의 숫자만 분석하였다(그림 2.2© 참조). 나머지 숫자 중 오른쪽 상단에 숫자 4가 있는데, 이전과 마찬가지로 숫자 4가 있는 행을 삭제하고 숫자 2행 2열만 남겨둔다(그림 2.2 (d) 참조).

나머지 2행 2열의 4개 숫자 중 오른쪽 상단에 있는 숫자가 우연히 우리가 찾고 있는 숫자 7이 되어 검색이 종료될 수 있습니다.
여기에 이미지 설명을 삽입하세요.
참고: 매트릭스에서 배경이 음영 처리된 영역이 다음 검색 범위입니다.

위의 검색 과정을 요약하면 다음과 같은 규칙을 발견했습니다. 먼저 배열의 오른쪽 상단에 있는 숫자를 선택합니다. 해당 숫자가 찾고 있는 숫자와 같으면 검색 프로세스가 종료됩니다. 숫자가 찾고 있는 숫자보다 크면 해당 숫자가 있는 열을 삭제합니다. 숫자가 찾고 있는 숫자보다 작으면 숫자가 있는 열을 삭제합니다. 찾고 있으면 해당 번호가 있는 행을 삭제하세요. 즉, 찾으려는 숫자가 배열의 오른쪽 상단에 없으면 매번 배열의 검색 범위에서 하나의 행이나 열이 제거되므로 각 단계에서 검색 범위가 좁아질 수 있습니다. 찾을 번호를 찾거나 검색 범위가 null이 될 때까지.

전체 검색 과정을 명확하게 분석한 후에는 코드 작성이 어렵지 않습니다. 다음은 위의 아이디어에 해당하는 참조 코드입니다.

코드 예

#include <cstdio>

bool Find(int* matrix, int rows, int columns, int number)
{
    
    
	bool found = false;

	if (matrix != nullptr && rows > 0 && columns > 0)
	{
    
    
		int row = 0;
		int column = columns - 1;
		while (row < rows && column >= 0)
		{
    
    
			if (matrix[row * columns + column] == number)
			{
    
    
				found = true;
				break;
			}
			else if (matrix[row * columns + column] > number)
				--column;
			else
				++row;
		}
	}

	return found;
}

// ====================测试代码====================
void Test(const char* testName, int* matrix, int rows, int columns, int number, bool expected)
{
    
    
	if (testName != nullptr)
		printf("%s begins: ", testName);

	bool result = Find(matrix, rows, columns, number);
	if (result == expected)
		printf("Passed.\n");
	else
		printf("Failed.\n");
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 要查找的数在数组中
void Test1()
{
    
    
	int matrix[][4] = {
    
     {
    
    1, 2, 8, 9}, {
    
    2, 4, 9, 12}, {
    
    4, 7, 10, 13}, {
    
    6, 8, 11, 15} };
	Test("Test1", (int*)matrix, 4, 4, 7, true);
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 要查找的数不在数组中
void Test2()
{
    
    
	int matrix[][4] = {
    
     {
    
    1, 2, 8, 9}, {
    
    2, 4, 9, 12}, {
    
    4, 7, 10, 13}, {
    
    6, 8, 11, 15} };
	Test("Test2", (int*)matrix, 4, 4, 5, false);
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 要查找的数是数组中最小的数字
void Test3()
{
    
    
	int matrix[][4] = {
    
     {
    
    1, 2, 8, 9}, {
    
    2, 4, 9, 12}, {
    
    4, 7, 10, 13}, {
    
    6, 8, 11, 15} };
	Test("Test3", (int*)matrix, 4, 4, 1, true);
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 要查找的数是数组中最大的数字
void Test4()
{
    
    
	int matrix[][4] = {
    
     {
    
    1, 2, 8, 9}, {
    
    2, 4, 9, 12}, {
    
    4, 7, 10, 13}, {
    
    6, 8, 11, 15} };
	Test("Test4", (int*)matrix, 4, 4, 15, true);
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 要查找的数比数组中最小的数字还小
void Test5()
{
    
    
	int matrix[][4] = {
    
     {
    
    1, 2, 8, 9}, {
    
    2, 4, 9, 12}, {
    
    4, 7, 10, 13}, {
    
    6, 8, 11, 15} };
	Test("Test5", (int*)matrix, 4, 4, 0, false);
}

//  1   2   8   9
//  2   4   9   12
//  4   7   10  13
//  6   8   11  15
// 要查找的数比数组中最大的数字还大
void Test6()
{
    
    
	int matrix[][4] = {
    
     {
    
    1, 2, 8, 9}, {
    
    2, 4, 9, 12}, {
    
    4, 7, 10, 13}, {
    
    6, 8, 11, 15} };
	Test("Test6", (int*)matrix, 4, 4, 16, false);
}

// 鲁棒性测试,输入空指针
void Test7()
{
    
    
	Test("Test7", nullptr, 0, 0, 16, false);
}

int main(int argc, char* argv[])
{
    
    
	Test1();
	Test2();
	Test3();
	Test4();
	Test5();
	Test6();
	Test7();

	return 0;
}

이전 분석에서는 매번 배열 검색 범위 내에서 오른쪽 상단 모서리 번호를 선택했습니다. 마찬가지로 왼쪽 하단에 있는 숫자를 선택할 수도 있습니다. 관심 있는 독자는 매번 왼쪽 하단 모서리를 선택하는 검색 과정을 분석하고 싶을 수도 있습니다. 하지만 왼쪽 위 모서리나 오른쪽 아래 모서리를 선택할 수는 없습니다. 왼쪽 상단을 예로 들면, 초기 숫자 1은 초기 배열의 왼쪽 상단에 위치하고, 1은 7보다 작으므로 7은 1의 오른쪽이나 아래에 위치해야 합니다. 이때 1이 위치한 행이나 1이 위치한 열을 검색 범위에서 제거할 수 없으므로 검색 범위를 좁힐 수 없습니다.

테스트 케이스

1) 2차원 배열에는 찾고자 하는 숫자가 들어있습니다(찾는 숫자는 배열의 최대값과 최소값이고, 구하려는 숫자는 배열의 최대값과 최소값 사이에 있습니다). 배열).
2) 2차원 배열에 검색된 숫자가 없습니다. (검색된 숫자가 배열의 최대값보다 큰 경우, 검색된 숫자가 배열의 최소값보다 작은 경우, 검색된 숫자가 최대값과 최대값 사이에 있는 경우) 배열의 최소값이지만 배열에 그러한 숫자가 없습니다).
3) 특수 입력 테스트(입력 널 포인터).

테스트 포인트

2차원 배열은 메모리에서 연속적인 공간을 차지합니다. 메모리의 각 행에 요소를 위에서 아래로 저장하고 같은 행에 왼쪽에서 오른쪽 순서로 저장합니다. 따라서 행 번호와 열 번호를 기반으로 배열의 첫 번째 주소를 기준으로 오프셋을 계산하여 해당 요소를 찾을 수 있습니다.

문제가 복잡하다고 생각될 때, 구체적인 예를 통해 규칙을 찾아낼 수 있는지 여부가 문제 해결의 열쇠입니다. 특정 2차원 배열의 오른쪽 상단부터 이 문제를 분석하기 시작하면 검색 패턴을 찾을 수 있고 문제 해결의 돌파구를 찾을 수 있습니다.

추천

출처blog.csdn.net/qq_36314864/article/details/132296018