java撞南墙:数据结构与算法

本章是java路径课程基础,数据结构与算法系列课程。

给定一个乱序数组,如何使其变得有序?

public class Main {
    public static void main(String[] args) {
        var nums = new int[] { 5, 1, 3, 2, 4, 6 };
        for (var i = 0; i < nums.length; i++)
            for (var j = 0; j < nums.length - i - 1; j++)
                if (nums[j] > nums[j + 1])
                    nums[j] = nums[j + 1] ^ nums[j] ^ (nums[j + 1] = nums[j]);
        System.out.println(java.util.Arrays.toString(nums)); // [1, 2, 3, 4, 5, 6]
    }
}

通过不断交换相邻元素的方式,就可以将无序的线性表有序化,是不是很神奇。数据结构与算法是程序的灵魂,本课程次从这里出发,之后则是设计模式提升,最后就是企业化开发可以包括桌面应用开发和网页服务开发。

课程的目的是要求大家掌握数据结构的特性和常用的算法,并不涉及工程化的实践,工程化的实践将在第二章设计模式中体现,所以这门课并不会使用工程化的代码。

不知道大家发现没有,排序设计到的知识点很多,很多书或者课程喜欢把排序放到第一章来讲,其实有点过早。这个课程会把算法分散在数据结构的过程中去将,比如插入选择排序会在串中讲(线性表),堆排会在树中说明,而快排和归并会在分治算法中讲。

数据结构:串

串就是线性表,最常见的结构就是字符串。串数据结构很简单,总体上可以分为数组结构和链表结构。

数组长度是固定的,如果不知道长度使用起来十分不方便,如何实现一个动态数组呢?

class Array {
    int[] data;
    int capacity;
    int len;

    public Array(int capacity) {
        this.capacity = capacity;
        this.data = new int[capacity];
    }

    public int get(int idx) {
        if (java.util.Objects.checkIndex(idx, len) >= 0)
            return data[idx];
        throw new java.lang.IndexOutOfBoundsException();
    }

    public void set(int idx, int val) {
        if (len <= capacity && java.util.Objects.checkIndex(idx, len) >= 0)
            data[idx] = val;
        if (idx >= 0 && idx < len)
            throw new java.lang.IndexOutOfBoundsException();
    }

    public void add(int val) {
        if (len < capacity) {
            data[len] = val;
            len += 1;
            return;
        }
        capacity += capacity >> 1;
        data = java.util.Arrays.copyOf(data, capacity);
        add(val);
    }
}

数组

数组串的处理方式多种多样。首先最简单也是必会的就是最值。

public class Main {
    public static void main(String[] args) {
        var nums = new int[] { 5, 1, 3, 2, 4, 6 };
        // code herr
        var minidx = 0;
        for (var j = 0; j < nums.length; j++)
            if (nums[j] < nums[minidx])
                minidx = j;
        System.out.println(nums[minidx]); // 1 
    }
}

这段处理其实比较简单,遍历一遍数组,如果遍历元素比最小元素还小,此时将最小下标更新。

这里要注意的就是我们使用的是串的下标,而不是一个广义上的极大值Integer.MAX_VALUE,没有经验的同学可能会使用这种方式来定义,实际上使用下标是更明智的选择。

紧接着我们可以加几行代码实现选择排序。

public class Main {
    public static void main(String[] args) {
        var nums = new int[] { 5, 1, 3, 2, 4, 6 };
        // code here
        for (int i = 0, minidx = 0; i < nums.length; i += 1, minidx = i) {
            for (var j = i; j < nums.length; j += 1)
                if (nums[j] < nums[minidx])
                    minidx = j;
            nums[i] = nums[minidx] ^ nums[i] ^ (nums[minidx] = nums[i]);
        }
        System.out.println(java.util.Arrays.toString(nums)); // [1, 2, 3, 4, 5, 6]
    }
}

这个算法即使从i到len(不做特殊说明,之后统一定义都是左闭又开)找到最小值和i号元素交换使其前i号有序。

讲完了处理ii到len的方式,来试一下前序处理。典型的例子就是插入排序。

public class Main {
    public static void main(String... args) {
        var nums = new int[] { 5, 1, 3, 2, 4, 6 };
        // code here
        for (int i = 0; i < nums.length; i += 1) {
            int j = i - 1, t = nums[j + 1];
            for (; j >= 0 && t < nums[j]; j -= 1)
                nums[j + 1] = nums[j];
            nums[j + 1] = t;
        }
        System.out.println(java.util.Arrays.toString(nums));
    }
}

