LeetCode 36 | 37 | 39 | 40 | 46 | 47 . (回溯)

本文解析了多个经典的回溯算法题目,包括有效数独验证、数独求解、组合总和及其变化等,提供了详细的代码实现及思路分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于回溯,推荐一个视频:(斯坦福的关于回溯的现场教学三个例子,老师课堂手写代码)

https://www.youtube.com/watch?v=78t_yHuGg-0&t=741s&list=PLnfg8b9vdpLn9exZweTJx44CII1bYczuk&index=18

https://www.youtube.com/watch?v=J_odcqzHGqw&list=PLnfg8b9vdpLn9exZweTJx44CII1bYczuk&index=18

https://www.youtube.com/watch?v=5v6zdfkImms&index=19&list=PLnfg8b9vdpLn9exZweTJx44CII1bYczuk

36Valid Sudoku

这道题是让你判断现有的数字是不是合法的,也就是说只需要去判断数字,不用去判断空的部分。(其实是数独解法的判断部分)

36题的解析:

9个数组来记录每行的重复情况,用数组下标来表示该数,比如 row[ 2 ] [ 3 ],表示第二行数字3的情况,如果 row[2][3] = 0,说明第三行已记录的没有3; 如果row[2][3] = 1,说明第三行已记录的已经有3,表示重复,直接return false就可以了。(注意下标,2应该是第三行,因为从0开始),为了对应的从0开始,所以数字也是要 board[ i ] [ j ] - '0' - 1

9个数组来计算每列的重复情况,参照上面。

9个数组来计算每隔cubic的重复情况,cubic的计算方法应该是:( i / 3 ) * 3 + ( j / 3 ), 如下图:


当然也可以不选用数组,用个哈希表就可以,哈希表的查找或者增加的操作可以用来判断是不是重复。

Determine if a 9x9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules:

  1. Each row must contain the digits 1-9 without repetition.
  2. Each column must contain the digits 1-9 without repetition.
  3. Each of the 9 3x3 sub-boxes of the grid must contain the digits 1-9 without repetition.


A partially filled sudoku which is valid.

The Sudoku board could be partially filled, where empty cells are filled with the character '.'.

Example 1:

