Java程序员面试笔试宝典-数据结构与算法(四)

本文详细介绍了Java程序员面试中常见的数据结构与算法问题,包括链表、栈与队列、排序算法、位运算、数组、字符串、二叉树等关键知识点。通过实例和技巧解析,帮助读者深入理解并掌握这些基础概念,提升面试竞争力。
摘要由CSDN通过智能技术生成

本文内容基于《Java程序员面试笔试宝典》,何昊、薛鹏、叶向阳著。


1. 链表

1.1 如何实现单链表的增删操作?

1.2 如何从链表中删除重复元素?

1.3 如何找出单链表中的倒数第k个元素?

1.4 如何实现链表的反转?

1.5 如何从尾到头输出单链表?

1.6 如何寻找单链表的中间节点?

1.7 如何检测一个链表是否有环?

1.8 如何在不知道头指针的情况下删除指定节点?

1.9 如何判断两个链表是否相交?

2. 栈与队列

2.1 如何实现栈?

2.2 如何用O(1)的时间复杂度求栈中最小的元素?

2.3 如何实现队列?

2.4 如何用两个栈模拟队列操作?

3. 排序

3.1 如何进行冒泡排序?

3.2 如何进行选择排序?

3.3 如何进行插入排序?

3.4 如何进行希尔排序?

3.5 如何进行快速排序?

3.6 如何进行归并排序?

3.7 如何进行堆排序?

3.8 排序算法总结

4. 位运算

4.1 如何用移位操作实现乘法运算?

4.2 如何判断一个数是否为2的n次方?

4.3 如何求二进制数中1的个数?

5. 数组

5.1 如何寻找数组中的最小值与最大值?

5.2 如何找出数组中第2大的数?

5.3 如何求最大子数组之和?(动态规划)

5.4 如何找出数组中重复元素最多的数?

5.5 如何求数组中两两相加等于k的组合种数?

5.6 如何把一个数组循环右移k位?

5.7 如何找出数组中第k个最小的数?

5.8 如何找到数组中只出现一次的数字?

5.9 如何找出数组中唯一的重复元素?

5.10 如何求数对之差的最大值?(动态规划)

5.11 如何求绝对值最小的数?

5.12 如何求数组中两个元素的最小距离?

5.13 如何求指定数字在数组中第一次出现的位置?

5.14 如何对数组的两个子有序段进行合并?

5.15 如何计算两个有序整型数组的交集?

5.16 如何求解数组中反序对的个数?

5.17 如何求解最小三元组距离?

6. 字符串

6.1 如何实现字符串单词的反转?

6.2 如何判断两个字符串是否由相同的字符组成?

6.3 如何删除字符串中重复的字符?

6.4 如何输出所有不重复字符的全排列?

6.5 如何输出字符串的所有组合?

7. 二叉树

7.1 二叉树的基本概念、实现、遍历

7.2 已知先序遍历和中序遍历,如何求后序遍历?

8. 其他

8.1 如何不使用比较运算就求出两个数哪个大哪个小?


1. 链表

1.1 如何实现单链表的增删操作?

public class MyLinkedList {

    private class Node {
        public Object data;
        public Node next;

        public Node(Object data) {
            this.data = data;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "data=" + data +
                    ", next=" + next +
                    '}';
        }
    }

    private Node root;

    @Override
    public String toString() {
        return "MyLinkedList{" +
                "root=" + root +
                '}';
    }

    public void add(Object data) {
        if (root == null) {
            root = new Node(data);
        } else {
            Node last = root;
            while (last.next != null) {
                last = last.next;
            }

            last.next = new Node(data);
        }
    }

