哈希

1.hashCode() 和equals() 方法的重要性体现在什么地方

Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。

2.HashMap的工作原理

HashMap类有一个叫做Entry的内部类。这个Entry类包含了key-value作为实例变量。 每当往hashmap里面存放key-value对的时候,都会为它们实例化一个Entry对象,这个Entry对象就会存储在前面提到的Entry数组table中。Entry具体存在table的那个位置是 根据key的hashcode()方法计算出来的hash值(来决定)。
1.8之后存数据的是静态内部类Node

3.什么是hashmap

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射
HashMap 继承于AbstractMap实现了Map、Cloneable、java.io.Serializable接口
HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的

HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子”。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

hashmap共有4个构造函数:

  • // 默认构造函数。HashMap()
  • // 指定“容量大小”的构造函数 HashMap(int capacity)
  • // 指定“容量大小”和“加载因子”的构造函数 HashMap(int capacity, float loadFactor)
  • // 包含“子Map”的构造函数 HashMap(Map<? extends K, ? extends V> map)

4.如何构造一致性 哈希算法

先构造一个长度为2^32的整数环(这个环被称为一致性Hash环),根据节点名称的Hash值(其分布为[0, 2^32-1])将服务器节点放置在这个Hash环上,然后根据数据的Key值计算得到其Hash值(其分布也为[0, 2^32-1]),接着在Hash环上顺时针查找距离这个Key值的Hash值最近的服务器节点,完成Key到服务器的映射查找。

这种算法解决了普通余数Hash算法伸缩性差的问题,可以保证在上线、下线服务器的情况下尽量有多的请求命中原来路由到的服务器。

5.Object作为HashMap的key的话,对Object有什么要求

1.重写hashCode(),因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;
2.重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性;


1.二叉树结构

二叉树每个节点至多有两个节点,左节点永远比右节点小,并且小于根节点。

查找最好时间复杂度O(longN), 最坏O(N)

插入删除操作时间复杂度跟查找差不多

2.平衡二叉树(AVL)

左右子树的高度差不超过1,并且左右子树都是平衡二叉树,超过1就旋转。

比二叉树稳定,查找时间复杂度O(logN)

插入操作最多需要旋转1次,时间复杂度O(logN)左右

删除时间复杂度O(2logN)

3.红黑树

每个节点上都有节点颜色,可以是红色或者黑色;根节点必须是黑色;每个红色节点的子节点,父节点是黑色;从任一节点到每个叶子节点的所有路径都包含相同数目的黑色节点。

查找效率最好情况下是O(logN),最坏情况下比平衡二叉树要差一些,但也比二叉树要好

插入和删除操作改变树的平衡性概率小于平衡二叉树

4.B+树 和 B-树

B+树 和 B-树

B+树 的中间节点不保存数据,所以磁盘页能容纳更多节点元素;

B+树查询必须查找到叶子节点,B树只要匹配到即可不用管元素位置,因此B+树查找更稳定

对于范围查找来说,B+树只需遍历叶子节点链表即可,B树却要重复地中序遍历

效率总结:https://blog.csdn.net/z702143...

区别对比:https://blog.csdn.net/wyqwill...

5.TreeMap和TreeSet在排序时如何比较元素?Collections工具类中的sort()方法如何比较元素

TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。

TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。

Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是Comparator接口的子类型(需要重写compare方法实现元素的比较),相当于一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对回调模式的应用(Java中对函数式编程的支持)。

