写这篇博客的原因是,对于要输出打印所有结果的题目:
比如:微软面试100题中的第4题,
在二元树中找出和为某一值的所有路径(树)
题目:输入一个整数和一棵二元树。
从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。
打印出和与输入整数相等的所有路径。
例如 输入整数 22 和如下二元树
10
/
\
5 12
/ \
4
7
则打印出两条路径:10, 12 和 10, 5, 7
以及在leetcode上面的 word break题目。
Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each word is a valid dictionary word.
Return all such possible sentences.
For example, given
s = "catsanddog",
dict = ["cat", "cats", "and", "sand", "dog"].
A solution is ["cats and dog", "cat sand dog"].
上述题目均要求返回所有的满足条件的结果,那么我们只需要记住我们遇到的所有的可行解,或者是边记录边输出。
首先讲微软的面试题,要求输出所有的满足条件的路径。
对于树,我们知道所有路径都是从根节点到叶子节点,那么每经过一个中间节点,我们需要记录下 sum - 当前路径的值,直到叶子节点,我们比较当前的值与叶子节点是否相等,如果相等,那么我们得到了一条合法的路径,输出即可。
输出之后呢,将面临回溯的问题(类似于DFS)。
完整代码如下:
#include<iostream>
#include<algorithm>
#include<vector>
#include<iterator>
using namespace std;
struct TreeNode{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x):val(x),left(NULL),right(NULL){};
};
void printPath(vector<int> & path)
{
// cout << root->val << endl;
// path.push_back(root->val);
copy(path.begin(), path.end(), ostream_iterator<int>(cout, " "));
cout << endl;
return;
}
/*
* treeSum如果传入的是path形参 并非它的引用,那么空间复杂度可能会是指数级别的。
* 如果传入的是引用,那么必须考虑在每一个节点的左孩子和右孩子考虑完毕之后,弹出该节点,
* 因为如果左右孩子中有合适的路径出现的话,那么该路径已经被输出,因此必须弹出该节点。
*/
void treeSum(TreeNode *root, int sum, vector<int> &path)
{
if(root == NULL)return;
if(root->left == NULL && root->right==NULL && root->val == sum)
printPath(path);
treeSum(root->left, sum-root->val, path);
treeSum(root->right, sum-root->val, path);
path.pop_back();//在每一个节点的左右孩子均考虑完之后,弹出该节点,继续DFS
}
int main()
{
TreeNode *node10 = new TreeNode(10);
TreeNode *node5 = new TreeNode(5);
TreeNode *node12 = new TreeNode(12);
TreeNode *node4 = new TreeNode(4);
TreeNode *node7 = new TreeNode(7);
TreeNode *node6 = new TreeNode(6);
node10->left = node5;
node10->right = node12;
node5->left = node4;
node5->right = node7;
//node12->left = node6;
TreeNode *root= node10;
int sum = 22;
vector<int> path;
treeSum(node10,sum, path);
delete node10;
delete node5;
delete node12;
delete node4;
delete node7;
delete node6;
return 0;
}
对于leetcode的word break II 问题,
首先,确定一个字符串是否可以拆分,动态规划解决。
在面临要输出所有的可行解的时候,需要使用一个备忘录记录下每一步的前驱结点。(即记录下每个单词的前一个单词的位置)。因为前一个单词可能不唯一,最大的个数为n(总共n个位置)。
注:对于第i个位置而言,判断s[0...i]是否可分,如果该单词本身就在字典中,那么它的前驱index是-1,如果s[j ... i - 1]是一个合法的单词,并且s[0 ... j - 1 ]也是合法的单词,那么它的前驱index应该是 j - 1 ,以此类推,每个位置的前驱节点的个数至多为n。
由于上述出现了-1这个负数,那么我们定义s的下标从1开始。即下述“第i个字符”表示s[i-1].
因此,定义二维数组备忘录prev[n+1][n],其中prev[i]表示第i个字符的所有的前驱结点的index的集合。
class Solution {
private:
void backTrack(string s, vector<vector<int> > &prev, vector<string > &ret, string &path, int step)
{
if(step == 0) ret.push_back(path);
if(prev[step].size())
{
for(int j = 0; j < prev[step].size(); ++j)
{
string temp = path;
if(!path.empty()) path = ' ' + path;
path = s.substr(prev[step][j], step - prev[step][j] ) + path;
backTrack(s,prev,ret,path,prev[step][j]);
path = temp;
}
}
}
public:
vector<string> wordBreak(string s, unordered_set<string> &dict) {
vector<string> ret;
if(s.empty()) return ret;
int len = s.size();
vector<vector< int > > prev(len+1,vector<int>());
vector<bool > memo(len+1,false);
memo[0] = true;
for(int i = 1 ; i < len+1 ; ++i)
{
for(int j = 0; j < i; ++j)
{
if(memo[j] && dict.find(s.substr(j,i-j)) != dict.end())
{
prev[i].push_back(j);
memo[i] = true;
}
}
}
string path;
backTrack(s,prev,ret,path,len);
return ret;
}
};
对于这样的题目,我一直感觉到很疑惑,对于递归分析的不熟悉,拿到这样的题目,不知如何下手,今写博客一篇,希望以后可以多加注意理解。谨以此文与君共勉。