编程总结
每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧
深度优先搜索属于图算法的一种,英文缩写为DFS即Depth First Search.其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次.
最经典的就是二叉树的深度了,DFS到左右子树叶子节点,取左右子树最大深度为二叉树的深度,下面使用递归来实现:
int maxDepth(struct TreeNode* root){
if (root == NULL) {
return 0;
}
int lenLeft = maxDepth(root->left) + 1;
int lenRight = maxDepth(root->right) + 1;
return lenLeft > lenRight ? lenLeft : lenRight;
}
101. 对称二叉树
二叉树往往都是递归调用,这里是DFS,先遍历左右节点的左节点的左值是否和右节点的右值相等,遍历完成后(有一个达到叶子节点就会满足递归条件退出),开始遍历左节点的右值和右节点的左值是否相等。
bool isSymmetric(struct TreeNode* root){
if (root == NULL) {
return true;
}
return fun(root->left, root->right);
}
int fun(struct TreeNode* l_root, struct TreeNode* r_root)
{
if (l_root == NULL && r_root == NULL) {
return true;
}
if (l_root == NULL || r_root == NULL) {
return false;
}
if ((l_root->val == r_root->val) && fun(l_root->left, r_root->right) && (fun(l_root->right, r_root->left))) {
return true;
}
return false;
}
110. 给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1
// 计算二叉树的深度
int height(struct TreeNode* root) {
if (root == NULL) {
return 0;
} else {
return fmax(height(root->left), height(root->right)) + 1;
}
}
bool isBalanced(struct TreeNode* root) {
if (root == NULL) { //达到根节点返回
return true;
} else {
return fabs(height(root->left) - height(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);
}
}
1)当前节点其左右深度差小于1
2)当前节点左右子节点进入递归
->以此来说明二叉树是平衡二叉树
17. 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
输入:“23”
输出:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”].
树形问题 – 递归调用的一个重要特征–回溯
const int digitsLen[8] = { 3, 3, 3, 3, 3, 4, 3, 4 }; // 每个按键组合的情况
const char *digitsStr[8] = { "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz", };
void dfs(char *digits, int len, char **res, int *returnSize, char *buf, int idx) {
int i;
if (*digits == '\0') { /* 递归终止条件 */
res[*returnSize] = (char*)malloc(sizeof(char) * (len + 1));
printf("------res[*returnSize]------ = %s\n", buf);
strcpy_s(res[(*returnSize)++], sizeof(buf) + 1, buf);
return;
}
/* 根据当前数字, 选择对应的长度和字符 */
for (i = 0; i < digitsLen[(*digits) - '2']; i++) { // 数字键盘从2开始的
buf[idx] = digitsStr[(*digits) - '2'][i];
printf("buf[idx] = %c, idx = %d\n", buf[idx], idx);
dfs(digits + 1, len, res, returnSize, buf, idx + 1); /* 递与归 */
}
}
char **letterCombinations(char *digits, int *returnSize) {
if (digits == NULL || strlen(digits) == 0) {
*returnSize = '\0';
return NULL;
}
int len = strlen(digits);
char **res = (char**)malloc(sizeof(char*) * 10000);
char *buf = (char*)malloc(sizeof(char) * (len + 1));
buf[len] = '\0';
*returnSize = 0;
// 上述是初始化操作
dfs(digits, len, res, returnSize, buf, 0);
return res;
}
int main()
{
char **str1 = NULL; // 指针的指针
int strsSize = 3;
char *a = NULL;
char tmp[4] = "234";
int returnSize = 0;
letterCombinations(tmp, &returnSize);
return 0;
}
下面打印出上述程序的结果,发现其中程序运行过程和那幅图一模一样,先递归下去,直到遇到递归终止条件终止,然后,调用 i++,调用下一个dfs程序,直到 i == digitslen 不符合条件时,当前层次退出循环,前一层的 i++ ,继续遍历调用,好神奇的算法!
沿着一条路径找答案,到 ‘底’ 的话,就回去上一层继续调用,直到根节点那层的递归调用都完成,整个程序才完成。
46. 全排列
给定一个没有重复数字的序列,返回其所有可能的全排列。
/*
1. 递归到第几层depth
2. Path 代表已经选了哪些数
3. visited 代表是否遍历
*/
int gCount;
void DFS(int *nums, int numsSize, int depth, int *path, int *visited, int **res)
{
// 递归终止条件,满足 depth == numSize,
if (depth == numsSize) {
res[gCount] = (int *)malloc(sizeof(int) * numsSize);
memcpy(res[gCount], path, sizeof(int) * numsSize);
gCount++;
return;
}
// 全排列遍历
for (int i = 0; i < numsSize; i++) {
// 如果已经遍历了,continue
if (visited[i] == true) {
continue;
}
path[depth] = nums[i];
visited[i] = true;
DFS(nums, numsSize, depth + 1, path, visited, res);
// 如果满足了递归终止条件,会下来,visited 需要重新赋值 false
// 回到上一层节点时需要状态重置, Path栈里的值会更新掉(无需重置)
visited[i] = false;
}
}
int **permute(int *nums, int numsSize, int *returnSize, int **returnColumnSizes)
{
(*returnSize) = 1; // 初始值
// 全排列 n!
for (int i = 1; i <= numsSize; i++) {
(*returnSize) *= i;
}
*returnColumnSizes = (int *)malloc(sizeof(int) * (*returnSize));
for (int i = 0; i < (*returnSize); i++) {
(*returnColumnSizes)[i] = numsSize;
}
int **res = (int **)malloc(sizeof(int *) * (*returnSize));
int *path = (int *)malloc(sizeof(int) * numsSize);
int *visited = (int *)malloc(numsSize * sizeof(int));
gCount = 0;
DFS(nums, numsSize, 0, path, visited, res);
return res;
}
int main() {
int **res = (int **)malloc(sizeof(int *) * 3);
int nums[3] = { 1, 2, 3 };
int numsSize = 3;
int returnSize = 3;
int *returnColumnSizes = (int *)malloc(sizeof(int *));
for (int i = 0; i < 3; i++) {
res[i] = (int *)malloc(sizeof(int) * 3);
}
res = permute(nums, numsSize, &returnSize, &returnColumnSizes);
return 0;
}
37. 编写一个程序,通过已填充的空格来解决数独问题
一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
解题思路
1, 申明三个布尔数组表明行,列,还有9宫格来标识数字是否被使用过,并且初始化
2, 回溯法,
1)控制变量的设定,选定行 row 列 col 作为回溯法的控制变量,不同的row和col标识回溯函数在不同的层
2)边界定位,当 row = 9 col = 9 时说明进行到了最后一个数字则返回成功
3)填充条件,当(row, col)位置有数字时,则直接调用下一个(row, col + 1)
当(row, col)位置为空时,1 - 9 循环判断是否可以填入,则填入数字,回溯调用下一个(row, col + 1)
4)回退条件,及回退措施,
回退条件:1 - 9无数可填
回退措施:将刚填的 board[row][col] = '.', 并且将 三个布尔数组恢复原值,让上一层能够回退回去进行填写下一个数
代码
//1,申明三个布尔数组表明行,列,还有9宫格来标识数字是否被使用过,并且初始化
//2,回溯法,
//1)控制变量的设定,选定行 row 列 col 作为回溯法的控制变量,不同的row和col标识回溯函数在不同的层
//2)边界定位,当 row=9 col=9 时说明进行到了最后一个数字则返回成功
//3)填充条件,当(row,col)位置有数字时,则直接调用下一个(row, col + 1)
//当(row,col)位置为空时,1-9 循环判断是否可以填入,则填入数字,回溯调用下一个(row, col + 1)
//4)回退条件,及回退措施,
//回退条件:1-9无数可填
//回退措施:将刚填的 board[row][col]='.',并且将 三个布尔数组恢复原值,让上一层能够回退回去进行填写下一个数
*/
bool traceBackSudoku(char **board, char **rowUsed, char **colUsed, char **spaceUsed, int row, int col) {
int i = 0;
// 3.边界条件
if (col == 9) {
col = 0;
row += 1; // 到下一行
if (row == 9) {
return true;
}
}
// 4.填充数字
if (board[row][col] == '.')
{
for (i = 1; i <= 9; i++)
{
printf("i=%d, row=%d, col=%d %d-%d-%d\n", i, row, col, rowUsed[row][i - 1], colUsed[col][i - 1], spaceUsed[(row / 3) * 3 + (col / 3)][i - 1]);
if ((0 == rowUsed[row][i - 1]) &&
(0 == colUsed[col][i - 1]) &&
(0 == spaceUsed[(row / 3) * 3 + (col / 3)][i - 1]))
{
board[row][col] = i + '0';
rowUsed[row][i - 1] = 1;
colUsed[col][i - 1] = 1;
spaceUsed[(row / 3) * 3 + (col / 3)][i - 1] = 1;
if (true == traceBackSudoku(board, rowUsed, colUsed, spaceUsed, row, col + 1)) {
return true;
}
else {
// 5.回退措施
board[row][col] = '.';
rowUsed[row][i - 1] = 0;
colUsed[col][i - 1] = 0;
spaceUsed[(row / 3) * 3 + (col / 3)][i - 1] = 0;
}
}
}
}
else {
return traceBackSudoku(board, rowUsed, colUsed, spaceUsed, row, col + 1);
}
return false;
}
void solveSudoku(char **board, int boardSize, int *boardColSize)
{
int i = 0;
int j = 0;
char **pRowUsed = (char **)malloc(sizeof(char *) * 9); // 行中是否有数字被使用过
char **pColUsed = (char **)malloc(sizeof(char *) * 9); // 列中是否有数字被使用过
char **pSpaUsed = (char **)malloc(sizeof(char *) * 9); // 九宫格中是否有数字被使用过
// 1.初始化
for (i = 0; i < 9; i++) {
pRowUsed[i] = (char *)malloc(sizeof(char) * 9);
memset(pRowUsed[i], 0x00, sizeof(char) * 9);
pColUsed[i] = (char *)malloc(sizeof(char) * 9);
memset(pColUsed[i], 0x00, sizeof(char) * 9);
pSpaUsed[i] = (char *)malloc(sizeof(char) * 9);
memset(pSpaUsed[i], 0x00, sizeof(char) * 9);
}
for (i = 0; i < 9; i++)
{
for (j = 0; j < 9; j++)
{
if ((board[i][j] >= '1') && (board[i][j] <= '9'))
{
pRowUsed[i][board[i][j] - '1'] = 1; // 将行中信息从board记录到pRowUsed数组里
pColUsed[j][board[i][j] - '1'] = 1; // 将列中信息从board记录到pRowUsed数组里
pSpaUsed[(i / 3) * 3 + (j / 3)][board[i][j] - '1'] = 1; // 将九宫格中信息从board记录到pRowUsed数组里 [(i / 3) * 3 + (j / 3)]把九宫格展成横的了
}
}
}
// 2,调用回溯函数
traceBackSudoku(board, pRowUsed, pColUsed, pSpaUsed, 0, 0);
return;
}
int main(void)
{
int boardColSize = 0;
char **board = NULL;
board = (char **)malloc(sizeof(char *) * 9);
char maze[9][9] = { {'5', '3', '.', '.', '7', '.', '.', '.', '.'},
{'6', '.', '.', '1', '9', '5', '.', '.', '.'},
{'.', '9', '8', '.', '.', '.', '.', '6', '.'},
{'8', '.', '.', '.', '6', '.', '.', '.', '3'},
{'4', '.', '.', '8', '.', '3', '.', '.', '1'},
{'7', '.', '.', '.', '2', '.', '.', '.', '6'},
{'.', '6', '.', '.', '.', '.', '2', '8', '.'},
{'.', '.', '.', '4', '1', '9', '.', '.', '5'},
{'.', '.', '.', '.', '8', '.', '.', '7', '9'} };
for (int i = 0; i < 9; i++) {
board[i] = (char *)malloc(9 * sizeof(char));
board[i] = &maze[i][0];
}
solveSudoku(board, 81, &boardColSize);
}
129. 求根到叶子节点数字之和
给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。
int dfs(struct TreeNode* root, int prevSum) {
if (root == NULL) {
return 0;
}
int sum = prevSum * 10 + root->val;
if (root->left == NULL && root->right == NULL) {
return sum;
} else {
return dfs(root->left, sum) + dfs(root->right, sum);
}
}
int sumNumbers(struct TreeNode* root) {
return dfs(root, 0);
}
872. 叶子相似的树
请考虑一棵二叉树上所有的叶子,这些叶子的值按从左到右的顺序排列形成一个 叶值序列 。
#define MAX_NUM 200
void FindLeafs(struct TreeNode *root, int *result, int *returnSize)
{
// 前序遍历,找叶子节点,并存入到 result数组里
if ((root->left == NULL) && (root->right == NULL)) {
result[(*returnSize)++] = root->val; // 小技巧,这里是好的处理,returSize记录叶子节点的个数
}
if (root->left != NULL) {
FindLeafs(root->left, result, returnSize);
}
if (root->right != NULL) {
FindLeafs(root->right, result, returnSize);
}
}
bool leafSimilar(struct TreeNode *root1, struct TreeNode *root2) {
int *result1 = (int *)malloc(MAX_NUM * sizeof(int));
int *result2 = (int *)malloc(MAX_NUM * sizeof(int));
int retSize1 = 0;
int retSize2 = 0;
FindLeafs(root1, result1, &retSize1);
FindLeafs(root2, result2, &retSize2);
if (retSize1 != retSize2) {
return false;
}
int i;
for (i = 0; i < retSize1; i++) {
if (result1[i] != result2[i]) {
return false;
}
}
return true;
}
78. 子集
// 字典排序
int **subsets(int *nums, int numsSize, int *returnSize, int **returnColumnSizes)
{
int **ans = malloc(sizeof(int *) * (1 << numsSize));
*returnColumnSizes = malloc(sizeof(int) * (1 << numsSize));
*returnSize = 1 << numsSize;
int t[numsSize];
for (int mask = 0; mask < (1 << numsSize); ++mask) { // 1. 生成0-2的n次方所有01字符串
int tSize = 0;
for (int i = 0; i < numsSize; ++i) { // 2. 如果字符串中该数位为1,则加入子集.
if (mask & (1 << i)) {
t[tSize++] = nums[i];
}
}
int *tmp = malloc(sizeof(int) * tSize);
memcpy(tmp, t, sizeof(int) * tSize);
(*returnColumnSizes)[mask] = tSize;
ans[mask] = tmp;
}
return ans;
}
90. 子集 II
int** subsetsWithDup(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
qsort(nums, numsSize, sizeof(int), cmp);
int n = numsSize;
*returnSize = 0;
*returnColumnSizes = malloc(sizeof(int) * (1 << n));
int** ans = malloc(sizeof(int*) * (1 << n));
for (int mask = 0; mask < (1 << n); ++mask) {
int* t = malloc(sizeof(int) * n);
int tSize = 0;
bool flag = true;
for (int i = 0; i < n; ++i) {
if (mask & (1 << i)) {
if (i > 0 && (mask >> (i - 1) & 1) == 0 && nums[i] == nums[i - 1]) {
flag = false;
break;
}
t[tSize++] = nums[i];
}
}
t = realloc(t, sizeof(int) * tSize);
if (flag) {
ans[*returnSize] = t;
(*returnColumnSizes)[(*returnSize)++] = tSize;
}
}
ans = realloc(ans, sizeof(int*) * (*returnSize));
return ans;
}
113. 路径总和 II
int **ret;
int *retColSize;
int retSize;
int *path;
int pathSize;
// 我们可以采用深度优先搜索的方式,枚举每一条从根节点到叶子节点的路径。
// 当我们遍历到叶子节点,且此时路径和恰为目标和时,我们就找到了一条满足条件的路径。
void dfs(struct TreeNode *root, int targetSum)
{
if (root == NULL) {
return;
}
path[pathSize] = root->val;
pathSize++;
targetSum = targetSum - root->val;
if (root->left == NULL && root->right == NULL && targetSum == 0) {
int *tmp = (int *)malloc(sizeof(int) * pathSize);
memcpy(tmp, path, sizeof(int) * pathSize);
ret[retSize] = tmp;
retColSize[retSize++] = pathSize;
}
dfs(root->left, targetSum);
dfs(root->right, targetSum);
pathSize--;
}
int **pathSum(struct TreeNode *root, int targetSum, int *returnSize, int **returnColumnSizes)
{
ret = (int **)malloc(sizeof(int*) * 2001);
retColSize = (int *)malloc(sizeof(int) * 2001);
path = (int *)malloc(sizeof(int) * 2001);
retSize = pathSize = 0;
dfs(root, targetSum);
*returnColumnSizes = retColSize;
*returnSize = retSize;
return ret;
}
695. 岛屿的最大面积
int dfs(int **grid, int gridSize, int *gridColSize, int row, int col)
{
//越界处理
if (row < 0 || row >= gridSize || col < 0 || col >= gridColSize[0] || grid[row][col] == 0) {
return 0;
}
//每次计算后清零
grid[row][col] = 0;
//dfs处理
return 1 + dfs(grid, gridSize, gridColSize, row + 1, col) +
dfs(grid, gridSize, gridColSize, row - 1, col) +
dfs(grid, gridSize, gridColSize, row, col + 1) +
dfs(grid, gridSize, gridColSize, row, col - 1);
}
int maxAreaOfIsland(int** grid, int gridSize, int* gridColSize)
{
int area, j, max = 0, i;
//遍历二维数组
for (i = 0; i < gridSize; i++) {
for (j = 0; j < gridColSize[0]; j++) {
area = dfs(grid, gridSize, gridColSize, i, j);
max = max > area ? max : area;
}
}
return max;
}