经常会有同学写插入排序的时候控制不好边界。处理的时候有时会写错,最后这里的nums[j + 1]就经常会被写成nums[j]。这种问题首先是你的习惯不好,在你上面定义的时候可能已经定义成了nums[i],这样就导致一个问题前后不对应。所以这里的一个处理的方式就是先定义j,之后所有的处理都和i没关系了。这样顺眼多了,也不容易写错。

插入的本质是后移,那么极端的情况下,[2,3,4,5,1]最后一个没拍好,后移了整个数组,导致时间复杂度指向极端。实际上可以做如下优化。

public class Main {
    public static void main(String... args) {
        var nums = new int[] { 5, 1, 3, 2, 4, 6 };
        // code herr
        for (var step = nums.length / 2; step > 0; step /= 2)
            for (var offset = 0; offset < step; offset += 1)
                sort(nums, offset, step);
        System.out.println(java.util.Arrays.toString(nums));
    }

    public static void sort(int[] nums, int offset, int step) {
        for (int i = offset; i < nums.length; i += step) {
            int j = i - step, t = nums[j + step];
            for (; j >= offset && t < nums[j]; j -= step)
                nums[j + step] = nums[j];
            nums[j + step] = t;
        }
    }
}

把插入排序部分查出来,全部的0替换成offset,全部的1替换成step,这样就可以提前将大块的排序元素抽走。防止每次后移过多导致时间复杂度过高。

有同学会说,这使用了四层循环,怎么可能会更快呢?为了证明有效性,我们写了如下的验证代码。

public class Main {
    public static void main(String... args) {
        var nums = new int[] { 5, 1, 3, 2, 4, 6 };
        // code herr
        for (var step = nums.length / 2; step > 0; step /= 2)
            for (var offset = 0; offset < step; offset += 1)
                sort(nums, offset, step);
        nums = new int[] { 5, 1, 3, 2, 4, 6 };
        sort(nums, 0, 1);
    }

    public static void sort(int[] nums, int offset, int step) {
        var cnt = 0;
        for (int i = offset; i < nums.length; i += step) {
            int j = i - step, t = nums[j + step];
            for (; j >= offset && t < nums[j]; j -= step) {
                nums[j + step] = nums[j];
                cnt++;
            }
            nums[j + step] = t;
        }
        System.out.println(cnt);
    }
}

这里会将后移的次数记录下来,比较下使用前后的后移次数。前者后移了1+0+0+2共3次,而普通的插入排序后移了5次。所以减少后移次数,算法会更快。

除了数组正序处理,逆序处理,还有一种创建的处理方法块处理,步长是内层循环决定的。此时有两个数组的两个部分{ 1, 3, 5 }{ 2, 4, 6 }有序,如何将整个数组使其有序呢,不使用辅助空间。

public class Main {
    public static void main(String... args) {
        var nums = new int[] { 1, 3, 5, 2, 4, 6 };
        // code here
        merge(nums, 0, nums.length - 1);
        System.out.println(java.util.Arrays.toString(nums)); // [1, 2, 3, 4, 5, 6]
    }

    public static void merge(int[] nums, int left, int right) {
        var mid = (left + right) / 2;
        int i = left, j = mid + 1;
        for (; i < j && j <= right;) {
            for (; i < j && j <= right && nums[i] < nums[j]; i++)
                continue;
            var idx = j;
            for (; i < j && j <= right && nums[j] < nums[i]; j++)
                continue;
            for (int p = i, q = idx - 1; p < q; p += 1, q -= 1)
                nums[p] = nums[q] ^ nums[p] ^ (nums[q] = nums[p]);
            for (int p = idx, q = j - 1; p < q; p += 1, q -= 1)
                nums[p] = nums[q] ^ nums[p] ^ (nums[q] = nums[p]);
            for (int p = i, q = j - 1; p < q; p += 1, q -= 1)
                nums[p] = nums[q] ^ nums[p] ^ (nums[q] = nums[p]);
            i += j - 1 - idx + 1;
        }
    }
}

链表

从范式上讲,链表存在两种表达。第一种表达方式是duumy表达(realnull)强调返回值,第二种是replace表达(fakenull)。

