Android 数据结构面试题

1.1 什么是冒泡排序?如何优化?

冒泡排序算法原理:(从小到大排序)

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,交换一趟后,最后的元素会是最大的数
  3. 针对所有的元素重复以上的步骤,除了最后一个
  4. 持续每次,对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

优化方案1(定义一个变量 l 来保存一趟交换中两两交换的次数,如果 l == 0,则说明排序已经完成,退出for循环)

优化方案2(假如有一个长度为50的数组,在一趟交换后,最后发生交换的位置是10,那么这个位置之后的40个数必定已经有序了,记录下这位置,下一趟交换只要从数组头部到这个位置就可以了)

定义一个变量 n 来保存一趟交换中最后一次发生交换的位置,并把它传递给下一趟交换

1.2请用 Java 实现一个简单的单链表?

定义链表:

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

基本操作

    public class SingleLinkedList {
        /**
         * 链表的头结点
         */
        ListNode head = null;

        /**
         * 链表添加结点:
         * 找到链表的末尾结点,把新添加的数据作为末尾结点的后续结点
         * @param data
         */
        public void addNode(int data) {
            ListNode newNode = new ListNode(data);
            if (head == null) {
                head = newNode;
                return;
            }
            ListNode temp = head;
            while (temp.next != null) {
                temp = temp.next;
            }
            temp.next = newNode;
        }

        /**
         * 链表删除节点:
         * 把要删除结点的前节点指向要删除节点的后节点,即直接跳过待删除节点
         * @param index
         * @return
         */
        public boolean deleteNode(int index) {
            if (index < 1 || index > length()) {//待删除节点不存在
                return false;
            }
            if (index == 1) {//删除头节点
                head = head.next;
                return true;
            }
            ListNode preNode = head;
            ListNode curNode = preNode.next;
            int i = 1;
            while (curNode != null) {
                if (i == index) {//寻找到待删除节点
                    //待删除节点的前节点指向待删除节点的后节点
                    preNode.next = curNode.next;
                    return true;
                }
                //当前节点和前节点同时向后移
                preNode = preNode.next;
                curNode = curNode.next;
                i++;
            }
            return true;
        }

        /**
         * 求链表的长度
         * @return
         */
        public int length() {
            int length = 0;
            ListNode curNode = head;
            while (curNode != null) {
                length++;
                curNode = curNode.next;
            }
            return length;
        }

        /**
         * 打印结点
         */
        public void printLink() {
            ListNode curNode = head;
            while (curNode != null) {
                System.out.print(curNode.val + "");
                curNode = curNode.next;
            }
            System.out.println();
        }

        /**
         * 查找正数第k个元素
         */
        public ListNode findNode(int k) {
            if (k < 1 || k > length()) {//不合法的k
                return null;
            }
            ListNode temp = head;
            for (int i = 0; i < k - 1; i++) {
                temp = temp.next;
            }
            return temp;
        }

        public static void main(String[] args) {
            SingleLinkedList myLinkedList = new SingleLinkedList();
            //添加链表结点
            myLinkedList.addNode(9);
            myLinkedList.addNode(8);
            myLinkedList.addNode(6);
            myLinkedList.addNode(3);
            myLinkedList.addNode(5);
            //打印链表
            myLinkedList.printLink();
            System.out.println("链表节点个数为:" + myLinkedList.length());
            myLinkedList.deleteNode(3);
            myLinkedList.printLink();
        }
    }

1.3 如何反转一个单链表?

思路:定义3个节点分别是preNode, curNode, nextNode.
先将curNode的next赋值给nextNode, 再curNodel的next指向preNode.至此curNode的指针调整完毕。 然后往后移动curNode, 将curNode赋值给preNode,将nextNode赋值给curNode,如此循环到curNode==null为止

代码:

    public static Node reverseListNode(Node head) {
        //单链表为空或只有一个节点,直接返回原单链表
        if (head == null || head.getNext() == null) {
            return head;
        }
        //前一个节点指针
        Node preNode = null;
        //当前节点指针
        Node curNode = head;
        //下一个节点指针
        Node nextNode = null;
        ...
        while (curNode != null) {
            nextNode = curNode.getNext();//nextNode 指向下一个节点
            curNode.setNext(preNode);//将当前节点next域指向前一个节点
            preNode = curNode;//preNode 指针向后移动
            curNode = nextNode;//curNode指针向后移动
        }
        return preNode;
    }

