leetcode964-表示数字的最少运算符

19 篇文章 0 订阅
13 篇文章 0 订阅

给定一个正整数 x,我们将会写出一个形如 x (op1) x (op2) x (op3) x … 的表达式,其中每个运算符 op1,op2,… 可以是加、减、乘、除(+,-,*,或是 /)之一。例如,对于 x = 3,我们可以写出表达式 3 * 3 / 3 + 3 - 3,该式的值为 3 。

在写这样的表达式时,我们需要遵守下面的惯例:

    除运算符(/)返回有理数。
    任何地方都没有括号。
    我们使用通常的操作顺序:乘法和除法发生在加法和减法之前。
    不允许使用一元否定运算符(-)。
    例如,“x - x” 是一个有效的表达式,因为它只使用减法,但是 “-x + x” 不是,因为它使用了否定运算符。 

我们希望编写一个能使表达式等于给定的目标值 target 且运算符最少的表达式。返回所用运算符的最少数量。

示例 1:

输入:x = 3, target = 19
输出:5
解释:3 * 3 + 3 * 3 + 3 / 3 。表达式包含 5 个运算符。

示例 2:

输入:x = 5, target = 501
输出:8
解释:5 * 5 * 5 * 5 - 5 * 5 * 5 + 5 / 5 。表达式包含 8 个运算符。

示例 3:

输入:x = 100, target = 100000000
输出:3
解释:100 * 100 * 100 * 100 。表达式包含 3 个运算符。

提示:

  • 2 <= x <= 100
  • 1 <= target <= 2 * 10^8

一、思路

(一)动态规划

1、踩过的坑

这是一道很明显的动态规划问题,一开始我只是把这题当成一般的动态规划问题,结果。。。

当时是这么想的,既然要把这个target拆成用x表达的计算式,那么不妨从最高位开始,举个例子:

输入:x = 5, target = 501
输出:8
解释:5 * 5 * 5 * 5 - 5 * 5 * 5 + 5 / 5 。表达式包含 8 个运算符。
实际上也可以从125(5*5*5)开始做加法式的计算:
5 * 5 * 5 + 5 * 5 * 5 + 5 * 5 * 5 + 5 * 5 * 5 + 5 / 5
不过这么做显然不妥。因为运算符数量不是最少的。

于是我得出结论,每次给出一个target,需要计算的时候,都有两个方向:

  • 减法方向:一个是比target要大的x的幂次(上面的例子中,这个数就是625(5 * 5 * 5 * 5))
  • 加法方向:一个是比target要小的x的幂次(上面的例子中,这个数就是125(5 * 5 * 5 ))

只要能够求出两个方向中较小的值,就能得出答案

求解方法是直接递归地求解加(减)之后的target了,还是以上面的例子为例:

如果我从减法方向入手:
首先求出625,然后减去target,得124,这就是我要求的新的target,直接递归求解
当target小于x时,不必递归直接算出答案

实际上不行,因为每次计算都涉及到两个方向,一加一减,有大量的重复,甚至出现自己调用自己的情况:

输入:x = 5, target = 375
第一次做减法得:625 - 375 = 250
第二次做减法得:625 - 250 = 375

这个自己调用自己的情况,是有条件出现的,我做了一点改进,使得每次做加法或者减法时都要看看里哪个方向更近,走近的路,可以避免这个错误
C++代码:

