写一些自己LeetCode的刷题过程及总结05(回溯)


##leetcode上有2000+的题,不可能都刷完,我的刷题顺序是先分类型,然后再分难度,不断提升,当然过程中也参考了其他大神们的一些建议,光刷题收获不大,最重要的还是不断归纳总结,没事刷两道,坚持写总结其实也挺有意思的。##
##还在不断更新总结!##
##本文仅用来记录自己平时的学习收获##
##有朝一日我也能写出漂亮的代码!##

一、回溯

1.1 关于回溯

1.1.1 回溯的相关介绍

这个是我最开始刷题时学到的第一个算法,刚开始感觉这个算法很牛的样子,但是随着理解的深入,其实也就那么回事。

首先,说一下什么是回溯回溯其实就是一种搜索方式,并且回溯与递归是分不开的,所以如果有人说道回溯函数,其实也就是递归函数。简单的理解回溯就是:先沿一条路走,当走不通的时候往回退一步,看看有没有其它的路可以走,如果还是没有那再回退。

其次,说一下回溯算法的效率。虽然回溯算法很难,并且也不好理解(跟递归扯上关系的貌似都不好理解。。。),但是回溯算法的效率其实并不高。从上面对回溯的解释不难看出,回溯其实就是穷举,就是暴力解法,穷举所有的可能,然后找到符合要求的答案。如果想要提高回溯的效率,可以在递归过程中进行一些剪枝操作,但其时间复杂度仍是暴力解法的时间复杂度。

最后,说一下为什么要用回溯,也就是回溯能解决什么问题。刚说了回溯其实就是暴力解法,效率并不高,那为什么还要用回溯?因为有些问题那怕是用暴力解法也没有办法解决,即没有办法找到所有的可能。这时就需要用到回溯了,这也就是为什么回溯给人一种很难的感觉,竟然还有用暴力解法都没法解决的问题,那会有多难。

1.1.2 回溯能解决的问题

这里总结一下回溯法能解决的问题:

	1、组合问题:N个数里按一定规则找出k个数的集合。
	2、切割问题:一个字符串按一定规则有几种切割方式。
	3、子集问题:一个N个数的集合里有多少符合条件的子集。
	4、排列问题:N个数按一定规则全排列,有几种排列方式。
	5、棋盘问题:著名的N皇后、解数独等。

1.2 leetcode部分回溯题目及代码

77.组合
39.组合总和
40.组合总和II
216.组合总和III
17.电话号码的字母组合
131.分割回文串
93.复原IP地址
78.子集
90.子集II
46.全排列
47.全排列II
51.N皇后
52.N皇后II
37.解数独
491.递增子序列

1.2.1 组合问题

1、77.组合

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> ret;
        if (n < k || n < 1) return ret;
        vector<int> cur;
        backTracking(ret, cur, n, k, 1);
        return ret;
    }
    //回溯三步:
    //1、确定递归函数参数及返回值
    void backTracking(vector<vector<int>>& ret, vector<int>& cur, int n, int k, int val) {
        //2、确定终止条件
        if (cur.size() == k) {
            ret.push_back(cur);
            return;
        }
        //3、单次递归过程
        for (int i = val; i <= n; ++i) {
            cur.push_back(i);
            backTracking(ret, cur, n, k, i + 1);
            cur.pop_back();//回溯
        }
    }
};

2、39.组合总和

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> ret;
        if (candidates.size() == 0) return ret;
        vector<int> cur;
        backTracking(ret, cur, candidates, target, 0);
        return ret;
    }
    //回溯三步:
    //1、确定递归函数参数及返回值
    void backTracking(vector<vector<int>>& ret, vector<int>& cur, vector<int>& candidates, int target, int index) {
        //2、确定终止条件
        if (target == 0) {
            ret.push_back(cur);
            return;
        }
        if (target < 0) return;
        //3、单词递归逻辑
        for (int i = index; i < candidates.size(); ++i) {
            cur.push_back(candidates[i]);
            backTracking(ret, cur, candidates, target - candidates[i], i);
            cur.pop_back(); //回溯
        }
    }
};

3、40.组合总和II

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> ret;
        vector<int> cur;
        sort(candidates.begin(), candidates.end());
        backTracking(ret, cur, candidates, target, 0);
        return ret;
    }
    //回溯三步:
    //1、确定递归函数参数及返回值
    void backTracking(vector<vector<int>>& ret, vector<int>& cur, vector<int>& candidates, int target, int index) {
        //2、确定终止条件
        if (target == 0) {
            ret.push_back(cur);
            return;
        }
        if (target < 0) return;
        //3、单次递归逻辑
        for (int i = index; i < candidates.size(); ++i) {
            if (i > index && candidates[i] == candidates[i - 1]) continue;
            cur.push_back(candidates[i]);
            backTracking(ret, cur, candidates, target - candidates[i], i + 1);
            cur.pop_back();
        }
    }
};

