递归和回溯相关面试算法总结

递归面试算法总结

通常用递归实现的代码比用循环实现的代码简洁,且也更加容易。如过面试没有特殊要求,则可以优先考虑用递归的方法编写程序。
递归虽然有简洁的优点,但也有显著的缺点。递归是函数调用自身,而函数调用自身是有时间和空间消耗的;同时递归中有很多计算是重复的,对性能有很大的影响。尤其要注意递归有可能引起调用栈溢出。

1. LeetCode 剑指 Offer 07 : 重建二叉树

LeetCode 剑指 Offer 07

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

/*
 * 1
 * LeetCode 剑指 Offer 07 : 重建二叉树
 * https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/
 */
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
{
    if(preorder.empty() || inorder.empty())
    {
        return NULL;
    }
    TreeNode* root = new TreeNode(preorder[0]);
    int mid = distance(begin(inorder), find(inorder.begin(), inorder.end(), preorder[0]));
    vector<int> leftPre(preorder.begin()+1, preorder.begin()+mid+1);
    vector<int> rightPre(preorder.begin()+mid+1, preorder.end());
    vector<int> leftIn(inorder.begin(), inorder.begin()+mid);
    vector<int> rightIn(inorder.begin()+mid+1, inorder.end());

    root->left = buildTree(leftPre, leftIn);
    root->right = buildTree(rightPre, rightIn);
    return root;
}

2. LeetCode 687 : 最长同值路径

LeetCode 687

题目:给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。
注意:两个节点之间的路径长度由它们之间的边数表示。

/*
 * 2
 * LeetCode 687 : 最长同值路径
 * https://leetcode-cn.com/problems/longest-univalue-path/
 */
int Length;
int arrowLength(TreeNode* root)
{
    if(root == NULL)
        return 0;
    int left = arrowLength(root->left);
    int right = arrowLength(root->right);
    int arrowLeft = 0, arrowRight = 0;
    if(root->left != NULL && root->left->val == root->val)
        arrowLeft += left + 1;
    if(root->right != NULL && root->right->val == root->val)
        arrowRight += right + 1;
    Length = max(Length, arrowLeft + arrowRight);
    return max(arrowLeft, arrowRight);
}
int longestUnivaluePath(TreeNode* root)
{
    Length = 0;
    arrowLength(root);
    return Length;
}

3. LeetCode 面试题 08.06 : 汉诺塔问题

LeetCode 面试题 08.06

题目:在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。
请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。
你需要原地修改栈。

/*
 * 3
 * LeetCode 面试题 08.06 : 汉诺塔问题
 * https://leetcode-cn.com/problems/hanota-lcci/
 */
void move(int n, vector<int>& A, vector<int>& B, vector<int>& C)
{
    if(n == 1)
    {
        C.push_back(A[0]);
        A.pop_back();
        return;
    }
    move(n-1, A, C, B);    // 将A上面n-1个通过C移到B
    C.push_back(A[0]);     // 将A最后一个移到C
    A.pop_back();          // 这时,A空了
    move(n-1, B, A, C);    // 将B上面n-1个通过空的A移到C
}
void hanota(vector<int>& A, vector<int>& B, vector<int>& C)
{
    int n = A.size();
    move(n, A, B, C);
}

4. LeetCode 894 : 所有可能的满二叉树

LeetCode 894

题目:满二叉树是一类二叉树,其中每个结点恰好有 0 或 2 个子结点。
返回包含 N 个结点的所有可能满二叉树的列表。 答案的每个元素都是一个可能树的根结点。
答案中每个树的每个结点都必须有 node.val=0。
你可以按任何顺序返回树的最终列表。

/*
 * 4
 * LeetCode 894 : 所有可能的满二叉树
 * https://leetcode-cn.com/problems/all-possible-full-binary-trees/
 */
vector<TreeNode*> allPossibleFBT(int N)
{
    if(N%2 == 0)
        return {};
    if(N == 1)
        return {new TreeNode(0)};
    vector<TreeNode*> ans;
    for(int lef = 1; lef + 1 < N; lef++)
    {
        vector<TreeNode*> left = allPossibleFBT(lef);
        vector<TreeNode*> right = allPossibleFBT(N-1-lef);
        for(TreeNode* l : left)
        {
            for(TreeNode* r : right)
            {
                TreeNode *root = new TreeNode(0);
                root->left = l;
                root->right = r;
                ans.push_back(root);
            }
        }
    }
    return ans;
}

