面试算法———回溯经典题目

方法论:

  1. 看题五分钟,不会做,看解析;
  2. 先看中文站,再看国际站;
  3. 选择最优解析;
  4. 回头再来写

面试四步走:

  1. 和面试官,探讨题目限制条件;
  2. 说说可能解,选择最优解;
  3. 码字;
  4. 跑测试用例

分治模板

C/C++
int divide_conquer(Problem *problem, int params) {
  // recursion terminator
  if (problem == nullptr) {
    process_result
    return return_result;
  } 

  // process current problem
  subproblems = split_problem(problem, data)
  subresult1 = divide_conquer(subproblem[0], p1)
  subresult2 = divide_conquer(subproblem[1], p1)
  subresult3 = divide_conquer(subproblem[2], p1)
  ...

  // merge
  result = process_result(subresult1, subresult2, subresult3)
  // revert the current level status
 
  return 0;
}

50.Pow(x, n)

题目:实现 pow(x, n) ,即计算 x 的 n 次幂函数。

可能解:

  1. O(n)暴力解
  2. O(longn)快速幂+递归
# 1.暴力解,会超出时间限制
class Solution {
public:
    double myPow(double x, int n) {
        double  res = 1;
        long long N = n;
        if (N < 0) {
            N = -N;
            x = 1/x;
        }

        for (long long i = 0; i < N; i++){
            res *= x;
        }
        return res;
    }
};
# 2. 快速幂+递归
class Solution {
public:
    double myPow(double x, int n) {
        long long N = n;
        if (N < 0) {
            N = -N;
            x = 1/x;
        }
        return fastPow(x, N);
    }

private: double fastPow(double x, long long n) {
    // terminator
    if (n == 0) return 1.0;

    // process current logic + drill down
    double half = fastPow(x, n/2);
    // restore current logic 
    return n % 2 == 0 ? half * half : half * half * x;
}
};

169. 多数元素

题目:给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

可能解:

  1. 因为给定数组总存在多数元素,因此,可以将数组排序,然后num.size() / 2一定是多数元素;
  2. 利用hashmap记录元素个数;
  3. 随机选取数组中的值,如果该值个数超过一半,那么就是所求值;
  4. 利用分治求解;
  5. 利用Moore 投票;
# 2 hashmap求解
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int, int> umap;
        for (int num : nums){
            umap[num]++;
            if (umap[num] > nums.size() / 2)    return num;
        }
        return -1;
    }
};
# 3. 随机求解
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        while(1){
            int candidate = nums[rand() % nums.size()];
            int count = 0;
            for (int num : nums){
                if (num == candidate)
                    ++count;
            }
            if (count > nums.size() / 2)    return candidate;
        }
    }
};
# 4.利用分治求解
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        return helper(nums, 0, nums.size() - 1);
    }

private:
    int helper(vector<int>& nums, int left, int right){
        // helper(nums, l, r),返回[l, r]中出现最多的数
        // terminator
        if (left == right)  return nums[left];

        // process current problem
        int mid = left + ((right - left) >> 1);
        int lm = helper(nums, left, mid);
        int rm = helper(nums, mid + 1, right);
        
        // merge and revert current status
        if (lm == rm)   return lm;
        return count(nums.begin() + left, nums.begin() + mid + 1, lm) > count(nums.begin() + mid + 1, nums.begin() + right + 1, rm) ? lm : rm;
    }
};
# Moore投票
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int count = 0;
        int major = -1;
        for (auto c : nums){
            if (count == 0){
                major = c;
            }
            count += (major == c) ? 1 : -1;
        }
        return major;
    }
};

78.子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

**说明:**解集不能包含重复的子集。

思路:子集的另一种解法,在这里有中新解法,给定数组nums,对于其中的每个元素,我们可以通过选或者不选的形式来写递归;

class Solution {
    void generateSub(const vector<int>& nums, vector<vector<int>>& res, vector<int>& path, int start){
        // terminator
        if (start == nums.size())   {
            res.push_back(path);
            return;
        }

        // not pick up the number at this index 
        generateSub(nums, res, path, start+1 );
        
        // pick the number at this index
        path.push_back(nums[start]);
        generateSub(nums, res, path, start+1 );
        path.pop_back();
    }
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> path; 
        generateSub(nums, res, path, 0);
        return res;
    }
};

17.电话号码的字母组合

题目:给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

可能解:

  1. 回溯解法:递归树中,每个节点可能的选择,就是手机数字上面的3或4个可选字母;
  2. 队列求解;17.电话号码字母组合,队列图解 My Java solution with FIFO queue
# 解法1
class Solution {
private:
    const string letterMap[10] = {
        "", //0
        "", //1
        "abc",  // 2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz"  //9
    };
    
    // 变化的是index, 和选路
    void findCombination(const string& digits, vector<string>& res, string& s, int start){
        if (start == digits.size()){
            res.push_back(s);
            return;
        }
        char c = digits[start];
        string letters = letterMap[c - '0'];

        // 选择列表
        for (int i = 0; i < letters.size(); i++){
            // 做选择
            s = s + letters[i];
            findCombination(digits, res, s, start+1);
            s.erase(s.end() -1);
        }
        return;
    }
public:
    vector<string> letterCombinations(string digits) {
        vector<string> res;
        if (digits == "")
            return res;
        string s = "";
        findCombination(digits, res, s, 0);
        return res;    
    }
};
# 解法2
class Solution {
private:
    const string letterMap[10] = {
        "", //0
        "", //1
        "abc",  // 2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz"  //9
    };

    
    // 变化的是index, 和选路
    void findCombination(const string& digits, vector<string>& res, string& s, int start){
        if (start == digits.size()){
            res.push_back(s);
            return;
        }
        char c = digits[start];
        string letters = letterMap[c - '0'];

        // 选择列表
        for (int i = 0; i < letters.size(); i++){
            // 做选择
            s = s + letters[i];
            findCombination(digits, res, s, start+1);
            s.erase(s.end() -1);
        }
        return;
    }
public:
    vector<string> letterCombinations(string digits) {
        deque<string> dq;
        if (digits.size() == 0) return {};
        dq.push_back("");

        for (int i = 0; i < digits.size(); i++) {
            string letters = letterMap[digits[i] - '0'];
            
            while (dq.front().size() == i) {
                string tmp = dq.front();
                dq.pop_front();
                for (char s : letters){
                    dq.push_back(tmp + s);
                }
            }
        }

        vector<string> res(dq.begin(), dq.end());
        return res;

    }
};

51.N皇后

题目:n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

思路:

  1. 定义columns集合,判断当前列是否摆放皇后;
  2. 定义dia1集合,判断对角1是否有皇后,定义的规则是 横坐标 - 纵坐标 是否相等;
  3. 定义dia2集合,判断对角线2是否有皇后,定义规则是 横坐标 + 纵坐标是否相等;
  4. 其他就是回溯套路:遍历当前节点的可选项,判断是否符合要求,符合就进入下一层,不符合就continue
# 更加偏向于下面代码
class Solution {

private:
    void putQueen(vector<vector<string>>& res, vector<int>& queen, int n, int start, unordered_set<int> columns, unordered_set<int> dia1, unordered_set<int> dia2){
        if (start == n){
            res.push_back( generateBoard(n, queen));
            return;
        }

        for(int i = 0; i < n ; i++){
            if (columns.find(i) != columns.end() || dia1.find(start - i) != dia1.end() || dia2.find(start + i) != dia2.end()){
                continue;
            }

            queen.push_back(i);
            columns.insert(i);
            dia1.insert(start - i);
            dia2.insert(start + i);

            putQueen(res, queen, n, start + 1, columns, dia1, dia2);

            columns.erase(i);
            dia1.erase(start - i);
            dia2.erase(start + i);
            queen.pop_back();

        }
    }

    vector<string> generateBoard(int n, vector<int>& queen){
        vector<string> board( n, string(n, '.'));
        for (int i = 0; i < n; i++){
            board[i][queen[i]] = 'Q';
        }
        return board;
    }
public:
    vector<vector<string>> solveNQueens(int n) {
        vector<vector<string>> res;
        
        unordered_set<int> columns;
        unordered_set<int> dia1;
        unordered_set<int> dia2; 

        vector<int> queen;
        putQueen(res, queen, n, 0, columns, dia1, dia2);

        return res;
    }
};

参考

  1. 极客时间-算法训练营-覃超
  2. C++ 6 Solutions for majority-element
  3. 17.电话号码字母组合,队列图解
  4. leetcode中文站
  5. leetcode国际站 (将力扣中文链接,后面的-cn去掉,就是该题的国际站)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值