4、216.组合总和III

class Solution {
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        vector<vector<int>> ret;
        vector<int> cur;
        backTracking(ret, cur, k, n, 0, 1);
        return ret;
    }
    //回溯三步:
    //1、确定递归函数参数及返回值
    void backTracking(vector<vector<int>>& ret, vector<int>& cur, int k, int n, int sum, int level) {
        //2、确定终止条件
        if (cur.size() == k && sum == n) {
            ret.push_back(cur);
            return;
        }
        //3、单次递归逻辑
        for (int i = level; i <= 9 && sum + i <= n; ++i) {
            cur.push_back(i);
            backTracking(ret, cur, k, n, sum + i, i + 1);
            cur.pop_back();
        }
    }
};

5、17.电话号码的字母组合

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        vector<string> ret;
        if (digits.size() == 0) return ret;
        string str;
        backTracking(ret, str, digits, 0);
        return ret;
    }
    //回溯三步:
    //1、确定递归函数参数及返回值
    void backTracking(vector<string>& ret, string& str, string& digits, int index) {
        //2、确定终止条件
        if (str.size() == digits.size()) {
            ret.push_back(str);
            return;
        }
        //3、单次递归逻辑
        int digit = digits[index] - '0';
        string s = letterMap[digit];
        for (int i = 0; i < s.size(); ++i) {
            str.push_back(s[i]);
            backTracking(ret, str, digits, index + 1); //这里是index + 1,与之前不同,因为下一次要处理下一个数字了
            str.pop_back(); //回溯
        }
    }
private:
    string letterMap[10] = {
        "",     //0
        "",     //1
        "abc",  //2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz"  //9
    };
};

小总结:组合问题根据模板去写就好,关于回溯的模板我放在了最后的总结里。

1.2.2 分割

1、131.分割回文串

class Solution {
public:
    vector<vector<string>> partition(string s) {
        vector<vector<string>> ret;
        if (s.size() == 0) return ret;
        vector<string> cur;
        backTracking(ret, cur, s, 0);
        return ret;
    }
    //回溯三步:
    //1、确定递归函数参数及返回值
    void backTracking(vector<vector<string>>& ret, vector<string>& cur, string& s, int startIndex) {
        //2、确定终止条件
        if (startIndex >= s.size()) {
            ret.push_back(cur);
            return;
        }
        //3、单次递归逻辑
        for (int i = startIndex; i < s.size(); ++i) {
            if (isPalindrome(s, startIndex, i)) {
                string str = s.substr(startIndex, i - startIndex + 1);
                cur.push_back(str);
            } else continue;
            backTracking(ret, cur, s, i + 1);
            cur.pop_back(); //回溯
        }
    }
    //判断是否是回文串
    bool isPalindrome(const string& s, int start, int end) {
        int left = start;
        int right = end;
        while (left < right) {
            if (s[left] != s[right]) return false;
            ++left;
            --right;
        }
        return true;
    }
};

2、93.复原IP地址

class Solution {
public:
    vector<string> restoreIpAddresses(string s) {
        vector<string> ret;
        if (s.size() < 4 || s.size() > 12) return ret;
        backTracking(ret, s, 0, 0);
        return ret;
    }
    //回溯三步:
    //1、确定递归函数参数及返回值
    void backTracking(vector<string>& ret, string s, int startIndex, int pointNum) {
        //2、确定终止条件
        //当“.”的个数为3时结束
        if (pointNum == 3) {
            if (isValid(s, startIndex, s.size() - 1)) {
                ret.push_back(s);
            }
            return;
        }
        //3、单次递归逻辑
        for (int i = startIndex; i < s.size(); ++i) {
            if (isValid(s, startIndex, i)) {
                s.insert(s.begin() + i + 1, '.');
                backTracking(ret, s, i + 2, pointNum + 1);//因为加入了’.‘所以要往后移动2位
                s.erase(s.begin() + i + 1);//回溯删掉’.‘
            } else break;
        }
    }
    bool isValid(const string& s, int start, int end) {
        if (start > end) return false;
        //0开头的数字不合法
        if (s[start] == '0' && start != end) return false;
        int num = 0;
        for (int i = start; i <= end; ++i) {
            //遇到非法的数字,不合法
            if (s[i] > '9' || s[i] < '0') return false;
            num = num * 10 + (s[i] - '0');
            //大于255,不合法
            if (num > 255) return false;
        }
        return true;
    }
};