1.4 谈谈你对时间复杂度和空间复杂度的理解?

在进行算法分析时,语句总的执行次数 T(n) 是关于问题规模 n 的函数,进而分析 T(n) 随 n 变化情况并确定 T(n) 的数量级。算法的时间复杂度,也就是算法的时间度量,记作:T(n) = O(f(n))。它表示随问题规模 n 的增大,算法执行时间的增长率和 f(n) 的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中 f(n) 是问题规模 n 的某个函数。

这样用大写 O()来体现算法时间复杂度的记法,我们称之为大O记法。随着 n 的增大,T(n) 增长最慢的算法为最优算法

算法的空间复杂度是通过计算算法所需的存储空间实现,记作:S(n) = O(f(n)),其中,n 为问题的规模,f(n) 为语句关于 n 所占存储空间的函数。

若算法执行时间所需要的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为 O(1)。

1.5 谈一谈如何判断一个链表成环?

Two ways to solve:
Two Pointers:

    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) {
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while (slow != fast) {
            if (fast == null || fast.next == null)
                return false;
            fast = fast.next.next;
            slow = slow.next;
        }
        return true;
    }

HashSet:

    public boolean hasCycle(ListNode head) {
        Set dict = new HashSet<>();
        while (head != null) {
            if (dict.contains(head)) {
                return true;
            } else {
                dict.add(head);
            }
            head = head.next;
        }
        return false;
    }

1.6 什么是红黑树?为什么要用红黑树?

java 的 HashMap, TreeMap, TreeSet, Linux 的虚拟内存管理;
了解红黑树之前, 先了解二叉搜索树:
1.. 左子树上所有结点的值均小于或等于它的根结点的值;
2.. 右子树上所有结点的值均大于或等于它的根结点的值;

但是这样的二叉树, 很有可能变成线性结构, 为了避免出现这样的情况, 产生了红黑树;
每个节点, 都包含5个属性:color, key, left, right 和parent;
如果一个节点没有子节点, 或者没有父节点, 则其对应属性值为 NULL;
我们可以把这些 NULL 视为指向二叉搜索树的叶子节点指针, 外部指针;
把带关键字的节点视为树的内部节点;

一般红黑树必须是满足以下属性的二叉搜索树:
1.. 每个节点是红色, 或者是黑色;
2.. 根节点是黑色;
3.. NULL 节点是黑色的;
4.. 如果一个节点是红色的, 那么他的两个子节点都是黑色的;
5.. 每个节点到所有叶子节点, 所经过的黑色节点数量相同;
6.. 最长路径不会超过最短路径的 2 倍;

从某个节点 N 出发, 达到一个叶子节点, 经过任意一个简单路径, 其黑色节点的个数称之为黑高, 记为 bh;
红黑树必须满足, 任意一个节点, 到其任意一个叶子节点, 经过任意的简单路径, 其黑高相等;

左旋传会带上左父亲, 右旋转会带上右父亲;

插入操作

基本描述
要将红黑树当作一棵二叉查找树, 找到插入的位置;
将节点着色为红色(默认就是红色);
通过旋转和重新着色, 对其修正, 使之成为一棵新的红黑树;
因为新插入的节点, 肯定是叶子节点, 所以要满足[每个节点到所有叶子节点, 所经过的黑色节点数量相同];
接下来, 还要依次满足剩余所有的基本属性;

什么情况下需要旋转, 什么情况下不需要旋转?
如果新插入的当前节点的父节点, 是黑色的, 就不需要做任何操作;
如果新插入的当前节点的父节点, 是红色的, 就需要重新着色, 旋转;
红黑树调整的整体思想是, 将红色的节点移到根节点, 然后,将根节点设为黑色;

