字符串笔试题-算法笔试笔记(一)

本文记录了作者在笔试中遇到的字符串相关题目,包括腾讯的巧克力分块问题、字节跳动的IP地址划分、无重复字符的最长子串、最长非递减子序列及华为的大数相乘等,通过具体的解题思路和代码实现,探讨了递归、动态规划和滑动窗口等算法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.前言

做了好几家公司的笔试编程题,其中涉及到了很多字符串的题,有做出来的也有没做出来后来上网学习大佬的代码的(有的还涉及到了最优解),于是在这里记录一下,加深自己的印象,题目是凭借的回忆,解决方法多数是参考的网络上其他的大佬。

2.字符串笔试题

腾讯笔试编程题的第二题(第三题是一个字符串的题,我太菜了实在是没有思路,最后放弃了),这个题难度不是很大,但是我自己的算法最后会提醒超出运行时间,只通过了百分之八十的测试用例,还是先记录一下,以后改了再更新博客。

题目的内容是:一排巧克力是由n个巧克力球组成的,有的巧克力球上面有榛果,现在要将这一排巧克力分成一个个小块,要求每个小块上有且只能有一个带榛果的小球,请问有多少种分法。输入有两行,第一行表示有n个巧克力球,第二行表示每一个巧克力求是否含有榛子,0代表没有,1代表有。输出有多少种分法。

思考:这个题我借鉴了之前的一道字符串划分IP地址的算法的思路,可以使用递归调用的方法,将巧克力分为已经分成小块的,还未分成小块的,再对还未划分成小块的巧克力递归调用划分算法,直到所有的巧克力都分完,方法数加1;问题的关键在于判断什么时候可以切分成小块。

我的思路是利用其没有榛果是0,有榛果是1的特性,可以遍历整个巧克力条,然后对巧克力球进行累加,累加和为0的时候继续累加;为1的时候继续累加,且此时可以切分,进行递归调用;为2的时候跳出循环。具体代码如下:

import java.util.Scanner;
public class Tengxun2 {
    public static int count = 0 ;
    public static void main(String[] args ){
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt() ;
        int a[] = new int[n];
        for( int i = 0 ; i < n ; i++ ){
            a[i] = scanner.nextInt() ;
        }
        findways( 0 , a );
        System.out.println(count);
    }
    public static void findways( int start , int a[]){
        int temp = 0 ;
        if( start == a.length ){
            count++ ; //已经将所有的巧克力切分完成,方法数加1
        }
        else{ //从还未切分处进行遍历,累加巧克力球的值
            for( int i =start ; i < a.length ; i ++ ){
                temp+=a[i];
                if( temp==1 ){
                    findways( i+1 , a); //切分巧克力,start从下一颗球开始
                }
                if( temp == 2){
                    break; //跳出循环
                }
            }
        }
    }
}

运行结果:

两种方式分别为 (10)(1)(1)以及(1)(01)(1)(1)



前面提到了字符串划分IP地址,这是字节跳动的笔试第三题(总共五个题)遇到的,输入是一行字符串,代表抹掉点的IP地址,输出是一个整数,表示原始可能的IP数量。

思考:我们可以将字符串划分为已经划分为IP地址的部分以及还未划分为IP地址的部位,然后遍历字符串,当数字的值是在0-255之间的时候,对剩余还未划分部分进行递归调用划分函数。同样当字符串划分到尾部的时候,方法数加1。还有一些小的细节具体见代码的注释:

import java.util.Scanner;
public class Test3 {
    public static int count = 0 ;
    public static void main (String []args){
        Scanner scanner = new Scanner(System.in);
        String s = scanner.next();
        find( 0 , 0 ,s );
        System.out.println(count+"");

    }
    //start代表开始划分的位数,finish代表已经完成的数量
    public static void find( int start , int finish , String s  ){
        //字符串长度超过了最大的值(3*还未完成的数量)
        if( s.length()- start > 3*(4-finish)){
            return;
        }
        //字符串长度小于最小的值(1*未完成的数量)
        if( s.length()- start < 4-finish ){
            return;
        }
        //当字符串遍历到了尾部,且已经划分为四个部分,方法数加1
        if( s.length() ==start && finish ==4 ){
            count++;
            return;
        }
        //计算temp,防止数组越界
        int temp = Math.min(s.length()-start , 3 );
        int num = 0 ;
        for( int i = start ; i<start+temp ; i ++ ){
            num= num*10 + (s.charAt(i)-'0');//求和
            if(num <=255){
                //满足要求,从下一位开始划分,且完成的数量加1
                find( i+1 , finish+1 ,s  );
            }
            //一个0是满足要求的,但是两个0就不行了
            if( num == 0 ){
                break;
            }
        }
    }

}