小总结:分割问题相对比较难,需要考虑切割的位置,和其它判断,比如是否是回文串、IP是否合法等。

1.2.3 子集

1、78.子集

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> ret;
        if (nums.size() == 0) return ret;
        vector<int> cur;
        backTracking(ret, cur, nums, 0);
        return ret;
    }
    //回溯三步:
    //1、确定递归函数参数及返回值
    void backTracking(vector<vector<int>>& ret, vector<int>& cur, vector<int>& nums, int index) {
        ret.push_back(cur);
        //2、确定终止条件
        if (index >= nums.size()) return;
        //3、单次递归逻辑
        for (int i = index; i < nums.size(); ++i) {
            cur.push_back(nums[i]);
            backTracking(ret, cur, nums, i + 1);
            cur.pop_back();//回溯
        } 
    }
};

2、90.子集II

class Solution {
public:
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<vector<int>> ret;
        if (nums.size() == 0) return ret;
        sort(nums.begin(), nums.end());
        vector<int> cur;
        backTracking(ret, cur, nums, 0);
        return ret;
    }
    //回溯三步:
    //1、确定递归函数参数及返回值
    void backTracking(vector<vector<int>>& ret, vector<int>& cur, vector<int>& nums, int index) {
        ret.push_back(cur);
        //2、确定终止条件
        if (index >= nums.size()) return;
        //3、单次递归逻辑
        for (int i = index; i < nums.size(); ++i) {
            if (i > index && nums[i] == nums[i - 1]) continue;
            cur.push_back(nums[i]);
            backTracking(ret, cur, nums, i + 1);
            cur.pop_back();
        }
    }
};

小总结:子集问题同样不难,直接照着模板写。

1.2.4 排列

1、46.全排列

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> ret;
        if (nums.size() == 0) return ret;
        vector<int> cur;
        vector<bool> used(nums.size(), false);
        backTracking(ret, cur, nums, used);
        return ret;
    }
    //回溯三步
    //1、确定递归函数参数及返回值
    void backTracking(vector<vector<int>>& ret, vector<int>& cur, vector<int>& nums, vector<bool>& used) {
        //2、确定终止条件
        if (cur.size() == nums.size()) {
            ret.push_back(cur);
        }
        //3、单次递归逻辑
        for (int i = 0; i < nums.size(); ++i) {
            if (used[i] == true) continue;
            used[i] = true;
            cur.push_back(nums[i]);
            backTracking(ret, cur, nums, used);
            cur.pop_back();
            used[i] = false;
        }
    }
};

2、47.全排列II

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> ret;
        if (nums.size() == 0) return ret;
        sort(nums.begin(), nums.end());
        vector<int> cur;
        vector<bool> used(nums.size(), false);
        backTracking(ret, cur, nums, used);
        return ret;
    }
    //回溯三步
    //1、确定递归函数参数及返回值
    void backTracking(vector<vector<int>>& ret, vector<int>& cur, vector<int>& nums, vector<bool>& used) {
        //2、确定终止条件
        if (cur.size() == nums.size()) {
            ret.push_back(cur);
            return;
        }
        //3、单次递归逻辑
        for (int i = 0; i < nums.size(); ++i) {
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) continue;
            if (used[i] == false) {
                used[i] = true;
                cur.push_back(nums[i]);
                backTracking(ret, cur, nums, used);
                cur.pop_back();
                used[i] = false;
            }
        }
    }
};

3、剑指offer 38

class Solution {
public:
    vector<string> permutation(string s) {
        set<string> ret;
        vector<string> vec;
        if (s.size() == 0) return vec;
        vector<bool> used(s.size(), false);
        string str = "";
        backTracking(ret, str, used, s);
        for (auto temp : ret) vec.push_back(temp);
        return vec;
    }

    //回溯三步
    //1、确定递归函数参数及返回值
    void backTracking(set<string>& ret, string& str, vector<bool>& used, string& s) {
        //2、确定终止条件
        if (str.size() == s.size()) {
            ret.insert(str);
            return;
        }
        //3、确定单词递归逻辑
        for (int i = 0; i < s.size(); ++i) {
            if (used[i] == true) continue;
            used[i] = true;
            str.push_back(s[i]);
            backTracking(ret, str, used, s);
            str.pop_back();
            used[i] = false;
        }
    }
};

1.2.5 棋盘问题

1、51.N皇后