    public boolean delete(int index) {
        if (root != null) {
            if (index == 0) {
                root = root.next;
                return true;
            }

            int i = 1;
            Node temp1 = root;
            Node temp2 = temp1.next;
            while (temp2 != null) {
                if (i == index) {
                    temp1.next = temp2.next;
                    return true;
                }
                temp1 = temp2;
                temp2 = temp2.next;
                i++;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(2);
        System.out.println(myLinkedList);
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(4);
        System.out.println(myLinkedList);
        myLinkedList.delete(0);
        System.out.println(myLinkedList);
        myLinkedList.delete(1);
        System.out.println(myLinkedList);
        myLinkedList.delete(1);
        System.out.println(myLinkedList);
    }
}

1.2 如何从链表中删除重复元素?

  • 哈希表;
  • 双重遍历,假设外循环当前节点为cur,内循环从cur开始向后遍历,若碰到相同的节点,则删除;
  • 双重遍历,假设外循环当前节点为cur,内循环从root开始向后遍历到cur之前的节点,若碰到相同的节点,则删除,内循环结束。
public class MyLinkedList {

    //...

    public void deleteDuplicate() {
        HashMap<Object, Object> map = new HashMap<>();
        Node temp = root;
        Node pre = null;
        while (temp != null) {
            if (!map.containsKey(temp.data)) {
                map.put(temp.data, temp.data);
                pre = temp;
            } else {
                pre.next = temp.next;
            }
            temp = temp.next;
        }
    }

    public void deleteDuplicate1() {
        Node p = root;
        while (p != null) {
            Node q = p;
            while (q.next != null) {
                if (q.next.data == p.data) {
                    q = q.next.next;
                } else {
                    q = q.next;
                }
            }
            p = p.next;
        }
    }

    public void deleteDuplicate2() {
        int i = 0;
        Node p = root;
        while (p != null) {
            int j = 0;
            Node q = root;
            Node qPre = null;
            while (q != null && j < i) {
                if (q.data == p.data) {
                    if (qPre == null) {
                        //头节点
                        root = root.next;
                        i--;
                    } else {
                        qPre.next = q.next;
                    }
                    break;
                } else {
                    qPre = q;
                }
                q = q.next;
                j++;
            }
            i++;
            p = p.next;
        }
    }

    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(2);
        System.out.println(myLinkedList);
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(4);
        System.out.println(myLinkedList);

        //myLinkedList.deleteDuplicate();
        //myLinkedList.deleteDuplicate1();
        myLinkedList.deleteDuplicate2();
        System.out.println(myLinkedList);
    }
}

1.3 如何找出单链表中的倒数第k个元素?

  • 首先遍历一遍单链表,求出整个单链表的长度n,然后将倒数第k个,转换为正数第n-k个,接下来遍历一次就可以得到结果;
  • 从头节点开始,依次对链表的每一个节点遍历它后面的k个元素,判断是否到达链表尾,直到找到;
  • 设置两个引用,让其中一个引用比另一个引用先前移k步,然后让两个引用同时往后移动,直到先前移的那个指针为null,没有前移的引用就是倒数第k个元素。
public class MyLinkedList {

    //...

    public Node findLastKElement(int k) {
        Node node = root;

        //获取链表长度
        int n = 0;
        while (node != null) {
            n++;
            node = node.next;
        }
        System.out.println(n);

        //找倒数第k个节点,即找正数第n-k个节点
        node = root;
        int m = 0;
        while (node != null) {
            m++;
            node = node.next;
            if (m == n - k) {
                return node;
            }
        }
        return null;
    }

    public Node findLastKElement1(int k) {
        Node node = root;

        while (node != null) {
            //找到该节点后面的第k个节点
            Node temp = node;
            for (int i = 0; i < k; i++) {
                temp = temp.next;
            }
            if (temp == null) {
                return node;
            }
            node = node.next;
        }
        return null;
    }

    public Node findLastKElement2(int k) {
        Node node1 = root;
        Node node2 = root;

        //一个引用前移K步
        for (int i = 0; i < k; i++) {
            node2 = node2.next;
        }

        //两个引用同时前移,直到先前移的节点为null
        while (node2 != null) {
            node1 = node1.next;
            node2 = node2.next;
        }
        return node1;
    }

    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(2);
        System.out.println(myLinkedList);
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(4);
        System.out.println(myLinkedList);

        System.out.println(myLinkedList.findLastKElement2(2));
    }
}

1.4 如何实现链表的反转?

  • 利用栈,先让所有节点入栈,然后再出栈组成新的单链表;
  • 直接遍历,需要提前保存三个节点的信息:当前节点、当前节点的上一个节点、当前节点的下一个节点。
public class MyLinkedList {

    //...

    public void reverseIteratively() {
        Stack<Node> stack = new Stack<>();
        Node temp = root;

        //所有节点入栈
        while (temp != null) {
            stack.push(temp);
            temp = temp.next;
        }

        //所有节点出栈组成新的单链表
        Node newRoot = stack.pop();
        temp = newRoot;
        while (!stack.isEmpty()) {
            Node node = stack.pop();
            node.next = null;
            temp.next = node;
            temp = temp.next;
        }
        root = newRoot;
    }

    public void reverseIteratively1() {
        Node pReversedHead = root;
        Node pNode = root;
        Node pPrev = null;
        while (pNode != null) {
            Node pNext = pNode.next;
            if (pNext == null) {
                pReversedHead = pNode;
            }
            pNode.next = pPrev;
            pPrev = pNode;
            pNode = pNext;
        }
        root = pReversedHead;
    }

    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(2);
        System.out.println(myLinkedList);
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(4);
        System.out.println(myLinkedList);

