0. Basic Concepts
0.1 When to use DFS?
When a problem need you to return all possible solutions, which can be permutation or combination, it is a must to use DFS.
Time Complexity: O( # of solutions * cost time of each solution)
0.2 Recall of Recursion?
In most cases, dfs can be implemented by Recursion. Three component of the recursion would be:
- Definition
- Decomposition
- Out-port
1. Problem Type:
1.1 Combination Problem
Problem will Ask: return all the combination that satisfy the conditions that …
Detection: The elements in the combination is not ordered.
Time complexity: O(2^n)
1.1.1 Examples:
Main idea: sort the candidates, remove the duplicates, then dfs the remained elements.
Tips
- Pay attention to the way to remove the duplicates
- In DFS function, we usually will set the combination as one of parameters.
class Solution {
public:
vector<vector<int>> result;
/**
* @param candidates: A list of integers
* @param target: An integer
* @return: A list of lists of integers
*/
vector<vector<int>> combinationSum(vector<int> &candidates, int target) {
// write your code here
if(candidates.empty())
return result;
// remove the dulpicates
sort(candidates.begin(), candidates.end());
int left = 0, right = candidates.size();
for(int i = 1; i < right; i++)
{
if(candidates[i] != candidates[left]){
left++;
candidates[left] = candidates[i];
}
}
candidates.resize(left +1);
vector<int> combination;
backTrace(candidates, target, combination, 0);
return result;
}
void backTrace(vector<int> &candidates,
int remainTarget,
vector<int> & combination,
int startIndex)
{
// corner case
if(remainTarget < 0)
return;
if(remainTarget == 0)
{
result.push_back(combination);
return;
}
for(int i = startIndex; i < candidates.size(); i++)
{
combination.push_back(candidates[i]);
// it could have duplicate elements, thus the startIndex is i.
backTrace(candidates, remainTarget - candidates[i], combination, i);
combination.pop_back();
}
}
};
In this backTrace/DFS function, we could have duplicate element in a combination but we need to remove the duplicate combination in result, but how to do so?
Pay attention to the method how to remove the duplicate here
We could write a condition to detect:
for(int i = startIndex; i < num.size(); i++)
{
if(i != startIndex && num[i] == num[i -1])
continue;
...
}
class Solution {
public:
vector<vector<int>> result;
/**
* @param num: Given the candidate numbers
* @param target: Given the target number
* @return: All the combinations that sum to target
*/
vector<vector<int>> combinationSum2(vector<int> &num, int target) {
// write your code here
sort(num.begin(), num.end());
vector<int> combination;
backTrace(num, target, combination, 0);
return result;
}
void backTrace(vector<int> &num, int remainTarget,
vector<int> & combination, int startIndex)
{
if(remainTarget < 0)
return;
if(remainTarget == 0)
{
result.push_back(combination);
return;
}
for(int i = startIndex; i < num.size(); i++)
{
if(i != startIndex && num[i] == num[i -1])
continue;
combination.push_back(num[i]);
backTrace(num, remainTarget - num[i], combination, i +1);
combination.pop_back();
}
}
};
Write a function to detect whether the string is Palindrome, and a function for back trace.
class Solution {
public:
vector<vector<string>> result;
/*
* @param s: A string
* @return: A list of lists of string
*/
vector<vector<string>> partition(string &s) {
// write your code here
vector<string> subset;
subset.clear();
helper(s, subset);
return result;
}
void helper(const string& remainStr, vector<string>& subset)
{
int len = remainStr.length();
if( len == 0)
{
result.push_back(subset);
return;
}
for(int i = 0; i < len; i++)
{
string tmp = remainStr.substr(0, i+1);
if(isPalindrome(tmp))
{
subset.push_back(tmp);
helper(remainStr.substr(i+1), subset);
subset.pop_back();
}
}
}
bool isPalindrome(string &s){
int left = 0, right = s.size() -1;
while(left < right)
{
if(s[left] != s[right]){
return false;
}
left++;
right --;
}
return true;
}
};
1.2 Permutation Problem
Problem will Ask: return all the permutation that satisfy the conditions that …
Detection: The elements in the combination is order-related.
Time complexity: O(n!)
1.2.1 Examples:
class Solution {
public:
/*
* @param nums: A list of integers.
* @return: A list of permutations.
*/
vector<vector<int>> permute(vector<int> &nums) {
// write your code here
vector<vector<int>> result;
if(nums.empty())
{
result.push_back(vector<int>());
return result;
}
helper(nums, result, nums.size() -1);
return result;
}
void helper(vector<int> &nums, vector<vector<int>> &result, int n)
{
if(n == 0)
result.push_back(nums);
for(int i = 0; i <= n; i++){
swap(nums[i], nums[n]);
helper(nums, result, n -1);
swap(nums[i], nums[n]);
}
}
};
Pay attention to the method how to remove the duplicate here
class Solution {
public:
vector<vector<int>> result;
/*
* @param : A list of integers
* @return: A list of unique permutations
*/
vector<vector<int>> permuteUnique(vector<int> &nums) {
// write your code here
vector<int> permutation;
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
helper(nums, permutation, used);
return result;
}
void helper(vector<int> &nums, vector<int> &permutation, vector<bool> & used)
{
if(nums.size() == permutation.size())
{
result.push_back(permutation);
return;
}
for(int i = 0; i < nums.size(); i++){
if(used[i])
continue;
if(i > 0 && used[i-1] == false && nums[i] == nums[i-1])
continue;
used[i] = true;
permutation.push_back(nums[i]);
helper(nums, permutation, used);
permutation.pop_back();
used[i] = false;
}
}
};
In N-Queens problem, queens are not allow to in the same row/column/Diagonal. Thus, besides the basic permutation, we also need to write a function to validate the result.
class Solution {
public:
vector<vector<string>> result;
/*
* @param n: The number of queens
* @return: All distinct solutions
*/
vector<vector<string>> solveNQueens(int n) {
// write your code here
if(n == 0){
result.push_back(vector<string>());
return result;
}
vector<string> permutation;
vector<bool> visited(n, false);
dfs(permutation, visited, n);
return result;
}
void dfs(vector<string>& permutation, vector<bool>& visited, int n){
if(! isValid(permutation))
return;
if(n == 0){
result.push_back(permutation);
return;
}
string tmp = "";
for(int i = 0; i < visited.size(); i++)
tmp += ".";
for(int i = 0; i < visited.size(); i++){
if(!visited[i])
{
visited[i] = true;
tmp[i] = 'Q';
permutation.push_back(tmp);
dfs(permutation, visited, n -1);
tmp[i] = '.';
permutation.pop_back();
visited[i] = false;
}
}
}
bool isValid(vector<string>& permutation)
{
int n = permutation.size();
if(n == 0)
return true;
int m = permutation[0].length();
vector<int> pos;
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++){
if(permutation[i][j] == 'Q'){
pos.push_back(j);
continue;
}
}
}
for(int i = 0; i < pos.size(); i++){
for(int j = i+1; j < pos.size(); j++){
if(abs(i - j) == abs(pos[i] - pos[j]))
return false;
}
}
return true;
}
};
1.3 Serach in a Graph
1.3.1 Examples:
int ladderLength(string &start, string &end, unordered_set<string> &dict) {
// write your code here
unordered_set<string>starts{start},ends{end};
auto grow=[&dict](unordered_set<string>& starts,unordered_set<string>& ends,int& times)->bool{
++times;
unordered_set<string> ns;
for(auto s:starts)
for(auto&c:s){
auto origi=c;
for(c='a';c<='z';++c)
if(c!=origi)
if(ends.count(s))
return false;
else if(dict.count(s)){
ns.insert(s);
dict.erase(s);
}
c=origi;
}
starts=ns;
return true;
};
auto i=1;
while(grow(starts,ends,i)&&grow(ends,starts,i));
return i;
}
1.3 Stack: Non-Recursion
66. Binary Tree Preorder Traversal
vector<int> preorderTraversal(TreeNode * root) {
// write your code here
vector<int> result;
if(!root)
return result;
stack<TreeNode* > stk;
stk.push(root);
while(! stk.empty() )
{
auto tmp = stk.top(); stk.pop();
result.push_back(tmp->val);
if(tmp ->right)
stk.push(tmp ->right);
if(tmp ->left)
stk.push(tmp ->left);
}
return result;
}
67. Binary Tree Inorder Traversal
public ArrayList<Integer> inorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
ArrayList<Integer> result = new ArrayList<>();
while (root != null) {
stack.push(root);
root = root.left;
}
while (!stack.isEmpty()) {
TreeNode node = stack.peek();
result.add(node.val);
if (node.right == null) {
node = stack.pop();
while (!stack.isEmpty() && stack.peek().right == node) {
node = stack.pop();
}
} else {
node = node.right;
while (node != null) {
stack.push(node);
node = node.left;
}
}
}
return result;
}
68. Binary Tree Postorder Traversal
public ArrayList<Integer> postorderTraversal(TreeNode root) {
ArrayList<Integer> result = new ArrayList<Integer>();
Stack<TreeNode> stack = new Stack<TreeNode>();
TreeNode prev = null; // previously traversed node
TreeNode curr = root;
if (root == null) {
return result;
}
stack.push(root);
while (!stack.empty()) {
curr = stack.peek();
if (prev == null || prev.left == curr || prev.right == curr) { // traverse down the tree
if (curr.left != null) {
stack.push(curr.left);
} else if (curr.right != null) {
stack.push(curr.right);
}
} else if (curr.left == prev) { // traverse up the tree from the left
if (curr.right != null) {
stack.push(curr.right);
}
} else { // traverse up the tree from the right
result.add(curr.val);
stack.pop();
}
prev = curr;
}
return result;
}
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<List<Integer>>();
int n = nums.length;
Arrays.sort(nums);
// 1 << n is 2^n
// each subset equals to an binary integer between 0 .. 2^n - 1
// 0 -> 000 -> []
// 1 -> 001 -> [1]
// 2 -> 010 -> [2]
// ..
// 7 -> 111 -> [1,2,3]
for (int i = 0; i < (1 << n); i++) {
List<Integer> subset = new ArrayList<Integer>();
for (int j = 0; j < n; j++) {
// check whether the jth digit in i's binary representation is 1
if ((i & (1 << j)) != 0) {
subset.add(nums[j]);
}
}
result.add(subset);
}
return result;
}
vector<vector<int> > permute(vector<int> nums) {
vector<vector<int> > permutations;
if (nums.size() == 0) {
permutations.push_back(vector<int>());
return permutations;
}
int n = nums.size();
vector<int> stack;
bool inStack[n];
for (int i = 0; i < n; i++) {
inStack[i] = false;
}
stack.push_back(-1);
while (stack.size() != 0) {
// pop the last
int last = stack[stack.size() - 1];
stack.pop_back();
if (last != -1) {
inStack[last] = false;
}
// increase the last, find the next bigger & avaiable number
int next = -1;
for (int i = last + 1; i < n; i++) {
if (inStack[i] == false) {
next = i;
break;
}
}
if (next == -1) {
continue;
}
// generate the next permutation
stack.push_back(next);
inStack[next] = true;
for (int i = 0; i < n; i++) {
if (!inStack[i]) {
stack.push_back(i);
inStack[i] = true;
}
}
// generate real permutation from index
vector<int> permutation;
for (int i = 0; i < n; i++) {
permutation.push_back(nums[stack[i]]);
}
permutations.push_back(permutation);
}
return permutations;
}