运行结果:

方法有且只有一种为10.0.0.1



字节跳动还有一道求字符串中无重复字符的最长字符串长度,这个题其实十分的常见,做法很多,但是存在优解,我在笔试的时候利用的是滑窗法(百分之百的用例通过率),具体实现则是利用了start和end两个指针分别指向字符串的最开始,然后遍历字符串,同时利用了set这一个数据结构(set中不包含有重复元素),当set中未包含有end指针所指向元素,将该元素加入到set当中,end指针后移;当set中包含了该元素,则删除start指针指向的元素,同时将start指针后移,直到未包含end指针指向的元素。整个步骤就像是一个在字符串上滑动的窗口,实现代码如下:

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class wuchongfuzichuan {
    public static void main( String [] args ){
        Scanner scanner = new Scanner(System.in);
        String s = scanner.next();
        Set<Character> list = new HashSet<>();
        int Max = 0 , start = 0 , end = 0 ;
        while( start < s.length() && end <s.length() ){
            if( !list.contains(s.charAt(end))){
                list.add(s.charAt(end));
                end ++ ;
                Max =Math.max( Max , end -start );
            }
            else{
                list.remove(s.charAt(start) );
                start++;
            }
        }
        System.out.println(Max+"");
    }

}

运行结果:

这个题后来上百度查了一下,发现还有更简单的利用哈希表实现的时间复杂度为O(n)的方法,把字符作为键值,其在字符串中的位置作为值,储存在哈希表中,无重复字串的长度便是当前位置减去前一个该字符出现位置的值了,代码实现如下:

import java.util.HashMap;
import java.util.Scanner;

public class wuchongfuzichuan2 {
    public static void main(String []args ){
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine() ;
        HashMap<Character , Integer >map = new HashMap<>();
        int Max = 0 ;
        for( int i = 0 ; i < s.length() ; i++ ){
            if( !map.containsKey(s.charAt(i))){
                map.put(s.charAt(i) , i ) ;
            }
            else{
                int temp = map.get(s.charAt(i)) ;
                int tempRes = i - temp  ;
                Max = Math.max(Max,tempRes) ;
                map.put(s.charAt(i) ,i );
            }
        }
        System.out.println(Max);
    }

}


同样还是字节跳动的笔试题,题目要求是求一个字符串中的最长非递减子序列,这是一道比较常见的动态规划的算法题,不考虑优化的思路:新建一个数组maxlist用来存储字符串中每个元素为末尾时候的最长非递减子序列的长度,遍历字符串,对于第i个元素,再一次遍历其前面的元素,遇到小于等于i元素的元素j时候,判断maxlist[i]与maxlist[j]+1的大小关系。

import java.util.Scanner;
public class LongNoDesc {
    public static void main(String []args){
        Scanner scanner = new Scanner(System.in) ;
        String s = scanner.nextLine() ;
        int a[] = new int[s.length()] ;
        for ( int i = 0 ; i <s.length() ; i++ ){
            a[i] = 1 ;
            for( int j = 0 ; j <i ; j ++ ){
                if( s.charAt(j) <= s.charAt(i) && a[j]+1 > a[i]){
                    a[i] = a[j]+1 ;
                }
            }
        }
        int Max = 0 ;
        for( int i = 0 ; i < s.length() ; i++ ){
            Max = Math.max(Max , a[i]);
        }
        System.out.println(Max);
    }

}

运行结果:

子序列为aabcedf

该算法的时间复杂度为O(n^2)的平方(从代码中的双重循环可以很显然的得到该结论),此代码还存在着二分查找的优化版本,这里就不做优化了,同时如果想输出该最长序列的话,可以再创建一个数组用于储存前驱结点。而关于动态规划这一算法,以后还会专门再码一篇博客进行学习,到时候再留一个传送门。



华为的笔试第三题是一个大数相乘的算法题,这个题其实略显尴尬,因为直接利用Java中的Bigdecimal类,再调用其相乘方法就可以百分之百的测试用例通过率,但是我做的时候完全没有反应过来,而是选择了操作字符串,模拟了乘法的笔算过程来实现。乘法笔算的时候我们将两个子符串的所有位数两两想乘,并按照位数的顺序进行排列 ,最后从低位向高位进行进位的操作。具体实现见代码以及注释:

