字符串题的小结

还是一样,上次写那个题用非正则一直没写出来,写了好久后,还是勉强写对了,还是重温这个题:

一.引用:PTAL1-064 估值一亿的AI核心代码

以上图片来自新浪微博。

本题要求你实现一个稍微更值钱一点的 AI 英文问答程序,规则是:

  • 无论用户说什么,首先把对方说的话在一行中原样打印出来;
  • 消除原文中多余空格:把相邻单词间的多个空格换成 1 个空格,把行首尾的空格全部删掉,把标点符号前面的空格删掉;
  • 把原文中所有大写英文字母变成小写,除了 I
  • 把原文中所有独立的 can youcould you 对应地换成 I canI could—— 这里“独立”是指被空格或标点符号分隔开的单词;
  • 把原文中所有独立的 I 和 me 换成 you
  • 把原文中所有的问号 ? 换成惊叹号 !
  • 在一行中输出替换后的句子作为 AI 的回答。

输入格式:

输入首先在第一行给出不超过 10 的正整数 N,随后 N 行,每行给出一句不超过 1000 个字符的、以回车结尾的用户的对话,对话为非空字符串,仅包括字母、数字、空格、可见的半角标点符号。

输出格式:

按题面要求输出,每个 AI 的回答前要加上 AI: 和一个空格。

输入样例:

6

Hello ?

Good to chat with you

can you speak Chinese?

Really?

Could you show me 5

What Is this prime? I,don 't know

输出样例:

Hello ?

AI: hello!

Good to chat with you

AI: good to chat with you

can you speak Chinese?

AI: I can speak chinese!

Really?

AI: really!

Could you show me 5

AI: I could show you 5

What Is this prime? I,don 't know

AI: what Is this prime! you,don't know

代码长度限制          时间限制          内存限制        栈限制

    16 KB                   400 ms              64 MB           8192 KB

事实上这个题目其是非常考细节,就他那个样列来看的话,其实可以分析出好多问题:

①如果我们先把原文中所有独立的 can you、could you 对应地换成 I can、I could,再把原文中所有独立的 I 和 me 换成 you的时候你就发现原先换了的I又被换为了you,这样的话就要先把can you、could you换成其他的比如说#,在把原文中所有独立的 I 和 me 换成 you后在进行转换,这样就可以避免这种问题。

②题目里的要求有多个独立二字,然后去看样例就发现如果忽视了这二字,在 what Is this prime! you,don't know这句的输出中,prime就会变成priyou,那是因为在替换的时候直接replace就会忽视这个问题,最好就是自己写一个方法来变换。

③题目中有多个空格换成 1 个空格,把行首尾的空格全部删掉,把标点符号前面的空格删掉,在自己手写(不用正则表达式)的时候就容易出现空格问题。

1.所以说如果不用replace("//s+"," "),在去多个空格为一个的时候,就要用到双指针了,就是说我们定义一个读指针,一个写指针,用读指针读到空格了,指针向后移来跳过连续的空格,如果不是空格了,那么就是在记录单词了,就把他复制给写指针,然后在还没到字符串末尾的时候在读到的单此后面加一个空格,因为前面我们把空格都吞掉了,所以说改一下要求:就是吞掉字符串中的空(没说单词之间有几个空格)就别不就行了,一直这样操作,就能实现:

public static String splitMoreSpace(String str){
    char []arr=str.toCharArray();//将字符串转为字符数组,方便原地修改
    int i=0,j=0;//i为读指针,j为写指针
    while(i<arr.length){//遍历整个字符数组
        while(i<arr.length&&arr[i]==' '){//跳过连续的空格
            i++;//读指针后移
        }
        while(i<arr.length&&arr[i]!=' '){//复制一个完整的单词
            arr[j++]=arr[i++];//将非空格字符复制到写指针位置
        }
        if(i<arr.length){//如果后面还有单词
            arr[j++]=' ';//在当前单词后加一个空格作为分隔
        }
    }
    String res=new String(arr,0,j);//截取有效部分生成新字符串
    return res;
}

2.然后就是去掉标点符号前面所有的空格,其实看样列的时候你就发现Don't中的'前面也有空格,所以你在你的标点符号判断中要加一个',但是直接写的话会编译错误的,就是说//'''是错的,要加一个转译符就应该是这样//'\''才是对的,这里的方法就是快慢指针,就是用快指针先找空格,如果空格后面不是标点符号,就让慢指针复制为他,如果是的话就不复制,相当于吞掉空格了,大概就是如下:

