魔法数字

描述:牛妹给牛牛写了一个数字n,然后又给自己写了一个数字m,她希望牛牛能执行最少的操作将他的数字转化成自己的。
操作共有三种,如下:
        1.在当前数字的基础上加一,如:4转化为5
        2.在当前数字的基础上减一,如:4转化为3
        3.将当前数字变成它的平方,如:4转化为16
返回最少需要的操作数。
示例
输入值:3,10
返回值:2
说明:

方法一

思路分析

    根据题目内容很容易得到当n>m时,只能进行减法操作将n转换为m,下面讨论n<m的情况如何执行最少的操作将n转换为m。
首先明确的是要想得到最少的操作数,n转换为m的操作有两种,一种为加法操作,另一种为平方操作,显然平方操作的执行会让n更靠近m,因此我们可以先计算根号m,得到的数如果大于n,则先将n进行加法操作,如果得到的数如果小于n,则先将n进行减法操作。具体的步骤如下:
  • 计算平方跟:sq_m=sqrt(double(m))
  • 比较m-sq_m*sq_m与abs(m-(sq_m+1)*(sq_m+1))两者的差别,决定是否需要先对sq_m进行加法操作
  • 目前操作数op=一个平方操作+平方后的加法操作或者减法操作
  • 接着递归计算n与sq_m之间需要的操作数
  • 递归结束条件:当n>=m时,只能进行减法操作将n转换为m

图解


核心代码

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param n int整型 表示牛牛的数字
     * @param m int整型 表示牛妹的数字
     * @return int整型
     */
    int solve(int n, int m) {
        // write code here
        if (n>=m) return n-m; //n大于m,进行减法操作得到最少的操作数
        else{
            int sq_m=sqrt(double(m));
            if(m-sq_m*sq_m>(sq_m+1)*(sq_m+1)-m) sq_m++;
            int s1=m-n;
            int op=1+abs(m-sq_m*sq_m);//一个平方操作+平方后的加法操作或者减法操作
            return min(s1,solve(n,sq_m)+op);
        }
    }
};
复杂度分析
  • 时间复杂度:最坏情况下,时间复杂度为$O(abs(m-n))$
  • 空间复杂度:递归需要使用栈,空间复杂度为$O(abs(m-n))$

方法二

思路分析

本题也可对其进行广度优先搜索,设置队列用于存储n经过操作成为的数,设置数组标记是否访问过,对于一个未访问过的数它可以做的操作为加法、减法、以及平方操作。当操作后的数为m时输出操作数。其中对加法、减法以及平方操作有如下说明:
当n大于m时进行减法操作
当n大于1时只能进行加法操作
当n不大于45时进行平方操作

核心代码

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * @param n int整型 表示牛牛的数字
     * @param m int整型 表示牛妹的数字
     * @return int整型
     */
    int solve(int n, int m) {
        // write code here
        if(n>=m) return n-m;
        queue<int> q;
        vector<int> s(2000,0);
        return operation(q,s,n,m);
    }
    
    int operation(queue<int> q,vector<int> s,int n,int m)//广度优先搜索的解法
    {
        int count = 0;//存储操作数
        q.push(n);
        s[n] = 1;
        while (!q.empty())
        {
            int q_length = q.size();
            for (int i = 0; i < q_length; i++)
            {
                int num = q.front();
                q.pop();
                if (num == m)
                    return count;
                if (num > 1 && s[num - 1] == 0)  
                {
                    q.push(num - 1);//当前数字大于1而且没有进入过
                    s[num - 1] = 1;
                }
                if (num < m && s[num + 1] == 0) 
                {
                    q.push(num + 1);//当前数字加一
                    s[num + 1] = 1;//当前数字标记
                }
                if (num < 45 && s[num * num] == 0) 
                {
                    q.push(num * num);//当前数字小于45,平方操作
                    s[num * num] = 1;
                }
            }
            count++;
        }
        return count;
    }
};
复杂度分析
  • 时间复杂度:循环对队列中的数进行操作,最坏情况下时间复杂度为O(abs(m-n))
  • 空间复杂度:设置队列以及标记数组,因此空间复杂度为O(n+m)