背景

回溯法(backtrack)常用于遍历列表所有子集,是 DFS 深度搜索一种,一般用于全排列,穷尽所有可能,遍历的过程实际上是一个决策树的遍历过程。时间复杂度一般 O(N!),它不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。

模板

  1. result = []
  2. function backtrack(选择列表,路径):
  3. if 满足结束条件:
  4. result.add(路径)
  5. return
  6. for 选择 in 选择列表:
  7. 做选择
  8. backtrack(选择列表,路径)
  9. 撤销选择

核心就是从选择列表里做一个选择,然后一直递归往下搜索答案,如果遇到路径不通,就返回来撤销这次选择。

练习

基础题

挑战题目

示例

subsets

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

遍历过程

DFS与回溯法 - 图1

  1. var subsets = function(nums) {
  2. let result = [];
  3. // 记录走过的路径
  4. let track = [];
  5. function backtrack( nums, start, track) {
  6. console.log(track,result);
  7. result.push([...track]);
  8. for (let i = start; i < nums.length; i++) {
  9. // 做选择
  10. track.push(nums[i]);
  11. // 回溯
  12. backtrack(nums, i + 1, track);
  13. // 撤销选择
  14. track.pop();
  15. }
  16. }
  17. backtrack(nums, 0, track);
  18. return result;
  19. };

subsets-ii

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

  1. /**
  2. * @param {number[]} nums
  3. * @return {number[][]}
  4. */
  5. let subsetsWithDup = function(nums) {
  6. let ans = [];
  7. let track = [];
  8. let n = nums.length;
  9. nums = nums.sort((a, b) => a - b);
  10. let dfs = function(start){
  11. ans.push([...track]);
  12. for( let i = start; i < n; i++ ) {
  13. if( i > start && nums[i] === nums[i-1]) continue;
  14. track.push(nums[i]);
  15. dfs(i+1);
  16. track.pop();
  17. }
  18. };
  19. dfs(0);
  20. return ans;
  21. };

permutations

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

思路:需要记录已经选择过的元素,满足条件的结果才进行返回

  1. var permute = function(nums) {
  2. let result = [];
  3. let track = [];
  4. let backTrack = function(){
  5. if(track.length === nums.length) {
  6. result.push([...track]);
  7. }
  8. for(let i =0; i < nums.length; i++) {
  9. if(track.indexOf(nums[i]) !== -1) continue;
  10. track.push(nums[i]);
  11. backTrack();
  12. track.pop();
  13. }
  14. };
  15. backTrack()
  16. return result;
  17. };

permutations-ii

给定一个可包含重复数字的序列,返回所有不重复的全排列。

  1. var permuteUnique = function(nums) {
  2. if(nums.length === 0) return [];
  3. let n = nums.length;
  4. let ans = [];
  5. let track = [];
  6. let st = [];
  7. nums = nums.sort((a,b) => {return a - b});
  8. function dfs(u, start){
  9. if(u === n) {
  10. ans.push([...track]);
  11. return;
  12. }
  13. for(let i = start; i < n; i++) {
  14. if(!st[i]){
  15. st[i] = true;
  16. track[i] = nums[u];
  17. let s = u + 1 < n && nums[u + 1] == nums[u] ? i + 1 :0;
  18. dfs(u + 1, s)
  19. st[i] = false;
  20. }
  21. }
  22. };
  23. dfs(0,0);
  24. return ans;
  25. };

letter-combinations-of-a-phone-number

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

  1. // 第一种,大雪菜上学的
  2. var letterCombinations = function(digits) {
  3. if(!digits) return [];
  4. let temp =['','','abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz'];
  5. let state = [''];
  6. for ( let i of digits.split('')) {
  7. let track = [];
  8. for( let c of temp[i]) {
  9. for( let s of state) {
  10. track.push(s + c );
  11. }
  12. }
  13. state = track;
  14. }
  15. return state
  16. };
  17. // 第二种回溯
  18. /**
  19. * @param {string} digits
  20. * @return {string[]}
  21. */
  22. var letterCombinations = function(digits) {
  23. if(!digits) return [];
  24. let digitsList = digits.split('');
  25. let temp =['','','abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz'];
  26. let result = [];
  27. let track = [];
  28. var dfs = function(start){
  29. if(start > digitsList.length) return;
  30. if(track.length === digitsList.length) {
  31. result.push(track.join(''));
  32. return;
  33. }
  34. let index = digitsList[start];
  35. let list = temp[index].split('');
  36. for(let i = 0; i < list.length; i++){
  37. track.push(list[i]);
  38. console.log(track);
  39. dfs(start + 1);
  40. track.pop();
  41. }
  42. };
  43. dfs(0);
  44. return result
  45. };

word-search

  1. /**
  2. * @param {character[][]} board
  3. * @param {string} word
  4. * @return {boolean}
  5. */
  6. let m, n;
  7. let dx = [-1, 0, 1, 0];
  8. let dy = [0, 1, 0, -1];
  9. var exist = function(board, word) {
  10. if(board.length === 0 || board[0].length == 0) return false;
  11. m = board.length, n = board[0].length;
  12. for(let i = 0; i < m; i++ ) {
  13. for( let j = 0; j < n; j++) {
  14. if(dfs(board, word,i, j , 0)){
  15. return true;
  16. }
  17. }
  18. }
  19. return false;
  20. };
  21. var dfs = function (board, word, x, y, u) {
  22. if(board[x][y] != word[u]) return false;
  23. if(u == word.length - 1) return true;
  24. board[x][y] = '.';
  25. for(let i = 0; i < 4; i++) {
  26. let a = x + dx[i], b = y + dy[i];
  27. if(a >= 0 && a < m && b >= 0 && b < n) {
  28. if(dfs(board, word, a, b , u + 1)) {
  29. return true;
  30. }
  31. }
  32. }
  33. board[x][y] = word[u];
  34. return false
  35. }

[x] combination-sum

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

  1. /**
  2. * @param {number[]} candidates
  3. * @param {number} target
  4. * @return {number[][]}
  5. */
  6. var combinationSum = function(candidates, target) {
  7. if(candidates.length === 0) return [];
  8. let result = [];
  9. // 记录走过的路径
  10. let track = [];
  11. function backtrack( start,leave) {
  12. if(leave === 0 ){
  13. result.push([...track]);
  14. return
  15. }
  16. for (let i = start; i < candidates.length; i++) {
  17. // 做选择
  18. if(leave - candidates[i] >= 0 ) {
  19. track.push(candidates[i]);
  20. // 回溯
  21. backtrack(i, leave - candidates[i] );
  22. // 撤销选择
  23. track.pop();
  24. }
  25. }
  26. }
  27. backtrack(0, target);
  28. return result;
  29. };