shopee2019編程筆試題


1.shopee的辦公室

鏈接:https://www.nowcoder.com/questionTerminal/a71f3bd890734201986cd1e171807d30?answerType=1&f=discussion
來源:牛客網
shopee的辦公室非常大,小蝦同學的位置坐落在右上角,而大門卻在左下角,可以把所有位置抽象為一個網格(門口的坐標為0,0),小蝦同學很聰明,每次只向上,或者向右走,因為這樣最容易接近目的地,但是小蝦同學不想讓自己的boss們看到自己經常在他們面前出沒,或者遲到被發現。他決定研究一下如果他不通過boss們的位置,他可以有多少種走法? 
輸入描述:
第一行 x,y,n (0<x<=30, 0<y<=30, 0<=n<= 20) 表示x,y小蝦的座位坐標,n 表示boss的數量( n <= 20)
接下來有n行, 表示boss們的坐標(0<xi<= x, 0<yi<=y,不會和小蝦位置重合)
x1, y1
x2, y2
……
xn, yn
輸出描述:
輸出小蝦有多少種走法
示例1

輸入

3 3 2
1 1
2 2

輸出

4

解析

維護一個動態規划表,當自己位置在(i,j)時,走法一共有boss[i][j]=boss[i-1][j]+boss[i][j-1]種,因為走法只有向右或者向上,而且只差一步到達(i,j),因此情況相加即可。

需要注意如果小蝦和廁所在同一列/同一行,那么走法只有一種,即一直向上走/一直向右走

解答

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //x,y表示小蝦的座位坐標
        int x = scanner.nextInt();
        int y = scanner.nextInt();
        //n表示boss的數量
        int n = scanner.nextInt();
        long[][] boss = new long[x+1][y+1];
        //接下來有n行, 表示boss們的坐標.-1表示不可走
        for(int i=0; i<n; i++) {
            int xn = scanner.nextInt();
            int yn = scanner.nextInt();
            boss[xn][yn] = -1;
        }
        //在同一列,只有一種情況,只能右移
        for(int i=0; i<=x; i++) {
            boss[i][0] = 1;
        }
        //在同一行,只能上移
        for(int j=0; j<=y; j++) {
            boss[0][j] = 1;
        }
        for(int i=1; i<=x; i++) {
            for(int j=1; j<=y; j++) {
                //如果該位置有boss,那么達到的方案數為0
                if (boss[i][j] == -1) {
                    boss[i][j] = 0;
                }
                //可以向上/向右
                //當自己位置在(i,j)時,走法一共有boss[i][j]=boss[i][j-1]+boss[i-1][j],因為這兩種情況都是只差一步就到(i,j)
                else {
                    boss[i][j] = boss[i][j-1]+boss[i-1][j];
                }
            }
        }
        System.out.println(boss[x][y]);
    }

 

2.shopee的零食櫃

鏈接:https://www.nowcoder.com/questionTerminal/24a1bb82b3784f86babec24e4a5c93e0?answerType=1&f=discussion
來源:牛客網

 shopee的零食櫃,有着各式各樣的零食,但是因為貪吃,小蝦同學體重日益增加,終於被人叫為小胖了,他終於下定決心減肥了,他決定每天晚上去操場跑兩圈,但是跑步太累人了,他想轉移注意力,忘記痛苦,正在聽着音樂的他,突然有個想法,他想跟着音樂的節奏來跑步,音樂有7種音符,對應的是1到7,那么他對應的步長就可以是1-7分米,這樣的話他就可以轉移注意力了,但是他想保持自己跑步的速度,在規定時間m分鍾跑完。為了避免被累死,他需要規划他每分鍾需要跑過的音符,這些音符的步長總和要盡量小。下面是小蝦同學聽的歌曲的音符,以及規定的時間,你能告訴他每分鍾他應該跑多少步長? 

輸入描述:
輸入的第一行輸入 n(1 ≤ n ≤ 1000000,表示音符數),m(1<=m< 1000000, m <= n)組成,
第二行有 n 個數,表示每個音符(1<= f <= 7)
輸出描述:
輸出每分鍾應該跑的步長
示例1

輸入

8 5 6 5 6 7 6 6 3 1

輸出

11

解析

這個題目,題意比較難理解。因為要求在m分鍾內跑完,那么規划每一分鍾跑的步長。其實就是把n個音符,划分成為m組,使得每一組的和的最大值盡可能小(“規划他每分鍾需要跑過的音符,這些音符的步長總和要盡量小”)。

因此可以類似於leetcode的410題“分割數組的最大值”,通過二分法來做。

1.當m=1時,即將所有的數組看成一個整體,也就是一分鍾跑完,最小的最大值就是音符的步長總和;

2.當m=n時,即將每一個數看成一個子數組,每一分鍾跑一個音符的步長,此時最小的最大值 也就是 所有音符步長的最大值

3.因此m的范圍為1<=m<=n,每分鍾跑的步長的范圍是[max(每一個音符),sum(每一個音符)]