public class
Student implements Comparable<Student> {
     private String name;        // 姓名
     private int age;            // 年龄
     public Student(String name, int age) {
         this.name = name;
         this.age = age;
     }
     @Override
     public String toString() {
         return "Student [name=" + name + ", age=" + age + "]";
     }
     @Override
     public int compareTo(Student o) {
         return this.age - o.age; // 比较年龄(年龄的升序)
     }
 }
 import java.util.Set;
 import java.util.TreeSet;
 class Test01 {
     public static void main(String[] args) {
         Set<Student> set = new TreeSet<>();     // Java 7
     }
 }
 import java.util.Set;
 import java.util.TreeSet;
 class Test01 {
     public static void main(String[] args) {
         Set<Student> set = new TreeSet<>();     // Java 7的钻石语法(构造器后面的尖括号中不需要写类型)
         set.add(new Student("Hao LUO", 33));
         set.add(new Student("XJ WANG", 32));
         set.add(new Student("Bruce LEE", 60));
         set.add(new Student("Bob YANG", 22));
 
         for(Student stu : set) {
            System.out.println(stu);
         }
 //      
         set.add(new Student("Hao LUO", 33));
         set.add(new Student("XJ WANG", 32));
         set.add(new Student("Bruce LEE", 60));
         set.add(new Student("Bob YANG", 22));
         for(Student stu : set) {
            System.out.println(stu);
         }
 //      输出结果: 
 //      Student [name=Bob YANG, age=22]
 //      Student [name=XJ WANG, age=32]
 //      Student [name=Hao LUO, age=33]
 //      Student [name=Bruce LEE, age=60]
     }
 }
 //      Student [name=Bob YANG, age=22]
 //      Student [name=XJ WANG, age=32]
 //      Student [name=Hao LUO, age=33]
 //      Student [name=Bruce LEE, age=60]
     }
 }

6.打印二叉树每层的节点

import java.util.ArrayList;
import java.util.Scanner;
public class Main{
    // 定义节点
    class Node{
        int val;
        Node left;
        Node right;
        publicNode(int val) {
            this.val = val;
        }
    }
   
    public ArrayList<Integer> gzy; // 保存根左右的序列
    public ArrayList<Integer> zgy; // 保存左跟右的序列
    public ArrayList<Node> pack;       // 保存已经排好的节点
   
    public static void main(String[] args) {
        Main main = new Main();
        main.getResult();
       
    }
   
    public void getResult() {
        // init
        Scanner scanner = new Scanner(System.in);
        int count = scanner.nextInt();
        gzy = new ArrayList<>();
        zgy = new ArrayList<>();
        for(int i = 0; i < count; i++) {
            gzy.add(scanner.nextInt());
        }
        for(int j = 0; j < count; j++) {
            zgy.add(scanner.nextInt());
        }
        pack = new ArrayList<>();       // 已经还原的节点
       
        // exception
        if(count== 1) {
            System.out.println(gzy.get(0));
            return;
        }
        // 构造最左侧节点的二叉树
        Node node = new Node(gzy.get(0));
        pack.add(node);
        int index1 = 1;     // 根左右的下标
        Node tmp = node;
        while(gzy.get(index1) != zgy.get(0)) {      // 如果没访问到最左边的叶子节点,继续还原最左侧二叉树
            tmp.left = new Node(gzy.get(index1++));
            tmp = tmp.left;
            pack.add(tmp);
        }
        tmp.left = new Node(gzy.get(index1++));
        pack.add(tmp.left);
       
        // 加入剩余的节点完善二叉树
        for(int k = index1; k < gzy.size(); k++) {
            fillErCS(gzy.get(k));
        }
       
        // 层次遍历
        ArrayList<Node> res = new ArrayList<>();
        res.add(node);
        int num = 0;
        while(res.size() != num) {
            System.out.print(res.get(num).val + " ");
            if(res.get(num).left != null) {
                res.add(res.get(num).left);
            }
            if(res.get(num).right != null) {
                res.add(res.get(num).right);
            }
            num++;
        }
    }
   
    // 将值为val的节点加入二叉树
    private void fillErCS(int val) {
        int index = zgy.indexOf(val);
        // 每一个遍历的节点都是val节点的根或者在其左边
        for(int i = index-1; i >= 0; i--) {
            if(findNode(zgy.get(i)) != null) {  // 找到待插入节点的根节点或者其左边的节点
                Node node = findNode(zgy.get(i));
                insert(node, val);
                break;
            }      
        }
    }
   
    // 将节点val插入二叉树
    private void insert(Node node, int val) {
        if(zgy.indexOf(node.val) > zgy.indexOf(val)) {  // node在待插入节点的右边
            if(node.left == null) {
                node.left = new Node(val);
                pack.add(node.left);
                return;
            }
            insert(node.left, val);
        }else {     //node在待插入节点的左边或是其根
            if(node.right == null) {
                node.right = new Node(val);
                pack.add(node.right);
                return;
            }
            insert(node.right, val);
        }
    }
    // 根据val找到pack里的节点
    private Node findNode(int val) {
        for(Node node : pack) {
            if(node.val == val) {
                return node;
            }
        }
        return null;
    }
}

