数位dp + 记忆化搜索

这里写目录标题

数位dp

1.数位dp的由来:数位dp也是动态规划的一种类型,而数位dp解决的问题往往是这样的:
题目会给你一个区间,然后让你去根据这个区间去找一些符合条件的数据。
但是这样的话我们最可能想到的一点就是遍历,但是在某些情况下就会出问题。

2.例如:
①例题:要给你一个数据是0~12345数据大小,让你在其中找到相邻位数相差大小大于2的情况的数字,并去统计这些数的数量,那么应该怎么操作呢?

对于上题,如果说我们只是通过暴力遍历的方法,那么在进行操作的时候,要取到每一个数,然后对这个数字进行去位,然后判断的话,那么就会用很长的时间,在一些题目中直接就超时了,那么我们这时就应该想到通过数位dp的方法进行解决

②数位dp的思想

要通过数位dp进行解决问题,我们首先要明白的是,其名如其操作,就是对一个数字的每个位进行操作的,所以会有一下步骤:

  • 给不足最大位数的数字添加前导0(因为操作的时候,需要位数一一对应)
  • 使用dfs进行递归操作(因为每次进行的操作都是一样的,递归无疑是最好的选择)
  • 对正确的数字进行记录

③解决问题:
现在我们对数位dp的思想有了了解,那么我们现在对上面这道题进行解决:


int Countnumber(int n)
{
	int temp = n;       
	int pos = 0;
	vector<int> digits;
	while (temp > 0)                  //先将每位对应的最大值加入到数组中
	{
		digits.emplace_back(temp % 10);
		temp /= 10;
	}
	reverse(digits.begin(), digits.end());    //因为是尾插,所以要翻转
	function<int(int, int, bool)> dfs = [&](int pos, int pre_num, bool bound)->int {   //递归
		if (pos >= digits.size())       //如果能递归到此处,那么证明该数是合格的,+1.
		{
			return 1;
		}
		int n = bound ? digits[pos] : 9;    //查看上一位的状态,如果是最大值,那么此时这个位的最大只能取得该位
		int ret = 0;                        //的最大值,否则可以选择0~9中任意一个数
		for (int i = 0; i <= n; ++i) 
		{
			if (abs(i - pre_num) > 2)       //根据题意进行判断
			{
				ret += dfs(pos + 1, i, bound && i == digits[pos]);  
			}           //             上一位如果是临界值并且当前位也是临界值时,那么下一位的操作就要被限制了
		}
		return ret;
	};

	return dfs(pos, 100, true);
}

通过这样的计算,那么题目也就解决了。

记忆化搜索

1.对于上面的数位dp其实并不是一个完美的操作,因为其做了很多重复的操作,而为了减少这些重复的操作,也就出现了一个记忆化搜索,来对重复的操作进行压缩。

2.还是同样的上面的题,对于上面题目我们发现,如果说此时遇到了11333这个数字,进行遍历完,然后继续往后遍历,就肯定会遍历到12333这个数字,此时我们其实可以当12333这个数字遍历到123前三位的时候就可以将这个数据是否为正确的数进行返回了,因为后面是不管出现什么情况,都已经在113??这五位数据中进行保存了,也就是当遍历到113的时候,以113为高位的数据有多少个正确的数都已经知道了,所以没必要进行重复的操作。

上面这只是其中的一个例子,当然还有很多,都是需要进行重复去除了,这样的操作会大大降低程序运行的效率。

3.进行修改:

int Countnumber(int n)
{
	int temp = n;       
	int pos = 0;
	vector<int> digits;
	while (temp > 0)                  //先将每位对应的最大值加入到数组中
	{
		digits.emplace_back(temp % 10);
		temp /= 10;
	}
	reverse(digits.begin(), digits.end());    //因为是尾插,所以要翻转
	int memy[10][10];                         //添加一个10x10的数组,去保存实现记忆化
	memset(memy, -1, sizeof(memy));           //首先,先给其全部赋值为-1
	function<int(int, int, bool)> dfs = [&](int pos, int pre_num, bool bound)->int {   //递归
		if (pos >= digits.size())       //如果能递归到此处,那么证明该数是合格的,+1.
		{
			return 1;
		}
		if (!bound && memy[pos][pre_num] != -1)   //当bound为false的时候,我们发现,此时往后的遍历就和前一个数
		{                                         //没有关系了,后面肯定是0~9,所以此时如果说这个数以及前一个数
			return memy[pos][pre_num];            //在矩阵中保存的位置不是-1,那么就可以直接返回了,因为后面的
		}                                         //遍历到的大小其实就是这个数组中保存的,没必要再二次遍历了。
		int n = bound ? digits[pos] : 9;    //查看上一位的状态,如果是最大值,那么此时这个位的最大只能取得该位
		int ret = 0;                        //的最大值,否则可以选择0~9中任意一个数
		for (int i = 0; i <= n; ++i) 
		{
			if (abs(i - pre_num) > 2)       //根据题意进行判断
			{
				ret += dfs(pos + 1, i, bound && i == digits[pos]);  
			}           //             上一位如果是临界值并且当前位也是临界值时,那么下一位的操作就要被限制了
		}
		if (!bound)    //如果此时前一位没有限制,那么直接赋值即可。
		{
			memy[pos][pre_num] = ret;
		}
		return ret;
	};

	return dfs(pos, 100, true);
}

例题

在这里插入图片描述
解法如下:

1.根据题目可以得到,当一个数是不是好数,取决于这个数中至少要有一个数是{2 ,5 ,6 ,9 }中的一个,对于{0 ,1 ,8}可有可无,但是不可以含有{3 ,4 ,7}。

2.那么这道题就非常适合用数位dp的做法进行操作,我们之间给一个数组,将0~9对应的下标设置为-1 0 1三个数,如下:

 const char check[10] = {0,0,1,-1,-1,1,1,-1,0,1};

其中表示为只要含有一个数对应的check数组中的数是-1,那么这个数就肯定不是好数,如果是对应的是1和0,那么就往后继续判断。

3.接下来就使用同样的手法进行数位dp+记忆化搜索了,解法如下:

const char check[10] = {0,0,1,-1,-1,1,1,-1,0,1};
class Solution {
public:
    int rotatedDigits(int n) 
    {
        vector<int> digits;
        while(n > 0)
        {
            digits.emplace_back(n%10);
            n/=10;
        }
        reverse(digits.begin(),digits.end());
        int memo[5][2][2];                                                                                                //标记数组,用来实现记忆化搜索
        memset(memo,-1,sizeof(memo));
        function<int(int,bool,bool)> dfs = [&](int pos,bool bound,bool diff)->int{        //lamdam表达式,用来实现递归
            if(pos == digits.size())
            {
                return diff;  //diff表示的是该数最后是否可以进行选择
                              //如果可以diff就是true也就是1
                              //如果不可以diff就是false,也就是0
            }
            if(memo[pos][bound][diff] != -1)
            {
                return memo[pos][bound][diff];
            }
            int n = bound ? digits[pos] : 9;
            int ret = 0;
            for(int i = 0;i <= n;++i)
            {
                if(check[i] != -1)   //如果是-1那么就没必要往后遍历了,该数肯定不是好数
                {
                    ret += dfs(pos+1,(bound) && (i == digits[pos]), diff || check[i]==1);   //好数的基础是必须含有一个{2,5,6,9},所以是diff||check[i]
                }
            }
            memo[pos][bound][diff] = ret;
            return ret;
        };
        return dfs(0,true,false);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值