import java.math.BigDecimal;
import java.util.Scanner;
public class Huawei3 {
    public static void  main( String[] args){
        Scanner scanner = new Scanner(System.in);
        String a = scanner.nextLine();
        String b = scanner.nextLine();
        BigDecimal ba = new BigDecimal(a);
        BigDecimal bb = new BigDecimal(b);
        System.out.println(mutiply(a,b));
        //利用BigDecimal可以直接实现,这里用于检查算法的正确性
        BigDecimal bres = ba.multiply(bb);
        if( bres.toString().equals(mutiply(a,b))){
            System.out.println("True");
        }
        else{
            System.out.println("False");
        }
    }
    //该算法的关键在于理解我们平时的笔算乘法过程,比如说我们19*19,我们将两个字符串的每个位数两两相乘
    // 得到 (0)(1*1)(1*9+9*1)( 9*9) = (0)(1)(18)(81) = (0)(1)(18+8)(1) =(0)(1+2)(6)(1) =361
    public static String mutiply( String a , String b){
        int flag = 0 ;
        String resultString="";
        //n位数乘n位数结果最多为n+n位数。
        int result[] = new int[a.length()+b.length()];
        for( int i =0 ; i < a.length() ; i ++ ){
            for( int j = 0 ; j< b.length() ; j ++ ){
                //两个字符串的每位数两两相乘,并储存在对应的位置
                result[ i+ j+1 ] += (a.charAt(i)-'0')*(b.charAt(j)-'0');
            }
        }
        //从最低位到最高位进行进位操作
        for( int i = result.length-1 ; i>0 ; i -- ){
            if( result[i]>=10 ){
                result[i-1] += result[i]/10 ; //将十位进位
                result[i] = result[i]%10 ; //留下个位
            }
        }
        for( int i = 0 ; i< result.length ; i++ ){
            //去除前缀0
            if( result[i] != 0 ){
                flag++ ;
            }
            if( flag!= 0 ){
                resultString+=result[i]+"";
            }

        }
        return resultString;
    }

}

运行结果:



最后是一道顺丰的笔试题,这个题当时并没有做出来,后来上网查了查别人的做法。

题目网址

代码来源原博客

我稍微将原博客的代码改了改,用java代码来实现

import java.util.ArrayList;
import java.util.Scanner;
public class Shunfeng2 {
    public static void main(String args[]){
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine();
        int n = scanner.nextInt();
        String words = "";
        ArrayList<String> list = new ArrayList<>();
        ArrayList<String> resultWords = new ArrayList<>();
        for( int i = 0 ; i < s.length() ; i ++ ){
            if( s.charAt(i) != ','){
                words+=s.charAt(i);
            }
            else{
                list.add(words);
                words="";
            }
        }
        list.add(words); //将输入中的每个words提取出来,存储到list当中
        int i = 0 ;
        while ( i < list.size()){
            String result ="";
            int j = i , len = 0;
            //计算判断加上下一个单词长度是否超过允许的最长长度,尽可能的为每一行放入更多单词(单词之间至少需要一个空格)
            while ( j < list.size() && len+list.get(j).length()+ j - i <=n){
                len += list.get(j).length() ;
                j++ ;
            }
            int space = n - len ;//计算空格数量
            for( int k = i ; k < j ; k++ ){
                result += list.get(k); //放入单词
                if( space > 0 ){ //需要放入空格
                    int temp=0 ;
                    if( j ==list.size() ){ //如果该行是单词的最后一行
                        if( j - k ==1 ){  //最后一个单词了,后面全部加空格
                            temp =space;
                        }
                        else{
                            temp = 1 ; //题目要求最后一行单词之间只能有一个空格
                        }
                    }
                    else{
                        if( j - k - 1 > 0 ){
                            if( space % ( j- k - 1 ) ==0){ //空格能够平均分配的时候
                                temp = space/(j - k - 1) ;
                            }
                            else{
                                temp = space/(j-k-1)+1; //左边要比右边的空格多 ;
                            }
                        }
                        else{
                            temp=space ;
                        }
                    }
                    //加上指定数量的空格
                    for( int m = 0 ; m < temp ; m++ ){
                        result+=" ";
                    }
                    space-=temp ;//减去已经放下的空格数量
                }
            }
            resultWords.add(result);
            i = j ;
        }
        for( int m = 0 ; m < resultWords.size()-1 ;m++ ){
            System.out.println(resultWords.get(m));
        }
        System.out.print(resultWords.get(resultWords.size()-1));
    }

}

运行结果:

3.总结

总的来说上面的几个题思路都还是比较常见,解决起来也没有用到什么复杂的算法(当然笔试题中还有其他很多毫无思路的难题,我这里并没有记录下来)。当然等我遇到了其他公司的笔试题的时候,还会持续更新这系列的博客。另外,上面中还用到了动态规划,这个我也打算再仔细了解一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值