5. LeetCode 面试题 17.12 : BiNode

LeetCode 面试题 17.12

题目:二叉树数据结构TreeNode可用来表示单向链表(其中left置空,right为下一个链表节点)。实现一个方法,把二叉搜索树转换为单向链表,要求依然符合二叉搜索树的性质,转换操作应是原址的,也就是在原始的二叉搜索树上直接修改。
返回转换后的单向链表的头节点。
注意:本题相对原题稍作改动

/*
 * 5
 * LeetCode 面试题 17.12 : BiNode
 * https://leetcode-cn.com/problems/binode-lcci/
 */
void inorderTraversal(TreeNode* root, TreeNode* &cur)
{
    //中序遍历
    if(root)
    {
        inorderTraversal(root->left, cur);
        cur->right = root; //将此节点赋给cur的右子树
        root->left = NULL; //将此节点的左子树赋值NULL
        cur = root; //更新
        inorderTraversal(root->right, cur);
    }
}
TreeNode* convertBiNode(TreeNode* root)
{
    TreeNode* head = new TreeNode(-1);
    TreeNode* cur = head;
    inorderTraversal(root, cur);
    return head->right;
}

6. LeetCode 783 : 二叉搜索树节点最小距离

LeetCode 783

题目:给定一个二叉搜索树的根节点 root,返回树中任意两节点的差的最小值。

/*
 * 6
 * LeetCode 783 : 二叉搜索树节点最小距离
 * https://leetcode-cn.com/problems/minimum-distance-between-bst-nodes/
 */
// 在二叉搜索树中,中序遍历会将树中节点按数值大小顺序输出。只需要遍历计算相邻数的差值,取其中最小的就可以了。
int _pre, _res;
bool _flag = true;
void inorder(TreeNode* root)
{
    if(root == NULL)
        return;
    inorder(root->left);
    if(!_flag)
    {
        _res = min(_res, root->val - _pre);
    }
    _pre = root->val;
    _flag = false;
    inorder(root->right);
}
int minDiffInBST(TreeNode* root)
{
    _res = INT_MAX;
    inorder(root);
    return _res;
}

回溯面试算法总结

回溯算法实际上一个类似枚举的搜索尝试过程。回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。

1. LeetCode 79 : 单词搜索

LeetCode 79

题目:给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

/*
 * 1
 * LeetCode 79 : 单词搜索
 * https://leetcode-cn.com/problems/word-search/
 */
vector<vector<int> > dirs = {{-1,0},{0,1},{1,0},{0,-1}};
bool dfs(vector<vector<char>>& board, string& word, int id, int dx, int dy, vector<vector<bool> >& visited)
{
    if(id == word.size()-1)
    {
        return word[id] == board[dx][dy];
    }
    if(word[id] == board[dx][dy])
    {
        for(int i = 0; i < 4; i++)
        {
            visited[dx][dy] = true;
            int ndx = dx + dirs[i][0];
            int ndy = dy + dirs[i][1];
            if(ndx >= 0 && ndx < board.size() && ndy >= 0 && ndy < board[0].size() && (!visited[ndx][ndy]) && dfs(board, word, id+1, ndx, ndy, visited))
            {
                return true;
            }
        }
        visited[dx][dy] = false;
    }
    return false;
}
bool exist(vector<vector<char>>& board, string word)
{
    if(board.size() == 0 || board[0].size() == 0)
    {
        return false;
    }
    if(word.empty())
    {
        return true;
    }
    vector<vector<bool> > visited(board.size(), vector<bool>(board[0].size(), false));
    for(int i = 0; i < board.size(); i++)
    {
        for(int j= 0; j < board[i].size(); j++)
        {
            if(dfs(board, word, 0, i, j, visited))
            {
                return true;
            }
        }
    }
    return false;
}

2. LeetCode 131 : 分割回文串

LeetCode 131

题目:给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例:
输入: “aab”
输出:
[
[“aa”,“b”],
[“a”,“a”,“b”]
]

/*
 * 2
 * LeetCode 131 : 分割回文串
 * https://leetcode-cn.com/problems/palindrome-partitioning/
 */