7.如何知道二叉树的深度

实现二叉树的深度方式有两种,递归以及非递归。

①递归实现:

为了求树的深度,可以先求其左子树的深度和右子树的深度,可以用递归实现,递归的出口就是节点为空。返回值为0;

②非递归实现:

利用层次遍历的算法,设置变量level记录当前节点所在的层数,设置变量last指向当前层的最后一个节点,当处理完当前层的最后一个节点,让level指向+1操作。设置变量cur记录当前层已经访问的节点的个数,当cur等于last时,表示该层访问结束。

层次遍历在求树的宽度、输出某一层节点,某一层节点个数,每一层节点个数都可以采取类似的算法。

树的宽度:在树的深度算法基础上,加一个记录访问过的层节点个数最多的变量max,在访问每层前max与last比较,如果max比较大,max不变,如果max小于last,把last赋值给max;

import java.util.LinkedList;
public class Deep {
    //递归实现1
  public int findDeep(BiTree root) {
      int deep = 0;
     
        if(root != null) {
          int lchilddeep = findDeep(root.left);
          int rchilddeep = findDeep(root.right);
          deep = lchilddeep > rchilddeep ? lchilddeep + 1 : rchilddeep + 1;    }
     
            return deep;
  }
 
 
  //递归实现2
  public int findDeep1(BiTree root) {
   
     if(root == null) {
          return 0;
      } else {
      
          int lchilddeep = findDeep1(root.left);//求左子树的深度
          int rchilddeep = findDeep1(root.left);//求右子树的深度
          return lchilddeep > rchilddeep ? lchilddeep + 1 : rchilddeep + 1;//左子树和右子树深度较大的那个加一等于整个树的深度
      }
  }
  //非递归实现
  public int findDeep2(BiTree root) {
     if(root == null)
         return 0;
   
     BiTree current = null;
     LinkedList<BiTree> queue = new LinkedList<BiTree>();
     queue.offer(root);
     int cur,last;
     int level = 0;
     while(!queue.isEmpty()){
         cur = 0;//记录本层已经遍历的节点个数
         last = queue.size();//当遍历完当前层以后,队列里元素全是下一层的元素,队列的长度是这一层的节点的个数
         while(cur < last)//当还没有遍历到本层最后一个节点时循环
         {
             current = queue.poll();//出队一个元素
             cur++;
             //把当前节点的左右节点入队(如果存在的话)
             if(current.left != null)
             {
                 queue.offer(current.left);
             }
             if(current.right != null)
             {
                 queue.offer(current.right);
             }
         }
         level++;//每遍历完一层level+1
     }
     return level;
  }
  public static void main(String[] args)
 {
    BiTree root = BiTree.buildTree();
    Deep deep = new Deep();
    System.out.println(deep.findDeep(root));
    System.out.println(deep.findDeep1(root));
    System.out.println(deep.findDeep2(root));  
 }
}

8.二叉树任意两个节点之间路径的最大长度

int maxDist(Tree root) { 
    //如果树是空的,则返回0  
    if(root == NULL) 
        return 0; 
    if(root->left != NULL) { 
        root->lm = maxDist(root->left) +1; 
    } 
    if(root->right != NULL) 
        root->rm = maxDist(root->right) +1; 
    //如果以该节点为根的子树中有最大的距离,那就更新最大距离  
    int sum = root->rm + root->lm; 
    if(sum > max) { 
        max = sum; 
    } 
 
    return root->rm > root->lm ? root->rm : root->lm; 
}  

9.二叉树层序遍历,进一步提问:要求每层打印出一个换行符

public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
        if (root == null) {
            return res;
        }
        queue.offer(root);
        while (queue.size() != 0) {
            List<Integer> l = new ArrayList<Integer>();
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode temp = queue.poll();
                l.add(temp.val);
                if (temp.left != null) {
                    queue.offer(temp.left);
                }
                if (temp.right != null) {
                    queue.offer(temp.right);
                }
            }
            res.add(l);
        }
        return res;
}

10.怎么求一个二叉树的深度?手撕代码