class Solution {
public:
    map<long, int> table;
    int leastOpsExpressTarget(int x, int target) {
        vector<int> dp(x + 5, 0);
        dp[0] = -1;
        for(int i=1; i <= x; i++){
            int temp = ((x - i) > i) ? (2 * i - 1) : 2 * (x - i);
            dp[i] = temp;
        }
        long max_num = computePow(x, target);
        if(table.count(target))
            return table[target];
        long num_up = x * x, num_low = x;
        long mid = (num_up + num_low) / 2;
        for(long i = 2 * x; i <= target + x; i += x){
            int temp;
            if(i < num_up){
                if(i <= mid){
                    temp = table[i - num_low] + table[num_low] + 1;
                }
                else{
                    temp = table[num_up] + table[num_up - i] + 1;
                }
                table.insert(map<long, int>::value_type(i, temp));
            }
            else{
                num_up *= x;
                num_low *= x;
                mid = (num_up + num_low) / 2;
            }
        }
        if(table.count(target))
            return table[target];
        mid = (num_up + num_low) / 2;
        int ans;
        if(true){   //
            long res = target % x, n = target / x;
            int ans1 = dp[res] + table[n * x] + 1, ans2 = table[(n + 1) * x] + dp[x - res] + 1;
            ans = (ans1 > ans2) ? ans2 : ans1;
        }
        if(true){   //
            long d = num_up - target;
            long n = d / x, res = d % x;
            int ans3 = INT32_MAX;
            if(table.count(n * x) && table.count((n + 1) * x)){
                int ans1 = table[num_up] + dp[res] + table[n * x] + 2, ans2 = table[num_up] + dp[x - res] + table[(n + 1) * x] + 2;
                ans3 = (ans1 > ans2) ? ans2 : ans1;
            }
            ans = (ans > ans3) ? ans3 : ans;
        }
        return ans;
    }

    long computePow(int& x, int& target){
        long num = x;
        int n = 0 ;
        while(num <= target + x){
            table.insert(map<long, int>::value_type(num, n));
            num *= x;
            n++;
        }
        table.insert(map<long, int>::value_type(num, n));
        return num;
    }
};

这个代码也是错的,虽然例子过了,但是还是错了

这是因为:离的近那一方,虽然从数值上来看较小,但是使用x来表示,其代价不一定比离得远的那一方大
于是,我们还是应该从两个方向入手,不过在加法方向上需要设置限制,以免无穷无尽的加

2、带备忘录的自顶向下的动态规划

这里,我们采用除法而不是乘法,来避免数值溢出

举个例子:

输入:x = 5, target = 501
第一次,通过加法或者减法(5的0次方),使target变为5的倍数(即:500或者505)
第二次,通过加法或者减法(5的1次方),使target变为5 * 5的倍数
.......
如此下去,因为每次计算都有加减两个方向,减法方向经过数次计算会变为0,到达递归出口,但是加法不会,而且还有数值溢出的风险

因此每次计算后,我们将指数+1,而将target除以5,来表示下一次

C++代码:

class Solution {
public:
    map<pair<int, int>, int> table;
    int leastOpsExpressTarget(int x, int target) {
		cost.push_back(2);
		for(int i = 1; i < 50; i++)
			cost.push_back(i);
        num = x;
        return dp(target, 0) - 1;
    }
	
	// 动态规划
	int dp(int target, int exp){
		pair<int, int> temp;
		temp.first = target;
		temp.second = exp;
		int ans;
		if(target == 0)
			ans = 0;
		else if(target == 1)
			ans = cost[exp];
		else if(exp > 32)
			ans = target;
		else{
			int remainder1 = target % num;
			int quotient = target / num, remainder2 = num - remainder1;
			int cost1 = remainder1 * cost[exp], cost2 = remainder2 * cost[exp];
			int ans1, ans2;
			// 做减法
			if(table.count(pair<int, int>(quotient, exp + 1)))
				ans1 = cost1 + table[pair<int, int>(quotient, exp + 1)];
			else
				ans1 = cost1 + dp(quotient, exp + 1);
			// 做加法
			if(table.count(pair<int, int>(quotient + 1, exp + 1)))
				ans2 = cost2 + table[pair<int, int>(quotient + 1, exp + 1)];
			else
				ans2 = cost2 + dp(quotient + 1, exp + 1);
			
			// min(sub, add)
			ans = min(ans1, ans2);
		}
		table.insert(map<pair<int, int>, int>::value_type(temp, ans));
		return ans;
	}
	
private:
    int num;
	vector<int> cost;
};

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值