bool isPalindrome(string s)
{
    int first = 0;
    int end = s.size()-1;
    while(first < end)
    {
        if(s[first++] != s[end--])
            return false;
    }
    return true;
}
void helper(string& s, int pos, vector<string>& path, vector<vector<string> >& result)
{
    if(pos == s.size())
    {
        result.push_back(path);
        return ;
    }
    for(int i = pos; i < s.size(); i++)
    {
        if(isPalindrome(s.substr(pos, i-pos+1)))
        {
            path.push_back(s.substr(pos, i-pos+1));
            helper(s, i+1, path, result);
            path.pop_back();
        }
    }
}
vector<vector<string>> partition(string s)
{
    vector<string> path;
    vector<vector<string> > result;
    helper(s, 0, path, result);
    return result;
}

3. LeetCode 面试题 08.08 : 有重复字符串的排列组合

LeetCode 面试题 08.08

题目:有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。
示例1:
输入:S = “qqe”
输出:[“eqq”,“qeq”,“qqe”]
示例2:
输入:S = “ab”
输出:[“ab”, “ba”]
提示:
字符都是英文字母。
字符串长度在[1, 9]之间。

/*
 * 3
 * LeetCode 面试题 08.08 : 有重复字符串的排列组合
 * https://leetcode-cn.com/problems/permutation-ii-lcci/
 */
void backtrack(const int len, map<char, int>& rec, string pre, vector<string>& res)
{
    if(pre.size() == len)
    {
        res.push_back(pre);
        return;
    }
    auto iter = rec.begin();
    for(; iter != rec.end(); iter++)
    {
        if(iter->second)
        {
            (iter->second)--;
            backtrack(len, rec, pre + iter->first, res);
            (iter->second)++;
        }
    }
    return;
}
vector<string> permutation(string S)
{
    vector<string> res;
    map<char, int> rec;
    for(int i = 0; i < S.size(); i++)
    {
        if(rec.count(S[i])) {
            rec[S[i]]++;
        } else {
            rec[S[i]] = 1;
        }
    }
    backtrack(S.size(), rec, "", res);
    return res;
}

4. LeetCode 39 : 组合总和

LeetCode 39

题目:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate 中的每个元素都是独一无二的。
1 <= target <= 500

/*
 * 4
 * LeetCode 39 : 组合总和
 * https://leetcode-cn.com/problems/combination-sum/
 */
void backtrack(vector<int>& candidates, int target, int id, vector<int>& item, vector<vector<int> >& res)
{
    if(target < 0)
    {
        return;
    }
    if(target == 0)
    {
        res.push_back(item);
        return;
    }
    for(int i = id; i < candidates.size(); i++)
    {
        item.push_back(candidates[i]);
        backtrack(candidates, target-candidates[i], i, item, res);
        item.pop_back();
    }
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target)
{
    vector<vector<int> > res;
    if(candidates.empty())
    {
        return res;
    }
    vector<int> item;
    backtrack(candidates, target, 0, item, res);
    return res;
}

5. LeetCode 40 : 组合总和 II

LeetCode 40

题目:给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]

/*
 * 5
 * LeetCode 40 : 组合总和 II
 * https://leetcode-cn.com/problems/combination-sum-ii/
 */
void backtrack2(vector<int>& candidates, int start, int target, vector<int>& item, vector<vector<int>> & res)
{
    if(target < 0)
        return;
    if(target == 0)
    {
        res.push_back(item);
        return;
    }
    for(int i = start; i < candidates.size(); i++)
    {
        if(i > start && candidates[i] ==candidates[i - 1])
            continue;
        item.push_back(candidates[i]);
        backtrack2(candidates, i+1, target - candidates[i], item, res);
        item.pop_back();
    }
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target)
{
    vector<vector<int>> res;
    vector<int> item;
    sort(candidates.begin(),candidates.end());
    backtrack2(candidates,0,target,item,res);
    return res;
}

6. LeetCode 面试题 08.07 : 无重复字符串的排列组合

LeetCode 面试题 08.07

题目:无重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合,字符串每个字符均不相同。
示例1:
输入:S = “qwe”
输出:[“qwe”, “qew”, “wqe”, “weq”, “ewq”, “eqw”]
示例2:
输入:S = “ab”
输出:[“ab”, “ba”]
提示:
字符都是英文字母。
字符串长度在[1, 9]之间。

