将自己刷题中遇到的有意思的,有巧妙解法的题记录在这,持续更新…
LeetCode 338. Counting Bits
给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。
很容易就想到遍历然后每个数分别进行统计。
public int[] countBits(int num) {
int[] res = new int[num+1];
for(int i = 1; i <= num; i++) {
res[i] = getOneDigit(i);
}
return res;
}
private int getOneDigit(int num) {
int count = 0;
while(num != 0) {
count += (num & 1);
num = (num >>> 1);
}
return count;
}
来看看优化。1,数 x 与 x * 2^n 的二进制1的个数相同。2,x * 2^n + 1的二进制1的个数比 x 多1。
public int[] countBits2(int num) {
int[] res = new int[num+1];
fill(res, num, 1,1);
return res;
}
// cur代表数组下标,count代表该下标所代表的数的二进制1的个数
private void fill(int[] res, int num, int cur, int count) {
// 超出 与 重复 时退出
if (cur > num || res[cur] != 0) return;
res[cur] = count;
// 这两处递归能够保证遍历完数组,因为任何数都可以写成 2*x | 2*x+1
fill(res, num, (cur << 1)+1, count+1);// x * 2^n + 1的二进制1的个数比 x 多1
fill(res, num, cur << 1, count); //数 x 与 x * 2^n 的二进制1的个数相同
}
计算 1 ~ N 中 1 出现的次数
计算任意一个正整数n(0<n<=2147483647),从1到n(包括n)的所有整数数字里含有多少个1。
例如 13 返回 6:1,10,11,12,13 共有6个1;
最直接的方法就是遍历每个数字,对于每个数字通过 除10 ,&1的方式计算1的个数。
还有种方法:首先关于1有这样一种规律
1~9 : 1
1~99 :10 + 10 * 1 = 20
1~999:100 + 10 * 20 = 300
1~9999:1000 + 10 * 300 = 4000
…
如一个数 35678:30000中1的个数 + 5000中1的个数 + 600中1的个数 + 70中1的个数 + 8 中1的个数。利用上面的规律 30000中1的个数 = (1~9999中1的个数) + 10000 + 2 * (1~9999中1的个数)。
public static long countOne(int n) {
if (n < 10) return 1;
long[] arr = new long[9]; // arr[0]:1~9 个数为1; arr[1]:1~99 个数为20.。。
int base = 10; // 与n的位数相同,即 n / base*10 == 0
arr[0] = 1;
int i = 0; // base 与 n 同位,arr[i] 代表 1~(base-1)中1的个数
while(n / base >= 10) {
arr[i+1] = base + arr[i]*10;
base *= 10;
i++;
}
return count(n, arr, i, base, 0);
}
// n 代表此次的数字;arr中存储着满位数字1的个数;
// i ,base 与 n 的关系是这样的,n / base*10 = 0, 若base为1000,
// 则arr[i] = 300,代表1~999中1的个数。
private static long count(int n, long[] arr, int i, int base, long result) {
int high = n/base; // 数字最高位
// 首先判断是否递归到个位
if (base == 1) {
return n == 0 ? result : result+1;
}
// 原数字在此位无值,往下递归。如20345 在千位无值,递归到此后无视,往下即可
if (high == 0) {
result += count(n%base, arr, i-1, base/10, result);
}
// 最高位为1。如 13457中1的计算分为:1~9999中1的个数 + (3457+1)个高位的1 + 递归计算3457部分的个数
else if (high == 1) {
int next = n%base;
result += next+1 + arr[i] + count(next, arr, i-1, base/10, result);
}
// 最高为不为1,如34567:(1~9999中1的个数)+ 10000个1 + 2*(1~9999中1的个数) + 剩余部分4567中1的个数
else {
result += base + high*arr[i] + count(n%base, arr, i-1, base/10, result);
}
return result;
}
层序和中序重构二叉树
给定二叉树T(树深度不超过H<=10,深度从1开始,节点个数N<1024,节点编号1~N)的层序和中序遍历,输出T从左向右叶子节点以及树先序和后序遍历序列。
方法一 :
关于层级配合中序有这样的规律:
二叉树:
------------3
--------5 ------4
-----2--------6—7
----------------------1
层级序列:3,5,4,2,6,7,1
中序遍历:2,5,3,6,4,7,1
下面mid指中序序列,help指辅助数组,left[ i ] 指该位置的左子树,right[ i ] 指该位置的右子树。
初始:
中序:2,5,3,6,4,7,1
help:0,0,0,0,0,0,0
left :—0,0,0,0,0,0,0
right :-0,0,0,0,0,0,0
level[0] = 3
中序:2,5,3,6,4,7,1
help:0,0,3,0,0,0,0
left :—0,0,0,0,0,0,0
right :-0,0,0,0,0,0,0
3在help中左右皆无其它元素,说明其是root节点。
level[1] = 5
中序:2,5,3,6,4,7,1
help:0,5,3,0,0,0,0
left :—0,0,5,0,0,0,0
right :-0,0,0,0,0,0,0
help中插入5后,先向左寻找最近非空元素,左为空则向右寻找,找到3,则其为3的左子节点,若3的左子节点不为空,则说明序列无法组成树。
level[2] = 4
中序:2,5,3,6,4,7,1
help:0,5,3,0,4,0,0
left :—0,0,5,0,0,0,0
right :-0,0,4,0,0,0,0
help中插入4后,先向左寻找最近非空元素,找到3,3的右子节点为空,则其为3的右子节点。若存在左节点且其右子节点已存在,则向右寻找最近节点,节点就是该最近节点的左子节点,若该最近节点的左子节点非空,则说明序列无法组成树。
…
规律总结:沿着level数组进行遍历,找到其在 in 中序数组中的位置,插入到相应的help数组位置,之后先沿着help数组先向左找寻最近节点,1,若存在,则校验其右子节点是否存在,不存在则赋值,存在则向右寻找最近,赋给它当左子节点;2,若不存在,则向右寻找最近,存在则赋给它当左子节点,否则说明该节点为root节点。
public class LevelInTree {
private static class Node{
int val;
Node left, right;
Node(int val) {
this.val = val;
}
}
public static void levelIn(int[] level, int[] in) {
int[] help = new int[in.length];
int[] left = new int[in.length];
int[] right = new int[in.length];
Map<Integer,Integer> inMap = new HashMap<>(); // val -> index in inArray
Map<Integer, Node> inIToNode = new HashMap<>(); // index in inArray -> Node(val)
for (int i = 0; i < in.length; i++) {
inMap.put(in[i], i);
}
for (int le : level) {
int inI = inMap.get(le);
help[inI] = le;
Node cur = new Node(le);
inIToNode.put(inI, cur);
int leftClosest = search(help, inI, true);
if (leftClosest != -1) {
if (right[leftClosest] == 0) {
right[leftClosest] = le;
inIToNode.get(leftClosest).right = cur;
}
else {
int rightClosest = search(help, inI, false);
assert rightClosest != -1 && left[rightClosest] == 0;
left[rightClosest] = le;
inIToNode.get(rightClosest).left = cur;
}
}else {
int rightClosest = search(help, inI, false);
if (rightClosest != -1) {
assert left[rightClosest] == 0;
left[rightClosest] = le;
inIToNode.get(rightClosest).left = cur;
}
}
}
Node root = inIToNode.get(inMap.get(level[0]));
StringBuilder nul = new StringBuilder();
String pre = preOrderAndNul(root, nul);
String post = postOrder(root);
System.out.println(nul.toString().trim()); // 叶子节点
System.out.println(pre); // 前序
System.out.println(post); // 后续
}
private static int search(int[] help, int i, boolean left) {
if (left) {
--i;
for (; i >= 0; i--) {
if (help[i] != 0) return i;
}
}else {
++i;
for (; i < help.length; i++) {
if (help[i] != 0) return i;
}
}
return -1;
}
// 前序遍历过程中找出叶子节点
private static String preOrderAndNul(Node root, StringBuilder nulString) {
StringBuilder builder = new StringBuilder();
ArrayDeque<Node> stack = new ArrayDeque<>();
stack.push(root);
while (!stack.isEmpty()) {
Node node = stack.pop();
builder.append(node.val).append(" ");
if (node.right != null) stack.push(node.right);
if (node.left != null) stack.push(node.left);
if (node.left == null && node.right == null) nulString.append(node.val).append(" ");
}
return builder.toString().trim();
}
private static String postOrder(Node root) {
StringBuilder builder = new StringBuilder();
Deque<Node> stack1 = new ArrayDeque<>();
Deque<Node> stack2 = new ArrayDeque<>();
stack1.push(root);
while (!stack1.isEmpty()) {
Node node = stack1.pop();
stack2.push(node);
if (node.left != null) stack1.push(node.left);
if (node.right != null) stack1.push(node.right);
}
while (!stack2.isEmpty()) {
builder.append(stack2.pop().val).append(" ");
}
return builder.toString().trim();
}
方法二:
利用递归构建树。
中序遍历的根节点左边是左子树,右边是右子树,在层次遍历中根节点是第一个,然后把左子树的层次遍历和右子树的层级遍历提取出来进行递归。
// 利用递归构建二叉树
public static Node levelIn2(int[] level, int[] in) {
if (in.length == 0) return null; // 终止条件
Node cur = new Node(level[0]); // 创建根节点
int index = 0; // 根节点level[0]在中序中的位置
while (in[index] != level[0]) index++;
int[] leftIn = new int[index]; // cur的左子树的中序序列
int[] rightIn = new int[in.length-index-1]; // cur右子树的中序序列
// 填充数据
System.arraycopy(in, 0, leftIn, 0, leftIn.length);
for (int i = 0; i < rightIn.length; i++) {
rightIn[i] = in[index+i+1];
}
// 左子树中序序列对应的层级序列
int[] leftLevel = new int[leftIn.length];
// 右子树中序序列对应的层级序列
int[] rightLevel = new int[rightIn.length];
// 填充数据
int li = 0, ri = 0;
for (int i = 1; i < level.length; i++) {
if (contains(leftIn, level[i])) {
leftLevel[li++] = level[i];
}else {
rightLevel[ri++] = level[i];
}
}
cur.left = levelIn2(leftLevel, leftIn);
cur.right = levelIn2(rightLevel, rightIn);
return cur;
}
private static boolean contains(int[] arr, int key) {
for (int ele : arr) {
if (ele == key) return true;
}
return false;
}
上面我本想用 Arrays.asList 来简化代码,可遇到了一个坑:关于Arrays.asList的坑