以链表插入为例子,duumy派主张找到单链表前驱节点并返回的做法。

public class Main {
    public static void main(String... args) {
        // var node = (java.util.Map) new java.util.HashMap
        // Object>();
        // var val = (Integer) node.get("val");
        // var next = (java.util.Map) node.get("next");
        // code here
        var head = insert(null, Integer.MIN_VALUE, 0);
        head = insert(head, Integer.MAX_VALUE, 2);
        head = insert(head, 1, 1);
        System.out.println(head); // {val=0, next={val=1, next={val=2, next=null}}}
    }

    public static java.util.Map<String, Object> insert(java.util.Map<String, Object> head, int idx, int val) {
        var dummy = (java.util.Map<String, Object>) new java.util.HashMap<String, Object>();
        dummy.put("next", head);
        var node = dummy;
        for (int i = 0; i < idx && java.util.Objects.nonNull(node.get("next")); i += 1)
            node = (java.util.Map<String, Object>) node.get("next");
        var n = (java.util.Map<String, Object>) new java.util.HashMap<String, Object>();
        n.put("val", val);
        n.put("next", node.get("next"));
        node.put("next", n);
        return (java.util.Map<String, Object>) dummy.get("next");
    }
}

replace派更关注当下。

public class Main {
    public static void main(String... args) {
        // var node = (java.util.Map) new java.util.HashMap
        // Object>();
        // var val = (Integer) node.get("val");
        // var next = (java.util.Map) node.get("next");
        // code here
        java.util.Map<String, Object> head = new java.util.HashMap<>();
        head.put("next", new java.util.HashMap<>());
        insert(head, Integer.MIN_VALUE, 0);
        insert(head, Integer.MAX_VALUE, 2);
        insert(head, 1, 1);
        System.out.println(head); // {val=0, next={val=1, next={val=2, next={next={}}}}}
    }

    public static void insert(java.util.Map<String, Object> head, int idx, int val) {
        var node = (java.util.HashMap<String, Object>) head;
        for (int i = 0; i < idx && node.size() > 1; i += 1)
            node = (java.util.HashMap<String, Object>) node.get("next");
        var next = node.clone();
        var n = (java.util.Map<String, Object>) new java.util.HashMap<String, Object>();
        n.put("val", val);
        n.put("next", next);
        node.clear();
        node.putAll(n);
    }
}

这种取巧的思路来自于无法前驱节点时的删除思路,写法比较优雅,但是判空会很难受,但是如果是python就会爽很多。

目前主流算法都是dummy实现,replace限制比较多,而且必须提供clone或者copy方法才能使用。给出dummy派的插入删除和遍历。插入和删除时,idx仅仅做辅助定位,链表遍历其实不应该关注idx,而是关注node和node的next的关系。

class ListNode {
    int val;
    ListNode next = null;

    static ListNode insertTail(ListNode head, int val) {
        return insert(head, Integer.MAX_VALUE, val);
    }

    static ListNode insertHead(ListNode head, int val) {
        return insert(head, Integer.MIN_VALUE, val);
    }

    static ListNode insert(ListNode head, int idx, int val) {
        ListNode dummy = new ListNode(0, head), node = dummy;
        for (; idx > 0 && node.next != null; node = node.next, idx -= 1)
            continue;
        var newnode = new ListNode(val, node.next);
        node.next = newnode;
        return dummy.next;
    }

    static ListNode removeTail(ListNode head) {
        return remove(head, Integer.MAX_VALUE);
    }

    static ListNode removeHead(ListNode head) {
        return remove(head, Integer.MIN_VALUE);
    }

    static ListNode remove(ListNode head, int idx) {
        ListNode dummy = new ListNode(0, head), node = dummy;
        for (; idx > 0 && node.next != null && node.next.next != null; node = node.next, idx -= 1)
            continue;
        if (node.next != null)
            node.next = node.next.next;
        return dummy.next;
    }