4.利用二分查找,找到符合m的最大值的最小的結果。以中位數作為一個子數組的最大容量 的初始值

5.假設新開辟一個子數組來存儲音符,count=1;利用貪心思想,按照順序將元素依次放入,因為要相鄰。直到加入下一個元素時,當前子數組里的音符步長和>中位數。

6.那么可以發現一個中位數容量的子數組無法容納整個數組元素,需要再擴充一個數組。count++

7.再將剩余元素放入另一個數組.......如果放不下,就繼續擴充

8.最終得到count,如果count>m,說明划分的子數組大於預期的時長,有太多的子數組,也就是一個子數組的容量太小了,那么就要擴大容量。原來的容量是中位數,那么現在應該left=mid+1,將整個部分挪到線段軸的右邊。反之說明子數組太少,需要減少容量,將整個部分挪到線段軸的左側,right=mid-1

9.直到left和right重合

解答

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        //音符數
        long n = scan.nextLong();
        //規定時間m分鍾跑完
        long m = scan.nextLong();
        int[] musics = new int[(int) n];
        long left = 0, right = 0;
        //有 n個數,表示每個音符
        for(int i=0; i<n; i++) {
            musics[i] = scan.nextInt();
            left = Math.max(left, musics[i]);
            right += musics[i];
        }
        while(left<right) {
            int count = 1;
            long mid = (left+right)>>>1;
            int sum = 0;
            for(int music: musics) {
                if (sum+music > mid) {
                    sum = 0;
                    count++;
                }
                sum += music;
            }
            //划分太多
            if (count>m) {
                left = mid+1;
            }
            //划分太少
            else {
                right = mid;
            }
        }
        System.out.println(left);
        
    }

番外:分割數組的最大值(leetcode410)

給定一個非負整數數組和一個整數 m,你需要將這個數組分成 m 個非空的連續子數組。設計一個算法使得這 m 個子數組各自和的最大值最小。

注意:
數組長度 n 滿足以下條件:


    1 ≤ n ≤ 1000
    1 ≤ m ≤ min(50, n)

示例: 
輸入:
nums = [7,2,5,10,8]
m = 2

輸出:
18

解釋:
一共有四種方法將nums分割為2個子數組。
其中最好的方式是將其分為[7,2,5] 和 [10,8],
因為此時這兩個子數組各自的和的最大值為18,在所有情況中最小。

來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/split-array-largest-sum
著作權歸領扣網絡所有。商業轉載請聯系官方授權,非商業轉載請注明出處。

解答

class Solution {
    public int splitArray(int[] nums, int m) {
        int left = 0;
        int right = 0;
        for(int num: nums){
            //當每個元素作為一個子數組,那么最小的最大值 也就是所有元素的最大值
            left = Math.max(left, num);
            //當整個數組作為一部分,最小的最大值 也就是所有元素的和
            right += num;
        }
        //利用二分法查找,找到符合m的最大值的最小的結果
        while(left<right){
            //剛開辟的用來存儲的子數組個數
            int count = 1;
            int mid = (left+right)>>>1;
            int sum = 0;
            //由於要連續數字,因此將數組元素按順序逐個往里放
            for(int num: nums){
                //直到下一個元素放不下了,就開辟一個新的數組
                if(num+sum > mid){
                    sum = 0;
                    count++;
                }
                sum += num;
            }
            //表示划分出太多的子數組,即數組的容量太少,mid應該加大,需要擴大容量
            if(count > m){
                left = mid+1;
            }
            //表示划分出太少的子數組,即數組的容量太大,需要減少容量
            else{
                right = mid;
            }
        }
        return left;
    }
}

 

3.實現字通配符*

鏈接:https://www.nowcoder.com/questionTerminal/bab19e5b95b54744aa824e0d7be51487
來源:牛客網
在Linux Shell命令下通配符'*'表示0個或多個字符, 現編寫一段代碼實現通配符'*'的功能,注意只需要實現'*', 不用實現其他通配符。 
輸入描述:
第一行輸入通配字符串
第二行輸入要匹配查找的字符串
輸出描述:
輸出所有匹配的字串起始位置和長度,每行一個匹配輸出
如果不匹配,則輸出 -1 0
如果有多個按照起始位置和長度的正序輸出。
示例1

輸入

shopee*.com
shopeemobile.com

輸出

0 16

解析

時刻注意檢查數組是否越界(left<right,當left>=right,說明目標串已經匹配到字符串末尾)

類似於劍指offer的正則表達式匹配

1.同時匹配到末尾,匹配成功

2.模式串到末尾,字符串沒有到末尾,匹配失敗

3.字符串到末尾,模式串沒到末尾,且模式串后一位不為*,匹配失敗

4.模式串后一位為*

  1)匹配到最后一個字符,pattern為*,那么將pattern看成空字串。pattern向后移一位

  2)匹配過程中,可以將*看成空字串,pattern向后移一位/將*看成多個字符,target向后移一位

