16进制转8进制的位运算 Java算法

又是一道算法题,这道题目其实很简单,看下题目:
这里写图片描述
题目要求是:
输入十六进制数的字符串形式, 然后转换成8进制数输出.

要求很简单, 因为Java内置的api就可以搞定, 下面是我最开始的思路和代码:

  • 最开始的思路:
    • 用字符串数组接收输入的16进制字符串
    • 然后用大数BigInteger(为什么用BigInteger呢?因为测试数据一共有10个,最后一个16进制数有10万位,你没看错,就是10万个数字组成的16进制数)的方法将16进制的字符串转成16进制,然后再接一个转成8进制的方法,最后按照格式输出即可, 代码如下.
import java.math.BigInteger;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        String[] arr = new String[n];
        //存储输入的十六进制数
        for (int i = 0; i < arr.length; i++) {
            arr[i] = sc.next();
        }
        //将十六进制字符串转换成8进制输出
        long start = System.currentTimeMillis();
        for (int i = 0; i < arr.length; i++) {
            System.out.println(new BigInteger(arr[i], 16).toString(8));
        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }
}

代码只需要20行左右, 思路也很简单, 是不是感觉很舒服? 这也符合程序员都喜欢偷懒的习惯, 原本我以为提交就行了, 结果测试要求测试完10个数据的时间不能超过 1秒! 告诉我运行超时, 我返回去测试了一下, 光是转换最后一个10万位的16进制数就要 3秒多(就算配置好一点的电脑也在秒级), 于是不得不想别的算法重做.

思考了很多种方案, 发现还是位运算的速度是最快的, 然后又在网上找了找资料, 借用了一个网友的思路, 如下:

  • 首先, 算法使用位运算, 计算机使用位运算都是采用2进制, 那么就考虑将16进制转换成2进制, 然后再由2进制转换成8进制输出.
  • 又由于一个16进制数对应 4位 2进制数, 一个8进制数对应 3位 2进制数, 它们的最小公倍数是 12位 2进制数, 于是在转换的时候就 每3个 16进制数 转换一次, 每次可以得到 4个 8进制数.
  • 考虑到补0的情况(不清楚这块儿的可以查一下计算机移位运算的规则), 如果人工补0会让代码变得复杂, 所以采用 右移运算 ,每次取最后面的 3个16进制数, 这样计算机会自动在高位补0(而且因为是取最低的3位,所以不用考虑高位补0后的情况),将会大大简化我们的操作.

    可能上述思路不是太清晰,结合图示应该比较好理解:
    这里写图片描述

有了思路,那么就可以开始用代码实现了, 下面是代码实现的步骤分析:

  1. 首先是要存储输入的16进制数, 采用字符串数组将其保存起来.
  2. 每次截取3个16进制数, 将其运算后转换成4个8进制数.
  3. 将得到的8进制数添加到一个集合中.
  4. 因为是逆序添加, 所以最后将集合中的8进制数逆序输出即可.

技术和问题分析:

  1. 首先是截取字符串的问题, 可以采用api中的截取方法, 但是这里要注意两个问题.
    第一:
    最开始我是采用每次从原来的字符串截取了3个数字后,就将原来的字符串赋值成截取后的新字符串, 因为这样做我们只需要每次截取字符串尾巴的后三位数字即可, 但是事实证明偷懒的代价就是效率不达标, 没办法还是超时, 因为对字符串重新赋值还有截取的操作可是很耗费时间的; 所以我的办法是自己写算法判断每次需要截取的部分, 保持原来的字符串不变.

    第二:
    我们每次截取的是字符串的后三位, 如果字符串的位数刚好能被3整除那还好,如果字符串不能被3整除呢, 这里就需要写两个算法, 分别对两种情况进行不同判断, 虽然麻烦了一点, 但是为了效率没办法.

  2. 获取到3个字符串后, 首先将3个字符串转换成10进制(因为输入的是16进制数, 而且计算机运算10进制数时采用的就是位运算), 这里转换成8进制数的方法是采用 和 7进行 &(与)运算 , 进行与运算会自动转成2进制数, 然后右移 3位, 如此循环 4次, 将 12位二进制数 转换完成即可.
    这里写图片描述
    这里写图片描述

  3. 将所有16进制数转换完成后, 得到的8进制数就保存到了集合中, 不过要注意的是: 集合中现在保存的是逆序的结果, 我们在输出的时候要逆序输出.
    问题:
    同样,这里也有一个问题, 转换的8进制数里面可能会有0, 而且这个0很可能会在高位(也就是8进制数的最左边)输出, 但是测试中明确要求输出的8进制数首位不能是0! 所以我们在输出前需要判断首位是否是0, 如果是0就跳过判断下一个数, 直到判断的下一个数不为0的时候才开始输出(中间的0不需要管).

