Đếm số phần tử cực đại trong ma trận
Đề bài
Đếm số lượng phần tử cực đại trong ma trận các số thực. Một phần tử được gọi là cực đại khi nó lớn hơn các phần tử xung quanh (tức là lớn hơn tất cả các phần tử lân cận với nó, kể cả các phần tử nằm chéo góc).
Dữ liệu vào và kết quả (mình bổ sung để các bạn chạy thử): bài tập gốc là một bài trong chương ma trận, chỉ yêu cầu viết hàm int CountMaximaInMatrix(double a[][MAX], int rows, int colums) trả về số phần tử cực đại. Để tiện kiểm tra, trong bài này mình quy ước:
- Dòng đầu gồm 2 số nguyên $rows$, $colums$ $(1 \le rows, colums \le 100)$: số dòng và số cột của ma trận.
- $rows$ dòng tiếp theo, mỗi dòng gồm $colums$ số thực là các phần tử của ma trận.
- Kết quả: một số nguyên là số lượng phần tử cực đại trong ma trận.
3 3
1 2 3
4 5 6
7 8 913 4
1 5 2 8
3 2 4 1
9 1 3 74Lời mở đầu
Bài này là một bạn học viên nhắn hỏi mình. Những dạng bài xét các phần tử lân cận thế này có cách code rất gọn, nên mình viết lại thành bài giải để anh em tham khảo nhé. Hy vọng mang đến được giá trị cho các bạn 😇.
Bạn nào muốn thử nộp bài để hệ thống chấm xem có đúng hay không thì link bài đây nhé:
Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.
Nộp bài trên CLUE OJGiải thích ví dụ. Với ma trận ở ví dụ 1, số $5$ ở giữa phải kiểm tra hết xung quanh, cả các đường chéo luôn: xung quanh một số có tối đa 8 số. Còn như số $1$ ở góc thì chỉ có 3 số xung quanh thôi ($2$, $4$, $5$); các số nằm ở biên (như số $2$) thì có 5 số xung quanh. Trong ma trận này chỉ có số $9$ là lớn hơn tất cả các số xung quanh nó ($5$, $6$, $8$) ⇒ đáp án là $1$.
Ở ví dụ 2 có 4 phần tử cực đại: $5$ (lớn hơn $1, 2, 3, 2, 4$), $8$ (lớn hơn $2, 4, 1$), $9$ (lớn hơn $3, 2, 1$) và $7$ (lớn hơn $4, 1, 3$). Số $4$ ở giữa thì không phải vì nó nhỏ hơn $5$, $8$, $7$.
Lộ trình lời giải, bấm vào để nhảy tới:
Cách 1Duyệt 8 ô lân cận bằng 2 mảng dx, dy – O(rows·colums)
Ta sẽ tạo 2 mảng dx và dy:
int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1};
dxlà mảng ứng với chỉ số dòng,dylà mảng ứng với chỉ số cột.- Mỗi mảng có 8 phần tử, là số lượng phần tử lân cận tối đa.
Vậy 2 mảng đó làm sao tính được sự chênh lệch, lúc $-1$ lúc $0$ lúc $1$ là dựa vào đâu? Các bạn nhìn nhé, xung quanh phần tử a[i][j] có tối đa 8 phần tử lân cận:
- Trái trên, ngay bên trên, phải trên: tất cả đều nằm ở dòng bé hơn 1 so với dòng hiện tại, thế nên khởi đầu mảng
dxlà 3 số: $-1, -1, -1$. - Bên trái, bên phải: tất cả đều cùng dòng với dòng hiện tại, thế nên tiếp theo sẽ là 2 số $0, 0$.
- Trái dưới, ngay bên dưới, phải dưới: tất cả đều nằm ở dòng lớn hơn 1 so với dòng hiện tại, thế nên tiếp theo sẽ là 3 số: $1, 1, 1$.
Mảng dy làm tương tự với cột: bên trái là $-1$, cùng cột là $0$, bên phải là $1$. Cặp (dx[k], dy[k]) chính là độ lệch (dòng, cột) của lân cận thứ $k$ so với a[i][j]:
| k = 0(−1, −1) | k = 1(−1, 0) | k = 2(−1, 1) |
| k = 3(0, −1) | a[i][j](0, 0) | k = 4(0, 1) |
| k = 5(1, −1) | k = 6(1, 0) | k = 7(1, 1) |
Thì lúc này, tại vị trí phần tử a[i][j] đang xét trong ma trận, từ index dòng $i$ ta chỉ cần lấy i + dx[k] với $k$ lần lượt là từng index phần tử trong mảng dx, thế là ta đã ra được vị trí index dòng của các phần tử lân cận; làm tương tự với cột j + dy[k]. Thay vì cả trăm dòng xét từng trường hợp thì giờ chỉ cần 2 mảng 😃:
for(int i = 0; i < rows; ++i)
{
for(int j = 0; j < cols; ++j)
{
// với mỗi phần tử a[i][j] trong ma trận, ta đi xét các phần tử kề với nó bằng cách:
for(int k = 0; k < 8; ++k)
{
int idx_row = i + dx[k];
int idx_col = j + dy[k];
if(idx_row >= 0 && idx_row < rows && idx_col >= 0 && idx_col < cols)
{
// lúc này a[idx_row][idx_col] là các phần tử kề với a[i][j]
}
}
}
}
Đơn giản vậy thôi, các bạn làm kinh nghiệm với mấy dạng bài xét các phần tử kề kiểu này nhé 😃. Áp dụng vào bài đếm phần tử cực đại:
- Vòng lặp
for klà để xét qua tất cả các phần tử lân cận vớia[i][j]. - Kiểm tra những index hợp lệ (câu
ifcó 4 điều kiện): bởi ví dụ những phần tử ở góc thì đâu đủ 8 phần tử lân cận xung quanh đúng không? Thành ra chỉ xét những index lân cận hợp lệ, tức là nằm trong ma trận. Nhờ vậy ta không cần tách riêng trường hợp góc hay biên nữa, đã xét cả đường chéo luôn. - Đề yêu cầu đếm các phần tử cực đại, tức là phải lớn hơn tất cả các phần tử lân cận nó. Nên chỉ cần có trường hợp nào mà
a[i][j]bé hơn hoặc bằng một phần tử lân cận hợp lệ thì lập tức cập nhật cờ hiệucheck = falsevà thoát ra. - Sau cùng chỉ cần kiểm tra biến cờ hiệu
check: nếu vẫn làtruetức là không có trường hợp nào màa[i][j]bị $\le$ phần tử lân cận bất kỳ của nó, thì lúc này hiểu rằnga[i][j]chính là 1 cực đại thỏa mãn ⇒count++để đếm. Cuối hàmreturn count;là đáp án bài toán cần.
Các bạn nhập ma trận rồi bấm Tiếp để xem từng ô được so với các lân cận của nó (ô viền nét đứt là vị trí nằm ngoài ma trận, bị bỏ qua):
Source code để các bạn tham khảo. Hàm xử lý chính CountMaximaInMatrix chỉ có khoảng 30 dòng, so với gần 500 dòng chia trường hợp 😆. Mình giữ nguyên tên hàm và tên biến colums như bài tập gốc, chỉ thêm hàm main để đọc dữ liệu và chạy thử:
#include <iostream>
using namespace std;
#define MAX 100
int CountMaximaInMatrix(double a[][MAX], int rows, int colums)
{
int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1};
int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1};
int count = 0;
for(int i = 0; i < rows; ++i)
{
for(int j = 0; j < colums; ++j)
{
// với mỗi phần tử a[i][j], ta đi xét các phần tử kề với nó
bool check = true;
for(int k = 0; k < 8; ++k)
{
int idx_row = i + dx[k];
int idx_col = j + dy[k];
if(idx_row >= 0 && idx_row < rows && idx_col >= 0 && idx_col < colums)
{
// lúc này a[idx_row][idx_col] là phần tử kề với a[i][j]
if(a[i][j] <= a[idx_row][idx_col])
{
check = false;
break;
}
}
}
if(check == true)
count++;
}
}
return count;
}
double a[MAX][MAX];
int main()
{
int rows, colums;
cin >> rows >> colums;
for(int i = 0; i < rows; ++i)
for(int j = 0; j < colums; ++j)
cin >> a[i][j];
cout << CountMaximaInMatrix(a, rows, colums);
return 0;
}
def count_maxima_in_matrix(a, rows, colums):
dx = [-1, -1, -1, 0, 0, 1, 1, 1]
dy = [-1, 0, 1, -1, 1, -1, 0, 1]
count = 0
for i in range(rows):
for j in range(colums):
# với mỗi phần tử a[i][j], ta đi xét các phần tử kề với nó
check = True
for k in range(8):
idx_row = i + dx[k]
idx_col = j + dy[k]
if 0 <= idx_row < rows and 0 <= idx_col < colums:
# lúc này a[idx_row][idx_col] là phần tử kề với a[i][j]
if a[i][j] <= a[idx_row][idx_col]:
check = False
break
if check:
count += 1
return count
data = open(0).read().split()
rows, colums = int(data[0]), int(data[1])
vals = [float(x) for x in data[2:2 + rows * colums]]
a = [vals[i * colums:(i + 1) * colums] for i in range(rows)]
print(count_maxima_in_matrix(a, rows, colums))
Khung chạy tay dưới đây chạy đúng từng dòng của code C++ ở trên, các bạn suy ngẫm thử chạy tay sẽ thấy ngay vì sao đường chéo cũng đã được xét:
Đánh giá độ phức tạp của cách làm này:
- Không gian: ngoài ma trận ra chỉ dùng thêm 2 mảng 8 phần tử và vài biến: $O(1)$.
- Thời gian: mỗi ô xét tối đa 8 lân cận: $O(8 \cdot rows \cdot colums) = O(rows \cdot colums)$.
dx, dy lưu lại sự chênh lệch index dòng và cột so với phần tử hiện tại, rồi duyệt vòng lặp lấy index hiện tại cộng với sự chênh lệch đó, index nào hợp lệ thì phần tử tại đó chính là phần tử lân cận. Mình không biết đây có phải là một kỹ thuật có tên sẵn hay không, nhưng thường gặp những dạng bài này thì mình hay làm như thế 😃. Các bài quân hậu, điểm yên ngựa trên ma trận hay các bài loang (BFS/DFS) trên lưới cũng dùng y chang ý này; muốn xét 4 hướng thì chỉ cần đổi 2 mảng còn 4 phần tử.check vẫn giữ true nên code đếm nó là 1 cực đại (lớn hơn "tất cả" lân cận, mà không có lân cận nào). Đề không nói rõ trường hợp này, các bạn để ý nếu thầy cô có quy ước khác.- Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
- Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
- Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
- Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
- Dễ nản giữa chừng khi không có ai theo sát.
- Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
- Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
- Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
- Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.
Nhận xét
Đăng nhận xét