1、最大子序列和(书2.4.3)
联机算法(对已读入的数据,能给出结果)、流式计算
//数组全负结果为0
public int maxSubSum(int[] a) {
int maxSum = 0;
int thisSum = 0;
for (int i = 0; i < a.length; i++) {
thisSum += a[i];
if (thisSum > maxSum) {
maxSum = thisSum;
} else if (thisSum < 0) {
thisSum = 0;
}
}
return maxSum;
}
2、二分查找(2.4.4)
注意边界条件 low <= high
public int binarySearch(int[] a, int x) {
int low = 0;
int high = a.length - 1;
while (low <= high) {
int mid = ((high - low) >> 1) + low;
if (a[mid] < x)
low = mid + 1;
else if (a[mid] > x)
high = mid - 1;
else
return mid;
}
return -(low + 1);
}
3、欧几里得最大公约数算法(2.4.4)
辗转相除法
public long gcd(long m, long n) {
while (n != 0) {
long rem = m % n;
m = n;
n = rem;
}
return m;
}
4、高效幂运算(2.4.4)
//未考虑n为负等情况,《剑指offer》上有各种情况的代码
public long pow(int x, int n) {
if (n == 0)
return 1;
if ((n & 1) == 0)
return pow(x * x, n >> 1);
else
return pow(x * x, n >> 1) * x;
}
5、后缀表达式(3.6.3 逆波兰表达式)
将中序表达式转化为后缀表达式:数字直接输出,符号进栈,如果符号优先级≤栈顶符号优先级,弹出栈顶符号直到符号优先级>栈顶符号优先级,如果')'进栈则弹栈直到遇到'('。表达式扫描完毕后弹空栈。
再用栈计算后缀表达式
例如,a+b*c+(d*e+f)*g,后缀表达式为abc*+de*f+g*+。abc先输出,遇到第二个+,弹出*+,第二个+入栈。接着输出de,栈内为+(*,遇到+,弹出*,栈内为+(+;输出f;)入栈后,弹出+,栈内为+;输出g,栈内为+*,最后输出*+,得到后缀表达式。
得到后缀表达式后,就不需要知道符号的优先级规则,用栈计算结果即可。
6、二叉树的遍历(4.2, 4.6)
多叉树转化为二叉树:左儿子右兄弟,原先最左的儿子为left,右侧相邻的兄弟节点成为right。
//先序遍历
public void preTraversal(TreeNode root) {
Stack s = new Stack<>();
if (root == null)
return;
s.push(root);
while (!s.isEmpty()) {
root = s.pop();
visit(root);
if (root.right != null)
s.push(root.right);
if (root.left != null)
s.push(root.left);
}
}
//先序遍历
public void preTraversal(TreeNode root) {
Stack s = new Stack<>();
while(root != null || !s.isEmpty()) {
while(root != null) {
s.push(root);
visit(root);
root = root.left;
}
if(!s.isEmpty()) {
root = s.pop();
root = root.right;
}
}
}
//中序遍历
public void inordTraversal(TreeNode root) {
Stack s = new Stack<>();
while(root != null || !s.isEmpty()) {
while(root != null) {
s.push(root);
root = root.left;
}
if(!s.isEmpty()) {
root = s.pop();
visit(root);
root = root.right;
}
}
}
// 后序遍历 双栈法
public void preTraversal(TreeNode root) {
if (root == null)
return;
Stack s1 = new Stack<>();
Stack s2 = new Stack<>();
s1.push(root);
while (!s1.isEmpty()) {
root = s1.pop();
s2.push(root);
if (root.left != null)
s1.push(root.left);
if (root.right != null)
s1.push(root.right);
}
while (!s2.isEmpty())
visit(s2.pop());
}
// 后序遍历
public void postTraversal(TreeNode root) {
Stack s = new Stack<>();
s.push(root);
TreeNode temp = root;
while (!s.isEmpty()) {
root = s.peek();
if (root.left == null & root.right == null || root.left == temp && root.right == null || p.right == temp) {
visit(root);
temp = root;
s.pop();
} else {
if (root.right != null)
s.push(root.right);
if (root.left != null)
s.push(root.left);
}
}
}
// 树的高度
public int height(TreeNode root) {
if (root == null)
return 0;
return 1 + Math.max(height(root.left), height(root.right));
}
// 反转树
public void reverseTree(TreeNode root) {
if (root == null)
return;
Stack s = new Stack<>();
s.push(root);
while (!s.isEmpty()) {
root = s.pop();
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
if (root.left != null)
s.push(root.left);
if (root.right != null)
s.push(root.right);
}
}
7、AVL树(4.4)
主要是L,R,LR,RL四种旋转。
AVL树详解
8、B树,B+树,B*树(4.7)
和二叉树相比,log函数的底更大,可以极大减少IO次数。
B+树:(1)数据项在叶子上
(2)根的儿子数在[2, M]之间(M阶B+树)
(3)其余非叶子节点儿子数在[M/2, M]之间,非叶子节点节点中,关键字的个数与其子树的个数相同,不像B树中,子树的个数总比关键字个数多1个。关键字为子节点中的最小值或最大值。
(4)叶子结点有指向下一个节点的指针,可以顺序查找(B树也有)这里之前写错了,B树不能顺序查找,想想也知道,B树中间层也有文件指针,如果可以顺序查找,next指针的处理会很麻烦。
(5)B+树分裂、合并只影响原节点和父节点,不需要指向兄弟节点的指针(B*树有)
(6)删除时优先与兄弟节点合并,不能合并再借一个儿子
B树:关键字分布在整棵树中,不会像B+树一样重复出现
B*树:块使用率为2/3,非叶子结点增加指向兄弟的指针(便于分裂,分裂时,兄弟节点未满则移入兄弟节点,已满则各移出1/3建立新节点)
B+树比B树,B*树更适合做索引:B树每个节点都有文件指针,占用空间,B+树一次可以读入更多节点,可以减少IO次数,且B+树可以顺序查找,很适合数据库的范围查询。
B树与B+树
9、散列解决冲突的办法(5.3, 5.4)
分离链接法和开放定址法
HashMap用分离链接法
开放定址法有线性探测法、平方探测法、双散列、完美散列(用散列解决冲突)等,线性探测、平方探测等方法只能懒惰删除
10、其他
(1)包装类型是不可变的,平时强调的比较多的是public final class String,内部的char[]也是final的,包装类倒没怎么注意过。
注意和BigDecimal的区别,BigDecimal数值是不可变,a.add(b)不会改变a的值,应该写作a = a.add(b)。
(2)数组是协变的,集合不是。