通过这个思路写好代码后测试发现转换10万位的16进制数需要 300多毫秒, 比起第一次的 3秒多, 速度提高了13倍, 终于满足了题目要求. (这个13倍仅仅是和我自己之前的算法比较, 有个大佬的算法(Java)算完10个数据只需要几十毫秒……)
这次的经验让我明白虽然有时候用自带的api写起来很轻松, 代码又少, 但是效率可能不会太高, 有时候还是需要自己动手想想算法.

以上就是全部的思路以及程序的步骤分析, 最后来看一下代码实现吧(Java):

import java.util.ArrayList;
import java.util.Scanner;
/*
 * 16进制转8进制思路:
 *      一个16进制数是4位二进制,一个8进制数是3为二进制,取最小公倍数12,即一次取3个16进制数进行转换(省去了人工补位的操作)
 *      1.将3个16进制数转换成10进制数,然后将得到的十进制数与数字7(111)进行&与运算,则可以得到这串二进制数的低三位
 *      2.将得到的低三位加上前缀'0'转成8进制数,再追加到新的字符串中
 *      3.将原来的12个二进制数右移三位,重复1,2步的操作一共四次,即可完成一次的转换
 */

public class Test_2 {
    // 定义变量num保存十进制数(二进制),str保存获取到的三位16进制数,ArrayList保存运算后的8进制数,
    // temp控制循环,字符数组保存最后结果
    static int num = 0;
    static int temp = 0;
    static String str = "";
    static ArrayList<Integer> array = new ArrayList<Integer>();

    public static void main(String[] args) {
        // 接收输入
        Scanner sc = new Scanner(System.in);
        // 记录输入的数字个数
        int n = sc.nextInt();
        // 字符串数组保存输入的十六进制数
        String[] arr = new String[n];
        // 存入十六进制数
        for (int i = 0; i < arr.length; i++) {
            arr[i] = sc.next();
        }
        // 计算时间
        long start = System.currentTimeMillis();
        // for循环遍历数组中的每个数
        for (int i = 0; i < arr.length; i++) {
            cutStr(arr[i], array);
            outputArray(array);
        }
        long end = System.currentTimeMillis();
        System.out.println(end-start);
    }

    //截取字符串存入str交给num处理
    public static void cutStr(String arr, ArrayList<Integer> array){
        //判断字符串是否小等于3
        if(arr.length() <= 3){
            str = arr;
            change(str, array);
        }else{
            temp = arr.length() / 3;
            //整除的情况
            if(arr.length() % 3 == 0){
                for (int i = temp; i >= 1; i--) {
                    if(i == temp){
                        str = arr.substring(arr.length()-3);
                        change(str, array);
                    }else if(i == 1){
                        str = arr.substring(0, 3);
                        change(str, array);
                    }else{
                        str = arr.substring((3*i-3), 3*i);
                        change(str, array);
                    }
                }
            }else{
                //非整除情况
                for (int i = 0; i <= temp; i++) {
                    if(i == 0){
                        str = arr.substring(arr.length()-3);
                        change(str, array);
                    }else if(i == temp){
                        str = arr.substring(0, arr.length() - (temp*3));
                        change(str, array);
                    }else{
                        str = arr.substring(arr.length()-(3*i)-3, arr.length()-(3*i));
                        change(str, array);
                    }
                }
            }
        }
    }

    // 将str解析成8进制存入集合
    public static void change(String str, ArrayList<Integer> array) {
        // 转成十进制数
        num = Integer.parseInt(str, 16);
        // 与运算后转成8进制保存进集合, 循环四次
        for (int j = 0; j < 4; j++) {
            array.add((num & 7));
            // 将num右移运算
            num = num >>> 3;
        }
    }

    // 将集合逆序输出并且清空
    public static void outputArray(ArrayList<Integer> array) {
        for (int j = array.size() - 1; j >= 0; j--) {
            // 忽略前置的0
            if (array.get(j) == 0) {
                continue;
            } else {
                for (int x = j; x >= 0; x--) {
                    System.out.print(array.get(x));
                }
                break;
            }
        }
        array.clear();
        System.out.println();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值