记录些有意思的算法题

将自己刷题中遇到的有意思的,有巧妙解法的题记录在这,持续更新…

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的坑

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值