饭后小甜点leetcode——回溯

文章目录

    • Backtracking
      • 1. 全排列
        • 没有重复元素的全排列
        • 有重复元素的全排列
        • 回文排列
      • 3. 组合
        • 求组合
        • 组合总和
      • 4. 子集
        • 求所有的子集合(没有重复元素)
        • 求所有的子集合(有重复元素)
      • 5. 数独
        • 验证数独结果是否正确
        • 解数独
      • 6. N皇后
        • 求N皇后的所有解
        • 求N皇后解的个数
      • 7. 括号生成

Backtracking

1. 全排列

没有重复元素的全排列

leetcode对应题目地址

【思路】keep一个visited数组,用来记录对应下标的元素是否被访问过,因为每次递归的时候,在for循环那块都会从0重新开始,所以为了避免重复把一个元素加进去好多次,要先判断一下该元素是否被访问过,因为给定输入数组中不存在重复元素,所以这样判断就足够了。
递归结束条件:当用来盛装当前的排列的temp list的元素个数达到输入的元素个数的时候,把temp加入到res中,然后返回。

【易错点】

  • 忘了return
  • 把temp加入到res中时,要把temp作为参数new一个list,否则会影响res结果。

【代码】

public class Solution {
    public IList<IList<int>> Permute(int[] nums) {
        var res = new List<IList<int>>();
        BackTracking(nums, new bool[nums.Length], res, new List<int>());
        return res;
    }
    
    public void BackTracking(int[] nums, bool[] visited, List<IList<int>> res, List<int> temp){
        if(temp.Count==nums.Length){
            res.Add(new List<int>(temp));
            return;
        }
        for(var i=0;i<nums.Length;i++){
            if(visited[i])
                continue;
            temp.Add(nums[i]);
            visited[i] = true;
            BackTracking(nums, visited, res, temp);
            temp.RemoveAt(temp.Count-1);
            visited[i] = false;
        }
    }
}

有重复元素的全排列

leetcode对应题目地址

【思路】和上一个的不同就是,先要排个序,因为这个后面需要判断一下nums[i-1]和nums[i]是否相同,如果相同,而且nums[i-1]没有被访问过,则continue,其他的地方跟上一个差不多。

【易错点】

  • 要先对数组排序
  • 在判断nums[i-1]和nums[i]是否相同的时候,注意同时要加上判断nums[i-1]没有被访问过的条件,如果不加的话,在某一轮未完的递归过程中,如果遇到nums[i-1]==nums[i],就直接continue了,例子:nums=[1,1,2],在第一次深入到递归里面的时候,如果忽略了那个条件就会把第二个1漏了,导致最后长度还没达到3就从for里面回调函数后面的部分自然走出循环,结束递归,结果返回空list。

【代码】

public class Solution {
    public IList<IList<int>> PermuteUnique(int[] nums) {
    	Array.Sort(nums);
        var res = new List<IList<int>>();
        BackTracking(nums, new bool[nums.Length], res, new List<int>());
        return res;
    }
    
    public void BackTracking(int[] nums, bool[] visited, List<IList<int>> res, List<int> temp){
        if(temp.Count==nums.Length){
            res.Add(new List<int>(temp));
            return;
        }
        for(var i=0;i<nums.Length;i++){
            if(visited[i]|| i>0&&nums[i]==nums[i-1]&&!visited[i-1])
                continue;
            temp.Add(nums[i]);
            visited[i] = true;
            BackTracking(nums, visited, res, temp);
            temp.RemoveAt(temp.Count-1);
            visited[i] = false;
        }
    }
}

顺便看一道题:下一个排列
例子:1432 -> 2134

public class Solution {
    public void NextPermutation(int[] nums) {
        var left = -1;
        for(var i=nums.Length-1;i>0;i--){
            if(nums[i]>nums[i-1]){
                left = i-1;break;
            }
        }
        
        if(left!=-1){
            var right = nums.Length-1;
            for(var i=nums.Length-1;i>left;i--){
                if(nums[i]>nums[left]){
                    right = i;break;
                }
            }
            Swap(nums, left, right);
        }
        Reverse(nums, left+1, nums.Length-1);
    }
    
    public void Reverse(int[] nums, int left, int right){
        while(left<right){
            Swap(nums,left,right);
            left++;right--;
        }
    }
    
    public void Swap(int[] nums, int left, int right){
        var temp = nums[left];
        nums[left] = nums[right];
        nums[right] = temp;
    }
}

回文排列

266. 回文排列
267. 回文排列 II

3. 组合

求组合

leetcode题目地址
【思路】类似排列,只不过组合是没有顺序的,所以[1,2]和[2,1]是一样的,所以for循环中的起始条件就应该是一个从上一次递归传来的start值,这个start值每深入一层递归就加一,也就是说深入递归的时候不管前面的数只管后面的数,所以就不需要visited数组了,反正也不管前面的数了:-)。
【易错点】注意start值的更新是i+1而不是start+1(细心)