/*
 * 6
 * LeetCode 面试题 08.07 : 无重复字符串的排列组合
 * https://leetcode-cn.com/problems/permutation-i-lcci/
 */
// method 1
vector<string> permutation(string S)
{
    vector<string> res;
    dfs(S, 0, res);
    return res;
}
void dfs(string& S, int index, vector<string>& res)
{
    if(index == S.size())
    {
        res.push_back(S);
        return;
    }
    for(int i = index; i < S.size(); ++i)
    {
        swap(S[i], S[index]);
        dfs(S, index + 1, res);
        swap(S[i], S[index]);
    }
}

// method 2
vector<string> permutation(string S)
{
    sort(S.begin(), S.end());
    vector<string> res;
    res.emplace_back(S);
    while(next_permutation(S.begin(), S.end()))
        res.emplace_back(S);
    return res;
}

// method 3
vector<string> permutation(string S)
{
    vector<string> res;
    if(S.size() == 0)
    {
        return res;
    }
    vector<bool> vis(S.size(), false);
    backtrack(S, "", vis, res);
    return res;
}
void backtrack(string& S, string item, vector<bool>& vis, vector<string>& res)
{
    if(S.size() == item.size())
    {
        res.push_back(item);
    } else {
        for(int i = 0; i < vis.size(); i++)
        {
            if(!vis[i])
            {
                item += S[i];
                vis[i] = true;
                backtrack(S, item, vis, res);
                item.pop_back();
                vis[i] = false;
            }
        }
    }
}

7. LeetCode 47 : 全排列 II

LeetCode 47

题目:给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]

/*
 * 7
 * LeetCode 47 : 全排列 II
 * https://leetcode-cn.com/problems/permutations-ii/
 */
void generate(vector<int>& nums, int index, vector<int>& item, vector<bool>& vis, vector<vector<int> >& res)
{
    if(index == nums.size()){
        res.push_back(item);
        return;
    }
    for(int i = 0; i < nums.size(); i++)
    {
        if(vis[i])
            continue;
        if((i > 0) && (nums[i-1] == nums[i]) && (!vis[i-1]))
            continue;
        item.push_back(nums[i]);
        vis[i] = true;
        generate(nums, index+1, item, vis, res);
        item.pop_back();
        vis[i] = false;
    }
}
vector<vector<int>> permuteUnique(vector<int>& nums)
{
    vector<vector<int> > res;
    if(nums.empty())
        return res;
    vector<bool> vis(nums.size(), false);
    vector<int> item;
    sort(nums.begin(),nums.end());
    generate(nums, 0, item, vis, res);
    return res;
}

8. LeetCode 1079 : 活字印刷

LeetCode 1079

题目:你有一套活字字模 tiles,其中每个字模上都刻有一个字母 tiles[i]。返回你可以印出的非空字母序列的数目。
注意:本题中,每个活字字模只能使用一次。
示例 1:
输入:“AAB”
输出:8
解释:可能的序列为 “A”, “B”, “AA”, “AB”, “BA”, “AAB”, “ABA”, “BAA”。
示例 2:
输入:“AAABBC”
输出:188
提示:
1 <= tiles.length <= 7
tiles 由大写英文字母组成

/*
 * 8
 * LeetCode 1079 : 活字印刷
 * https://leetcode-cn.com/problems/letter-tile-possibilities/
 */
int mres;
void dfs(string& tiles, vector<bool>& vis)
{
    for(int i = 0; i < tiles.size(); i++)
    {
        if((i > 0) && (tiles[i] == tiles[i-1] && !vis[i-1]))
            continue;
        if(!vis[i])
        {
            vis[i] = true;
            mres++;
            dfs(tiles, vis);
            vis[i] = false;
        }
    }
}
int numTilePossibilities(string tiles)
{
    mres = 0;
    if(tiles.empty())
        return mres;
    sort(tiles.begin(), tiles.end());
    vector<bool> vis(tiles.size(), false);
    dfs(tiles, vis);
    return mres;
}

总结

以上是递归和回溯相关面试算法题目汇总,个别题目也给出了解题思路和注释。
所有代码都可以去我的GitHub网站查看,后续也将继续补充其他算法方面的相关面试题目。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值