调整红黑树
(1)当前节点是根节点, 直接设为黑色的;
(2)当前节点的父节点是黑色的, 无序任何操作;
(3)当前节点的父节点是红色的, 叔叔节点是红色的;
将父亲节点, 设为黑色;
将叔叔节点, 设为黑色;
将祖父节点, 设为红色;
将祖父节点, 设为新的当前节点, 继续判断;
(4)当前节点的父节点, 是红色的, 叔叔节点是 T.nil 节点或者是黑色的, 当前节点是父节点的右孩子;
将祖父节点设为红色;
将父节点设为黑色;
当前节点的父节点, 设为新的当前节点;
以当前节点, 为支点进行左旋, 继续判断;
(5)当前节点的父节点, 是红色的, 叔叔节点是 T.nil 节点或者是黑色的, 当前节点是父节点的左孩子;
将祖父节点设为红色;
将父节点设为黑色;
当前节点的父节点, 设为新的当前节点;
以当前节点, 为支点进行右旋, 继续判断;

删除操作

待删除的节点情况可以分为 3 种:
1.. 是叶子节点;
2.. 只有左子树或只有右子树;
3.. 有左子树和右子树;

调整红黑树
后继节点, 就是右子树上最小的节点;
前驱节点, 就是左子树上最大的节点;
先看待删除的节点的颜色, 再看兄弟节点的颜色, 先看远侄子再看近侄子, 最后看父亲节点的颜色;
case.. 待删除的节点, 是红色的, 没有子节点, 直接删除既可;
case.. 待删除的节点, 是红色的, 只有一个子节点, 子节点一定是黑色的, 父节点也一定是黑色的;
直接删除当前节点, 子节点替换当前节点, 设为黑色即可;
case.. 待删除的节点, 是红色的, 它有两个子树, 当然左右子树都是黑色的, 父节点是黑色的;
从待删除的右子树, 找到最小的节点;
待删除节点与右子树最小节点, 数值互换;
右子树最小节点作为新的待删除节点, 继续判断;
case.. 待删除的节点, 是黑色的, 只有一个子节点, 子节点是红色的;
直接删除当前节点, 子节点替换当前节点, 设为黑色即可;
case.. 待删除的节点, 是黑色的, 是父节点的左子树, 并且兄弟节点是红色的;
父节点和兄弟节点颜色互换, 以兄弟节点为支点, 左旋传, 继续判断;
case.. 待删除的节点, 是黑色的, 是父节点的左子树, 它的兄弟节点是黑色的, 远侄子是红色的;
父节点和兄弟节点颜色互换, 远侄子设为黑色, 删除当前节点, 以兄弟节点为支点, 左旋传即可;
case.. 待删除的节点, 是黑色的, 是父节点的左子树, 它的兄弟节点是黑色的, 近侄子是红色的;
兄弟节点和近侄子颜色互换, 以兄弟节点为支点, 右旋传, 继续判断;
case.. 待删除的节点, 是黑色的, 是父节点的左子树, 它的兄弟节点是黑色的, 两个侄子都是黑色的;
从待删除的右子树, 找到最小的节点;
待删除节点与右子树最小节点, 数值互换;
右子树最小节点作为新的待删除节点, 继续判断;
case.. 待删除的节点, 是黑色的, 是父节点的右子树, 并且兄弟节点是红色的;
父节点和兄弟节点颜色互换, 以兄弟节点为支点, 右旋传, 继续判断;
case.. 待删除的节点, 是黑色的, 是父节点的右子树, 它的兄弟节点是黑色的, 远侄子是红色的;
父节点和兄弟节点颜色互换, 远侄子设为黑色, 删除当前节点, 以兄弟节点为支点, 右旋转即可;
case.. 待删除的节点, 是黑色的, 是父节点的右子树, 它的兄弟节点是黑色的, 近侄子是红色的;
兄弟节点和近侄子颜色互换, 以兄弟节点为支点, 左旋传, 继续判断;
case.. 待删除的节点, 是黑色的, 是父节点的右子树, 它的兄弟节点是黑色的, 两个侄子都是黑色的;
从待删除的右子树, 找到最小的节点;
待删除节点与右子树最小节点, 数值互换;
右子树最小节点作为新的待删除节点, 继续判断;
case.. 待删除的节点, 是黑色的, 它的兄弟节点是黑色的, 它和兄弟节点都没有子节点;
父节点设为黑色, 兄弟节点设为红色, 删除当前节点即可;

1.7 什么是快速排序?如何优化?

快速排序是从冒泡排序演变而来的算法,但是其比冒泡排序要高效,所以叫做快速排序,简单理解如下。