//先写一个判断标点符号的函数,就是把常见的标点符号计入
public static boolean ispun(char c){
    //判断字符是否为标点符号:. ? ! , ' ; :
    return c=='.'||c=='?'||c=='!'||c==','||c=='\''||c==';'||c==':';
}
//然后删除标点符号前的空格
public static String splitBlankbeforePun(String str){
    //功能:删除标点符号前的空格(如 "Hello , world" → "Hello, world")
    char []arr=str.toCharArray();
    int slow=0;//慢指针,指向写入位置
    int len=arr.length;
    for(int fast=0;fast<len;fast++){//快指针遍历每个字符
        if(arr[fast]!=' '){//当前字符不是空格,直接复制
            arr[slow++]=arr[fast];
        }else{//当前字符是空格
            if(fast<len-1&&ispun(arr[fast+1])){//如果空格后面紧跟标点符号
                continue;//跳过这个空格(不写入)
            }
            arr[slow++]=arr[fast];//否则保留这个空格
        }
    }
    String res=new String(arr,0,slow);//截取有效部分
    return res;
}

3.也就是把原文中所有独立的 I 和 me 换成 you这一步的手写方法,本质上就只要加一步,就是判断它是否独立,用指针标记,然后那一段就使用一个新的容器如StringBuilder来装,其实就是遍历字符串,发现目标需替换的单词就检验它是否独立,如果是就让StringBuilder加入新的,不是就是原来是的:

public static String replaceWord(String s, String word, String replacement) {
    //功能:替换字符串中的完整单词(不替换单词的一部分)
    StringBuilder sb = new StringBuilder();
    int i = 0;
    int len = s.length();
    int wordlen = word.length();
    while (i < len) {
        if (i + wordlen <= len && s.substring(i, i + wordlen).equals(word)) {
            //检查单词边界:左边不是字母数字,右边不是字母数字
            boolean leftOk = (i == 0) || !Character.isLetterOrDigit(s.charAt(i - 1));
            boolean rightOk = (i + wordlen == len) || !Character.isLetterOrDigit(s.charAt(i + wordlen));
            if (leftOk && rightOk) {//确认是完整单词
                sb.append(replacement);//替换为目标词
                i += wordlen;//跳过被替换的单词
                continue;
            }
        }
        sb.append(s.charAt(i));//不匹配则保留原字符
        i++;
    }
    return sb.toString();
}

最后带入主测试类就能写出来了(当然前面要加Main类):

public static void main(String[] args) throws IOException{
        int n=Integer.parseInt(br.readLine().trim());//读取需要处理的字符串行数
        String str;
        while(n-->0){
            str=br.readLine();//读取一行原始字符串
            out.println(str);//输出原始字符串
            StringBuilder sb=new StringBuilder();
            for (char c : str.toCharArray()) {
                if (c >= 'A' && c <= 'Z') {//如果是大写字母
                    sb.append(c == 'I' ? 'I' : (char)(c + 32));//保留'I'不变,其他大写转小写
                } else {
                    sb.append(c);//非大写字母直接保留
                }
            }
            //处理流程:压缩空格 → 去除首尾空格 → 删除标点前空格
            String s = splitBlankbeforePun(splitMoreSpace(sb.toString().trim()));
            //替换特定短语(用占位符避免重复替换)
            s = replaceWord(s, "could you", "#1");//先替换为占位符
            s = replaceWord(s, "can you", "#2");//先替换为占位符
            s = replaceWord(s, "I", "you");//替换人称
            s = replaceWord(s, "me", "you");//替换人称
            s = s.replace("#1", "I could");//占位符还原
            s = s.replace("#2", "I can");//占位符还原
            s = s.replace("?", "!");//问号变感叹号
            out.println("AI: " + s);//输出处理后的结果
            out.flush();
        }
        out.flush();
        br.close();
        out.close();
    }
}

注:这个因为我在前面定义了流:

static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
static PrintWriter out=new PrintWriter(System.out);

二.引用洛谷P1914小书童——凯撒密码

## 题目背景

某蒟蒻迷上了 “小书童”,有一天登陆时忘记密码了(他没绑定邮箱 or 手机),于是便把问题抛给了神犇你。

## 题目描述