    static void traverse(ListNode head) {
        for (var node = head; node != null; node = node.next)
            System.out.print(node.val + "->");
        System.out.println("null");
    }

    ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

改了下写法过了leetcode,应该没啥问题。dummy写法过leetcode

链表遍历判断链表是否存在环。

public class Main {
    public static void main(String... args) {
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        // code here
        var cycle = false;
        for (ListNode i = head, j = head; j != null && j.next != null;) {
            i = i.next;
            j = j.next.next;
            if (i == j) {
                cycle = true;
                break;
            }
        }
        System.out.println(cycle);
    }
}

这是一个非常简单的代码但是要注意这里的循环只是再遍历,在发现无法遍历到结尾时退出。而一些解法在遍历时检查,就要注意初始化要初始化jhead.next

链表反转。

public class Main {
    public static void main(String... args) {
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        // code here
        ListNode prev = null, node = head, post = head.next;
        for (; node != null; prev = node, node = post) {
            post = node.next;
            node.next = prev;
        }
        ListNode.traverse(prev);
    }
}

链表合并。

public class Main {
    public static void main(String... args) {
        ListNode head1 = new ListNode(1);
        head1.next = new ListNode(3);
        head1.next.next = new ListNode(4);
        ListNode.traverse(head1); // 1->3->4->null

        ListNode head2 = new ListNode(2);
        head2.next = new ListNode(6);
        head2.next.next = new ListNode(7);
        ListNode.traverse(head2); // 2->6->7->null

        ListNode dummy = new ListNode(0, null), n = dummy;
        ListNode n1 = head1, n2 = head2;
        for (; n1 != null && n2 != null; n = n.next) {
            if (n1.val > n2.val) {
                n.next = n2;
                n2 = n2.next;
            } else {
                n.next = n1;
                n1 = n1.next;
            }
        }
        for (; n1 != null; n1 = n1.next, n = n.next)
            n.next = n1;
        for (; n2 != null; n2 = n2.next, n = n.next)
            n.next = n2;
        ListNode.traverse(dummy.next); // 1->2->3->4->6->7->null
    }
}

线性结构串到这里接近尾声了,相信大家已经掌握了串的基本操作。串是一切问题的基础,一切问题也围绕这串去服务,所以对于基础的串操作要掌握,接下来是栈。

数据结构:栈

package algo;

import java.util.Arrays;
import java.util.Stack;

public class Temperature {
    public static void main(String[] args) {
        Temperature.temperature(new int[] { 63, 54, 76, 56, 37, 89, 23, 74 });
    }

    public static void temperature(int[] tempera) {
        var destack = new Stack<Integer>();
        var distance = new int[tempera.length];
        for (var i = 0; i < tempera.length; i++) { // 一轮i从0到len对每个不破坏栈内单调性的元素
            while (!destack.isEmpty() && tempera[destack.peek()] < tempera[i]) { // 栈内单调性被破坏
                int pop = destack.pop();
                distance[pop] = i - pop;
            }
            destack.push(i);
        }
        System.out.println(Arrays.toString(distance));
    }
}

数据结构:队列

数据结构:二叉树

package algo;

import java.util.Deque;
import java.util.LinkedList;

import struct.TreeNode;

public class TreeOrder {
    public static void main(String[] args) {
        TreeOrder.layer(TreeNode.random());
    }

    public static void layer(TreeNode root) {
        var queue = (Deque<TreeNode>) new LinkedList<TreeNode>();
        queue.add(withdepth(root, 0)); // 队列
        var depth = 0;
        while (!queue.isEmpty()) { // 直到队列为空队列中的元素
            var r = queue.poll();
            if (r != null) {
                if (depth != (int) r.ext.get("depth")) {
                    depth = (int) r.ext.get("depth");
                    System.out.println();
                }
                System.out.print(r.val);
                queue.add(withdepth(r.left, (int) r.ext.get("depth") + 1));
                queue.add(withdepth(r.right, (int) r.ext.get("depth") + 1));
            }
        }
        System.out.println();
    }

    public static TreeNode withdepth(TreeNode root, int depth) {
        if (root != null) {
            root.ext.put("depth", depth);
        }
        return root;
    }
}

数据结构:图

算法:递归

二分递归

还记的在数组块处理中的不借助辅助空间排序的例子么?归并这里就是用到这个函数,一起来看下。归并按照中间划分,划分到不能再划分就开始合并。合并函数我们已经实现,那现在归并就很简单了。

public class Main {
    public static void main(String[] args) {
        var nums = new int[] { 5, 1, 3, 2, 4, 6 };
        // code here
        sort(nums, 0, nums.length - 1);
        System.out.println(java.util.Arrays.toString(nums)); // [1, 2, 3, 4, 5, 6]
    }

