设计一个函数,判断在矩阵中是否存在一条包含某字符串所有字符的路径。路径可从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。
例如:对于以下矩阵
a b t f
c f c s
j d e h
该矩阵包含bfce
路径,但不包含abfb
路径。
这道题目是一道典型的回溯法题目。
首先在main函数中定义好矩阵和待检验的字符串;
然后在 matrix_path函数中写好逻辑,即循环检测每个方格的路径;
vertify_path函数是核心的判断函数,用来检测从某个方格开始是否有待检测路径;
最后,通过两个测试用例完成功能测试。
注意:边界值测试可以令matrix或str指向空来测试,当然也可以令rows/cols小于等于0来判断。
#include<stdio.h>
#include<stdlib.h> //包含malloc函数
#include<string.h> //包含memset函数
#define true 1
#define false 0
int matrix_path(char* matrix, int rows,int cols, char* str) {
if ((matrix == NULL) || (str == NULL) || (rows <= 0) || (cols <= 0))
return false;
int* visited;
visited = (int*)malloc((rows * cols) * sizeof(int));
memset(visited, 0, (rows * cols) * sizeof(int));
// 测试visited数组是否初始化成功
//for (int i = 0; i < rows; i++) {
// for (int j = 0; j < cols; j++) {
// printf("%d ", visited[i * cols + j]);
// }
// printf("\n");
//}
int pathLength = 0;
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (vertify_path(matrix, rows, cols, str, row, col, pathLength, visited))
return true;
}
}
free(visited);
return false;
}
int vertify_path(const char* matrix, int rows, int cols, char* str, int row, int col, int pathLength, int* visited) {
if (visited == NULL)
return false;
if (str[pathLength] == '\0')
return true;
int path = false;
if ((row >= 0) && (row < rows) && (col >= 0) && (col < cols) && (matrix[row * cols + col] == str[pathLength]) && (!visited[row * cols + col])) {
pathLength++;
visited[row * cols + col] = true;
path = vertify_path(matrix, rows, cols, str, row, col - 1, pathLength, visited) ||
vertify_path(matrix, rows, cols, str, row, col + 1, pathLength, visited) ||
vertify_path(matrix, rows, cols, str, row - 1, col, pathLength, visited) ||
vertify_path(matrix, rows, cols, str, row + 1, col, pathLength, visited);
if (!path) {
pathLength--;
visited[row * cols + col] = false;
}
}
return path;
}
//功能测试
int test1() {
int rows = 3;
int cols = 4;
char matrix[3][4] = { 'a','b','t','g',\
'c','f','c','s',\
'j','d','e','h' };
char str[5] = { 'b','f','c','e' };
int result = matrix_path(matrix, rows, cols, str);
return result;
}
int test2() {
int rows = 3;
int cols = 4;
char matrix[3][4] = { 'a','b','t','g',\
'c','f','c','s',\
'j','d','e','h' };
char str[5] = { 'a','b','f','b' };
int result = matrix_path(matrix, rows, cols, str);
return result;
}
void main() {
int result1 = test1();
if (result1)
printf("find it!\n");
else
printf("This path no exist!\n");
int result2 = test2();
if (result2)
printf("find it!\n");
else
printf("This path no exist!\n");
}
运行结果:
这道题的思路理解起来并不难,但我在编写过程中还是遇到了一些坑。现在把这些问题总结一下。
由于《剑指Offer》中的算法使用C++编写的,所以有些函数和标准C语言中不同,导致我的编程遇到了写没有预料到的bug。
memset
memset函数的头文件是string.h
,它的作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零的最快的方法。
它的定义是:void *memset(void *s, int ch, size_t n);
用法:memset(s,ch,n);
意义:将s中当前位置后面的n个字符用ch替换。
为什么我要强调字符呢?
这个是百度百科里对该函数的解释。一开始,我初始化visited时使用了如下语句:
memset(visited, 0, rows * cols);
运行一下程序,同时把visited输出:
可以看到,只有前3个字符初始化成功,后面的都失败了。当我把初始化语句改为:
memset(visited, 0, (rows * cols) * sizeof(int));
可以看到visited初始化成功!
二维数组
由于计算机的内存是一维的,多维数组的元素应排成线性序列后存入存储器。在C语言中,数组按行优先顺序存储。
在本题中,我定义matrix
时使用的是二维数组:
char matrix[3][4] = { 'a','b','t','g',\
'c','f','c','s',\
'j','d','e','h' };
而在调用过程中,我用一维数组的方式去调用:
matrix[row * cols + col]
程序正常运行,没有任何问题。所以这个二维只是便于我们理解,计算机内部的存储还是按照一维数组来的。
动态数组
已知数组规模的情况下,数组的定义为:
int a[常量表达式]; //定义一个固定长度的数组
其中,常量表达式可以是数字,也可以是宏定义的量。
比如:
//例一
int a[5];
//例二
#define NUM 5
int a[NUM];
若用变量表示则会出错:
在Visual Studio中提示num
必须是常量。
若提前不知数组规模,可以使用动态数组。关于动态数组的定义:
int* matrix; //定义一个指针
int num;
scanf("%d\n",num); //用scanf函数接收num的值
matrix = (int*)malloc(num * sizeof(int)); //给matrix分配内存
for(int i = 0;i < num;i++){
scanf("%d ",matrix[i]); //给matrix分配数据
}
如果题目要求从键盘输入num和matrix的具体值,可用此方法初始化各部分的值。
malloc <–> free
由于malloc
函数和free
函数是成对出现的,因此在使用完后需使用free函数
将matrix的空间释放,否则可能造成内存泄露。