Leetcode1663
Leetcode 1663 具有给定数值的最小字符串
degree:mid
description
小写字符 的 数值 是它在字母表中的位置(从 1 开始),因此 a 的数值为 1 ,b 的数值为 2 ,c 的数值为 3 ,以此类推。
字符串由若干小写字符组成,字符串的数值 为各字符的数值之和。例如,字符串 "abe" 的数值等于 1 + 2 + 5 = 8 。
给你两个整数 n 和 k 。返回 长度 等于 n 且 数值 等于 k 的 字典序最小 的字符串。
注意,如果字符串 x 在字典排序中位于 y 之前,就认为 x 字典序比 y 小,有以下两种情况:
- x 是 y 的一个前缀;
- 如果 i 是 x[i] != y[i] 的第一个位置,且 x[i] 在字母表中的位置比 y[i] 靠前。
示例 1:
输入:n = 3, k = 27
输出:"aay"
解释:字符串的数值为 1 + 1 + 25 = 27,它是数值满足要求且长度等于 3 字典序最小的字符串。
示例 2:
输入:n = 5, k = 73
输出:"aaszz"
解析
首先很容易想到的是,如果所有都是a,那么能表示的数是 n ,如果 k<n ,那么肯定不存在;而 a~z 这26个数是递增的,如果 k>n ,那么总可以从最后一个a开始一个一个增加,直到所有的数都变成"z"即26。所以一开始可以得到一个简单的判断不能实现的条件
if (k < n || n < 1 || k>26*n){
        return "";
} 
处理了简单的情况后,就要处理更一般的情形,但是自己的想法又和较佳的想法产生了偏差,其实从这里可以总结出数学和算法和编程语言和程序相互之间的很深刻的关系,这一点下一篇博客重点讨论。
在这里,就简单的理解成:其实程序的初衷是为了解决一个问题,具体点,为了解决可计算或者可转化为计算的问题;而这些问题的背后,有些时候是具有客观规律在里面的。所以说,如果某个问题可以总结出一些具体的或者半具体的规律的话,就可以使得时间以及空间复杂度大大降低,甚至就如这道题的时间复杂度降到了o(1),达到了人力计算的水平
所以说,客观规律是解决问题最本质的东西,而如果我们无法总结出规律,那么就需要用计算机来去遍历所有情况,而这又涉及到了算法设计,这就是:贪心,动态规划,二叉树,链表\(\cdots\)发挥作用的时候了,这些算法还有结构的出现,就是为了解决快速计算,减少内存消耗,降低问题的复杂度,缩短代码量。比如一个需要去遍历 n 个元素的问题,也即需要遍历n种情况的问题,如果能够找到某种分类方式,比如 2 类情况的问题,那么我们就只需要去将原始的可能性空间不断二分,选择符合要求的那种,这样的话,n个元素不断的二分,在n为2的倍数时最多需要 \(log_{2}{n}\)次,我们就可以找到最优解(?目标元素?最优情形?目标==最优 ?)
下面回到这道题上,主要分析自己的思路的不足还有 归纳下的最优秀答案
【1】自己的思路
 对应n个位置从后往前遍历遍历 b~z,如果在不断累加的过程中刚好取到了那个数,success;否则fail,返回空字符。
可以看到这里没有任何的优化,是非常朴素的想法
code:
string getSmallestString(int n, int k)
{
    if (k < n || n < 1)
    {
        return "";
    }
    string ans(n, 'a');q
    int count = n;
    int l_index = 1;
    int num = 1;
    for (int num = n - 1; num >= 0; --num)
    {
        for (int i = 2; i <= 26; ++i)
        {
            if (count == k)
            {
                return ans;
            }
            else
            {
                char c = (char)('a' + i - 1);
                stringstream c_m;
                c_m << c;
                ans.replace(num, 1, c_m.str());
                ++count;
            }
        }
    }
    return count >= k ? ans : "";
}
这里还涉及了字符串库 string 的一些函数
string有多种初始化方法,这里选择
string(n,char): 返回的是一个由 n 个 char 组成的字符串
注意:直接使用
string a="dsadw";
a[0]='a';
这样是无法修改字符的,string的修改使用 replace 函数,它也有多种重载,这里使用
 replace(index,num,new__sub_string):将index后面(包括自身)的num个字符组成的子串修改为 new_sub_string;
如果只修改一个字符,那么可以这么写
string s;
char c;
int index;
s.replace( index , 1 , string(1,'c') );
或者使用自己代码里的类似:
char c = (char)('a' + i - 1);
stringstream c_m;
c_m << c;
ans.replace(num, 1, c_m.str());
自己一开始的这种想法是可行的,也是极为暴力的,所以最后提交的时候导致了运行超时,也是必不可免的。
【2】更优的思路(来自评论区)
实际上,为了解决这个问题,我们可以先进行一下思考,结果里既不是 'a' 也不是 'z' 的字符最多一个,即可能是‘a’也可能是a~y里某一个这样的字符最多一个,也就是甚至有可能全部都是‘a’或者‘z’
令x表示除了那个特殊字符之外'a'的数量,y表示'z'的数量,把那个特殊字符的值设为 p 那么会有如下的关系:
化简得
也就是说我们只需要确定y的值即可得到p和x,而且由于结果是按照字典序升序排的,所以说结果就已经得出了。
下一步就是要确定y的值,评论区里的答案似乎是去遍历1~26来得出的,但是这样时间复杂度就是o(26)了,(如果中间涉及到其他的大计算量代码会很麻烦),实际上可以直接得到y
我们先去排除两种情况,确保如果k % 26 == 0(有可能全是'z',那么x会变成-1,p为1,这样也不对,上面公式必须保证至少一个不为'z')
那么如果
k/26 > n:fail
k/26 == n:全为'z'
剩下的情况那就是要在使得字典序最小的情况下,确定y
会有\(y=floor((k-n)/25)\)
(因为必须有\(26y+n-y\le{}k\))
到此答案就可以得出了!!!
code:
string getSmallestString(int n, int k)
{
    if (k < n || n < 1 || k>26*n){
        return "";
    } 
    if (k % 26 == 0 ){
        if(k/26 > n){
            return "";
        }
        else if(k/26 == n){
            return string(n,'z');
        }
    }
    string ans(n, 'a');
    int y = (k-n) / 25; // z 的数量
    int a = k - n + 1 - 25 * y;
    int x = n - 1 - y;
    ans.replace(0,x,string(x,'a'));
    ans.replace(x,1,string(1,(char)('a'+a-1)));
    ans.replace(x+1,y,string(y,'z'));
    return ans;
}

 
                
             
         浙公网安备 33010602011771号
浙公网安备 33010602011771号