    public static void sort(int[] nums, int left, int right) {
        if (left >= right)
            return;
        sort(nums, left, (left + right) / 2);
        sort(nums, (left + right) / 2 + 1, right);
        merge(nums, left, right);
    }

    public static void merge(int[] nums, int left, int right) { // 数组中已经实现
        var mid = (left + right) / 2;
        int i = left, j = mid + 1;
        for (; i < j && j <= right;) {
            for (; i < j && j <= right && nums[i] < nums[j]; i++)
                continue;
            var idx = j;
            for (; i < j && j <= right && nums[j] < nums[i]; j++)
                continue;
            for (int p = i, q = idx - 1; p < q; p += 1, q -= 1)
                nums[p] = nums[q] ^ nums[p] ^ (nums[q] = nums[p]);
            for (int p = idx, q = j - 1; p < q; p += 1, q -= 1)
                nums[p] = nums[q] ^ nums[p] ^ (nums[q] = nums[p]);
            for (int p = i, q = j - 1; p < q; p += 1, q -= 1)
                nums[p] = nums[q] ^ nums[p] ^ (nums[q] = nums[p]);
            i += j - 1 - idx + 1;
        }
    }
}

快速排序,主要还是串的处理手法,将串分解成两个部分,使其左边小于某个数,右边大于这个数。

public class Main {
    public static void main(String[] args) {
        var nums = new int[] { 5, 1, 3, 2, 4, 6 };
        // code here
        sort(nums, 0, nums.length - 1);
        System.out.println(java.util.Arrays.toString(nums)); // [1, 2, 3, 4, 5, 6]
    }

    public static void sort(int[] nums, int left, int right) {
        if (left >= right)
            return;
        int i = left, j = right;
        var base = nums[i];
        for (; i < j;) {
            for (; i < j && nums[j] >= base; j -= 1)
                continue;
            nums[i] = nums[j];
            for (; i < j && nums[i] <= base; i += 1)
                continue;
            nums[j] = nums[i];
        }
        nums[i] = base;
        sort(nums, left, i);
        sort(nums, i + 1, right);
    }
}

写起来也是十分清爽干净,函数参数是数组左右边界,相似的常见处理手法还有偏移加限制,像是之后的堆排序就用到这种处理手法。接下来看下堆排。

深度优先

阶乘。

public class Main {
    public static void main(String[] args) {
        Factorial.get(4);
    }
}

class Factorial {
    static void get(int data) {
        System.out.println(calc(data));
    }

    static int calc(int data) {
        if (data == 0)
            return 1;
        return calc(data - 1) * data;
    }
}

全排列。

public class Main {
    public static void main(String[] args) {
        Permutation.generate(new int[] { 1, 2, 3 });
    }
}

class Permutation {
    static void generate(int[] nums) {
        var gens = new java.util.ArrayList<int[]>();
        dfs(nums, new boolean[nums.length], 0, new int[nums.length], gens);
        gens.stream().forEach(e -> System.out.println(java.util.Arrays.toString(e)));
    }

    static void dfs(int[] nums, boolean[] table, int idx, int[] gen, java.util.List<int[]> gens) {
        if (idx == nums.length) {
            gens.add(java.util.Arrays.copyOf(gen, nums.length));
            return;
        }

        for (var i = 0; i < nums.length; i++) {
            if (table[i])
                continue;
            table[i] = true;
            gen[idx] = nums[i];
            dfs(nums, table, idx + 1, gen, gens);
            table[i] = false;
        }
    }
}

算法:排序

选择排序,堆排为选择的优化也会在这里介绍。

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        Select.sort(new int[] { 5, 1, 3, 4, 2, 6 });
        Select.heap(new int[] { 5, 1, 3, 4, 2, 6 });
    }
}

class Select {
    public static void sort(int[] nums) {
        for (var i = 0; i < nums.length; i++) {
            var min = i;
            for (var j = i; j < nums.length; j++) {
                if (nums[min] > nums[j]) {
                    min = j;
                }
            }
            nums[i] = nums[min] ^ nums[i] ^ (nums[min] = nums[i]);
        }
        System.out.println(Arrays.toString(nums));
    }

    public static void heap(int[] nums) {
        for (var root = nums.length / 2 - 1; root >= 0; root--)
            heapfy(nums, root, nums.length);
        for (var root = nums.length - 1; root >= 0; root--) {
            nums[0] = nums[root] ^ nums[0] ^ (nums[root] = nums[0]);
            heapfy(nums, 0, root);
        }
        System.out.println(Arrays.toString(nums));
    }

