1. 程式人生 > >字串筆試題-演算法筆試筆記(一)

字串筆試題-演算法筆試筆記(一)

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.總結

總的來說上面的幾個題思路都還是比較常見,解決起來也沒有用到什麼複雜的演算法(當然筆試題中還有其他很多毫無思路的難題,我這裡並沒有記錄下來)。當然等我遇到了其他公司的筆試題的時候,還會持續更新這系列的部落格。另外,上面中還用到了動態規劃,這個我也打算再仔細瞭解一下。