Input:
[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
Output: true

Example 2:

Input:
[
  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
Output: false
Explanation: Same as Example 1, except with the 5 in the top left corner being 
    modified to 8. Since there are two 8's in the top left 3x3 sub-box, it is invalid.

Note:

  • A Sudoku board (partially filled) could be valid but is not necessarily solvable.
  • Only the filled cells need to be validated according to the mentioned rules.
  • The given board contain only digits 1-9 and the character '.'.
  • The given board size is always 9x9.

  bool isValidSudoku(vector<vector<char>>& board) {
        int row[9][9] = {0};  
        int col[9][9] = {0};
        int cubic[9][9] = {0};
        
        for(int i = 0; i < board.size(); i++){
            for(int j = 0; j < board.size(); j++){
                int num = board[i][j] - '0' -1;
                if(board[i][j] != '.'){
                    if(row[i][num] || col[j][num] || cubic[3 * (i/3) + j /3][num])
                        return false;
                    row[i][num] = col[j][num] = cubic[3 * (i/3) + j /3][num] = 1;
                   
                }
            }
        }
        return true;
    }

37Sudoku Solver

Write a program to solve a Sudoku puzzle by filling the empty cells.

A sudoku solution must satisfy all of the following rules:

  1. Each of the digits 1-9 must occur exactly once in each row.
  2. Each of the digits 1-9 must occur exactly once in each column.
  3. Each of the the digits 1-9 must occur exactly once in each of the 9 3x3 sub-boxes of the grid.

Empty cells are indicated by the character '.'.


A sudoku puzzle...


...and its solution numbers marked in red.

Note:

  • The given board contain only digits 1-9 and the character '.'.
  • You may assume that the given Sudoku puzzle will have a single unique solution.
  • The given board size is always 9x9.

AC代码:

class Solution {
public:
    
    void solveSudoku(vector<vector<char>>& board) {
        solve(board);
    }
    
    bool solve(vector<vector<char> > & board){
        for(int i = 0; i < board.size(); i++){
            for(int j = 0;j < board.size(); j++){
                if(board[i][j] == '.'){
                    for(int k = '1'; k <= '9'; k++){
                        if(isValid(board,i,j,k)){
                            board[i][j] = k;
                            if (solve(board))
                                return true;
                            board[i][j] = '.';  //回撤
                        }
                    }
                    return false;   // 9 个数都不行就返回false
                }
            }
        }
        return true;  //最后成功的返回true
    }
    
    //只需要判断一行 一列 一个cubic
    bool isValid(vector<vector<char>>& board, int rowNum, int colNum, char num){

        for(int i = 0; i < 9; i++){
            if(board[rowNum][i] == num)  return false;   
            if(board[i][colNum] == num)  return false;
        }
        
        for(int i = 0; i < 3; i++){
            for(int j = 0; j < 3; j++)
                if( board[i + rowNum - rowNum%3][j + colNum - colNum%3] == num)
                    return false;
        }
        
        return true;
    }
};

39Combination Sum

Given a set of candidate numbers (candidates(without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

The same repeated number may be chosen from candidates unlimited number of times.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example 1:

Input: candidates = [2,3,6,7], target = 7,
A solution set is:
[
  [7],
  [2,2,3]
]

Example 2:

Input: candidates = [2,3,5], target = 8,
A solution set is:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

AC代码:

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        vector <vector <int> > res;
        vector<int> sol;
        search(candidates, 0, target, res, sol);
        return res;
    }
    
    void search(vector<int> & candidates, int begin, int target, vector<vector<int>> &res, vector<int> &sol){
        if(target == 0){
            res.push_back(sol);
            return;
        }
        for(int i = begin; i < candidates.size(); i++){
            if(target > 0){  
                sol.push_back(candidates[i]);
                search(candidates, i, target - candidates[i], res, sol);
                sol.pop_back();  //回溯
            }else   return;  //因为已经有序,后面的都不用再找了
        }
    }
};

40Combination Sum II

这道题就是把上一题稍微变一下,只加了两行,也就是:

对相同的 i 进行搜索的时候直接跳过就好,以防发生重复。

Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

Each number in candidates may only be used once in the combination.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example 1:

Input: candidates = [10,1,2,7,6,1,5], target = 8,
A solution set is:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

Example 2:

Input: candidates = [2,5,2,1,2], target = 5,
A solution set is:
[
  [1,2,2],
  [5]
]

AC代码:

class Solution {
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<vector<int>> res;
        sort(candidates.begin(), candidates.end());
        vector<int > sol;
        backTracking(candidates, 0, target, res, sol);
        return res;
    }
    
   void backTracking(vector<int> & candidates, int begin, int target, vector<vector<int>> &res, vector<int> & sol){
        if(target == 0){
            res.push_back(sol);
            return;
        }
        for(int i = begin; i < candidates.size(); i ++){
            if(candidates[i] <= target ){
                sol.push_back(candidates[i]);
                backTracking(candidates, i + 1, target - candidates[i], res, sol);
                sol.pop_back();
            }else
                return;
            while(candidates[i] == candidates[i+1])  //加了这两行,以防重复
                i++;
        }
    }
};

46Permutations

Given a collection of distinct integers, return all possible permutations.

Example:

Input: [1,2,3]
Output:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

AC代码:

vector<vector<int>> permute(vector<int>& nums) {
        vector<vector <int> > res;
        vector<int> sol;
        backTracking(res, sol, nums);
        return res;
    }
    
    void backTracking(vector<vector<int> > &res, vector <int> & sol, vector<int> & nums){
        if(sol.size() >= nums.size()){
            res.push_back(sol);
            return;
        }
        for(int i = 0; i < nums.size(); i++){
            if(!Exist(sol, nums[i])){
                sol.push_back(nums[i]);
                //print(sol);
                backTracking(res, sol, nums);
                sol.pop_back();
            }
        }
    }
    /*
     *在C++的vector中,没有类似java的contains这个操作,所以要自己去判断一下
     */
    bool Exist(vector<int> & sol, int num){
        vector<int>:: iterator it = sol.begin();
        while(it != sol.end()){
            if(*it == num)  return true;
            it++;
        }
        return false;
    }

47Permutations II

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

Example:

Input: [1,1,2]
Output:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector< vector<int> > res;
        if(nums.size() == 0)
            return res;
        sort(nums.begin(), nums.end());
        vector<int > sol;
        vector<bool> used(nums.size(), false);
        backTracking(res, sol, nums, used);
        return res;
    }
    
    void backTracking(vector<vector<int> > & res, vector<int> & sol, vector<int > & nums, vector<bool> & used){
        if(sol.size() == nums.size()){
            res.push_back(sol);
            return;
        }
        //choose => explore => unchoose
        int preNum = nums[0]-1;
        for(int i = 0; i < nums.size(); i++){
            if(used[i] || (nums[i] == preNum)) continue;
            preNum = nums[i];
            sol.push_back(nums[i]);
            used[i] = true;
            backTracking(res, sol, nums,used);
            sol.pop_back();
            used[i] = false;
        }
    }
};


