代码随想录算法训练营第二十五~二十八天|组合问题、分割、子集、排列

本文介绍了LeetCode中涉及的多种算法问题,如组合总和系列、回文串判断、递增子序列、全排列以及子集问题,重点讲解了递归、剪枝和去重技巧的应用。

二十五天

组合总和|||

216. 组合总和 III - 力扣(LeetCode)

public class Solution {
    public IList<IList<int>> result = new List<IList<int>>();
    public IList<int> path = new List<int>();
    public IList<IList<int>> CombinationSum3(int k, int n) {
        backtracking(n,k,0,1);
        return result;
    }

    private void backtracking(int targetsum,int k,int sum,int startindex){
        if (path.Count == k) {
            if (sum == targetsum) result.Add(new List<int>(path));
            return; // 如果path.Count == k 但sum != targetSum 直接返回
        }

        //剪枝版本(for(int i=startindex;i<=9-(k-path.Count)+1;i++))道理和组合那道题一样,都                    
        //是为了把k值后面的给删除掉
        for (int i = startindex; i <= 9; i++) {
            sum += i; // 处理
            path.Add(i); // 处理
            backtracking(targetsum, k, sum, i + 1); // 注意i+1调整startIndex
            sum -= i; // 回溯
            path.RemoveAt(path.Count - 1); // 回溯
        }
    }
}

电话号码的字母组合

17. 电话号码的字母组合 - 力扣(LeetCode)



public class Solution {
    收集数字对应的字符串
    private readonly string[] letterMap = {
        "", // 0
        "", // 1
        "abc", // 2
        "def", // 3
        "ghi", // 4
        "jkl", // 5
        "mno", // 6
        "pqrs", // 7
        "tuv", // 8
        "wxyz" // 9
    };
    
    public List<string> result = new List<string>();
    public string s;
    
    public void Backtracking(string digits, int index) {
        长度相同就退出,index是记录长度
        if (index == digits.Length) {
            result.Add(s);
            return;
        }
        字符转数字
        int digit = digits[index] - '0';
        得到的数字找对应的字符串
        string letters = letterMap[digit];
        
        for (int i = 0; i < letters.Length; i++) {
            s += letters[i];
            Backtracking(digits, index + 1);
            s = s.Remove(s.Length - 1); 回溯
        }
    }
    
    public List<string> LetterCombinations(string digits) {
        s = "";
        result.Clear();
        
        if (digits.Length == 0) {
            return result;
        }
        
        Backtracking(digits, 0);
        return result;
    }
}

二十六天

 组合总和

39. 组合总和 - 力扣(LeetCode)

这题道理和组合、组合总和|||一样,只是没有深度限制和元素重复利用限制

public class Solution {
    private IList<IList<int>> result = new List<IList<int>>();
    private IList<int> path = new List<int>();

    public IList<IList<int>> CombinationSum(int[] candidates, int target) {
        backtracking(candidates,target,0,0);
        return result;
    }
    private void backtracking(int[] candidates,int target,int sum,int startindex)
    {
        if(sum > target){return ;}
        if(sum == target){
            result.Add(new List<int>(path));
            return;
        }
        //for(int i=startindex;i<candidates.Length && sum+candidates[i]<=target;i++)剪枝版本
        //因为只要sum比target大,后面的for循环没意义
        for(int i = startindex;i < candidates.Length;i++){
            sum+=candidates[i];
            path.Add(candidates[i]);
            backtracking(candidates,target,sum,i);
            sum-=candidates[i];
            path.RemoveAt(path.Count-1);
        }
    }
}

组合总和||

40. 组合总和 II - 力扣(LeetCode)

要点:两个定义,树层去重(横向)、树枝去重(纵向)。这题要树层去重,纵向无所谓,题目没限制。

假如提供数组{1,2,2,3,6,9},因为有两个元素2,为了结果不重复,得对第二次出现的元素2去重。先说明,for循环是横向循环,backtracking函数是纵向循环。贴上代码随想录的图片

这题去重主要就在数组中的每个元素贴上bool,以此做标记,能方便判断是否排除(也可以不用used数组,毕竟直接把相同元素的for循环下的分支结果剪枝。)

tips:used数组可以不用,if(i > startindex && candidates[i] == candidates[i-1])这样就行了。

public class Solution {
    private IList<IList<int>> result = new List<IList<int>>();
    private IList<int> path = new List<int>();