5.當前模式串位置不是*,且匹配成功。pattern和target都向后移一位

6.當前模式串不是*,且匹配失敗。匹配失敗

7.其余情況,匹配失敗。

解答

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //通配字符串
        String pattern = scanner.nextLine();
        //要匹配查找的字符串
        String target = scanner.nextLine();
        char[] patternChar = pattern.toCharArray();
        char[] targetChar = target.toCharArray();
        int count = 0;
        for(int i=0; i<targetChar.length; i++) {
            for(int j=i; j<targetChar.length; j++) {
                if(match(patternChar, targetChar, 0, i, j)) {
                    System.out.println(i+" "+(j-i+1));
                    count++;
                }
            }
        }
        if (count == 0) {
            System.out.println("-1 0");
        }
        
    }

    //匹配target[left,...,right],匹配到模式串的第i位
    private static boolean match(char[] pattern, char[] target, int i, int left, int right) {
        // TODO Auto-generated method stub
        //剛好匹配完
        if (i==pattern.length && left>right) {
            return true;
        }
        //target已經匹配完,但是pattern還沒到結尾,且pattern的后一位還不是*,沒法看成空字符串
        if (left>right && i!=pattern.length && pattern[i]!='*') {
            return false;
        }
        //已經到了模式串的最后一位,但是target還沒匹配完
        if (i==pattern.length && left<=right) {
            return false;
        }
        //當通配字符是*時,通配字符向后移動/查找字符向后移動
        else if (pattern[i]=='*') {
            //匹配到最后一個字符,pattern后面還有*,那么將*看成空字符,pattern往后移一位匹配
            if (left>right) {
                return match(pattern, target, i+1, left, right);
            }
            //匹配過程中遇到*,那么如果*看成空字串,則pattern后移一位
       //如果*看成多個字符,首先*與當前字符匹配,然后就開始與target的下一個字符匹配,那么target向后移一位
else { return match(pattern, target, i, left+1, right) || match(pattern, target, i+1, left, right); } } //當前模式串是字符,且與目標串匹配,則后移匹配下一字符 else if(left<=right && pattern[i]==target[left]){ return match(pattern, target, i+1, left+1, right); } //匹配中出現了不同的字符,匹配失敗 else if(left<=right && pattern[i]!=target[left]){ return false; } else { return false; } }

 

4.建物流中轉站

鏈接:https://www.nowcoder.com/questionTerminal/c82efaf9e2cc42cda0a8ad795845eceb?answerType=1&f=discussion
來源:牛客網

Shopee物流會有很多個中轉站。在選址的過程中,會選擇離用戶最近的地方建一個物流中轉站。

假設給你一個二維平面網格,每個格子是房子則為1,或者是空地則為0。找到一個空地修建一個物流中轉站,使得這個物流中轉站到所有的房子的距離之和最小。 能修建,則返回最小的距離和。如果無法修建,則返回 -1。 

 

若范圍限制在100*100以內的網格,如何計算出最小的距離和?

當平面網格非常大的情況下,如何避免不必要的計算? 

輸入描述:
4
0 1 1 0
1 1 0 1
0 0 1 0
0 0 0 0

先輸入方陣階數,然后逐行輸入房子和空地的數據,以空格分隔。
輸出描述:
8

能修建,則返回最小的距離和。如果無法修建,則返回 -1。
示例1

輸入

4
0 1 1 0
1 1 0 1
0 0 1 0
0 0 0 0

輸出

8

解析

因為范圍限定100*100,所以其實可以直接暴力了。

對於每一個空地,去遍歷其與房子的距離。找到最短的距離

解答

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //方陣階數
        int n = scanner.nextInt();
        //逐行輸入房子和空地的數據,每個格子是房子則為1,或者是空地則為0
        int[][] matrix = new int[n][n];
        ArrayList<int[]> house = new ArrayList<>();
        for(int i=0; i<n; i++) {
            for(int j=0; j<n; j++) {
                matrix[i][j] = scanner.nextInt();
                if (matrix[i][j]==1) {
                    int[] xy = new int[2];
                    xy[0] = i;
                    xy[1] = j;
                    house.add(xy);
                }
            }
        }
        int ret = Integer.MAX_VALUE;
        //找到一個空地修建一個物流中轉站,使得這個物流中轉站到所有的房子的距離之和最小。
        for(int i=0; i<n; i++) {
            for(int j=0; j<n; j++) {
                int temp = 0;
                //遍歷每一個空地,看其到所有房子的距離
                if (matrix[i][j]==0) {
                    for(int k=0; k<house.size(); k++) {
                        temp+=Math.abs(house.get(k)[0]-i)+
                                Math.abs(house.get(k)[1]-j);
                    }
                    //ret存儲當前房子距離最小的可能性。如果temp小於則替換
                    ret = ret>temp?temp:ret;
                }
            }
        }
        //能修建,則返回最小的距離和。如果無法修建,則返回 -1
        System.out.println(ret==Integer.MAX_VALUE?-1:ret);
    }
    

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM