leetcode 回溯算法之 37、39、40、46,47,78,79

题目39题

编写一个程序,通过已填充的空格来解决数独问题。

一个数独的解法需遵循如下规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 '.' 表示。

leetcode 回溯算法之 37、39、40、46,47,78,79_第1张图片

给定的数独序列只包含数字 1-9 和字符 '.' 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sudoku-solver
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这个和leetcode51很像,但有不同点
 

代码

​
class Solution {
public:
    void solveSudoku(vector>& board) {
        helper(board, 0, 0);
    }
    bool helper(vector>& board, int i, int j) {
        if (i == 9) return true;
        if (j >= 9) return helper(board, i + 1, 0);
        if (board[i][j] != '.') return helper(board, i, j + 1);
        for (char c = '1'; c <= '9'; ++c) {
            if (!isValid(board, i , j, c)) continue;
            board[i][j] = c;
            if (helper(board, i, j + 1)) return true;
            board[i][j] = '.';
        }
        return false;
    }
    bool isValid(vector>& board, int i, int j, char val) {
        for (int x = 0; x < 9; ++x) {
            if (board[x][j] == val) return false;
        }
        for (int y = 0; y < 9; ++y) {
            if (board[i][y] == val) return false;
        }
        int row = i - i % 3, col = j - j % 3;
        for (int x = 0; x < 3; ++x) {
            for (int y = 0; y < 3; ++y) {
                if (board[x + row][y + col] == val) return false;
            }
        }
        return true;
    }
};

​

不同点:

1、发现没,它是有返回值的,因为他求得不是全部可行解,而是一个可行解即可,具体可看题目。所以通过boolean的返回值,控制可行解的个数。那又是如何控制的呢?

看这里

 for (char c = '1'; c <= '9'; ++c) {
            if (!isValid(board, i , j, c)) continue;
            board[i][j] = c;
            if (helper(board, i, j + 1)) return true;
            board[i][j] = '.';
        }

当在子选择中有一个是返回值为true,就直接返回return true;不在遍历其他子选择。

2、n皇后只需要每行只有一个皇后,即在列中选择一个子选择,这一行的选择就结束了;但这道题不是的,每行都有若干需要确定的“皇后”,因此行列都有遍历,当j>=9遍历下一行,当i==9就结束。可以学习一下对于这种二维数组应该如何应对

同类只求一个解即可的题

79、单词搜索

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例:

board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

给定 word = "ABCCED", 返回 true.
给定 word = "SEE", 返回 true.
给定 word = "ABCB", 返回 false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-search
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

原二维数组就像是一个迷宫,可以上下左右四个方向行走,我们以二维数组中每一个数都作为起点和给定字符串做匹配,我们还需要一个和原数组等大小的 visited 数组,是 bool 型的,用来记录当前位置是否已经被访问过,因为题目要求一个 cell 只能被访问一次。如果二维数组 board 的当前字符和目标字符串 word 对应的字符相等,则对其上下左右四个邻字符分别调用 DFS 的递归函数,只要有一个返回 true,那么就表示可以找到对应的字符串,否则就不能找到

代码:

class Solution {
public:
    bool exist(vector>& board, string word) {
        if (board.empty() || board[0].empty()) return false;
        int m = board.size(), n = board[0].size();
        vector> visited(m, vector(n));
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (search(board, word, 0, i, j, visited)) return true;
            }
        }
        return false;
    }
    bool search(vector>& board, string word, int idx, int i, int j, vector>& visited) {
        if (idx == word.size()) return true;
        int m = board.size(), n = board[0].size();
        if (i < 0 || j < 0 || i >= m || j >= n || visited[i][j] || board[i][j] != word[idx]) return false;
        visited[i][j] = true;
        bool res = search(board, word, idx + 1, i - 1, j, visited) 
                 || search(board, word, idx + 1, i + 1, j, visited)
                 || search(board, word, idx + 1, i, j - 1, visited)
                 || search(board, word, idx + 1, i, j + 1, visited);
        visited[i][j] = false;
        return res;
    }
};

特点:

1、由于只要一个满足即可,故这种形式很机智

bool res = search(board, word, idx + 1, i - 1, j, visited) 
                 || search(board, word, idx + 1, i + 1, j, visited)
                 || search(board, word, idx + 1, i, j - 1, visited)
                 || search(board, word, idx + 1, i, j + 1, visited);

39题:

题目

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

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。 

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码:

class Solution {
public:
    vector> combinationSum(vector& candidates, int target) {
        vector> res;
        vector out;
        combinationSumDFS(candidates, target, 0, out, res);
        return res;
    }
    void combinationSumDFS(vector& candidates, int target, int start, vector& out, vector>& res) {
        if (target < 0) return;
        if (target == 0) {res.push_back(out); return;}
        for (int i = start; i < candidates.size(); ++i) {
            out.push_back(candidates[i]);
            combinationSumDFS(candidates, target - candidates[i], i, out, res);
            out.pop_back();
        }
    }
};

注意点:

1、咋一看,好像是可行解的集合是 一维数组,但由于可行解是整数,因此集合是二维数组,而且对于这个out由于是整数不能通过+字符串的形式表示加入子选择,故只能通过真实修改out的值,因此注意要复原