我举个简单例子来理解吧:
比如我们即将排序的数组如下:

1 8 9 5 6 3 0
我们一般将首位 1 或者最后一个数字 0 认为是基准元素,然后左右对比,大致规律如下:
第一次 : 将 1 移出,从最右边 数字0 开始,如果 <= 基准数1,则将其移到左边第一个位置,此时 最右边的数字相当于被挖空。
如下,其中 — 代表被挖空的数字
0 8 9 5 6 3 —
接下来从左边开始,如果大于等于基准数1,则将移到右边
刚才挖空的位置上,如下:
0 — 9 5 6 3 8
接下来继续从右边开始,刚才右边我们进行到3了,继续左移,如果遇到 <= 基准数 1,那么将其移到刚才 挖的 坑上,如果没有遇到,并且左右操作的数相同时,此时将基准数移动到这个空着的坑位。
如下:
0 1 9 5 6 3 8
我们可以发现,基准数1左边的都小于其,右边的都大于其,所以两边各自继续按照刚才上面的逻辑继续递归。(虽然这里最左边只是0,可以忽略)
接下来的过程如下:
0 1 — 5 6 3 8 (基准数9)

0 1 8 5 6 3 —

0 1 8 5 6 3 9
0 1 — 5 6 3 9 (基准数8)

0 1 3 5 6 9 _
0 1 3 5 6 _ 9

0 1 3 5 6 8 9 (基准数6)

优化:
快速排序在序列中元素很少时,效率将比较低,不如插入排序,按需使用。
基准数采用随机。
尾递归优化。
快速排序和分治排序算法一样,都有两次递归调用,而且快排的递归在尾部,所以我们可以对快排代码实施尾递归优化。减少堆栈深度。
将每次分割结束后,将于本次基数相等的元素聚集在一起,再次分割时,避免对聚集过的元素进行分割。
多线程优化,基于分治法的思想,将一个规模为 n 的问题分解为 k个规模较小的问题。这些子问题互相独立且与原问题相同。求解这些子问题,然后将子问题的解合并,从而得到原问题的解。

1.8 说说循环队列?