    public IList<IList<int>> CombinationSum2(int[] candidates, int target) {
        Array.Sort(candidates);
        backtracking(candidates,target,0,0,new List<bool>(new bool[candidates.Length]));
        return result;
    }

    private void backtracking(int[] candidates,int target,int sum ,int startindex,List<bool> used){
        if(sum == target){
            result.Add(new List<int>(path));
            return;
        }

        for(int i=startindex;i<candidates.Length && sum + candidates[i] <=target;i++){
            //这里判断的条件有:i比下标的值大,提供的数组的i元素和i-1元素值相同,used数组i-1元素等于false。
            if(i > startindex && candidates[i] == candidates[i-1] && used[i-1] == false){
                continue;
            }
            sum+=candidates[i];
            path.Add(candidates[i]);
            used[i] = true;
            backtracking(candidates,target,sum,i+1,used);
            used[i] = false;
            sum-=candidates[i];
            path.RemoveAt(path.Count-1);
        }
    }
}

分割回文串

131. 分割回文串 - 力扣(LeetCode)

要点:回文串(例如:"aa","b","acca") 。判断逻辑:双指针,一头一尾滑动。有不一样直接false,否则true。

这题主要是每次循环都要判断从startindex到for循环的i值之间的字符元素是否符合回文串。不符合,直接跳过然后递归函数backtracking(注意下标要加1,因为是纵向循环),以此类推,直到符合回文串了,加入result。之后继续递归直到for循环(横向循环)结束。

public class Solution {
    private IList<IList<string>> result = new List<IList<string>>();
    private IList<string> path = new List<string>();
    public IList<IList<string>> Partition(string s) {
        Backtracking(s,0);
        return result;
    }
    
    private void Backtracking(string s, int startIndex) {
        // 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
        if (startIndex >= s.Length) {
            result.Add(new List<string>(path));
            return;
        }
        
        for (int i = startIndex; i < s.Length; i++) {
            if (IsPalindrome(s, startIndex, i)) {   // 是回文子串
                // 获取[startIndex,i]在s中的子串
                string str = s.Substring(startIndex, i - startIndex + 1);
                path.Add(str);
            } else {   // 不是回文,跳过
                continue;
            }
            
            Backtracking(s, i + 1); // 寻找i+1为起始位置的子串
            path.RemoveAt(path.Count - 1); // 回溯过程,弹出本次已经添加的子串
        }
    }
    
    private bool IsPalindrome(string s, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            if (s[i] != s[j]) {
                return false;
            }
        }
        return true;
    }
}

二十七天

复原IP地址

93. 复原 IP 地址 - 力扣(LeetCode)

要点:和分割回文串一样的思路,不过就是得加上"."和计数("."出现次数),还有一些IP地址条件的判断,递归时startindex要加2,因为有"."。

public class Solution {
    private IList<string> result = new List<string>();

    public IList<string> RestoreIpAddresses(string s) {
        if(s.Length < 4 || s.Length > 12){return result;}
        backtracking(s,0,0);
        return result;
    }

    private void backtracking(string s,int startindex,int pointNum){
        if(pointNum==3){
            if(IsVaild(s,startindex,s.Length-1)){
                result.Add(s);
            }
            return;
        }

        for(int i=startindex;i<s.Length;i++){
            if(IsVaild(s,startindex,i)){
                s = s.Insert(i+1,".");
                pointNum++;
                backtracking(s,i+2,pointNum);
                pointNum--;
                s = s.Remove(i+1,1);
            }else break;
        }
    }

    private bool IsVaild(string s,int start,int end){
        if(start > end){return false;}
        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');
            if(num > 255){return false;}
        }
        return true;
    }
}

子集

78. 子集 - 力扣(LeetCode)

要点:子集问题,就是收集树形结构的所有节点结果,不用考虑剪枝,把每一个结果加到result就是了

public class Solution {
    private IList<IList<int>> result = new List<IList<int>>();
    private IList<int> path = new List<int>();
    public IList<IList<int>> Subsets(int[] nums) {
        backtracking(nums,0);
        return result;
    }

    private void backtracking(int[] nums,int startindex){
        result.Add(new List<int>(path));
        if(startindex >= nums.Length){return;}

        for(int i=startindex;i<nums.Length;i++){
            path.Add(nums[i]);
            backtracking(nums,i+1);
            path.RemoveAt(path.Count-1);
        }
    }
}

子集||

