劍指offer第六週
79. 滑動窗口的最大值
給定一個數組和滑動窗口的大小,請找出所有滑動窗口裏的最大值。
例如,如果輸入數組[2, 3, 4, 2, 6, 2, 5, 1]及滑動窗口的大小3,那麼一共存在6個滑動窗口,它們的最大值分別爲[4, 4, 6, 6, 6, 5]。
注意:
- 數據保證k大於0,且k小於等於數組長度。
樣例
輸入:[2, 3, 4, 2, 6, 2, 5, 1] , k=3
輸出: [4, 4, 6, 6, 6, 5]
單調隊列模板 — 注意放的是下標
class Solution {
public int[] maxInWindows(int[] nums, int k) {
List<Integer> list = new ArrayList<>();
ArrayDeque<Integer> q = new ArrayDeque<>();
for(int i = 0; i < nums.length; i++){
// 當 隊尾的數 小於或等於 當前數nums[i]時,則 隊尾的數字就沒有用了,Poll();
while(!q.isEmpty() && nums[q.peekLast()] <= nums[i])q.pollLast();
// 如果 隊列的 大小大於等於k, 則代表隊首位置的數 畢業了;
while(!q.isEmpty() && i - q.peek() >= k)q.poll();
q.offer(i);
if(i >= k - 1)list.add(nums[q.peek()]);
}
int[] res = new int[list.size()];
for(int i = 0; i < list.size(); i++)res[i] = list.get(i);
return res;
}
}
80. 骰子的點數
將一個骰子投擲n次,獲得的總點數爲s,s的可能範圍爲n~6n。
擲出某一點數,可能有多種擲法,例如投擲2次,擲出3點,共有[1,2],[2,1]兩種擲法。
請求出投擲n次,擲出n~6n點分別有多少種擲法。
樣例1
輸入:n=1
輸出:[1, 1, 1, 1, 1, 1]
解釋:投擲1次,可能出現的點數爲1-6,共計6種。每種點數都只有1種擲法。所以輸出[1, 1, 1, 1, 1, 1]。
樣例2
輸入:n=2
輸出:[1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]
解釋:投擲2次,可能出現的點數爲2-12,共計11種。每種點數可能擲法數目分別爲1,2,3,4,5,6,5,4,3,2,1。
所以輸出[1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]。
f【i】【j】 表示 扔i次 得到點數j的次數
第j次丟的點數是由j - 1丟的點數來的;
f[i][j] += f[i - 1][j - k];
class Solution {
public int[] numberOfDice(int n) {
int[][] f = new int[n + 1][6 * n + 1];
f[0][0] = 1;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= 6 * n; j ++)
for(int k = 1; k <= Math.min(j,6); k++)
f[i][j] += f[i - 1][j - k];
int[] res = new int[6 * n - n + 1];
for(int i = n; i <= 6 * n; i ++)res[i - n] = f[n][i];
return res;
}
}
81. 撲克牌的順子
從撲克牌中隨機抽5張牌,判斷是不是一個順子,即這5張牌是不是連續的。
2~10爲數字本身,A爲1,J爲11,Q爲12,K爲13,大小王可以看做任意數字。
爲了方便,大小王均以0來表示,並且假設這副牌中大小王均有兩張。
樣例1
輸入:[8,9,10,11,12]
輸出:true
樣例2
輸入:[0,8,9,11,12]
輸出:true
排序,如果裏面有相同的數返回false,如果沒有,查看第一個不爲0的數與最大的數之間的差是否在4以內即可,大王可以隨意變換數字;
class Solution {
public boolean isContinuous(int [] nums) {
Arrays.sort(nums);
for(int i = 1; i < nums.length; i ++)if(nums[i] > 0 && nums[i] == nums[i - 1])return false;
for(int num : nums)
if(num > 0) return nums[nums.length - 1] - num <= 4;
return false;
}
}
82.圓圈中最後剩下的數字
0, 1, …, n-1這n個數字(n>0)排成一個圓圈,從數字0開始每次從這個圓圈裏刪除第m個數字。
求出這個圓圈裏剩下的最後一個數字。
樣例
輸入:n=5 , m=3
輸出:3
反推,當最後面只剩他一個人的時候,他必定在第0個位置上;
當只剩兩人的時候,他的位置now在 now = pre(前一個位置) + m(數多少個數) % i (當前總人數)
也就是說在其位置前加m個人,則每次都不會選到它;
最後推出總人數爲n時,now當前的序號
class Solution {
// now = pre(前一個位置) + m(數多少個數) % i (當前總人數)
public int lastRemaining(int n, int m) {
int pre = 0;
for(int i = 2; i <= n; i ++){
pre = (pre + m) % i;
}
return pre;
}
}
83. 股票的最大利潤
假設把某股票的價格按照時間先後順序存儲在數組中,請問買賣 一次 該股票可能獲得的利潤是多少?
例如一隻股票在某些時間節點的價格爲[9, 11, 8, 5, 7, 12, 16, 14]。
如果我們能在價格爲5的時候買入並在價格爲16時賣出,則能收穫最大的利潤11。
樣例
輸入:[9, 11, 8, 5, 7, 12, 16, 14]
輸出:11
貪心:動態記錄最小值,將後面的值 - 最小值,來更新最大利潤
class Solution {
public int maxDiff(int[] nums) {
if(nums.length < 2) return 0;
int minn = nums[0], n = nums.length, res = 0;
for(int i = 1; i < n; i++){
res = Math.max(res,nums[i] - minn);
minn = Math.min(minn,nums[i]);
}
return res;
}
}
84.求1+2+…+n
求1+2+…+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。
樣例
輸入:10
輸出:55
等差數列求和
class Solution {
public int getSum(int n) {
long temp = (1l + n) * n;
return (int) (temp / 2);
}
}
題目說不能用乘除法,故用遞歸
class Solution {
public int getSum(int n) {
return dfs(n);
}
public int dfs(int n){
if(n == 0)return 0;
int res = n;
res += dfs(n - 1);
return res;
}
}
85. 不用加減乘除做加法
寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、×、÷ 四則運算符號。
樣例
輸入:num1 = 1 , num2 = 2
輸出:3
class Solution {
// num2 k 0;
// 左移後 至少 k + 1 0;
// 異或 ^ 1 1 = 0, 1 0 = 1, 0 0 = 0, 相當於不進位加法
// & 當 兩個都是1時 進位, 再左移 相當於 進位的數,相加則爲 原來的數
public int add(int num1, int num2) {
while(num2 > 0){
int sum = num1 ^ num2;
num2 = (num1 & num2) << 1;
num1 = sum;
}
return num1;
}
}
86. 構建乘積數組
給定一個數組A[0, 1, …, n-1]
,請構建一個數組B[0, 1, …, n-1]
,其中B中的元素B[i]=A[0]×A[1]×… ×A[i-1]×A[i+1]×…×A[n-1]
。
不能使用除法。
樣例
輸入:[1, 2, 3, 4, 5]
輸出:[120, 60, 40, 30, 24]
思考題:
- 能不能只使用常數空間?(除了輸出的數組之外)
先枚舉前一半,然後後一半
class Solution {
public int[] multiply(int[] A) {
int[] res = new int[A.length];
for(int i = 0, p = 1; i < A.length; i ++){
res[i] = p;
p *= A[i];
}
for(int i = A.length - 1,p = 1; ~i != 0; i --){
res[i] *= p;
p *= A[i];
}
return res;
}
}
87. 把字符串轉換成整數
請你寫一個函數StrToInt,實現把字符串轉換成整數這個功能。
當然,不能使用atoi或者其他類似的庫函數。
樣例
輸入:"123"
輸出:123
注意:
你的函數應滿足下列條件:
- 忽略所有行首空格,找到第一個非空格字符,可以是 ‘+/−’ 表示是正數或者負數,緊隨其後找到最長的一串連續數字,將其解析成一個整數;
- 整數後可能有任意非數字字符,請將其忽略;
- 如果整數長度爲0,則返回0;
- 如果整數大於INT_MAX(2^31 − 1),請返回INT_MAX;如果整數小於INT_MIN(−2^31) ,請返回INT_MIN;
模擬題
class Solution {
public int strToInt(String str) {
int maxx = Integer.MAX_VALUE, minn = Integer.MIN_VALUE;
if(str.length() == 0) return 0;
// res 答案, flag 記錄符號,digit記錄 res的長度
int res = 0, flag = 1,digit = 0;
// 去掉首位空字符串
str = str.trim();
// 判斷是否是 非字符串開始,如果是直接break
boolean start = false;
for(int i = 0; i < str.length(); i ++){
if(str.charAt(i) == '-'){flag = -1;continue;}
if(str.charAt(i) == '+')continue;
if(str.charAt(i) > '9' || str.charAt(i) < '0'){
if(start)continue;
else break;
}
// 接下來要加的數字
int bit = (str.charAt(i) - '0');
if(flag == 1){
if((res < maxx / 10 || digit == 9 && bit <= 7 && res / 1000000000 <= 2))res = res * 10 + bit;
else return maxx;
}else{
if((res < maxx / 10 || digit == 9 && bit <= 8 && res / 1000000000 <= 2))res = res * 10 + bit;
else return minn;
}
digit ++;
start = true;
}
return res * flag;
}
}
88. 樹中兩個結點的最低公共祖先
給出一個二叉樹,輸入兩個樹節點,求它們的最低公共祖先。
一個樹節點的祖先節點包括它本身。
注意:
- 輸入的二叉樹不爲空;
- 輸入的兩個節點一定不爲空,且是二叉樹中的節點;
樣例
二叉樹[8, 12, 2, null, null, 6, 4, null, null, null, null]如下圖所示:
8
/ \
12 2
/ \
6 4
1. 如果輸入的樹節點爲2和12,則輸出的最低公共祖先爲樹節點8。
2. 如果輸入的樹節點爲2和6,則輸出的最低公共祖先爲樹節點2。
從根開始尋找左右子樹,如果發現某棵樹就是要找的p,或者q,則直接返回p,q如果左邊找到 p,q 右邊沒找到,那麼左邊的就是最近祖先,如果都找到則返回其公共祖先,root,如果都沒找到,返回null;
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null)return null;
if(root == p || root == q)return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left != null && right != null) return root;
if(left == null && right != null)return right;
if(right == null && left != null)return left;
return null;
}
}