    public static void heapfy(int[] nums, int root, int len) {
        var child = 2 * root + 1;
        if (child < len) {
            if (child + 1 < len && nums[child + 1] > nums[child])
                child += 1;
            if (nums[child] > nums[root]) {
                nums[child] = nums[root] ^ nums[child] ^ (nums[root] = nums[child]);
                heapfy(nums, child, len);
            }
        }
    }
}

算法:位运算

算法:常见问题处理方式

日期处理

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        DateDiff.format(new int[] { 20230504, 20000101 });
    }
}

class DateDiff {
    static int[][] m = new int[2][];
    static {
        m[0] = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        m[1] = Arrays.copyOf(m[0], m[0].length);
        m[1][1] += 1;
    }

    static int[] y = new int[2];
    static {
        y[0] = 365;
        y[1] = 366;
    }

    public static void format(int[] date) {
        assert date.length == 2;

        // static export

        int d0 = date[0] % 100, m0 = date[0] / 100 % 100, y0 = date[0] / 10000 % 10000;
        int d1 = date[1] % 100, m1 = date[1] / 100 % 100, y1 = date[1] / 10000 % 10000;

        int sum0 = d0;
        for (var yy = 1; yy < y0; yy += 1)
            sum0 += y[isleap(yy)];
        for (var mm = 1; mm < m0; mm += 1)
            sum0 += m[isleap(y0)][mm - 1];

        int sum1 = d1;
        for (var yy = 1; yy < y1; yy += 1)
            sum1 += y[isleap(yy)];
        for (var mm = 1; mm < m1; mm += 1)
            sum1 += m[isleap(y1)][mm - 1];

        System.out.println(Math.abs(sum0 - sum1));
    }

    public static int isleap(int yy) {
        return ((yy % 4 == 0) && (yy % 100 != 0)) || yy % 400 == 0 ? 1 : 0;
    }
}

进制转化

public class Main {
    public static void main(String[] args) {
        HexConvert.fromN(123, 8);
        HexConvert.toN(83, 8);
    }
}

class HexConvert {
    public static void fromN(int data, int nn) { // nn转10
        assert 0 < nn && nn <= 10;

        int ndata = 0;
        for (var i = 0; data != 0; data /= 10, i++) {
            ndata += (data % 10) * (int) Math.pow(nn, i);
        }
        System.out.println(ndata);
    }

    public static void toN(int data, int nn) { // 10转nn
        assert 0 < nn && nn <= 10;

        int ndata = 0;
        for (var i = 0; data != 0; data /= nn, i++) {
            ndata += (data % nn) * (int) Math.pow(10, i);
        }
        System.out.println(ndata);
    }
}

用到pow还是不够优雅,优化为这个版本,使用了java中的lambda。

不知道大家注意到没有lambda实际上不能写递归的,算是一个缺点吧。

public class Main {
    public static void main(String[] args) {
        Radix.to10("12a3", 16); // 4771
        Radix.toN("4771", 16); // 12a3
    }
}

class Radix {
    static void to10(String data, int nn) {
        java.util.function.Function<Character, Integer> atoi = (c) -> {
            if ((int) (c - 'a') >= 0) {
                return (int) (c - 'a') + 10;
            }
            if ((int) (c - 'A') >= 0) {
                return (int) (c - 'A') + 10;
            }
            if ((int) (c - '0') >= 0) {
                return (int) (c - '0');
            }
            return 0;
        };

        char[] datachars = data.toCharArray();
        var ndata = 0;
        for (var i = 0; i < datachars.length; i++)
            ndata = ndata * nn + atoi.apply(datachars[i]);
        System.out.println(String.format("%s", ndata));
    }

    static void toN(String data, int nn) {
        java.util.function.Function<Integer, Character> itoa = (i) -> {
            if (i >= 10) {
                return (char) ('a' + i - 10);
            }
            return (char) ('0' + i);
        };

        var sb = new StringBuilder();
        for (int idata = Integer.valueOf(data); idata != 0; idata /= nn)
            sb.insert(0, itoa.apply(idata % nn));
        System.out.println(sb.toString());
    }
}

你可能感兴趣的:(细节吃透,java,链表,数据结构)