90. 子集 II - 力扣(LeetCode)

思路:其实就是在原有子集问题上加个组合问题||中的剪枝条件(if(i>startindex && nums[i] == nums[i-1]))就解决了。

public class Solution {
    private IList<IList<int>> result = new List<IList<int>>();
    private IList<int> path = new List<int>();
    public IList<IList<int>> SubsetsWithDup(int[] nums) {
        Array.Sort(nums);
        backtracking(nums,0);
        return result;
    }

    private void backtracking(int[] nums,int startindex){
        result.Add(new List<int>(path));
        if(startindex >= nums.Length){return;}

        for(int i=startindex;i<nums.Length;i++){
            if(i>startindex && nums[i] == nums[i-1]){
                continue;
            }

            path.Add(nums[i]);
            backtracking(nums,i+1);
            path.RemoveAt(path.Count-1);
        }
    }
}

二十八天

递增子序列

491. 递增子序列 - 力扣(LeetCode)

要点:这题不用考虑到排序,题目已经提供递增序列了。其他的和子集||相似,只有递归逻辑不同,题目说了可以重复,但是只能由前向后,不能后面的元素找到前面一样的元素,然后就是记录方式,可以字典或者直接数组记录,题目已经提供的范围在[-100,100]之间,所以创建新二维数组201。

说明一下判断压入条件if(path.Count > 0 && nums[i] < path[path.Count-1] || used[nums[i]+100] == 1),path是有压入元素的;nums[i]<path[path.Count-1],保证是递增的序列;used[nums[i]+100] == 1,验证之前收集的元素是否存在。具备则continue,不具备则能添加进序列。

public class Solution {
    private IList<IList<int>> result = new List<IList<int>>();
    private IList<int> path = new List<int>();
    public IList<IList<int>> FindSubsequences(int[] nums) {
        backtracking(nums,0);
        return result;
    }

    private void backtracking(int[] nums,int startindex){
        if(path.Count > 1){
            result.Add(new List<int>(path));
        }
        int[] used = new int[201]; 
        for(int i=startindex;i<nums.Length;i++){
            if(path.Count > 0 && nums[i] < path[path.Count-1] || used[nums[i]+100] == 1){continue;}

            used[nums[i] + 100] = 1;
            path.Add(nums[i]);
            backtracking(nums,i+1);
            path.RemoveAt(path.Count-1);
        }
    }
}

全排列

46. 全排列 - 力扣(LeetCode)

要点:这里的全排列,是把所有的排列组合收集,例如{1,2,3},结果是[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]],但是要求集合里的元素不重复。

所以,这次单层逻辑就不能用startindex去标记了,因为每一次收集都要重头到尾遍历收集,然后加个bool数组给nums数组做标记哪个被用到,用到的跳过,没有就收集。

public class Solution {
    private IList<IList<int>> result = new List<IList<int>>();
    private IList<int> path = new List<int>();
    public IList<IList<int>> Permute(int[] nums) {
        bool[] used = new bool[nums.Length];
        backtracking(nums,used);
        return result;
    }

    private void backtracking(int[] nums,bool[] used){
        if(path.Count == nums.Length){
            result.Add(new List<int>(path));
            return;
        }
        for(int i=0;i<nums.Length;i++){
            if(used[i]==true){continue;}

            used[i] = true;
            path.Add(nums[i]);
            backtracking(nums,used);
            path.RemoveAt(path.Count-1);
            used[i] = false;
        }
    }
}

全排列||

47. 全排列 II - 力扣(LeetCode)

要点:在排列问题上多加上组合总和中的去重逻辑就行

public class Solution {
    private IList<IList<int>> result = new List<IList<int>>();
    private IList<int> path = new List<int>();
    public IList<IList<int>> PermuteUnique(int[] nums) {
        Array.Sort(nums);
        bool[] used = new bool[nums.Length];
        backtracking(nums,used);
        return result;
    }

    private void backtracking(int[] nums,bool[] used){
        if(path.Count == nums.Length){
            result.Add(new List<int>(path));
            return;
        }

        for(int i=0;i<nums.Length;i++){
            //去重
            if(i>0 && nums[i] == nums[i-1] && used[i-1] == false){continue;}
            
            if(used[i] == false){
                used[i] = true;
                path.Add(nums[i]);
                backtracking(nums,used);
                path.RemoveAt(path.Count-1);
                used[i] = false;   
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值