1.12 LeetCode总结(基本算法)_DFS类

编程总结

每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧

深度优先搜索属于图算法的一种,英文缩写为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;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值