c语言中dfs用pos做参数,LeetCode算法练习——深度优先搜索 DFS(2)

更多干货就在我的个人博客 BlackBlog.tech 欢迎关注!

也可以关注我的csdn博客:黑哥的博客

谢谢大家!

我们继续LeetCode之旅.

做了一段时间的LeetCode,感觉还是不错的。算法很基础,没有特别难的(至少我看在做的),很适合考试,面试,就业之前的训练。对提升基本功很有帮助,我觉得如果有时间的话,每天都做上几道,日积月累,代码能力必然有提高。

这一篇继续进行DFS的练习,预计还会有十道题。

给个目录:

LeetCode109 有序链表转换二叉搜索树

LeetCode332 重新安排行程

LeetCode337 打家劫舍 III

LeetCode394 字符串解码

LeetCode417 太平洋大西洋水流问题

LeetCode473 火柴拼正方形

LeetCode494 目标和

LeetCode491 递增子序列

LeetCode515 在每个树行中找最大值

LeetCode513 找树左下角的值

LeetCode109 有序链表转换二叉搜索树

题目

给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。

示例:

给定的有序链表: [-10, -3, 0, 5, 9],

一个可能的答案是:[0, -3, 9, -10, null, 5], 它可以表示下面这个高度平衡二叉搜索树:

0

/ \

-3 9

/ /

-10 5

C++代码

class Solution {

public:

bool isValidBST(TreeNode* root) {

return dfs(root,LONG_MAX,LONG_MIN);

}

bool dfs(TreeNode* root, long max,long min){

if(!root) return true;

if(root->val<=min || root->val>=max) return false;

else return (dfs(root->left,root->val,min) && dfs(root->right,max,root->val));

}

};

体会

这个题利用了二叉检索树自身的性质,左边节点小于根节点,右边节点大于根节点。初始化时带入系统最大值和最小值,在递归过程中换成它们自己的节点值,用long代替int就是为了包括int的边界条件。

如果这棵树遍历到了叶节点,则返回true。如果在遍历的过程中出现了当前节点大于等于父节点(左子树)或小于等于父节点(右子树)则返回false。对结果做&&运算。返回最终的结果。

LeetCode332 重新安排行程

题目

给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 出发。

说明:

如果存在多种有效的行程,你可以按字符自然排序返回最小的行程组合。例如,行程 ["JFK", "LGA"] 与 ["JFK", "LGB"] 相比就更小,排序更靠前

所有的机场都用三个大写字母表示(机场代码)。

假定所有机票至少存在一种合理的行程

示例 1:

tickets = [["MUC", "LHR"], ["JFK", "MUC"], ["SFO", "SJC"], ["LHR", "SFO"]]

返回["JFK", "MUC", "LHR", "SFO", "SJC"]

示例 2:

tickets = [["JFK","SFO"],["JFK","ATL"],["SFO","ATL"],["ATL","JFK"],["ATL","SFO"]]

返回["JFK","ATL","JFK","SFO","ATL","SFO"]

另一种有效的行程是 ["JFK","SFO","ATL","JFK","ATL","SFO"]。但是它自然排序更大更靠后。

C++代码

class Solution {

public:

unordered_map> hash; //用来存储起点与终点的信息

vector res; //最终的路径结果

vector findItinerary(vector> tickets) {

if(tickets.size()==0) return {}; //如果输入为空 返回空

for (int i=0;i

hash[tickets[i].first].insert(tickets[i].second); //将ticket转化为hash

}

dfs("JFK"); //dfs

reverse(res.begin(), res.end()); //因为最终得到的结果是一个反序列的 我们要返回来

return res;

}

void dfs(string from){

while(hash[from].size()>0){ //如果当前起点还有终点没有去过的话

string tem = *hash[from].begin(); //记录下当前起点去往的第一个终点

hash[from].erase(hash[from].begin()); //将第一个终点从hash中删除

dfs(tem); //继续dfs

}

res.push_back(from); //将当前的出发点加入res

}

};

体会

题目看起来并没有什么难度。DFS+回溯,但我觉得有坑。。。

第一次写,写的这个代码,所有样例测试完全正确。

