题目解析:字典序最小回文构造问题| 豆包MarsCode AI刷题

62 阅读4分钟

一、题目需求:

小R手中有一个由小写英文字母组成的字符串。她希望将这个字符串转换为回文字符串,并且要求字典序尽可能小。在这个过程中,小R最多可以更改字符串中的两个字符。每个字符可以被更改为任意的小写字母。现在你的任务是帮助小R构造出在满足条件的前提下字典序最小的回文字符串。

例如:对于字符串 acca,通过更改两个字符,可以得到回文字符串 aaaa,这是字典序最小的解。


测试样例

样例1:

输入:s = "acca" 输出:'aaaa'

样例2:

输入:s = "racecar" 输出:'aacecaa'

样例3:

输入:s = "fecdef" 输出:'feccef'

二、实现思路

首先我们需要了解一下相关概念

  • 回文数:简单来说就是从头读到尾以及从读尾到头的结果是一样的,比如“101”“aba“等
  • 字典序最小:在字符串对比中,对比规则为:若字符串长度不同,则短的字符串对应字典序较小;若字符串长度相同,是根据每一索引处的字符大小来决定字典序的,例如“abc”与"abcd"相对比,"abc"字典序较小;"aae"与“abc"对比时,1索引处前一字符串对应字符较小,故前面的字符串对应字典序较小

2.1 回文数的构造实现

需要构造一个回文数,那么就需要我们先对字符串是否是回文数进行一个判定

一般来说我们可以利用String中的reverse()方法来进行equal判断

String strS="abb";
        StringBuilder sb=new StringBuilder(strS);
        String strF = sb.reverse().toString();
        System.out.println(strS.equals(strF));//输出结果为false

但在本体中,并没有给出不能构成回文数的情况应该返回什么值,所以可以认为最终一定是可以输出一个回文数的,故在最后环节不做回文判断,现在让我们直接来进行构造

构造过程我们采用双指针法,其中要注意的是,本题已经注明只能更改两个字符,故我们设置chance=2,在使用双指针时,注意左右索引不要超出范围。

            StringBuilder sbl=new StringBuilder();
            StringBuilder sbr=new StringBuilder();
            int chance=2;
            int leftIndex=0;
            int rightIndex=s.length()-1;
            char flag=' ';//标记外侧元素
            String result=s;

以上为初始变量

  • 其中sbl和sbr用于储存左右两个指针已读取的安全数据(无需修改的)
  • leftIndex和rightIndex为左右指针
  • flag用于标记外侧元素(具体是干什么后面会说)
  • result用于最后输出,这里传递了一个s的初始值
while(leftIndex<rightIndex&&chance>0){
                   if (s.charAt(leftIndex)==s.charAt(rightIndex)){
                        sbr.append(s.charAt(rightIndex));
                        sbl.append(s.charAt(leftIndex));
                        leftIndex++;
                        rightIndex--;
                    }else{
                        int diff = s.charAt(leftIndex) - s.charAt(rightIndex);
                        if (diff>0){
                            //左边大
                            sbl.append(s.charAt(rightIndex));
                            sbr.append(s.charAt(rightIndex));
                            if (flag==' '){
                                flag=s.charAt(rightIndex);
                            }
                            leftIndex++;
                            rightIndex--;
                        }else {
                            //右边大
                            sbl.append(s.charAt(leftIndex));
                            sbr.append(s.charAt(leftIndex));
                            if (flag==' '){
                                flag=s.charAt(leftIndex);
                            }
                            leftIndex++;
                            rightIndex--;
                        }
                        chance--;
                    }
                    if (leftIndex==rightIndex){
                        sbl.append(s.charAt(leftIndex));
                    }
                }

在循环中大概可以分为以下几类情况

1.左指针与右指针所指向的元素内容一致:这时候需要将数据分别存入sbl与sbr,指针对应进行加减

if (s.charAt(leftIndex)==s.charAt(rightIndex)){
                        sbr.append(s.charAt(rightIndex));
                        sbl.append(s.charAt(leftIndex));
                        leftIndex++;
                        rightIndex--;
                    }

2.左指针与右指针所指向的元素内容不一致:对指针元素进行差值判断,若左边小则将右边元素改为左侧元素,同理若右边小,则将左边改为右边元素,再进行数据存储与指针加减

else{
      int diff = s.charAt(leftIndex) - s.charAt(rightIndex);
                       if (diff>0){
                            //左边大
                            sbl.append(s.charAt(rightIndex));
                            sbr.append(s.charAt(rightIndex));
                            if (flag==' '){
                                flag=s.charAt(rightIndex);
                            }
                            leftIndex++;
                            rightIndex--;
                        }else {
                            //右边大
                            sbl.append(s.charAt(leftIndex));
                            sbr.append(s.charAt(leftIndex));
                            if (flag==' '){
                                flag=s.charAt(leftIndex);
                            }
                            leftIndex++;
                            rightIndex--;
                        }
                        chance--;
}

那么在进行遍历处理后,我们会得到两个填充完成的sbl与sbr对象,这时候我们只需要把sbr进行reverse()操作后再将二者进行拼接,即可得到一个我们所需要的字符串。

然而——这只是我们最理想的情况,但是在更多的时候,chance是没有被用完的,这就代表字符串可能存在:已经是回文字符串,但并非最小字典序的情况

可以根据chance的不同分为以下几种情况

1.chance=2:证明在双指针中并未对字符进行操作,即输入时已是回文字符串,例:“bffb”,这种情况我们只需要找到最外层的字符,将外层字符变换为最小的‘a’字符即可

if (chance==2){
                leftIndex=0;
                rightIndex=result.length()-1;
                while (chance>0){
                    if (result.charAt(leftIndex)!='a'){
                        if (rightIndex>leftIndex) {
                            result = result.substring(0, leftIndex) + 'a' +                                         result.substring(leftIndex + 1, rightIndex)
                                    + 'a' + result.substring(rightIndex + 1);
                            chance = 0;
                        }else {
                            result =    result.substring(0,leftIndex)+'a'+result.substring(leftIndex+1);
                            chance = 0;
                        }
                    }
                    leftIndex++;
                    rightIndex--;
                }

2.chance=1:证明在双指针中对字符进行了一次变换操作,例:"abca"→"abba"这时候我们相应的进行操作即可

if (chance==1){
                for (int i = 0; i < result.length(); i++) {
                    if (result.charAt(i)!='a'){
                        if (result.charAt(i)==flag){
                            chance=2;
                            break;
                        }else {
                            break;
                        }
                    }
                }
                if(result.length()%2==1){
                    int mid=(result.length()+1)/2;
                    if (result.charAt(mid-1)!='a'){
                        result= result.substring(0,mid-1)+'a'+result.substring(mid);
                        chance--;
                    }
                }
            }

最后我们将我们存入的result字符串进行输出就为正确答案啦,但是也要注意比如字符索引的限制,字符串的长度等等问题,希望你也能找到最优解!