        myLinkedList.reverseIteratively1();
        System.out.println(myLinkedList);
    }
}

1.5 如何从尾到头输出单链表?

  • 用1.4中的方法反转链表后输出;
  • 递归实现,每访问到一个节点,先递归输出它后面的节点,再输出该节点自身。
public class MyLinkedList {

    //...

    public void printListReversely(Node node) {
        if (node != null) {
            printListReversely(node.next);
            System.out.println(node.data);
        }
    }

    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(2);
        System.out.println(myLinkedList);
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(4);
        System.out.println(myLinkedList);

        myLinkedList.printListReversely(myLinkedList.root);
    }
}

1.6 如何寻找单链表的中间节点?

【注1】与1.3很相似,完全可以参考1.3的方法,做一点变形。

【注2】长度为奇数,中间节点只有1个,长度为偶数,中间节点有2个。

  • 先遍历一遍获取单链表的长度,然后遍历到长度一半的位置,就是中间节点;
  • 记录头节点到当前节点的距离,然后遍历当前节点后面所有节点,计算到尾节点的距离,比较距离;
  • 设置两个引用,一个引用一次走1步,一个引用一次走2步,当走的快的引用到达尾节点,走的慢的引用就是中间节点。
public class MyLinkedList {

    //...

    public Node[] searchMid() {
        //获取链表长度
        Node temp = root;
        int n = 0;
        while (temp != null) {
            n++;
            temp = temp.next;
        }

        //找到链表长度一半的节点
        temp = root;
        //长度为偶数时,找到第一个中间节点,长度为奇数时,找到中间节点
        int k = n % 2 == 0 ? n / 2 - 1 : n / 2;
        for (int i = 0; i < k; i++) {
            temp = temp.next;
        }

        Node[] nodes = new Node[2];
        nodes[0] = temp;
        if (n % 2 == 0) {
            nodes[1] = temp.next;
        }
        return nodes;
    }

    public Node[] searchMid1() {
        //记录头节点到当前节点的距离
        Node temp = root;
        int i = 0;
        while (temp != null) {
            i++;

            //记录当前节点到尾节点的距离
            Node temp1 = temp.next;
            int j = 0;
            while (temp1 != null) {
                j++;
                if (j > i) {
                    break;
                }
                temp1 = temp1.next;
            }
            //如果长度为偶数,则有2个中间节点,如果长度为奇数,则有1个中间节点
            if (i == j || i == j + 1) {
                Node[] nodes = new Node[2];
                nodes[0] = temp;
                if (i == j) {
                    nodes[1] = temp.next;
                }
                return nodes;
            }

            temp = temp.next;
        }
        return null;
    }

    public Node[] searchMid2() {
        //两个引用,一个每次前进2步,一个每次前进1步,直到快引用不能再往后移动
        Node p = root;
        Node q = root;
        while (p != null && p.next != null && p.next.next != null) {
            p = p.next.next;
            q = q.next;
        }

        //如果长度为偶数,则有2个中间节点,如果长度为奇数,则有1个中间节点
        Node[] nodes = new Node[2];
        nodes[0] = q;
        if (p.next != null) {
            nodes[1] = q.next;
        }
        return nodes;
    }

    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(2);
        System.out.println(myLinkedList);
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(4);
        System.out.println(myLinkedList);

        System.out.println(Arrays.toString(myLinkedList.searchMid2()));
    }
}

1.7 如何检测一个链表是否有环?

https://blog.csdn.net/qq_28958301/article/details/93192577#1.%20%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E9%93%BE%E8%A1%A8%E6%9C%89%E7%8E%AF%EF%BC%9F

1.8 如何在不知道头指针的情况下删除指定节点?

public class MyLinkedList {

    //...

    public boolean deleteNode(Node node) {
        //尾节点无法删除
        if (node == null || node.next == null) {
            return false;
        }

        //当前节点的值设置为下一个节点的值
        node.data = node.next.data;
        //下一个节点设置为下下一个节点
        node.next = node.next.next;
        return true;
    }

    public static void main(String[] args) {
        MyLinkedList myLinkedList = new MyLinkedList();
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(2);
        System.out.println(myLinkedList);
        myLinkedList.add(3);
        System.out.println(myLinkedList);
        myLinkedList.add(4);
        System.out.println(myLinkedList);

        myLinkedList.deleteNode(myLinkedList.root);
        System.out.println(myLinkedList);
    }
}

1.9 如何判断两个链表是否相交?

如果两个链表相交,那么它们一定有着相同的尾节点。

public class MyLinkedList {

