回溯算法-DFS

回溯模板

场景:原问题查找子字符串问题转化为,已知分到i处(i为原字符串索引),剩余问题仍是一个找字串的原问题

解决问题需要的变量有:
s:原字符串
start:已经分到的位置
n:需要找的最终满足的条件
sum:逼近最终条件过程中的值


bool dfs(const string& s, int start, const int& n, int sum) {
	if (start == s.size()) {
		// 最终返回的条件,可以是false/true. 也可以是存储的路径
		return n == sum;
	}
	for (int j = start; j < s.size(); j++) {
	    // start到新的分割点的中间结果
		int cur = stoi(s.substr(start, j - start + 1));
		sum += cur; // 结果的输入到下一轮的计算中
		// 对结果的判断
		if (dfs(s, j + 1, n, sum)) {
			return true;
		}
		sum -= cur; // 回溯去除本轮的计算结果
	}
	return false;
}

排列 - 组合/子集

排列 - 无重复不可复选

void backtrack(vector<int>& nums) {
     // base case,到达叶子节点
     if (track.size() == nums.size()) {
         // 收集叶子节点上的值
         res.push_back(vector<int>(track.begin(), track.end()));
         return;
     }
     // 回溯算法标准框架
     for (int i = 0; i < nums.size(); i++) {
         // 已经存在 track 中的元素,不能重复选择
         if (used[i]) {
             continue;
         }
         // 做选择
         used[i] = true;
         track.push_back(nums[i]);
         // 进入下一层回溯树
         backtrack(nums);
         // 取消选择
         track.pop_back();
         used[i] = false;
     }
 }

排列 - 可重复不可复选

需要注意提前排序,注意减枝

sort(nums.begin(), nums.end());

void backtrack(vector<int>& nums) {
     // 当长度相等时,将结果记录
     if (track.size() == nums.size()) {
         res.push_back(track);
         return;
     }

     // 遍历没有被使用过的元素
     for (int i = 0; i < nums.size(); i++) {
         if (used[i]) {
             continue;
         }
         // 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置
         if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
             continue;
         }
         // 添加元素,标记为使用过
         track.push_back(nums[i]);
         used[i] = true;
         // 继续回溯
         backtrack(nums);
         // 回溯
         track.pop_back();
         used[i] = false;
     }
 }

子集- 无重复不可复选

void backtrack(vector<int>& nums, int start) {
     // 前序位置,每个节点的值都是一个子集
     res.push_back(track);

     // 回溯算法标准框架
     for (int i = start; i < nums.size(); i++) {
         // 做选择
         track.push_back(nums[i]);
         // 通过 start 参数控制树枝的遍历,避免产生重复的子集
         backtrack(nums, i + 1);
         // 撤销选择
         track.pop_back();
     }
}

组合/子集 - 可重复不可复选(*)

需要注意提前排序,注意减枝

sort(nums.begin(), nums.end());

void backtrack(vector<int>& nums, int start) { // start 为当前的枚举位置
     res.emplace_back(track); // 前序位置,每个节点的值都是一个子集
     for(int i = start; i < nums.size(); i++) {
         if (i > start && nums[i] == nums[i - 1]) { // 剪枝逻辑,值相同的相邻树枝,只遍历第一条
             continue;
         }
         track.emplace_back(nums[i]); // 添加至路径
         backtrack(nums, i + 1); // 进入下一层决策树
         track.pop_back(); // 回溯
     }
 }

Leetcode用例

131: 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。

示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]

示例 2:
输入:s = "a"
输出:[["a"]]

提示:
1 <= s.length <= 16
s 仅由小写英文字母组成

class Solution {
public:
    vector<vector<string>> path;
    vector<string> ans;

    void dfs(const string& s, int start) {
        if (start == s.size()) {
            path.push_back(ans);
            return ;
        }
        for (int j = start; j < s.size(); j++) {
            string temp = s.substr(start, j - start + 1);
            if (temp == string(temp.rbegin(), temp.rend())) {
                ans.push_back(temp);
                dfs(s, j + 1);
                ans.pop_back();
            }
        }
    }

    vector<vector<string>> partition(string s) {
        dfs(s, 0);
        return path;
    }
};

6441:求一个整数的惩罚数

给你一个正整数 n ,请你返回 n 的 惩罚数 。 n 的 惩罚数 定义为所有满足以下条件 i 的数的平方和:
说明: 1 <= i <= n i * i 的十进制表示的字符串可以分割成若干连续子字符串,且这些子字符串对应的整数值之和等于 i 。

示例1: 
输入:n = 37 
输出:1478 
解释:总共有 4 个整数 i 满足要求:
- 1 ,因为 1 * 1 = 1
- 9 ,因为 9 * 9 = 81 ,且 81 可以分割成 8 + 1- 10 ,因为 10 * 10 = 100 ,且 100 可以分割成 10 + 0- 36 ,因为 36 * 36 = 1296 ,且 1296 可以分割成 1 + 29 + 6 。 因此,37 的惩罚数为 1 + 81 + 100 + 1296 = 1478

提示:
1 <= n <= 1000

class Solution {
public:
    bool dfs(const string& s, int start, const int& i, int sum) {
        if (start == s.size()) {
            return i == sum;
        }
        for (int j = start; j < s.size(); j++) {
            int temp = stoi(s.substr(start, j - start + 1));
            sum += temp;
            if (sum > i) {
                break;
            }
            if (dfs(s, j + 1, i, sum)) {
                return true;
            }
            sum -= temp; 
        }
        return false;
    }

    bool conditionNumber(const int i) {
        if (i * i == i) {
            return true;
        }
        string s = to_string(i * i);
        return dfs(s, 0, i, 0);
    }

    int punishmentNumber(int n) {
        int count = 0;
        for (size_t i = 1; i <=n ; i++) {
            if (conditionNumber(i)) {
                count += i * i;
            }
        }
        return count;
    }
};

DEBUG

前提:
void printDebug(int  n) {
	for (int i = 0; i < n; i++) {
	    std::cout << "   ";
	}
}

使用方法:
1、初始化全局变量int n = 0;
2、在函数开始处添加打印,且需要添加换行符
printDebug(n++);
std::cout << std::endl;
3、在函数return前添加打印,且需要添加换行符
printDebug(--n);
std::cout << std::endl;

例如:以下代码去掉注释行
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值