每天10道:Android面试题整理五(数据结构算法)
在这篇帖子中,我将分享10 道数据结构及算法的题目,相信对于任何Android开发者来说,面试题能够给予他们有所帮助。
糟糕的程序员担心代码。优秀的程序员担心数据结构及其算法。 ——Linus Torvalds
由于怕文章太长我就不做太多说明了,看题目就知道这是啥了,ok,废话不多说,看下面整理出来的题,希望可以对想从事Android开发的兄弟姐妹们有所帮助,下面的题整理出来的,并不全面,欢迎各位提问和补充!Android面试题和答案已按照规范已整理完成,大家可看文末或评论/私信,一起交流技术、进阶提升~
1. Vector,ArrayList与LinkedList有什么区别,应用场景是什么?
● Vector实现了基于动态Object数组的数据结构,线程安全,可以设置增长因子,效率比较低,不建议使用。
● ArrayList实现了基于动态Object数组的数据结构,非线程安全,地址连续,查询效率比较高,插入和删除效率比较低。适合查询操作频繁的场景。
● LinkedList实现了基于链表的数据结构,非线程安全,地址不连续,查询效率比较低,插入和删除效率比较高。适合插入和删除操作频繁的场景。
2. 简单说一说SpareArray的插入流程?
SpareArray的key是一个int有序数组,查找过程使用的二分查找。
- 用二分查找法查找元素的key。
- 如果插入的数据冲突了,则直接覆盖原则。
- 如果当前插入的key上的数据为DELETE,则直接覆盖。
- 如果前面几步都失败了,则检查是否需要gc()并且在索引上插入数据。
3. 从长度为m的int数组中随机取出n个元素,每次取的元素都是之前未取过的,如何优化?
string: Hello length: 5 0 1 2 3 4 before: H e l l o after: o l l e H index sum 0: H--->o 0-->4 4 1: e--->l 1-->3 4 2: l--->l 2-->2 4
解法一:使用数组
- 将字符串转换为char数组
- 遍历循环给char数组赋值
public static String strReverseWithArray2(String string){ if(string==null||string.length()==0)return string; int length = string.length(); char [] array = string.toCharArray(); for(int i = 0;i<length/2;i++){ array[i] = string.charAt(length-1-i); array[length-1-i] = string.charAt(i); } return new String(array); }
解法二:使用栈
- 将字符串转换为char数组
- 将char数组中的字符依次压入栈中
- 将栈中的字符依次弹出赋值给char数组
public static String strReverseWithStack(String string){ if(string==null||string.length()==0)return string; Stack<Character> stringStack = new Stack<>(); char [] array = string.toCharArray(); for(Character c:array){ stringStack.push(c); } int length = string.length(); for(int i= 0;i<length;i++){ array[i] = stringStack.pop(); } return new String(array); }
解法三:逆序遍历
- 逆序遍历字符串中的字符,并将它依次添加到StringBuilder中
public static String strReverseWithReverseLoop(String string){ if(string==null||string.length()==0)return string; StringBuilder sb = new StringBuilder(); for(int i = string.length()-1;i>=0;i--){ sb.append(string.charAt(i)); } return sb.toString(); }
4. HashSet、LinkedHashSet与TreeSet有什么区别,应用场景是什么?
● HashSet:基于HashMap实现,非线程安全,地址不连续,查询效率比较低,插入和删除效率比较高。适合插入和删除操作频繁的场景。
● LinkedHashSet:基于hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起来像是以插入顺序保存的。
● TreeSet基于红黑树实现,非线程安全,可以按照自然顺序或者自定义顺序自动排序,不允许插入null值。适合需要排序的场景。
● HashSet基于hash表实现,非线程安全,允许插入null,查找效率高。适合查找操作频繁的场景。
5. 简单说一说SpareArray的插入流程?
SpareArray的key是一个int有序数组,查找过程使用的二分查找。
- 用二分查找法查找元素的key。
- 如果插入的数据冲突了,则直接覆盖原则。
- 如果当前插入的key上的数据为DELETE,则直接覆盖。
- 如果前面几步都失败了,则检查是否需要gc()并且在索引上插入数据。
6. 单链表反转,合并多个单链表
单链表的结构就像一个火车的结构,火车头拉着许多车厢,实现链表翻转,可以利用递归翻转法,在反转当前节点之前先反转后续节点。这样从头结点开始,层层深入直到尾结点才开始反转指针域的指向。简单的说就是从尾结点开始,逆向反转各个结点的指针域指向。
public class LinkedListReverse { public static void main(String[] args) { Node head = new Node(0); Node node1 = new Node(1); Node node2 = new Node(2); Node node3 = new Node(3); head.setNext(node1); node1.setNext(node2); node2.setNext(node3); // 打印反转前的链表 Node h = head; while (null != h) { System.out.print(h.getData() + " "); h = h.getNext(); } // 调用反转方法 head = Reverse1(head); System.out.println("\n**************************"); // 打印反转后的结果 while (null != head) { System.out.print(head.getData() + " "); head = head.getNext(); } } /** * 递归,在反转当前节点之前先反转后续节点 */ public static Node Reverse1(Node head) { // head看作是前一结点,head.getNext()是当前结点,reHead是反转后新链表的头结点 if (head == null || head.getNext() == null) { return head;// 若为空链或者当前结点在尾结点,则直接还回 } Node reHead = Reverse1(head.getNext());// 先反转后续节点head.getNext() head.getNext().setNext(head);// 将当前结点的指针域指向前一结点 head.setNext(null);// 前一结点的指针域令为null; return reHead;// 反转后新链表的头结点 } } class Node { private int Data;// 数据域 private Node Next;// 指针域 public Node(int Data) { // super(); this.Data = Data; } public int getData() { return Data; } public void setData(int Data) { this.Data = Data; } public Node getNext() { return Next; } public void setNext(Node Next) { this.Next = Next; } }
7. 字符串反转技术
public class ReverseInPlace { static char[] str=null; public static void main(String s[]) { if(s.length==0) System.exit(-1); str=s[0].toCharArray(); int begin=0; int end=str.length-1; System.out.print("Original string="); for(int i=0; i<str.length; i++){ System.out.print(str[i]); } while(begin<end){ str[begin]= (char) (str[begin]^str[end]); str[end]= (char) (str[begin]^str[end]); str[begin]= (char) (str[end]^str[begin]); begin++; end--; } System.out.print("\n" + "Reversed string="); for(int i=0; i<str.length; i++){ System.out.print(str[i]); } } } public AbstractStringBuilder reverse() { boolean hasSurrogate = false; int n = count - 1; for (int j = (n-1) >> 1; j >= 0; --j) { char temp = value[j]; char temp2 = value[n - j]; if (!hasSurrogate) { hasSurrogate = (temp >= Character.MIN_SURROGATE && temp <= Character.MAX_SURROGATE) || (temp2 >= Character.MIN_SURROGATE && temp2 <= Character.MAX_SURROGATE); } value[j] = temp2; value[n - j] = temp; } if (hasSurrogate) { // Reverse back all valid surrogate pairs for (int i = 0; i < count - 1; i++) { char c2 = value[i]; if (Character.isLowSurrogate(c2)) { char c1 = value[i + 1]; if (Character.isHighSurrogate(c1)) { value[i++] = c1; value[i] = c2; } } } } return this; }
8. 说说搜索算法中你知道的算法
- 无序线性搜索
- 排序/有序线性搜索
- 二进制搜索
9. 给定 n 个[0,n)区间内的数,统计每个数出现的次数,不使用额外空间
vector<int> nums; void init(vector<int>& nums) { for (int i = 0; i < nums.size(); i++) { nums[nums[i]] += n; } } int cnt(int k) { return nums[k] / n; }
10. 如何判断单链表交叉
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 Jasper 专注分享面试题|面试技巧|Android学习资料。(dd:16)