public int
maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int left = maxDepth(root.left);
        int right = maxDepth(root.right);
        int bigger = Math.max(left, right);
        return bigger + 1;
    }

11.二叉树 Z 字型遍历

public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        LinkedList<TreeNode> l = new LinkedList<TreeNode>();
        boolean flag = true;
        if (root == null) {
            return res;
        }
        l.add(root);
        while (l.size() != 0) {
            flag = !flag;
            int size = l.size();
            List<Integer> list = new ArrayList<Integer>();
            for (int i = 0; i < size; i++) {
                TreeNode temp = l.remove();
                list.add(temp.val);
                if (temp.left != null)
                    l.add(temp.left);
                if (temp.right != null)
                    l.add(temp.right);
            }
            if (flag) {
                Collections.reverse(list);
            }
            res.add(list);
        }
        return res;
    }

12.写一个函数,找到一个文件夹下所有文件,包括子文件夹

import
java.io.File;
public class
Counter2 {
    public static void main(String[] args) {
        //取得目标目录
        File
file = new File("D:");
        //获取目录下子文件及子文件夹
        File[]
files = file.listFiles();
        readfile(files);
       
    }
   
    public static void readfile(File[] files) {
        if
(files == null) {// 如果目录为空,直接退出  
            return; 
        }
        for(File
f:files) {
            //如果是文件,直接输出名字
            if(f.isFile()) {
                System.out.println(f.getName());
            }
            //如果是文件夹,递归调用
            else if(f.isDirectory()) {
                readfile(f.listFiles());
            }
        }
    }
}

链表

1.判断链表中是否出现了环

单链表有环,是指单链表中某个节点的next指针域指向的是链表中在它之前的某一个节点,这样在链表的尾部形成一个环形结构。
// 链表的节点结构如下 typedef struct node { int data; struct node *next; } NODE;

最常用方法:
定义两个指针,同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环形链表;如果走得快的指针走到了链表的末尾(next指向 NULL)都没有追上第一个指针,那么链表就不是环形链表。

通过使用STL库中的map表进行映射。首先定义 map<NODE *, int> m; 将一个 NODE * 指针映射成数组的下标,并赋值为一个 int 类型的数值。然后从链表的头指针开始往后遍历,每次遇到一个指针p,就判断 m[p] 是否为0。如果为0,则将m[p]赋值为1,表示该节点第一次访问;而如果m[p]的值为1,则说明这个节点已经被访问过一次了,于是就形成了环

2.有一个链表,奇数位升序偶数位降序,如何将链表变成升序

public class OddIncreaseEvenDecrease {
    /**
     * 按照奇偶位拆分成两个链表
     * @param head
     * @return
     */
    public static Node[] getLists(Node head){
        Node head1 = null;
        Node head2 = null;
       
        Node cur1 = null;
        Node cur2 = null;
        int count = 1;//用来计数
        while(head != null){
            if(count % 2 == 1){
                if(cur1 != null){
                    cur1.next = head;
                    cur1 = cur1.next;
                }else{
                    cur1 = head;
                    head1 = cur1;
                }
            }else{
                if(cur2 != null){
                    cur2.next = head;
                    cur2 = cur2.next;
                }else{
                    cur2 = head;
                    head2 = cur2;
                }
            }
            head = head.next;
            count++;
        }
        //跳出循环,要让最后两个末尾元素的下一个都指向null
        cur1.next = null;
        cur2.next = null;
       
        Node[] nodes = new Node[]{head1,head2};
        return nodes;
    }
   
