JZ59-I 滑动窗口的最大值
使用双端队列(尾入头出)实现单调队列(出队队首为最大值,可自定义类实现),根据窗口长度将数组中数字分别入队出队:入队时如果队尾元素小于当前元素,则弹出队尾元素再将当前元素入队(队尾元素的位置在当前元素之前,一定先出队);出队时如果当前元素等于队首元素,则弹出队首元素,否则不处理(队列中较小值且下标在前的元素已经弹出)
class Solution {
public int [ ] maxSlidingWindow ( int [ ] nums, int k) {
if ( nums. length == 0 ) return new int [ 0 ] ;
Deque < Integer > deque = new LinkedList < > ( ) ;
int [ ] res = new int [ nums. length - k + 1 ] ;
int index = 0 ;
for ( int i = 0 ; i < k; i++ ) {
while ( ! deque. isEmpty ( ) && deque. peekLast ( ) < nums[ i] ) deque. pollLast ( ) ;
deque. offer ( nums[ i] ) ;
}
res[ index++ ] = deque. peek ( ) ;
for ( int i = k; i < nums. length; i++ ) {
if ( nums[ i - k] == deque. peek ( ) ) deque. poll ( ) ;
while ( ! deque. isEmpty ( ) && deque. peekLast ( ) < nums[ i] ) deque. pollLast ( ) ;
deque. offer ( nums[ i] ) ;
res[ index++ ] = deque. peek ( ) ;
}
return res;
}
}
JZ59-II 队列的最大值
使用两个双端队列(尾入头出)实现单调队列(出队队首为最大值),入队时如果队尾元素小于当前元素,则弹出队尾元素再将当前元素入队(队尾元素的位置在当前元素之前,一定先出队),出队列正常入队;出队时如果出队列不为空,且出队列队首元素等于队列队首元素,则弹队列队首元素(队列中较小值且下标在前的元素已经弹出),出队列正常出队
class MaxQueue {
Deque < Integer > deque;
Deque < Integer > dequeOut;
public MaxQueue ( ) {
deque = new LinkedList < > ( ) ;
dequeOut = new LinkedList < > ( ) ;
}
public int max_value ( ) {
if ( ! deque. isEmpty ( ) ) return deque. peek ( ) ;
return - 1 ;
}
public void push_back ( int value) {
while ( ! deque. isEmpty ( ) && deque. peekLast ( ) < value) deque. pollLast ( ) ;
deque. offer ( value) ;
dequeOut. offer ( value) ;
}
public int pop_front ( ) {
if ( ! dequeOut. isEmpty ( ) ) {
int out = dequeOut. peek ( ) ;
if ( out == deque. peek ( ) ) deque. poll ( ) ;
return dequeOut. poll ( ) ;
}
return - 1 ;
}
}
JZ60 n个骰子的点数
动态规划,定义二维dp数组表示投掷完i个骰子后,点数j的出现次数;投掷完第i个骰子后,点数j的出现次数可以由投掷完i-1个骰子后点数j-1,j-2,……,j-6推出(第i个骰子可能为1-6);注意点数应该 > 0,可以使用一维数组优化
class Solution {
public double [ ] dicesProbability ( int n) {
int [ ] [ ] dp = new int [ n + 1 ] [ n * 6 + 1 ] ;
for ( int i = 1 ; i <= 6 ; i++ ) {
dp[ 1 ] [ i] = 1 ;
}
for ( int i = 2 ; i <= n; i++ ) {
for ( int j = i; j <= 6 * i; j++ ) {
for ( int k = 1 ; k <= 6 ; k++ ) {
if ( j <= k) break ;
dp[ i] [ j] += dp[ i - 1 ] [ j - k] ;
}
}
}
double [ ] res = new double [ 5 * n + 1 ] ;
int index = 0 ;
double sum = Math . pow ( 6 , n) ;
for ( int i = n; i <= 6 * n; i++ ) {
res[ index++ ] = dp[ n] [ i] / sum;
}
return res;
}
}
class Solution {
public double [ ] dicesProbability ( int n) {
int [ ] dp = new int [ n * 6 + 1 ] ;
for ( int i = 1 ; i <= 6 ; i++ ) {
dp[ i] = 1 ;
}
for ( int i = 2 ; i <= n; i++ ) {
for ( int j = 6 * i; j >= i; j-- ) {
dp[ j] = 0 ;
for ( int k = 1 ; k <= 6 ; k++ ) {
if ( j - k < i - 1 ) break ;
dp[ j] += dp[ j - k] ;
}
}
}
double [ ] res = new double [ 5 * n + 1 ] ;
int index = 0 ;
double sum = Math . pow ( 6 , n) ;
for ( int i = n; i <= 6 * n; i++ ) {
res[ index++ ] = dp[ i] / sum;
}
return res;
}
}
JZ61 扑克牌中的顺子
先将数组排序,从头开始遍历,记录0的数量,如果前后相差大于1则用0去填补,最后通过0的数量判断是否顺子,注意:当出现两张一样的不为0的数字时必不为顺子
class Solution {
public boolean isStraight ( int [ ] nums) {
Arrays . sort ( nums) ;
int count = 0 ;
for ( int i = 0 ; i < 5 ; i++ ) {
if ( nums[ i] == 0 ) {
count++ ;
} else {
if ( i > 0 && nums[ i - 1 ] != 0 ) {
if ( nums[ i] == nums[ i - 1 ] ) return false ;
if ( nums[ i] - nums[ i - 1 ] > 1 ) {
count -= nums[ i] - nums[ i - 1 ] - 1 ;
}
}
}
}
return count >= 0 ;
}
}
使用set,遍历数组,将除了0的数字加入set中(加入前先判断是否重复),记录最大和最小值,若差值小于5则为顺子
class Solution {
public boolean isStraight ( int [ ] nums) {
Set < Integer > set = new HashSet < > ( ) ;
int max = 0 ;
int min = 14 ;
for ( int num : nums) {
if ( num == 0 ) continue ;
if ( set. contains ( num) ) return false ;
set. add ( num) ;
if ( num > max) max = num;
if ( num < min) min = num;
}
return max - min < 5 ;
}
}
JZ62 圆圈中最后剩下的数字
反推解决约瑟夫环问题,找规律:首先正推,数组第一位(每次删除后的下一位)的下标变化规律为+m后对当时数组长度取模,直到数组剩最后一位(下标为0);反推得到数组最后下标为0在开始时的下标,从0开始每次+3后对当时数组长度(从2开始)取模,直到回到最开始的数组
class Solution {
public int lastRemaining ( int n, int m) {
int k = 0 ;
for ( int i = 2 ; i <= n; i++ ) {
k = ( k + m) % i;
}
return k;
}
}
JZ63 股票的最大利润
动态规划,定义dp数组表示第i天卖出股票(在0到i-1天买入)的最大利润为dp[i]
class Solution {
public int maxProfit ( int [ ] prices) {
if ( prices. length == 0 ) return 0 ;
int [ ] dp = new int [ prices. length] ;
int max = 0 ;
dp[ 0 ] = 0 ;
for ( int i = 1 ; i < prices. length; i++ ) {
dp[ i] = Math . max ( dp[ i - 1 ] + prices[ i] - prices[ i - 1 ] , 0 ) ;
max = Math . max ( max, dp[ i] ) ;
}
return max;
}
}
class Solution {
public int maxProfit ( int [ ] prices) {
if ( prices. length == 0 ) return 0 ;
int low = prices[ 0 ] ;
int profit = 0 ;
for ( int i = 1 ; i < prices. length; i++ ) {
low = Math . min ( low, prices[ i] ) ;
profit = Math . max ( profit, prices[ i] - low) ;
}
return profit;
}
}
JZ64 求1+2+…+n
递归,不适用if,使用与或非终止递归(n = 1时)
class Solution {
int res = 0 ;
public int sumNums ( int n) {
boolean flag = n > 0 && sumNums ( n - 1 ) > 0 ;
res += n;
return res;
}
public int sumNums ( int n) {
boolean flag = n > 0 && ( n += sumNums ( n - 1 ) ) > 0 ;
return n;
}
}
JZ65 不用加减乘除做加法
使用异或运算(无进位)和与运算(有进位),注意计算进位时左移一位(进的是下一位),和为无进位+进位(再次循环)
class Solution {
public int add ( int a, int b) {
while ( b != 0 ) {
int c = ( a & b) << 1 ;
a ^= b;
b = c;
}
return a;
}
}
JZ66 构建乘积数组
定义两个dp数组,分别存储i左边和i右边的乘积,可以不用dp数组直接遍历计算存储结果,优化空间
时间复杂度O(n),空间复杂度O(n),优化为O(1)
class Solution {
public int [ ] constructArr ( int [ ] a) {
int leng = a. length;
if ( leng == 0 ) return new int [ 0 ] ;
int [ ] dpL = new int [ leng] ;
int [ ] dpR = new int [ leng] ;
dpL[ 0 ] = 1 ;
dpR[ leng - 1 ] = 1 ;
for ( int i = 1 ; i < leng; i++ ) {
dpL[ i] = dpL[ i - 1 ] * a[ i - 1 ] ;
}
for ( int j = leng - 2 ; j >= 0 ; j-- ) {
dpR[ j] = dpR[ j + 1 ] * a[ j + 1 ] ;
}
int [ ] b = new int [ leng] ;
for ( int i = 0 ; i < leng; i++ ) {
b[ i] = dpL[ i] * dpR[ i] ;
}
return b;
}
public int [ ] constructArr ( int [ ] a) {
int leng = a. length;
if ( leng == 0 ) return new int [ 0 ] ;
int [ ] b = new int [ leng] ;
b[ 0 ] = 1 ;
for ( int i = 1 ; i < leng; i++ ) {
b[ i] = b[ i - 1 ] * a[ i - 1 ] ;
}
int temp = 1 ;
for ( int j = leng - 2 ; j >= 0 ; j-- ) {
temp *= a[ j + 1 ] ;
b[ j] *= temp;
}
return b;
}
}
JZ67 把字符串转换成整数
直接遍历,如果第一位为非符号非字符直接返回0,如果第一位为符号位则存储符号位(方便后面返回),存储数字拼接的结果(每遍历一位结果乘10),注意:每次拼接前进行判断是否越界。也可以使用状态机(相似题目:JZ20、JZ56-II)
时间复杂度O(n),空间复杂度O(n),不使用trim()则为O(1)
class Solution {
public int strToInt ( String str) {
str = str. trim ( ) ;
if ( str. length ( ) == 0 ) return 0 ;
char [ ] strArr = str. toCharArray ( ) ;
int border = Integer . MAX_VALUE / 10 ;
int i = 1 ;
int flag = 1 ;
if ( strArr[ 0 ] == '-' ) {
flag = - 1 ;
} else if ( strArr[ 0 ] != '+' ) {
i = 0 ;
}
int res = 0 ;
for ( int j = i; j < strArr. length; j++ ) {
if ( strArr[ j] < '0' || strArr[ j] > '9' ) break ;
if ( res > border || ( res == border && strArr[ j] > '7' ) ) return flag == 1 ? Integer . MAX_VALUE : Integer . MIN_VALUE;
res = res * 10 + ( strArr[ j] - '0' ) ;
}
return flag * res;
}
}
class Solution {
public int strToInt ( String str) {
int border = Integer . MAX_VALUE / 10 ;
int i = 0 ;
while ( i < str. length ( ) ) {
if ( str. charAt ( i) != ' ' ) break ;
i++ ;
}
if ( i == str. length ( ) ) return 0 ;
int flag = 1 ;
if ( str. charAt ( i) == '-' ) {
flag = - 1 ;
i++ ;
} else if ( str. charAt ( i) == '+' ) {
i++ ;
}
int res = 0 ;
for ( int j = i; j < str. length ( ) ; j++ ) {
if ( str. charAt ( j) < '0' || str. charAt ( j) > '9' ) break ;
if ( res > border || ( res == border && str. charAt ( j) > '7' ) ) return flag == 1 ? Integer . MAX_VALUE : Integer . MIN_VALUE;
res = res * 10 + ( str. charAt ( j) - '0' ) ;
}
return flag * res;
}
}
JZ68-I 二叉搜索树的最近公共祖先
后序遍历,从底向上递归回溯,如果当前节点为要查找的节点或为空则直接返回当前节点,如果递归左(右)子树返回节点不为空,说明有一个目标节点在左(右)子树:若当前节点左右子树均不为空,则返回当前节点(当前节点为最近公共祖先),若当前节点只有一个子树不为空,则返回该子树,否则返回空。
class Solution {
public TreeNode lowestCommonAncestor ( TreeNode root, TreeNode p, TreeNode q) {
if ( root == p || root == q || root == null ) return root;
TreeNode left = lowestCommonAncestor ( root. left, p, q) ;
TreeNode right = lowestCommonAncestor ( root. right, p, q) ;
if ( left != null && right != null ) {
return root;
} else if ( left != null && right == null ) {
return left;
} else if ( left == null && right != null ) {
return right;
}
return null ;
}
}
JZ68-II 二叉树的最近公共祖先
迭代,对于二叉搜索树(中序遍历为递增序列,左小右大),从上往下遍历,只要遍历时发现某个节点值在目标节点值之间,当前节点即为最近公共祖先
时间复杂度O(n),空间复杂度O(1)(递归为O(n))
class Solution {
public TreeNode lowestCommonAncestor ( TreeNode root, TreeNode p, TreeNode q) {
while ( root != null ) {
if ( root. val > p. val && root. val > q. val) {
root = root. left;
} else if ( root. val < p. val && root. val < q. val) {
root = root. right;
} else {
return root;
}
}
return null ;
}
}