public IList<IList<int>> Combine(int n, int k) {
    var res = new List<IList<int>>();
    BackTracking(n, k, res, new List<int>(), 1);
    return res;
}

public void BackTracking(int n, int k, List<IList<int>> res, List<int> temp, int start){
    if(temp.Count==k){
        res.Add(new List<int>(temp));
        return;
    }
    for(var i=start;i<=n;i++){
        temp.Add(i);
        BackTracking(n, k, res, temp, i+1);
        temp.RemoveAt(temp.Count-1);
    }
}

组合总和

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

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

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

示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]

示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

【思路】不能向回走,所以得有一个start变量
【易错点】如果sum步子迈大了一下子超过了target,那就没必要往前走了直接return

public IList<IList<int>> CombinationSum(int[] candidates, int target) {
    var res = new List<IList<int>>();
    BackTracking(candidates, target, res, new List<int>(), 0, 0);
    return res;
}

public void BackTracking(int[] candidates, int target, List<IList<int>> res, List<int> temp, int start, int sum){
    if(sum==target){
        res.Add(new List<int>(temp));
    }
    else if(target<sum){
        return;
    }
    
    for(var i=start;i<candidates.Length;i++){
        temp.Add(candidates[i]);
        sum += candidates[i];
        BackTracking(candidates, target, res, temp, i, sum);
        temp.RemoveAt(temp.Count-1);
        sum -= candidates[i];
    }
}

40. 组合总和 II
和上一题不同在于:candidates 中的每个数字在每个组合中只能使用一次。

public class Solution {
    public List<IList<int>> Ans = new List<IList<int>>();
    public IList<IList<int>> CombinationSum2(int[] candidates, int target) {
        Array.Sort(candidates);
        BackTrack(candidates, target, new List<int>(), 0);
        return Ans;
    }
    
    public void BackTrack(int[] candidates, int cur, List<int> temp, int start){
        if(cur==0){
            Ans.Add(new List<int>(temp));
        }
        else if(cur<0) return;
        
        for(var i=start;i<candidates.Length;i++){
            if(i>start&&candidates[i]==candidates[i-1]) continue;
            temp.Add(candidates[i]);
            BackTrack(candidates, cur-candidates[i], temp, i+1);
            temp.RemoveAt(temp.Count-1);
        }
    }
}

377. 组合总和 Ⅳ
突然冒出来一个dp问题。。
跟I不同的地方在于:给定的数组中含有负数

public class Solution {
    //google, facebook
    public int CombinationSum4(int[] nums, int target) {
        var dp = new int[target + 1];
        dp[0] = 1;
        
        for (int i = 1; i <= target; i++)
        {
            for (int j = 0; j < nums.Length; j++)
            {
                if (nums[j] <= i)
                {
                    dp[i] = dp[i] + dp[i - nums[j]];
                }
            }
        }

        return dp[target];
    }
}

216. 组合总和 III
【题目】
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。

说明:
所有数字都是正整数。
解集不能包含重复的组合。

示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]

示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]

public class Solution {
    public List<IList<int>> result = new List<IList<int>>();
    public IList<IList<int>> CombinationSum3(int k, int n) {
        BackTrack(k,n,new List<int>(), 0, 1);
        return result;
    }
    
    public void BackTrack(int k, int n, List<int> current, int sum, int start){
        if(current.Count==k&&sum==n){
            result.Add(new List<int>(current));
            return;
        }
        
        if (sum > n || start > 9 || current.Count > k)
        {
            return;
        }
        
        for(var i=start;i<10;i++){
            current.Add(i);
            BackTrack(k, n, current, sum+i, i+1);
            current.Remove(i);
        }
    }
}

4. 子集

求所有的子集合(没有重复元素)

leetcode题目地址

public class Solution {
    public List<IList<int>> result = new List<IList<int>>();
    public IList<IList<int>> Subsets(int[] nums) {
        BackTrack(nums, new List<int>(), 0);
        return result;
    }
    
    public void BackTrack(int[] nums, List<int> current, int start){
        result.Add(new List<int>(current));
        
        for(var i=start;i<nums.Length;i++){
            current.Add(nums[i]);
            BackTrack(nums, current, i+1);
            current.Remove(nums[i]);
        }
    }
}

求所有的子集合(有重复元素)

leetcode题目地址

public class Solution {
    public List<IList<int>> Ans = new List<IList<int>>();
    public IList<IList<int>> SubsetsWithDup(int[] nums) {
        Array.Sort(nums);
        BackTrack(nums, new List<int>(), 0);
        return Ans;
    }
    