    //...

    public boolean isIntersect(Node p, Node q) {
        if (p == null || q == null) {
            return false;
        }

        //找到链表p的尾节点
        Node pTemp = p;
        while (pTemp.next != null) {
            pTemp = pTemp.next;
        }

        //找到链表q的尾节点
        Node qTemp = q;
        while (qTemp.next != null) {
            qTemp = qTemp.next;
        }

        //返回尾节点是否相同,即链表p、q是否相交
        return pTemp == qTemp;
    }

    public static void main(String[] args) {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        Node node5 = new Node(5);
        node5.next = node2;
        System.out.println(myLinkedList.isIntersect(node5, node1));
    }
}

【扩展】如果两个链表相交,如何找到它们的相交点?

【思路】如果两个链表A、B的长度分别为len1、len2,并且len1 > len2,设置两个引用p、q,分别指向链表A、B的头节点,p向后先走len1 - len2步,此时p到相交点的距离和q到相交点的距离相同。

public class MyLinkedList {

    //...

    public Node getMeetNode(Node p, Node q) {
        if (!isIntersect(p, q)) {
            return null;
        }

        //求链表p的长度
        int pLength = 0;
        Node pTemp = p;
        while (pTemp != null) {
            pLength++;
            pTemp = pTemp.next;
        }

        //求链表q的长度
        int qLength = 0;
        Node qTemp = q;
        while (qTemp != null) {
            qLength++;
            qTemp = qTemp.next;
        }

        //让长的链表先走长度差的距离,此时它们到相遇点的距离相同
        pTemp = p;
        qTemp = q;
        for (int i = 0; i < Math.abs(pLength - qLength); i++) {
            if (pLength > qLength) {
                pTemp = pTemp.next;
            } else if (pLength < qLength) {
                qTemp = qTemp.next;
            }
        }

        //同时走,如果相同,则是相遇点
        while (pTemp.next != null && qTemp.next != null) {
            if (pTemp == qTemp) {
                return pTemp;
            }
            pTemp = pTemp.next;
            qTemp = qTemp.next;
        }
        return null;
    }

    public static void main(String[] args) {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        Node node5 = new Node(5);
        node5.next = node2;
        System.out.println(myLinkedList.getMeetNode(node5, node1));
    }
}

2. 栈与队列

2.1 如何实现栈?

【注】用数组实现栈,没有实现扩容。

public class MyStack {

    private int size;
    private int[] array;
    private int top;
    private int currentSize;

    public MyStack(int size) {
        this.size = size;
        this.array = new int[size];
        this.top = -1;
        this.currentSize = 0;
    }

    public void push(int e) throws Exception {
        if (currentSize >= size) {
            throw new Exception("full");
        }

        array[++top] = e;
        currentSize++;
    }

    public int pop() throws Exception {
        if (currentSize <= 0) {
            throw new Exception("empty");
        }

        currentSize--;
        return array[top--];
    }

    public void print() {
        for (int i = 0; i < top + 1; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.print
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书针对当前各大it企业面试笔试中常见的问题以及注意事项,进行了深层次的分析。本书除了对传统的计算机相关知识(c/c++、数据结构与算法、操作系统、计算机网络与通信、软件工程、数据库、智力题、英语面试等)进行介绍外,还根据当前计算机技术的发展潮流,对面试笔试中常见的海量数据处理进行了详细的分析。同时,为了更具说服力,本书特邀多位it名企面试官现身说法,对面试过程中求职者存在的问题进行了深度剖析,同时本书引入了一批来自于名牌高校、就职于明星企业的职场达人的真实求职案例,通过他们的求职经验与教训,抛砖引玉,将整个求职过程生动形象地展示在读者面前,进而对求职者起到一定的指引作用。本书也对各种类型的it企业的招聘环节进行了庖丁解牛式的分析,帮助求职者能够更加有针对性地 进行求职准备。 本书是一本计算机相关专业毕业生面试笔试的求职用书,同时也适合期望在计算机软硬件行业大显身手的计算机爱好者阅读。 程序员面试笔试宝典 目录 前言 上篇 面试笔试经验技巧篇 第1章 面试官箴言 2 第2章 面试心得交流 9 第3章 企业面试笔试攻略 20 第4章 面试笔试技巧 42 第5章 英文面试攻略 82 第6章 智力题攻略 102 下篇 面试笔试技术攻克篇 第7章 程序设计基础 122 第8章 数据库 240 第9章 网络与通信 254 第10章 操作系统 270 第11章 软件工程 278 第12章 发散思维 289 第13章 数据结构与算法 295 第14章 海量数据处理 390
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值