class Solution {

private:

vector res;

public:

vector findItinerary(vector> tickets) {

vector path;

vector mark;

for(int i=0;i

mark.push_back(0);

path.push_back("JFK");

dfs(tickets,mark,path,"JFK");

tickets.clear();

path.clear();

mark.clear();

return res;

}

void dfs(vector> tickets, vector mark, vector path, string from){

int flag = true;

for(int i=0;i

if(mark[i]==0) flag=false;

}

if(flag == true){

res = path;

return;

}

for(int i=0;i

if(tickets[i].first==from && mark[i]==0){

path.push_back(tickets[i].second);

mark[i] = 1;

dfs(tickets,mark,path,tickets[i].second);

mark[i]=0;

path.pop_back();

}

}

return;

}

};

可是,每次提交都会报一个错误,本地调试也没有问题。就很迷。

换了思路重新写。

各位有兴趣麻烦帮我检查一下代码。

另外,此题少给出了一个条件。就是默认输出最长的路径,路径长度相同时,再以字典序列输出。

LeetCode337 打家劫舍 III

题目

小偷又发现一个新的可行窃的地点。 这个地区只有一个入口,称为“根”。 除了根部之外,每栋房子有且只有一个父房子。 一番侦察之后,聪明的小偷意识到“这个地方的所有房屋形成了一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

在不触动警报的情况下,计算小偷一晚能盗取的最高金额。

示例 1:

3

/ \

2 3

\ \

3 1

能盗取的最高金额 = 3 + 3 + 1 = 7.

示例 2:

3

/ \

4 5

/ \ \

1 3 1

能盗取的最高金额 = 4 + 5 = 9.

C++代码

class Solution {

public:

//偷 对第一个根节点 采取偷与不偷的方式 最后求最大值

int rob(TreeNode* root) {

if(!root) return 0;

return std::max(robRoot(root),notRobRoot(root));

}

//偷根节点的情况 就不能偷两个子节点

int robRoot(TreeNode *root){

if(!root) return 0;

return notRobRoot(root->left) +notRobRoot(root->right)+root->val;

}

//不偷根节点的情况 可以偷两个子节点 但不一定就要偷

int notRobRoot(TreeNode *root){

if(!root) return 0;

return rob(root->left) + rob(root->right);

}

};

体会

这个题属于思路很清晰就很简单的题。

首先我们来分析题目,题目中说如果两个节点连在一起,那么偷东西就会被发现。所有只要我们偷东西的节点不连在一起就没有问题。连在一起的情况只有根节点与子节点。

对于一个根节点分为偷与不偷两种情况,如果偷的话,那么他的子节点就不能偷;如果不偷的话,可以偷他的子节点,也可以不偷,根据偷到的金额去做判断。对于每个根节点,我们要把这两周情况都考虑上,代码如上。

LeetCode394 字符串解码

题目

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例:

s = "3[a]2[bc]", 返回 "aaabcbc".

s = "3[a2[c]]", 返回 "accaccacc".

s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef".

C++代码

class Solution {

public:

string decodeString(string s) {

dfs(s);

return s;

}

void dfs(string &s){

int l=0,r=0; //方括号的左起点 右起点

string num_s=""; //存储数字的部分

int num_len =0; //数字的长度

int num; //数字本身

//遍历找方括号

for(int i=0;i

if(s[i]=='[') { //如果是左括号

l = i; //记录位置

num = atoi(num_s.data()); //将之前存储的num转换为数字

num_len=num_s.size(); //得到数字的长度 用于后面裁切字符串

num_s.clear(); //清空 方式影响后面内容

}

else if(s[i]==']') {

r = i; //记录右括号位置

break; //结束循环

}

else if(s[i]<='9' && s[i]>='0') num_s+=s[i]; //记录每一位的数字

}

if(l>=r) return ; //结束条件

string newstr=""; //预备重复的部分字符串

for(int i=0;i

newstr+=s.substr(l+1,r-l-1); //重复字符串

s = s.substr(0,l-num_len)+newstr+s.substr(r+1,s.length()-r-1); //制作新的字符串

dfs(s);//递归调用

}

};

体会

算是一道完全的模拟题。

我用的思路比较简单,每次都只处理最里面的一个[],将其中的内容重复。逐次递归调用,最后解析完所有的字符串。注意s = s.substr(0,l-num_len)+newstr+s.substr(r+1,s.length()-r-1);各个字符串的起始位置和裁切长度。

LeetCode417 太平洋大西洋水流问题

题目

给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。

规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。

请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。

提示:

输出坐标的顺序不重要

m 和 n 都小于150

示例:

给定下面的 5x5 矩阵:

太平洋 ~ ~ ~ ~ ~

~ 1 2 2 3 (5) *

~ 3 2 3 (4) (4) *

~ 2 4 (5) 3 1 *

~ (6) (7) 1 4 5 *

~ (5) 1 1 2 4 *

* * * * * 大西洋

返回:

[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (上图中带括号的单元).

C++代码

class Solution {

public:

vector> pacificAtlantic(vector>& matrix) {

if (matrix.empty() || matrix[0].empty()) return {};

int m = matrix.size(); //矩阵行数

int n = matrix[0].size(); //矩阵列数

vector> res; //最终结果

vector> pacific(m, vector(n, false)); //太平洋

vector> atlantic(m, vector(n, false)); //大西洋

//遍历第一行,最底下一行

for(int i=0;i

dfs(matrix,pacific,i,0,matrix[i][0]);

dfs(matrix,atlantic,i,n-1,matrix[i][n-1]);

}

//遍历第一列,最右边一列

for(int i=0;i

dfs(matrix,pacific,0,i,matrix[0][i]);

dfs(matrix,atlantic,m-1,i,matrix[m-1][i]);

}

//将pacific与atlantic重合的部分 作为最终结果

for(int i=0;i

for(int j=0;j

if(atlantic[i][j] && pacific[i][j])

res.push_back(make_pair(i,j));

}

}

return res;

}

void dfs(vector>& matrix,vector> &visited,int x,int y,int val){

//越界 被访问 或者 当前值小于上一个值的都直接返回

if (x<0 || x>matrix.size()-1 || y<0 || y>matrix[0].size()-1 || visited[x][y] || matrix[x][y] < val) return;

//将该坐标设置为访问过

visited[x][y] = true;

dfs(matrix,visited,x+1,y,matrix[x][y]);

dfs(matrix,visited,x-1,y,matrix[x][y]);

dfs(matrix,visited,x,y+1,matrix[x][y]);

dfs(matrix,visited,x,y-1,matrix[x][y]);

}

};

体会

这个题第一眼看到觉得是一个很简单DFS搜索题。第一个想法肯定是对每一个点进行dfs,看他是否能够走到太平洋或者大西洋。显然这样,时间复杂度会很高。我们可以对其优化,每一次得到一条路径后,将路径上的所有点都记录下来,这样可以减少很多次dfs的过程,但优化效果不明显。

这个题,我们采用倒推法的思路。从大西洋、太平洋向内搜索,每次找比当前节点高的或者相同的节点,将这些节点设置为True。最终将大西洋中为True的,太平洋中也为True的节点作为最终的答案。

LeetCode473 火柴拼正方形

题目

还记得童话《卖火柴的小女孩》吗?现在,你知道小女孩有多少根火柴,请找出一种能使用所有火柴拼成一个正方形的方法。不能折断火柴,可以把火柴连接起来,并且每根火柴都要用到。

输入为小女孩拥有火柴的数目,每根火柴用其长度表示。输出即为是否能用所有的火柴拼成正方形。

示例 1:

输入: [1,1,2,2,2]

输出: true

解释: 能拼成一个边长为2的正方形,每边两根火柴。

示例 2:

输入: [3,3,3,3,4]

输出: false

解释: 不能用所有火柴拼成一个正方形。

注意:

给定的火柴长度和在 0 到 10^9之间。

火柴数组的长度不超过15。

C++代码

class Solution {

public:

bool makesquare(vector& nums) {

if(nums.size()<4 || nums.size()==0) return false; //如果数组的大小不满足4 就直接返回0

int sum = accumulate(nums.begin(),nums.end(),0); //对整个数组进行求和

int target = sum /4 ; //计算每一条边需要的长度

vector sums(4,0); //新建一个长度为4的数组,用来记录每一个边当前的长度

sort(nums.rbegin(),nums.rend()); //对数组进行排序,先计算大的,这样可以优化不少效率

bool res = dfs(nums,sums,0,target); // dfs

return res;

}

bool dfs(vector& nums,vector sums, int pos, int target){

if(pos>nums.size()-1){ //如果pos>nums.size()-1证明数组已经被遍历完,判断当前所有的边是否都满足要求

return sums[0]==target && sums[1]==target && sums[2]==target && sums[3]==target;

}

//对每一条边进行尝试

for(int i=0;i<4;i++){

if(sums[i]+nums[pos]>target) continue; //如果i边 放入一个数字后,超过target则不放入,进入下一条边

sums[i]+=nums[pos]; //i边 长度增加

if(dfs(nums,sums,pos+1,target)) return true; //pos+1 继续dfs

sums[i]-=nums[pos]; //回溯

}

return false;

}

};

体会

这个题是一个比较标准DFS+回溯题。这问题可以理解为,一个数组如何分为四个相同的数。我们创建一个数组,长度为四,用来表明四条边当前的长度,每次遍历时将当前的长度与待放入长度相加后target进行比较。大于,则不放入,小于,则放入,不断dfs,每次pos+1。当pos超出范围时,证明所有的数字都被尝试过,返回当前的状态。

LeetCode494 目标和

题目

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例 1:

输入: nums: [1, 1, 1, 1, 1], S: 3

输出: 5

解释:

-1+1+1+1+1 = 3

+1-1+1+1+1 = 3

+1+1-1+1+1 = 3

+1+1+1-1+1 = 3

+1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。

注意:

数组的长度不会超过20,并且数组中的值全为正数。

初始的数组的和不会超过1000。

保证返回的最终结果为32位整数。

C++代码

class Solution {

public:

int solution =0; //方案数

int findTargetSumWays(vector& nums, int S) {

int sum = 0;

dfs(nums,S,0,sum);

return solution;

}

void dfs(vector& nums,int S,int pos,int sum){

//S表示最后结果,pos表示当前位置,sum表示目前总和

if(pos>nums.size()-1) {

if(sum == S) solution+=1; //如果当前sum==S 方案数+1

return ;

}

sum += nums[pos]; //先尝试+

dfs(nums,S,pos+1,sum); //dfs

sum -= nums[pos]; //回溯

sum -= nums[pos]; //尝试-

dfs(nums,S,pos+1,sum); //dfs

sum += nums[pos]; //回溯

return ;

}

};

体会

一道非常清晰的DFS+回溯题,我觉得都可以做标准模版了。S表示最后结果,pos表示当前位置,sum表示目前总和,solution表示方案数。dfs中先尝试+,回溯后,尝试-。当sum == S时,方案数+1。最终返回方案数。

LeetCode491 递增子序列

题目

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

输入: [4, 6, 7, 7]

输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

说明:

给定数组的长度不会超过15。

数组中的整数范围是 [-100,100]。

给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

C++代码

class Solution {

public:

set> res; //使用set 实现去除重复的功能

vector> findSubsequences(vector& nums) {

vector> ans; //最终结果

vector path; //每一次遍历的路径

//对数组中的每一个元素都要dfs

for(int i=0;i

dfs(nums,path,i);

}

//将set中的数据转换到vector

set>::iterator it; //迭代器

for(it=res.begin();it!=res.end();it++){

ans.push_back(*it);

}

return ans;

}

void dfs(vector nums,vector path,int pos){

//如果path的大小为0,则证明这是第一个数据,直接放入

if(path.size()==0) path.push_back(nums[pos]);

//向后遍历,逐个添加

for(int i=1;i

//如果待添加的值小于path的最后一个值 不添加,如果 当前的位置大于数组长度 证明 nums已经遍历完 此时continue其实就是return了

if(nums[pos+i]nums.size()-1)) continue;

path.push_back(nums[pos+i]);//path放入新数据

res.insert(path); //将path添加进set

dfs(nums, path, pos + i); //对新的数据再进行dfs

path.pop_back(); //回溯

}

}

};

体会

求一个序列中的所有递增子序列。首先区分一下子序列与子串,子序列中的数字不需要是连续的,子串的数字是连续的。因为数据中不能有重复,我们使用set来达到这一效果。dfs内部,path表示当前的路径,pos表示当前的位置。思路很清晰,我们从当前数据开始,对其后面的每一个数据做插入操作后,继续dfs,知道完成整个序列。注意回溯。

LeetCode515 在每个树行中找最大值

题目

您需要在二叉树的每一行中找到最大的值。

示例:

输入:

1

/ \

3 2

/ \ \

5 3 9

输出: [1, 3, 9]

C++代码

class Solution {

public:

map> hash; //key为树的深度 vector存放该层的数据

vector largestValues(TreeNode* root) {

dfs(root,0);

vector res;

for(auto key : hash){

res.push_back(*max_element(key.second.begin(),key.second.end())); //将每一行的最大值存在res中

}

return res;

}

void dfs(TreeNode* root,int depth){

if(!root) return;

hash[depth].push_back(root->val); //将当前节点存在hash中对应层的数组中

if(root->left) dfs(root->left,depth+1); //遍历左孩子

if(root->right) dfs(root->right,depth+1); //遍历右孩子

}

};

体会

思路很清晰,map的key表示第几层,value表示对应的节点。我们使用DFS的方法遍历整个树。遍历的过程中,记录下每一个节点的深度,将每一层的节点都存在map中对应数组中。最后将每一层最大的数据存储来即可。

LeetCode513 找树左下角的值

题目

给定一个二叉树,在树的最后一行找到最左边的值。

示例 1:

输入:

2

/ \

1 3

输出:

1

示例 2:

输入:

1

/ \

2 3

/ / \

4 5 6

/

7

输出:

7

C++代码

class Solution {

public:

map> hash;

int max_depth = INT_MIN; //初始化最大深度为一个极小值

int findBottomLeftValue(TreeNode* root) {

dfs(root,0); //从根节点开始dfs

return hash[max_depth][0]; //将最底下一层最左边的元素返回出来

}

void dfs(TreeNode* root,int depth){

if(!root) { // 如果是叶节点下面的节点 对最大深度作出修改

if(depth-1>max_depth) max_depth=depth-1;

return;

}

hash[depth].push_back(root->val); //将对应层的节点依次放入

dfs(root->left,depth+1); //dfs左子树

dfs(root->right,depth+1); //dfs右子树

}

};

体会

这个题跟上一个题的思路完全一致,没有任何区别。唯一的不同就在于我们这一次不需要将一行中的最大值求出来,我们只需要返回最左边的那个值就可以了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值