蒟蒻虽然忘记密码,但他还记得密码是由一个字符串组成。密码是由原文字符串(由不超过 50 个小写字母组成)中每个字母向后移动 n 位形成的。`z` 的下一个字母是 `a`,如此循环。他现在找到了移动前的原文字符串及 n,请你求出密码。

## 输入格式

第一行:n。第二行:未移动前的一串字母。

## 输出格式

一行,是此蒟蒻的密码。

## 输入输出样例 #1

### 输入 #1
1
qwe

### 输出 #1
rxf

## 说明/提示

字符串长度 <= 50,1 <=n <=26。

这个题是一个很经典的题,前移,后移的问题,就是用我们这个字符-'a'变为0~25然后加上偏移量(当然要模26),再转为字符:

那么代码就好写了:

import java.io.*;
public class Main{
    public static void main(String[] args)throws IOException{
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        PrintWriter out=new PrintWriter(System.out);
        int n=Integer.parseInt(br.readLine().trim());
        String s=br.readLine().trim();
        StringBuilder sb=new StringBuilder();
        for(int i=0;i<s.length();i++){
            char c=s.charAt(i);
            if(c>='a'&&c<='z'){
                sb.append((char)((c - 'a' + n) % 26 + 'a'));
            }
        }
        out.print(sb.toString());
        out.flush();
        br.close();
        out.close();
    }
}

3.引用:洛谷P[NOIP 2011 普及组] 统计单词数

## 题目描述

一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数。

现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给定的文章中出现的次数和第一次出现的位置。注意:匹配单词时,不区分大小写,但要求完全匹配,即给定单词必须与文章中的某一独立单词在不区分大小写的情况下完全相同(参见样例 1),如果给定单词仅是文章中某一单词的一部分则不算匹配(参见样例 2)。

## 输入格式

共 2行。

第 1行为一个字符串,其中只含字母,表示给定单词;

第 2行为一个字符串,其中只可能包含字母和空格,表示给定的文章。

## 输出格式

一行,如果在文章中找到给定单词则输出两个整数,两个整数之间用一个空格隔开,分别是单词在文章中出现的次数和第一次出现的位置(即在文章中第一次出现时,单词首字母在文章中的位置,位置从 0 开始);如果单词在文章中没有出现,则直接输出一个整数 -1。

注意:空格占一个字母位

## 输入输出样例 #1

### 输入 #1
To
to be or not to be is a question

### 输出 #1
2 0

## 输入输出样例 #2

### 输入 #2
to
Did the Ottoman Empire lose its power at that time

### 输出 #2
-1

## 说明/提示

数据范围

1<=第一行单词长度 <=10。

1<=文章长度 <=10^6。

noip2011 普及组第 2 题

这个题目比一般的统记单词个数要麻烦一点,一般的我都会去split一下,然后用容器去统计个数,不过此题不分大小写+第一次出现的下标就没这么好写了,不过对付大小写的问题,直接全转为小写或者大写其实就解决了,而找下标其实就Java里的indexOf或C++里的find就可以解决了,不过,这里也有个坑,就是这两个方法可能找到的target是某个单词的一部分,而不是完整单词了,所以我们要用它,就要加一个判断条件,代码如下:

#include <bits/stdc++.h>
using namespace std;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    string tar, txt;
    //一次读入一行
    getline(cin, tar);
    getline(cin, txt);
    //都转为小写
    for (char &c: tar) c=tolower(c);
    for (char &c: txt) c=tolower(c);
    int count = 0;
    int first = -1;
    int position = 0;
    int Len = tar.size();
    while ((position = txt.find(tar, position)) != string::npos) {
        // 左边判断:是开头 或者 左边是空格
        bool left = (position == 0) || (txt[position - 1] == ' ');
        // 右边判断:是结尾 或者 右边是空格
        bool right = (position + Len == txt.size()) || (txt[position + Len] == ' ');
        if (left && right) { // 是完整单词
            count++;
            if (count == 1) first = position;
        }
        position++; // 继续往后找
    }
    if (count == 0) cout << -1 << endl;// 没有找到就输出-1
    else cout << count << " " << first << endl;
    return 0;
}

总的来说其实字符串类的转换题目,我觉得是要多刷题目的,有自我的熟练程度才能更快地想出思路并着手写出来,而对PTA那道题真的是细节决定了成败啊,之前写了好多次都没全过其实是不够细心,发现这个题的陷阱,所以以后多刷多总结,也要细心看题,加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值