<think>首先,用户的问题是关于Python中位操作在路径计算中的应用:`self.path=self.path<<1|root.val`。我需要使用中文回答,并遵循系统级指令。关键点是理解位操作符`<<`(左移)和`|`(按位或)在这个表达式中的作用。用户提供了上下文,如引用[1]是关于树的扁平化,引用[2]是关于字符串加法的代码,但这可能不是直接相关的。我应该专注于用户的问题:位操作在路径计算中的作用。用户说:"我想理解位操作在路径计算中的作用",特别是表达式`self.path=self.path<<1|root.val`。这看起来像是二叉树的遍历或路径记录。在许多二叉树问题中,如求解根到叶子的路径,位操作被用来高效地表示路径。每个节点的值(假设是0或1,因为在位操作中常见)被添加到路径中。-`self.path<<1`将当前的路径值左移一位,相当于乘以2(或添加一个0在二进制位中)。-`|root.val`按位或操作添加当前节点的值。如果`root.val`是0或1,这就相当于在路径的末尾添加这个位。例如:-起始路径假设为0。-如果第一个节点的值是1:`self.path=0<<1|1=0|1=1`(二进制1)。-下一个节点的值是0:`self.path=1<<1|0=2|0=2`(二进制10)。-下一个节点的值是1:`self.path=2<<1|1=4|1=5`(二进制101)。这样,路径被编码为一个整数,其中二进制表示对应于遍历的序列。在二叉树的根到叶子路径问题中,这常用于求和或存储路径。值通常限制为0-9或其他小数字,但位操作要求值是二进制友好的(如0或1)。用户提到`root.val`,可能假设值是1位数字。在Python代码中,可能是DFS或树的遍历中记录路径。回答结构:-系统指令:行内数学表达式用`$...$`,但这里主要是代码。代码应该用代码块或行内。-引用:末尾添加引用标识。用户提供了引用[1]和[^2],但[^2]有双句号错误。引用[1]是关于树的扁平化,可能相关;引用[2]是关于字符串加法,不直接相关。我应该参考,但专注用户问题。-用户上一次的问题:之前没有交互,系统给了示例。用户说"上述引用仅供参考",所以我应该基于用户当前问题。-生成相关问题:指令中说"生成相关问题",所以在回答后添加相关问题。回答步骤:1.解释表达式的作用。2.用示例说明。3.讨论优势和典型应用场景。4.使用中文。确保真实可靠:位操作用于路径编码是常见的,尤其在二叉树遍历中。**问题理解:**用户的问题是:"我想理解位操作在路径计算中的作用",具体代码`self.path=self.path<<1|root.val`。所以,我聚焦于这个表达式。**计划回答:**-引言:解释位操作在路径计算中的目的。-详细解释表达式:分解`<<`和`|`。-示例:用数字展示路径构建。-为什么有效:高效性、节省空间。-潜在问题:值范围(需要是0或1)。-引用:由于用户提供引用,我可以提到在引用[^1]中有关树遍历,但不太直接。添加引用标识时自然插入。-相关问题:生成2-3个相关问题。相关问题示例:1.位操作在路径计算中的优势是什么?2.如何处理非二进制值的路径?3.Python中有其他高效路径表示方法吗?最后,添加引用标识。完整回答结构:1.解释表达式。2.示例。3.应用场景。4.注意事项。5.相关问题。</think> ### 理解位操作在路径计算中的作用 在二叉树或图的遍历问题中,位操作常用于高效地表示和计算路径。您的查询涉及表达式 `self.path = self.path << 1 | root.val`,这是在Python中常用的一种技巧,特别是在根到叶子路径的求和或存储场景中(例如LeetCode问题如[112. 路径总和](https://leetcode.com/problems/path-sum/))。接下来,我将逐步解释这个表达式的含义、作用和优势。 #### 1. 表达式分解与含义 - **初始状态**:`self.path` 表示当前路径的整数编码,初始值通常为0。 - **位左移操作 `<<`**:`self.path << 1` 将 `self.path` 的二进制表示左移一位,相当于乘以2(即添加一个最低位0)。例如: - 如果当前 `self.path = 3`(二进制 $11_2$),则 `3 << 1 = 6`(二进制 $110_2$)。 - **按位或操作 `|`**:`| root.val` 将当前节点值添加到路径末尾。这里假设 `root.val` 是0或1(因为位操作要求值是二进制兼容的),如果 `root.val=1`,则会将路径的最低位置1。 - 例如:`6 | 1 = 7`(二进制 $111_2$)。 - **整体作用**:表达式 `self.path = self.path << 1 | root.val` 相当于: - 将当前路径“左移”以腾出空间。 - 在最低位“添加”节点值 `root.val`。 - 这样,路径被编码为一个整数,其二进制表示直接对应遍历路径的顺序(0表示左节点选择或其他默认值,1表示当前节点值)。 #### 2. 关键作用:路径编码的示例 假设在二叉树遍历中,`root.val` 限定为0或1(常见于二值路径问题),起始 `self.path = 0`。用代码示例演示: ```python # Python 伪代码 (基于DFS二叉树遍历) def dfs(root, self): if not root: # 空节点,返回 return # 更新路径: self.path = self.path << 1 | root.val self.path = self.path << 1 | root.val # 核心表达式 # 如果是叶子节点,输出当前路径(作为整数表示的二进制) if not root.left and not root.right: print(f"路径值(整数): {self.path}, 二进制路径: {bin(self.path)}") # 递归左右子树 self.dfs(root.left) self.dfs(root.right) # 回溯时撤销位操作(实际中可能不需要,取决于需求) ``` - **计算示例**: 假设树路径为 `root.val=1 -> root.val=0 -> root.val=1`。 - **步骤1**: `self.path = 0 << 1 | 1 = 0 | 1 = 1` (二进制 $1_2$,表示路径 `1`)。 - **步骤2**: `self.path = 1 << 1 | 0 = 2 | 0 = 2` (二进制 $10_2$,表示路径 `1 -> 0`)。 - **步骤3**: `self.path = 2 << 1 | 1 = 4 | 1 = 5` (二进制 $101_2$,表示路径 `1 -> 0 -> 1`)。 - **输出**: 完整路径被编码为整数5,二进制为 `0b101`。 这种方式高效地将路径存储为一个整数,避免使用列表等数据结构,节省时间和空间[^1]。 #### 3. 优势与应用场景 - **空间与时间效率**: - **空间优化**:使用一个整数代替列表存储路径(例如,路径长度10只需要一个整数,而非列表)。 - **时间高效**:位操作(左移、或)是常数时间操作($O(1)$),而列表追加操作最差为 $O(n)$(n为路径长度)。在深度优先搜索(DFS)中,整体遍历仍为 $O(n)$(n为节点数),但每步路径更新更快。 - **典型问题场景**: - **根到叶子路径求和**:如果每个节点值被加到路径中,最终可解码二进制得到总和。例如,路径 `101`(二进制)对应十进制5。 - **状态压缩**:在图搜索(如状态机)中用于高效表示路径序列。 - 常见于算法问题,如二叉树序列化或路径判断[^1]。 #### 4. 注意事项与限制 - **值范围要求**:`root.val` 必须为0或1;如果值是更大的数字(如2),表达式可能失效或需要额外处理(例如,用位掩码或其他编码)。在您提供的引用中,[^1]使用树操作,但不涉及位操作,但它展示了树遍历的上下文中高效编码的常见思路。 - **Python特性**:Python整数可以任意精度,所以路径长度不受限制(但实际中受内存限制)。 - **回溯考虑**:在DFS中,完成后需撤销路径(可能用其他变量保存状态,而不是直接修改`self.path`)。 #### 5. 相关问题 为帮助您深入学习,以下是相关衍生问题: 1. 位操作在路径计算中的优势是什么?如何用于路径求和? 2. 如何处理 `root.val` 不是0或1的情况(例如,值在0-9范围)? 3. Python中有其他高效路径表示方法(如字符串拼接或列表)与位操作比较的优缺点? 4. 位操作在二叉树遍历中如何支持回溯机制? 如果您有具体代码场景或更多细节(如节点值类型),我可以进一步调整解释或提供完整Python示例。[^1] [^1]: 参考自LeetCode树遍历问题和相关算法实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值