//这应该是回溯中最经典的一道题了
//其实回溯部分并不难,但要考虑清楚放下一个皇后之后要对棋盘中的哪些位置进行跟新
class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        vector<vector<string>> ret;
        if (n == 0) return ret;
        vector<string> queen(n, string(n, '.'));
        vector<vector<int>> attack(n, vector<int> (n, 0));
        backTracking(ret, queen, attack, 0, n);
        return ret;
    }
    void putQueens(vector<vector<int>>& attack, int x, int y) {
        static const int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1};
        static const int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1};
        attack[x][y] = 1;
        for (int i = 1; i < attack.size(); ++i) {
            for (int j = 0; j < 8; ++j) {
                int newX = x + i * dx[j];
                int newY = y + i * dy[j];
                if (newX >= 0 && newX < attack.size() && newY >= 0 && newY < attack.size()) {
                    attack[newX][newY] = 1;
                }
            }
        }
    }
    void backTracking(vector<vector<string>>& ret, vector<string>& queen, vector<vector<int>>& attack, int m, int n) {
        if (m == n) {
            ret.push_back(queen);
            return;
        }
        for (int j = 0; j < n; ++j) {
            if (attack[m][j] == 0) {
                vector<vector<int>> temp = attack;
                queen[m][j] = 'Q';
                putQueens(attack, m, j);
                backTracking(ret, queen, attack, m + 1, n);
                queen[m][j] = '.';
                attack = temp;
            }
        }
    }

};

2、52.N皇后II

//与上一道题类似
class Solution {
public:
    int totalNQueens(int n) {
        if (n == 0) return 0;
        int ret = 0;
        vector<vector<int>> attack(n, vector<int> (n, 0));
        backTracking(ret, attack, 0, n);
        return ret;
    }
    void putQueens(vector<vector<int>>& attack, int x, int y) {
        static const int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1};
        static const int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1};
        attack[x][y] = 1;
        for (int i = 1; i < attack.size(); ++i) {
            for (int j = 0; j < 8; ++j) {
                int newX = x + i * dx[j];
                int newY = y + i * dy[j];
                if (newX >= 0 && newX < attack.size() && newY >= 0 && newY < attack.size()) {
                    attack[newX][newY] = 1;
                }
            }
        } 
    }
    void backTracking(int& ret, vector<vector<int>>& attack, int m, int n) {
        if (m == n) {
            ++ret;
            return;
        }
        for (int j = 0; j < n; ++j) {
            if (attack[m][j] == 0) {
                vector<vector<int>> temp = attack;
                putQueens(attack, m, j);
                backTracking(ret, attack, m + 1, n);
                attack = temp;
            }
        }
    }
};

3、37.解数独

class Solution {
public:
    void solveSudoku(vector<vector<char>>& board) {
        backTracking(board);
    }
    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 j = 0; j < 9; ++j) {
            if (board[j][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,找到一组解,就返回
    bool backTracking(vector<vector<char>>& board) {
        //遍历行
        for (int i = 0; i < board.size(); ++i) {
            //遍历列
            for (int j = 0; j < board[0].size(); ++j) {
                if (board[i][j] != '.') continue;
                //依次放入1~9
                for (char k = '1'; k <= '9'; ++k) {
                    //如果可以放入
                    if (isValid(i, j, k, board)) {
                        board[i][j] = k;
                        //如果找到了一组合适的解,返回
                        if (backTracking(board)) return true;
                        //回溯
                        board[i][j] = '.';
                    }
                }
                //1~9都试完了还没有合适的解,返回false
                return false;
            }
        }
        //遍历完没有返回false,返回true
        return true;
    }
};

1.2.6 其它

1、491.递增子序列

class Solution {
public:
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        vector<vector<int>> ret;
        if (nums.size() == 0) return ret;
        vector<int> cur;
        backTracking(ret, cur, nums, 0);
        return ret;
    }

    //回溯三步
    //1、确定递归函数参数及返回值
    void backTracking(vector<vector<int>>& ret, vector<int>& cur, vector<int>& nums, int index) {
        //2、确定终止条件
        if (cur.size() > 1) {
            ret.push_back(cur);
        }
        if (index >= nums.size()) {
            return;
        }
        //3、单次递归逻辑
        unordered_set<int> tempSet;//用set对本层元素进行去重
        for (int i = index; i < nums.size(); ++i) {
            if ((!cur.empty() && nums[i] < cur.back()) || tempSet.find(nums[i]) != tempSet.end()) continue;
            cur.push_back(nums[i]);
            tempSet.insert(nums[i]);
            backTracking(ret, cur, nums, i + 1);
            cur.pop_back();
        }
    }
};

1.3 回溯总结

整理一下回溯的模板

void backTracking(参数) {
	if (终止条件) {
		存放结果;
		return;
	}
	for (遍历) {
		处理节点;
		backTracking(参数); //递归
		回溯;
	}
}

回溯类的问题基本上照着模板写就行,无非是有些难题需要在每次的搜索过程中多去增加一些判断条件。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值