2、注意一下,我们之前的lever参数都比较明确,即该考虑哪一层了,但这一题由于可行解的长度不定,组成元素可以重复出现,因此lever不好确定,但不得不称写出来这个代码的真是大佬(哈哈,可不是我写的哟,仰仗大佬而已,学学),如果你以为既然可以重复选则,那每次子选项不都一样,就是全部数组里的元素,那就错啦,你运行一下会发现,你的解集出现重复的解。所以Lever设置为i,既能保证可以重复选择,有保证即使是重复,重复的元素都聚在一起,这样保证了解的唯一性。

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]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码:

class Solution {
public:
    vector> combinationSum2(vector& num, int target) {
        vector> res;
        vector out;
        sort(num.begin(), num.end());
        helper(num, target, 0, out, res);
        return res;
    }
    void helper(vector& num, int target, int start, vector& out, vector>& res) {
        if (target < 0) return;
        if (target == 0) { res.push_back(out); return; }
        for (int i = start; i < num.size(); ++i) {
            if (i > start && num[i] == num[i - 1]) continue;
            out.push_back(num[i]);
            helper(num, target - num[i], i + 1, out, res);
            out.pop_back();
        }
    }
};

注意:

1、由于不能重复使用数组的元素,故lever由i改成i+1,

2、同时由于本身提供的数组有重复元素,又要保证解的独特性,通过 if (i > start && num[i] == num[i - 1]) continue;限制,比如(1,1,1,2)当start=1,可以第一遍选出[1]的1,第二遍,选[2]的1就出现重复了,因此需要限制。

3、先排序也是很优秀的,方便使用 num[i] == num[i - 1],让相等的挨着

46题目

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

示例:

输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

class Solution {
public:
    vector> permute(vector& num) {
        vector> res;
        vector out, visited(num.size(), 0);
        permuteDFS(num, 0, visited, out, res);
        return res;
    }
    void permuteDFS(vector& num, int level, vector& visited, vector& out, vector>& res) {
        if (level == num.size()) {res.push_back(out); return;}
        for (int i = 0; i < num.size(); ++i) {
            if (visited[i] == 1) continue;
            visited[i] = 1;
            out.push_back(num[i]);
            permuteDFS(num, level + 1, visited, out, res);
            out.pop_back();
            visited[i] = 0;
        }
    }
};

47题目

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

示例:

输入: [1,1,2]
输出:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

class Solution {
public:
    vector> permuteUnique(vector& nums) {
        vector> res;
        vector out, visited(nums.size(), 0);
        sort(nums.begin(), nums.end());
        permuteUniqueDFS(nums, 0, visited, out, res);
        return res;
    }
    void permuteUniqueDFS(vector& nums, int level, vector& visited, vector& out, vector>& res) {
        if (level >= nums.size()) {res.push_back(out); return;}
        for (int i = 0; i < nums.size(); ++i) {
            if (visited[i] == 1) continue;
            if (i > 0 && nums[i] == nums[i - 1] && visited[i - 1] == 0) continue;
            visited[i] = 1;
            out.push_back(nums[i]);
            permuteUniqueDFS(nums, level + 1, visited, out, res);
            out.pop_back();
            visited[i] = 0;
        }
    }
};

不同点:

1、保证解的唯一性,由于每次都可以从数组中选择任一个元素,因此采用标志的方式

2、注意恢复的时候不仅解要恢复,标志也要恢复

题目78

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

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

示例:

输入: nums = [1,2,3]
输出:
[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subsets
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

我们可以一位一位的网上叠加,比如对于题目中给的例子 [1,2,3] 来说,最开始是空集,那么我们现在要处理1,就在空集上加1,为 [1],现在我们有两个自己 [] 和 [1],下面我们来处理2,我们在之前的子集基础上,每个都加个2,可以分别得到 [2],[1, 2],那么现在所有的子集合为 [], [1], [2], [1, 2],同理处理3的情况可得 [3], [1, 3], [2, 3], [1, 2, 3], 再加上之前的子集就是所有的子集合了,

代码:

class Solution {
public:
    vector > subsets(vector &S) {
        vector > res;
        vector out;
        sort(S.begin(), S.end());
        getSubsets(S, 0, out, res);
        return res;
    }
    void getSubsets(vector &S, int pos, vector &out, vector > &res) {
        res.push_back(out);
        for (int i = pos; i < S.size(); ++i) {
            out.push_back(S[i]);
            getSubsets(S, i + 1, out, res);
            out.pop_back();
        }
    }
};

 

总结:

1、当元素含有重复元素,但要求结果不重复时,要先排序注意使用 if (i > start && num[i] == num[i - 1]) continue;限制,该语句含义要根据题意修改,但本质原则不变。start为该层次可以选择元素的起始位置, num[i] == num[i - 1]为原序列,目的是,保证除了首元素,别的子选择不能选择和前一个子选择相同的元素

2、如果要保证只有一个解,用bool函数,且注意遍历时子选择一个即可,使用if (helper(board, i, j + 1)) return true;且函数默认返回值为false.

3、可以重复选择元素 且要保证解不重复,先排序,可以重复选择,可以使用lever不变。

4、行列都有遍历,当j>=9遍历下一行,当i==9就结束。可以学习一下对于这种二维数组应该如何应对

5、保证不会选择可行解之前选择过的元素,使用标记的方式,同时复原时,标记也要复原。同时visited也要当成实参的一部分。

6、排列、组合的区别是,排列的子选择是全部元素,组合是lever之后的。

参考链接:

https://www.cnblogs.com/grandyang/p/4358848.html

https://www.cnblogs.com/grandyang/p/4419386.html

https://www.cnblogs.com/grandyang/p/4419259.html

https://www.cnblogs.com/grandyang/p/4421852.html

你可能感兴趣的:(leetcode)