1.shopee的辦公室
鏈接:https://www.nowcoder.com/questionTerminal/a71f3bd890734201986cd1e171807d30?answerType=1&f=discussion
來源:牛客網
輸入描述:
第一行 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
輸出描述:
輸出小蝦有多少種走法
輸入
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)
輸出描述:
輸出每分鍾應該跑的步長
輸入
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
來源:牛客網
輸入描述:
第一行輸入通配字符串
第二行輸入要匹配查找的字符串
輸出描述:
輸出所有匹配的字串起始位置和長度,每行一個匹配輸出
如果不匹配,則輸出 -1 0
如果有多個按照起始位置和長度的正序輸出。
輸入
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。
輸入
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); }