算法题复习(回溯)

base code

回溯法大致思路

void backtracking(参数)
{
	if(终止条件) 
	{
		存放结果;
		return;
	}
	for(选择:本层集合中的元素(树中节点孩子的数量就是集合的大小)) 
	{
		处理节点;
		backtracking(路径,选择列表);		//递归
		回溯,撤销处理结果
	}
}

棋盘问题

51. N 皇后

https://leetcode-cn.com/problems/n-queens/

class Solution {
private:
    vector<vector<string>> result;
public:
    bool isValid(int row, int col, vector<string>& chessboard, int n)
    {
        //检查列
        for(int i = 0; i < row; i++)
        {
            if(chessboard[i][col] == 'Q') return false;
        }
        //检查左上方有无
        for(int i = row - 1,j = col - 1; i >= 0 && j >= 0; i--, j--)
        {
            if(chessboard[i][j] == 'Q') return false;
        }
        //检查右上方有无
        for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++)
        {
            if(chessboard[i][j] == 'Q') return false;
        }
        return true;
    }
    //n表示棋盘边长,row表示当前遍历到第几层了
    void backtracking(int n, int row, vector<string>& chessboard)
    {
        //当递归到棋盘最底层的时候,收集结果
        if(row == n)
        {
            result.push_back(chessboard);
            return;
        }
        //单层逻辑,递归深度row控制行,每一层for循环col控制列
        for(int col = 0; col < n; col++)
        {
            if(isValid(row,col,chessboard,n) == true)
            {
                chessboard[row][col] = 'Q';    //放置皇后
                backtracking(n,row+1,chessboard);
                chessboard[row][col] = '.';
            }
        }
        return ;
    }
    vector<vector<string>> solveNQueens(int n) {
        result.clear();
        //注意一下棋盘的初始化方式
        vector<string> chessboard(n,string(n,'.'));
        backtracking(n,0,chessboard);
        return result;
    }
};

37. 解数独

https://leetcode-cn.com/problems/sudoku-solver/

class Solution {
public:
    bool isValid(int row, int col, char val,vector<vector<char>>& board)
    {
        //判断同行是否重复
        for(int i = 0; i < 9; i++)
        {
            if(board[row][i] == val) return false;
        }
        //判断同列是否重复
        for(int i = 0; i < 9; i++)
        {
            if(board[i][col] == val) return false;
        }
        //判断9方格里是否重复
        int startRow = (row / 3) * 3;
        int startCol = (col / 3) * 3;
        for(int i = startRow; i < startRow + 3; i++)
        {
            for(int j = startCol; j < startCol + 3; j++)
            {
                if(board[i][j] == val) return false;
            }
        }
        //如果都没错
        return true;
    }
    bool backtracking(vector<vector<char>>& board)
    {
        for(int i = 0; i < board.size(); i++)
        {
            for(int j = 0; j < board[0].size(); j++)
            {
                //如果已经填入,continue
                if(board[i][j] != '.')  continue;
                for(char k = '1'; k <= '9'; k++)    //观察(i,j)这个位置放置k是否合适
                {
                    if(isValid(i,j,k,board))
                    {
                        board[i][j] = k;            //放置
                        if(backtracking(board)) return true;    //如果找到一组合适的解,立刻返回
                        board[i][j] = '.';          //回溯,撤销k
                    }
                }
                return false;                       //9个数都试完了,都不行,返回false;
            }
        }
        return true;                                //遍历完没有返回false,说明找到了合适的棋盘
    }
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);
    }
};

组合问题

77. 组合

未剪枝优化

https://leetcode-cn.com/problems/combinations/
逻辑树同一层:从左向右取数,取过的数不再重复取
n相当于树宽,k相当于树深度。
每次搜索到叶子节点,就找到了一个结果。

class Solution {
private:
vector<vector<int>> result;     //用来存放结果集合
vector<int> path;               //用来存放结果
public:
    void backtracking(int n, int k, int startIndex)
    {
        if(path.size() == k)
        {
            result.push_back(path);
            return;
        }
        //单层逻辑
        for(int i = startIndex; i <= n; i++)     //控制树的横向遍历
        {
            path.push_back(i);
            backtracking(n,k,i+1);  //下一层搜索从i+1开始
            path.pop_back();        //回溯
        }  
    }
    vector<vector<int>> combine(int n, int k) {
        result.clear();
        path.clear();
        backtracking(n,k,1);
        return result;
    }
};

剪枝优化

单层逻辑中
如果for循环选择的起始位置之后的元素个数已经不足,就没有必要再次搜索。
已经选择path.size(),还需要k - path.size(),所以可以这样写:

for(int i = startIndex; i <= n -(k - path.size()) + 1; i++)

216. 组合总和 III

https://leetcode-cn.com/problems/combination-sum-iii/

未剪枝优化

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    //  目标和  树深度  当前path路径和  本层起始点   
    void backtracking(int targetSum, int k, int sum, int startIndex)
    {
        if(path.size() == k)    //到达树底部
        {
            if(sum == targetSum) result.push_back(path);
            return;
        }
        for(int i = startIndex; i <= 9; i++)
        {
            sum += i;
            path.push_back(i);
            backtracking(targetSum,k,sum,i+1);
            path.pop_back();
            sum -= i;
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        result.clear();
        path.clear();
        backtracking(n,k,0,1);
        return result;
    }
};

剪枝优化

如果sum > targetSum ,那么往后遍历就没有意义了。于是在backtracking函数一开始加上:

if(sum > targetSum) return;

17. 电话号码的字母组合

https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/
细节:
数字与字母的映射.与前两题不同的是,本题是求不同集合之间的组合,前两题是求同一个集合中的组合。

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
};
public:
    vector<string> result;
    string s;
    void backtracking(const string& digits, int index)
    {
        if(index == digits.size())
        {
            result.push_back(s);
            return;
        }
        int digit = digits[index] - '0';
        string letters = letterMap[digit];
        for(int i = 0; i < letters.size(); i++)
        {
            s.push_back(letters[i]);
            backtracking(digits,index + 1); //下一层处理下一个数
            s.pop_back();
        }
    }
    vector<string> letterCombinations(string digits) {
        s.clear();
        result.clear();
        if(digits.size() == 0) return result;
        backtracking(digits,0);
        return result;
    }
};

39. 组合总和

https://leetcode-cn.com/problems/combination-sum/
需要注意的点:
组合没有数量要求,元素可以无限重复选取。

未剪枝优化

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
    {
        if(sum > target) return;
        if(sum == target) {
            result.push_back(path);
            return;
        }
        for(int i = startIndex; i < candidates.size(); i++)
        {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,target,sum,i);
            path.pop_back();
            sum -= candidates[i];
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        backtracking(candidates,target,0,0);
        return result;
    }
};

剪枝优化

对总集合先排序,如果本层sum + candidates[i] 已经大于target,就没有必要再向本层之后的元素遍历了。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
    {
        if(sum > target) return;
        if(sum == target) {
            result.push_back(path);
            return;
        }
        for(int i = startIndex; i < candidates.size() && candidates[i] + sum <= target; i++)
        {
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,target,sum,i);
            path.pop_back();
            sum -= candidates[i];
        }
    }
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        sort(candidates.begin(),candidates.end());
        backtracking(candidates,target,0,0);
        return result;
    }
};

40. 组合总和 II,挺重要的,涉及到去重了

这一题和上一题有区别:
本题candidates中的每个数字再每个组合中只能用一次,并且candidates中有元素是重复的,并且解集中不能包含重复的组合。要在搜索的过程中就去掉重复组合。
去重:使用过的元素不能重复选取。注意组合问题的逻辑树有两个维度:深度和广度
元素在同一个组合内可以重复,但是两个组合不能相同,显然是树层广度上的去重。
同时注意,对于树层去重不需要使用辅助数组。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
    {
        if(sum > target) return;
        if(sum == target)
        {
            result.push_back(path);
            return;
        }
        for(int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++)
        {
            //去重
            if(i > startIndex  && candidates[i] == candidates[i - 1]) continue;
            sum += candidates[i];
            path.push_back(candidates[i]);
            backtracking(candidates,target,sum,i+1);
            path.pop_back();
            sum -= candidates[i];
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        result.clear();
        path.clear();
        //首先排序
        sort(candidates.begin(),candidates.end());
        backtracking(candidates,target,0,0);
        return result;
    }
};

切割问题

切割问题类似于组合问题。

131. 分割回文串

https://leetcode-cn.com/problems/palindrome-partitioning/

class Solution {
public:
    vector<vector<string>> result;
    vector<string> path;
    bool isHuiWen(const string& s,int start,int end)
    {
        int i = start;
        int j = end;
        while(i < j)
        {
            if(s[i] != s[j]) return false;
            i++;
            j--;
        }
        return true;
    }
    void backtracking(const string& s,int startIndex)
    {
        if(startIndex >= s.size())
        {
            result.push_back(path);
            return;
        }
        for(int i = startIndex; i < s.size(); i++)
        {
            //如果是回文子串
            if(isHuiWen(s,startIndex,i))
            {
                //获取子串
                string str = s.substr(startIndex,i - startIndex + 1);
                path.push_back(str);
                backtracking(s,i+1);
                path.pop_back();
            }
        }
    }
    vector<vector<string>> partition(string s) {
        result.clear();
        path.clear();
        backtracking(s,0);
        return result;
    }
};

子集问题

子集问题也是一种组合问题。不过不同的是组合问题是在到达叶子节点时候才将path送入result。而子集问题每次进入这一层就要把path加入result,这才是子集。

78. 子集

https://leetcode-cn.com/problems/subsets/

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex)
    {
        //
        result.push_back(path);
        if(startIndex > nums.size()) return;
        for(int i = startIndex; i < nums.size(); i++)
        {
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums,0);
        return result;
    }
};

90. 子集 II,有树层去重

https://leetcode-cn.com/problems/subsets-ii/
比之前加上一个树层去重

class Solution {
public:

    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex)
    {
        //
        result.push_back(path);
        if(startIndex > nums.size()) return;
        for(int i = startIndex; i < nums.size(); i++)
        {
            //树层去重
            if(i > startIndex && nums[i-1] == nums[i]) continue;
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(),nums.end());
        backtracking(nums,0);
        return result;
    }
};

491. 递增子序列,也需要数层去重,使用unordered_set

https://leetcode-cn.com/problems/increasing-subsequences/
去重逻辑:
1、使用unordered_set用来记录本层元素是否重复,重复则continue
2、观察待插入的元素nums[i]是否是比path尾部元素大等的,否则构成不了递增序列。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, int startIndex)
    {
        //子序列长度>=2
        if(path.size() > 1)
            result.push_back(path);
        if(startIndex > nums.size()) return;
        unordered_set<int> uset;
        for(int i = startIndex; i < nums.size(); i++)
        {
            //树层去重
            if(!path.empty() && nums[i] < path.back()) continue;
            if(uset.find(nums[i]) != uset.end()) continue;
            uset.insert(nums[i]);
            path.push_back(nums[i]);
            backtracking(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums,0);
        return result;
    }
};

排列问题

46. 全排列

https://leetcode-cn.com/problems/permutations/
与组合的区别在于for循环从0开始,但是需要注意取过的元素不能再取,这里使用used数组进行记录。

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, vector<bool>& used)
    {
        if(path.size() == nums.size()) 
        {
            result.push_back(path);
            return;
        }

        for(int i = 0; i < nums.size(); i++)
        {
            //用过的元素不需要使用
            if(used[i] == true) continue;
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums,used);
            path.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        result.clear();
        path.clear();
        vector<bool> used(nums.size(),false);
        backtracking(nums,used);
        return result;
    }
};

47. 全排列 II + 数层去重

https://leetcode-cn.com/problems/permutations-ii/

class Solution {
public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(vector<int>& nums, vector<bool>& used)
    {
        if(path.size() == nums.size()) 
        {
            result.push_back(path);
            return;
        }

        for(int i = 0; i < nums.size(); i++)
        {
            //去重,同一层nums[i-1]使用过的话直接跳过
            if(i > 0 && nums[i] == nums[i-1] && used[i-1] == true) continue;
            //用过的元素不需要使用
            if(used[i] == true) continue;
            used[i] = true;
            path.push_back(nums[i]);
            backtracking(nums,used);
            path.pop_back();
            used[i] = false;
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();
        path.clear();
        sort(nums.begin(),nums.end());
        vector<bool> used(nums.size(),false);
        backtracking(nums,used);
        return result;
    }
};

行程安排+高级容器的使用,现在还没吃透

https://leetcode-cn.com/problems/reconstruct-itinerary/
还得再多看看。

class Solution {
public:
    //unordered_map<出发机场,map<到达机场,航班次数>> targets;
    unordered_map<string,map<string,int>> targets;
    //还有多少航班 tickets
    //由于只需要找到一个行程,所以找到直接返回,使用bool正好
    bool backtracking(int ticketNum,vector<string>& result)
    {
        if(result.size() == ticketNum + 1)  return true;
        for(pair<const string,int>& target : targets[result[result.size() - 1]])
        {
            if(target.second > 0) {
                result.push_back(target.first);
                target.second--;
                if(backtracking(ticketNum,result)) return true;
                result.pop_back();
                target.second++;
            }
        }
        return false;
    }
    vector<string> findItinerary(vector<vector<string>>& tickets) 
    {
        targets.clear();
        vector<string> result;
        //初始化targets
        for(const vector<string>& vec : tickets)
        {
            targets[vec[0]][vec[1]]++;  //记录映射关系
        }
        result.push_back("JFK");    //起始机场
        backtracking(tickets.size(),result);
        return result;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拾牙慧者

欢迎请作者喝奶茶

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值