1 求数中最多连续的1
左移一次和原数相与,直到变为0,计操作次数既是最多连续的1。(题解来自:Mcdull)
应用:LeetCode211.最大正方形
求1组成的正方形的宽
正常解法:
暴力求解的话就是穷举,遍历二维数组将每一个1作为正方形的左上角,如果想扩大正方形,则新增一行一列且必须由连续1组成。
- 如何判断新增的一行一列是否是连续的1?令k从1开始直到目前可达最大宽/高(min{宽、高}),这里的宽是
row-i
,高是col-j
。即k是可以增加的区域。
令m是从0-k,这样可以遍历到增加的一行和一列的所有元素
/**
* 判断新增的行列是否合法
*/
for(int k=1;k<maxWidth-j;k++){
if(a[i+k][j+k]!='1'){
break;
}
//flag表示这新增的一行一列是否合法
boolean flag = true;
for(int m=0;m<k;m++){
if(a[i+k][j+m]!='1'||a[i+m][j+k]!='1'){
flag = false;//因为这里只能跳一层循环所以需要一个flag来跳第二层k的循环
break;
}
}
//如果合法就更新maxSide
if(flag){
maxSide = Math.max(maxSide,1+k);
} else {
break;
}
}
- 遍历时间复杂度
O(mn)
- 为了凑成正方形,需要遍历判断正方形内每个数是不是1,所以需要取宽、高中较小值的平方。总的时间复杂度为
O(mn*min(m,n)^2)
优化解法
减少重复遍历,需要用到动态规划。这次不再从左上角开始考虑正方形,而是从右下角,这样可以用到之前遍历左上部分时的数据。
使用dp(i,j)
表示以i,j
为右下角的最大正方形边长,可以得到:
- 如果
a[i][j]
为0而不是1,则不可能组成正方形的 - 如果
a[i][j]
为1,则正方形边长由其左、左上、上方的元素决定。但如果i,j
本身是在边界上,那么最大边长只能为1.
为什么由其左、左上、上方的元素决定?i,j
相当于在其左上元素往右往下增加了一列一行,那么可以得到
dp[i][j] = min{dp[i-1][j],dp[i][j-1],dp[i-1][j-1]}+1
其左、左上、上方dp的最小值+1 - 这样,遍历所有元素一遍即可求出所有dp,在求dp的过程中记录最大值。时间复杂度为O(mn)
- 空间复杂度为O(mn)
2 求完全二叉树的节点个数
LeetCode222.完全二叉树的节点个数
首先,这道题可以用遍历二叉树来解决,但考虑完全二叉树的性质:
- 它是一颗空树,或者,它的叶子节点只出现在最后两层,且最后一层如果不满则叶子结点聚集在左侧。
- 那么我们可以求root左右子树的高度,如果
left>right
则说明左子树比右子树高一层,右子树成为满二叉树,其节点数为Math.pow(2,right)-1
(root没有计算在内)。
满二叉树节点计算:如果层数为n,则满二叉树节点数为2^n-1
- 如果
left==right
说明左子树是满二叉树,右子树左侧聚集了最后一层剩下的叶子结点。那么继续对右子树进行递归统计。
小技巧:如何计算2^n?可以通过1左移n位来进行位运算。
那么,最后需要解决的就是如何计算完全二叉树的层数?
完全二叉树的右子树只有可能小于或等于左子树的层数,所以只要遍历左节点就可以了
while(cur!=null){
depth++;
cur=cur.left;
}
这样就省去递归了
但是仍然需要递归求解左或右子树的节点数(满二叉树那一边可以直接得到,即加上root节点是2^n
,然后递归另一个子树即可)
参考题解:Wanglihao
3 矩形重叠后的总面积
LeetCode223.矩形面积
这里需要注意的是当测试数据为-1500000000 - 1500000000时计算结果会超出int的最小值导致溢出,所以要保证取CG最小值要大于AE最大值才有意义。
重叠面积的宽为右上角横坐标最小值-左下角横坐标最大值
高同理
参考题解:燕少江湖
4 基本计算器
实现一个基本的计算器来计算一个简单的字符串表达式的值。
字符串表达式可以包含左括号 ( ,右括号 ),加号 + ,减号 -,非负整数和空格 。
LeetCode224. 基本计算器
(在金山云的面试里遇见过这道题,不过当时没有选。现在一看难度是困难= =)
思路:从右往左遍历字符串,如果遇到(
说明要计算一个括号()
内结果,则不断出栈直到遇到)
,然后计算这一段的结果,我们用一个方法calInStack()
来完成。
该方法有这么些个功能:
- 出栈开始计算
- 翻转字符串得到正常的表达式(主函数中从右往左遍历
- 遇到
)
则结束,不再继续弹出 - 鉴于该计算方法关注左右括号,那么需要把原始字符串用括号扩起来,避免最后的元素没有被计算
calInStack()
实现:
private static int calInStack(Stack<Object> st){
int res = 0;
//取出栈顶数字
if(!st.isEmpty()){
res = (int)st.pop();
}
//继续取和计算
while(!st.isEmpty()&&(char)st.peek()!=')'){
char sig = st.pop();
if(sig=='+'){
res+=(int)st.pop();
}else{
res-=(int)st.pop();
}
}
return res;
}
主函数:
public int calculator(String s){
s = "("+s+")";//使calInStack能计算全部的
int p=0;//记录这个数字是整个数的第几位
int sum = 0;//记录整个数
Stack<Object> st = new Stack<>();
for(int i=s.length()-1;i>=0;i--){
//从后往前遍历
char ch = s.charAt(i);
if(Character.isDigit(ch)){
sum += (int)Math.pow(10,p)*(ch-'0');
p++;
}else if(ch!=' '){
//+或-或括号
//查看p是否不为0,说明之前正在拼数,把数放入栈
if(p!=0){
st.push(sum);
sum=0;
p=0;
}
if(ch=='('){
//遇到左括号,需要计算栈中第一个右括号之间的结果
int res = calInStack(st);
st.pop();//右括号出栈
st.push(res);//结算当前括号并入栈
}else{
//+或-
st.push(ch);
}
}
return calInStack(st);
}
5 队列实现栈
LeetCode225. 队列实现栈
考虑用两个队列,队列A用来存储,队列B用来倒腾
每次有新元素入栈时,先放到B,然后把A中的元素出列并放入B。然后AB互换