引用的前面设计的数组类 Array.java

    /**
     * Array
     */
    public class Array {
        private E[] data;
        private int size;

        /**
         * 构造函数
         *
         * @param capacity 传入数值初始值大小
         */
        public Array(int capacity) {
            //Java不支持直接构造函数中new 泛型数据类型,使用强制装换
            // data = new E[capacity];
            data = (E[]) new Object[capacity];
            size = 0;
        }

        /**
         * 无参构造函数,默认容量capacity=10
         */
        public Array() {
            this(10);
        }

        /**
         * @return 返回数组元素的个数
         */
        public int getSize() {
            return size;
        }

        /**
         * @return 返回数组的容量
         */
        public int getCapacity() {
            return data.length;
        }

        public boolean isEmpty() {
            return size == 0;
        }

        /**
         * 向数组中添加一个元素
         *
         * @param e
         */
        public void addLast(E e) {
            add(size, e);
        }

        /**
         * 向数组添加一个 元素
         *
         * @param e
         */
        public void addFirst(E e) {
            add(0, e);
        }

        /**
         * 在index位置插入一个元素
         *
         * @param index 位置
         * @param e     参数值
         */
        public void add(int index, E e) {
            if (index < 0 || index > size) {
                throw new IllegalArgumentException("AddList failed.Require index>=0 and index <= size");
            }
            if (size == data.length) {
                resize(2 * data.length);
            }
            for (int i = size - 1; i >= index; i--) {
                data[i + 1] = data[i];
            }
            data[index] = e;
            size++;
        }

        /**
         * 获取数组中某一元素
         *
         * @param index
         * @return
         */
        public E get(int index) {
            if (index < 0 || index >= size) {
                throw new
                        IllegalArgumentException("Get failed.Index is illegal.");
            }
            return data[index];
        }

        public E getLast() {
            return get(size - 1);
        }

        public E getFirst() {
            return get(0);
        }

        /**
         * 设置某一位置的元素值
         *
         * @param index
         * @param e
         */
        public void set(int index, E e) {
            if (index < 0 || index >= size) {
                throw new
                        IllegalArgumentException("Get failed.Index is illegal.");
            }
            data[index] = e;
        }

        /**
         * 查找数组中是否包含某一元素e
         *
         * @return
         */
        public boolean contains(E e) {
            for (int i = 0; i < size; i++) {
                if (data[i].equals(e)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * 查找数组中元素e的位置
         *
         * @param e 查找元素
         * @return 成功返回元素下标,不成功返回-1
         */
        public int find(E e) {
            for (int i = 0; i < size; i++) {
                //比较是否为同一对象
                if (data[i].equals(e)) {
                    return i;
                }
            }
            return -1;
        }

        /**
         * 删除index位置的元素,返回index中的元素
         *
         * @param index
         * @return
         */
        public E remove(int index) {
            if (index < 0 || index >= size) {
                throw new
                        IllegalArgumentException("Get failed.Index is illegal.");
            }
            E res = data[index];
            for (int i = index + 1; i < size; i++) {
                data[i - 1] = data[i];
            }
            size--;
            data[size] = null; //loitering object != memory leak
            //减少容量,不能创建数组大小为0
            if (size == data.length / 4 &&
                    data.length / 2 != 0) {
                resize(data.length / 2);
            }
            return res;
        }

        /**
         * 删除数组首元素
         *
         * @return
         */
        public E removeFirst() {
            return remove(0);
        }

        /**
         * 删除数组末尾元素
         */
        public E removeLast() {
            return remove(size - 1);
        }

        /**
         * 删除数组中指定元素
         *
         * @param e
         * @return
         */
        public boolean removeElement(E e) {
            int index = find(e);
            if (index != -1) {
                remove(index);
                return true;
            }
            return false;
        }

        /**
         * @return 返回数组大小、容量
         */
        @Override
        public String toString() {
            StringBuilder res = new
                    StringBuilder();
            res.append(String.format("Array: size = %d , capacity = %d\n", size, data.length));
            res.append('[');
            for (int i = 0; i < size; i++) {
                res.append(data[i]);
                if (i != size - 1) {
                    res.append(", ");
                }
            }
            res.append(']');
            return res.toString();
        }

        /**
         * 数组扩容
         *
         * @param newCapacity
         */
        private void resize(int newCapacity) {
            E[] newData = (E[]) new Object[newCapacity];
            for (int i = 0; i < size; i++) {
                newData[i] = data[i];
            }
            data = newData;
        }
    }

定义队列的接口 Queue.java

    /**
     * Queue
     */
    public interface Queue {
        int getSize();
        boolean isEmpty();
        void enqueue(E e);
        E dequeue();
        E getFront();
    }

普通队列 ArrayQueue.java

    /**
     * ArrayQueue
     *
     */
    public class ArrayQueue implements Queue {
        private Array array;

        public ArrayQueue(int capacity) {
            array = new Array<>(capacity);
        }

        public ArrayQueue() {
            array = new Array<>();
        }

        @Override
        public int getSize() {
            return array.getSize();
        }

        @Override
        public boolean isEmpty() {
            return array.isEmpty();
        }

        public int getCapacity() {
            return array.getCapacity();
        }

        /**
         * 添加元素
         */
        @Override
        public void enqueue(E e) {
            array.addLast(e);
        }

        /**
         * 取出元素
         *
         * @return
         */
        @Override
        public E dequeue() {
            return array.removeFirst();
        }

        /**
         * 查看队首元素
         *
         * @return
         */
        @Override
        public E getFront() {
            return array.getFirst();
        }

        @Override
        public String toString() {
            StringBuilder res = new
                    StringBuilder();
            res.append("Queue: ");
            res.append("front [");
            for (int i = 0; i < array.getSize();
                 i++) {
                res.append(array.get(i));
                if (i != array.getSize() - 1) {
                    res.append(", ");
                }
            }
            res.append("] tail");
            return res.toString();
        }
    }

循环队列 LoopQueue.java

    /**
     * LoopQueue
     */
    public class LoopQueue implements Queue {
        private E[] data; //数组
        private int front; //队头
        private int tail; //队尾
        private int size; //容量

        public LoopQueue(int capacity) {
            data = (E[]) new Object[capacity + 1];
            front = 0;
            tail = 0;
            size = 0;
        }

        public LoopQueue() {
            this(10);
        }

        /**
         * 容量
         *
         * @return
         */
        public int getCapacity() {
            return data.length - 1;
        }

        @Override
        public int getSize() {
            return size;
        }

        @Override
        public boolean isEmpty() {
            return front == tail;
        }

        /**
         * 入队
         *
         * @param e
         */
        @Override
        public void enqueue(E e) {
            if ((tail + 1) % data.length == front) {
                resize(getCapacity() * 2);
            }
            data[tail] = e;
            tail = (tail + 1) % data.length;
            size++;
        }

        /**
         * 出队
         *
         * @return
         */
        @Override
        public E dequeue() {
            if (isEmpty()) {
                throw new
                        IllegalArgumentException("Cannot dequeue from an queue.");
            }
            E ret = data[front];
            data[front] = null;
            front = (front + 1) % data.length;
            size--;
            if (size == getCapacity() / 4 &&
                    getCapacity() / 2 != 0) {
                resize(getCapacity() / 2);
            }
            return ret;
        }

        /**
         * 获取队头元素
         *
         * @return
         */
        @Override
        public E getFront() {
            if (isEmpty()) {
                throw new IllegalArgumentException("Queue is empty.");
            }
            return data[front];
        }

        /***
         * 队列扩容或缩减
         * @param newCapacity 扩容或缩减大小
         */
        private void resize(int newCapacity) {
            E[] newData = (E[]) new Object[newCapacity + 1];
            for (int i = 0; i < size; i++) {
                newData[i] = data[(i + front % data.length)];
            }
            data = newData;
            front = 0;
            tail = size;
        }

        @Override
        public String toString() {
            StringBuilder res = new
                    StringBuilder();
            res.append(String.format("Queue: size = %d, capacity = %d\n", size, getCapacity()));
            res.append("front [");
            for (int i = front; i != tail; i = (i + 1) % data.length) {
                res.append(data[i]);
                if ((i + 1) % data.length != tail) {
                    res.append(", ");
                }
            }
            res.append("] tail");
            return res.toString();
        }

        public static void main(String[] args) {
            LoopQueue queue = new
                    LoopQueue<>();
            for (int i = 0; i < 10; i++) {
                queue.enqueue(i);
                System.out.println(queue);
                if (i % 3 == 2) {
                    queue.dequeue();
                    System.out.println(queue);
                }
                System.out.println("---");
            }
        }
    }

队列的运行比较 Main.java

    public class Main {
        private static double testQueue(Queue q, int opCount) {
            long startTime = System.nanoTime();
            Random random = new Random();
            for (int i = 0; i < opCount; i++) {
                q.enqueue(random.nextInt(Integer.MAX_VALUE));
            }
            for (int i = 0; i < opCount; i++) {
                q.dequeue();
            }
            long endTime = System.nanoTime();
            return (endTime - startTime) /
                    1000000000.0;
        }

        public static void main(String[] args) {
            int opCount = 100000;
            ArrayQueue arrayQueue = new ArrayQueue<>();
            double time1 = testQueue(arrayQueue, opCount);
            System.out.println("ArrayQueue, time: " + time1 + "s");
            LoopQueue loopQueue = new LoopQueue<>();
            double time2 = testQueue(loopQueue, opCount);
            System.out.println("LoopQueue, time: " + time2 + "s");
        }
    }

1.9 如何判断单链表交叉

这个题应该是问假如两个单键表相交,请找出相交的位置的节点。

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ```
        ListNode currA = headA;
        ListNode currB = headB;
        int lengthA = 0;
        int lengthB = 0;
        // 让长的先走到剩余长度和短的一样
        while (currA != null) {
            currA = currA.next;
            lengthA++;
        }
        while (currB != null) {
            currB = currB.next;
            lengthB++;
        }
        currA = headA;
        currB = headB;
        while (lengthA > lengthB) {
            currA = currA.next;
            lengthA--;
        }
        while (lengthB > lengthA) {
            currB = currB.next;
            lengthB--;
        }
        // 然后同时走到第一个相同的地方
        while (currA != currB) {
            currA = currA.next;
            currB = currB.next;
        }
        // 返回交叉开始的节点
        return currA;
        ```
    }

你可能感兴趣的:(Android 数据结构面试题)