    public void BackTrack(int[] nums, List<int> temp, int start){
        Ans.Add(new List<int>(temp));
        for(var i=start;i<nums.Length;i++){
            if(i>start&&nums[i]==nums[i-1]) continue;
            temp.Add(nums[i]);
            BackTrack(nums, temp, i+1);
            temp.Remove(nums[i]);
        }
    }
}

5. 数独

验证数独结果是否正确

leetcode题目地址

public class Solution {
    public bool IsValidSudoku(char[,] board) {
        var hDict = new bool[9,9];
        var vDict = new bool[9,9];
        var xDict = new bool[9,9];
        
        for (int hIndex = 0; hIndex < 9; hIndex++)
        {
            for (int vIndex = 0; vIndex < 9; vIndex++)
            {
                var c = board[hIndex, vIndex];
                if (c == '.')
                {
                    continue;
                }
                
                var cIndex = c - '1';
                
                if (hDict[cIndex, hIndex])
                {
                    return false;
                }

                if (vDict[cIndex, vIndex])
                {
                    return false;
                }
                
                var xIndex = (vIndex / 3) * 3 + (hIndex / 3);
                if (xDict[cIndex, xIndex])
                {
                    return false;
                }
                
                hDict[cIndex, hIndex] = true;
                vDict[cIndex, vIndex] = true;
                xDict[cIndex, xIndex] = true;
            }
        }
        
        return true;
    }
}

解数独

leetcode题目地址

public class Solution {
    public void SolveSudoku(char[,] board) {
        Solve(board);
    }
    
    public bool Solve(char[,] board){
        for(var i=0;i<board.GetLength(0);i++){
            for(var j=0;j<board.GetLength(1);j++){
                if(board[i,j]=='.'){
                    for(var c='1';c<='9';c++){
                        if(IsValid(i, j, c, board)){
                            board[i,j]=c;
                            if(Solve(board)){
                                return true;
                            }
                            else{
                                board[i,j]='.';
                            }
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }
    
    public bool IsValid(int r, int c, int n, char[,] board){
        for(var i=0;i<9;i++){
            if(board[r,i]==n || board[i,c]==n) return false;
        }
        for(var i = r/3*3;i<r/3*3+3;i++){
            for(var j = c/3*3;j<c/3*3+3;j++){
                if(board[i,j]==n) return false;
            }
        }
        return true;
    }
}

6. N皇后

求N皇后的所有解

leetcode题目地址
column数组下标表示第几行,值表示把皇后放在第几列

public class Solution {
    List<IList<string>> res = new List<IList<string>>();
    int num = 0;
    public IList<IList<string>> SolveNQueens(int n) {
        num = n;
        dfs(new List<int>());
        return res;
    }
    
    public void dfs(List<int> column) {
        if(column.Count==num){
            res.Add(new List<string>(draw(column)));
            return;
        }
        for(var i=0;i<num;i++){
            if(isValid(i, column)){
                column.Add(i);
                dfs(column);
                column.RemoveAt(column.Count-1);
            }
        }
    }
    
    public bool isValid(int index, List<int> column){
        var total = column.Count;
        for(var i=0;i<total;i++){
            if(column[i]==index||i+column[i]==total+index || i-column[i]==total-index) return false;
        }
        return true;
    }
    
    public List<string> draw(List<int> column) {
        var newTable = new List<string>();
        for(var i=0;i<num;i++){
            var line = new char[num];
            for(var j=0;j<num;j++){
                if(j==column[i]){
                    line[j]='Q';
                }
                else{
                    line[j]='.';
                }
            }
            newTable.Add(new string(line));
        }
        return newTable;
    }
}

求N皇后解的个数

leetcode题目地址

public class Solution {
    int res = 0;
    int num = 0;
    public int TotalNQueens(int n) {
        num = n;
        dfs(new List<int>());
        return res;
    }
    
    public void dfs(List<int> column) {
        if(column.Count==num){
            res++;
            return;
        }
        for(var i=0;i<num;i++){
            if(isValid(i, column)){
                column.Add(i);
                dfs(column);
                column.Remove(i);
            }
        }
    }
    
    public bool isValid(int index, List<int> column){
        var total = column.Count;
        for(var i=0;i<total;i++){
            if(column[i]==index) return false;
            if(i+column[i]==total+index || i-column[i]==total-index) return false;
        }
        return true;
    }
}

7. 括号生成

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/generate-parentheses

public class Solution {
    public List<string> result = new List<string>();
    public IList<string> GenerateParenthesis(int n) {
        BackTrack(n,n,n,"");
        return result;
    }
    
    public void BackTrack(int n, int open, int close, string cur){
        if(cur.Length==n*2){
            result.Add(cur);
            return;
        }
        
        if(open>0){
            BackTrack(n, open-1, close, cur+"(");
        }
        if(open<close){
            BackTrack(n, open, close-1, cur+")");
        }
    }
}

你可能感兴趣的:(基础算法)