    /**
     * 反转链表
     * @param head
     * @return
     */
    public static Node reverseList(Node head){
        Node pre = null;
        Node next = null;
        while(head != null){
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
    
    /**
     * 合并两个有序链表
     * @param head1
     * @param head2
     * @return
     */
    public static Node CombineList(Node head1, Node head2){
        if(head1 == null || head2 == null){
            return head1 != null ? head1 :head2;
        }
        Node head = head1.value < head2.value ? head1 : head2;
        Node cur1 = head == head1 ? head1 : head2;
        Node cur2 = head == head1 ? head2 : head1;
        Node pre = null;
        Node next = null;
        while(cur1 != null && cur2 !=null){
            if(cur1.value <= cur2.value){//这里一定要有=,否则一旦cur1的value和cur2的value相等的话,下面的pre.next会出现空指针异常
                pre = cur1;
                cur1 = cur1.next;
            }else{
                next = cur2.next;
                pre.next = cur2;
                cur2.next = cur1;
                pre = cur2;
                cur2 = next;
            }
        }
        pre.next = cur1 == null ? cur2 : cur1;
       
        return head;
    }
}

3.随机链表的复制

public RandomListNode copyRandomList(RandomListNode head) {
 
    if (head == null)
        return null;
 
    RandomListNode p = head;
 
    // copy every node and insert to list
    while (p != null) {
        RandomListNode copy = new RandomListNode(p.label);
        copy.next = p.next;
        p.next = copy;
        p = copy.next;
    }
 
    // copy random pointer for each new node
    p = head;
    while (p != null) {
        if (p.random != null)
            p.next.random = p.random.next;
        p = p.next.next;
    }
 
    // break list to two
    p = head;
    RandomListNode newHead = head.next;
    while (p != null) {
        RandomListNode temp = p.next;
        p.next = temp.next;
        if (temp.next != null)
            temp.next = temp.next.next;
        p = p.next;
    }
 
    return newHead;
}

4.反转单链表

ListNode reverseList(ListNode* head) {
        if(head == nullptr || head->next == nullptr)
            return head;
        ListNode* p;
        ListNode* q;
        ListNode* r;
        p = head;
        q = head->next;
        head->next = nullptr;//旧的头指针是新的尾指针 指向NULL
        while(q){
            r = q->next;//用来保存下一步要处理的指针
            q->next = p;//p q 交替处理 进行反转单链表
            p = q;
            q = r;
        }
        head = p;//最后的q必定指向NULL,p就成了新链表的头指针
        return head;
}

数组

1.二维数组顺时针旋转90度

倒着的第一列变第一行...

public void rotate(int[][] matrix) {
        int n = matrix.length;
        for (int i = 0; i < n/2; i++) {
            for (int j = i; j < n-1-i; j++){
                int temp = matrix[i][j];
                matrix[i][j] =matrix[n-1-j][i];
                matrix[n-1-j][i] =matrix[n-1-i][n-1-j];
                matrix[n-1-i][n-1-j] =matrix[j][n-1-i];
                matrix[j][n-1-i] = temp;
            }
        }
}

2.一个数组,除一个元素外其它都是两两相等,求那个元素

因为数组中除了一个元素只出现一次之外,其它的元素都出现两次,如果把所有的数都异或,相同的数字异或为0,最后只剩下出现一次的数字,它和0异或,结果就是它本身。

 public static int find1From2(int[] a){
        int len = a.length, res = 0;
        for(int i = 0; i < len; i++){
            res= res ^ a[i];
        }
        return res;
}

3.找出数组中和为S的一对组合,找出一组就行

public int[] twoSum(int[] nums, int target) {
        HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
        int[] a = new int[2];
        map.put(nums[0], 0);
        for (int i = 1; i < nums.length;i++) {
            if (map.containsKey(target - nums[i])) {
                a[0] = map.get(target - nums[i]);
                a[1] = i;
                return a;
            } else {
                map.put(nums[i], i);
            }
        }
        return a;
}

4.求一个数组中连续子向量的最大和

public int maxSubArray(int[] nums) {
        int sum = 0;
        int maxSum = Integer.MIN_VALUE;
        if (nums == null || nums.length == 0) {
            return sum;
        }
        for (int i = 0; i < nums.length;i++) {
            sum += nums[i];
            maxSum = Math.max(maxSum, sum);
            if (sum < 0) {
                sum = 0;
            }
        }
        return maxSum;
}

5.寻找一数组中前K个最大的数

public static void TOPK(int[] data,int k){
        PriorityQueue<Integer> pq=new PriorityQueue<>(k);
        for (int i = 0; i < data.length; i++) {
            if(i<k){
                pq.offer(data[i]);
            }else{
                if(data[i]>pq.peek()){
                    pq.poll();
                    pq.offer(data[i]);
                }
            }
        }
        for (int i = 0; i < k; i++) {
            System.out.print(pq.poll()+" ");
        }
    }

Derrick
1 声望0 粉丝