最后
由于文章篇幅原因,我只把面试题列了出来,详细的答案,我整理成了一份PDF文档,这份文档还包括了还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 ,帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
2、将p后面的元素删除,然后添加到g的后面。也即头插法。
3、根据m和n重复步骤(2)
4、返回dummyHead.next
- code:
/迭代法/
class Solution{
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode g = dummyHead;
ListNode p = dummyHead.next;
int step = 0;
while (step < m - 1) {
g = g.next; p = p.next;
step ++;
}
for (int i = 0; i < n - m; i++) {
ListNode removed = p.next;
p.next = p.next.next;
removed.next = g.next;
g.next = removed;
}
return dummyHead.next;
}
}
/递归法/
class Solution {
private boolean stop;
private ListNode left;
public void recurseAndReverse(ListNode right, int m, int n) {
if (n == 1) {
return;
}
right = right.next;
if (m > 1) {
this.left = this.left.next;
}
this.recurseAndReverse(right, m - 1, n - 1);
if (this.left == right || right.next == this.left) {
this.stop = true;
}
if (!this.stop) {
int t = this.left.val;
this.left.val = right.val;
right.val = t;
this.left = this.left.next;
}
}
public ListNode reverseBetween(ListNode head, int m, int n) {
this.left = head;
this.stop = false;
this.recurseAndReverse(head, m, n);
return head;
}
}
-
复杂度分析:
-
| 复杂度 | 迭代法 | 递归法 |
| — | — | — |
| 时间复杂度 | O(N) | O(N) |
| 空间复杂度 | O(1) | O(N) |
迭代法:时间复杂度是O(N), 并且我们是在原链表上进行指针移动的,所以空间复杂度为O(1)
递归法:每个节点最多需遍历两次,一次是常规的递归,一次是回溯的递归,所以时间复杂度是O(N),在最坏的情况下,我们需要翻转整个链表,并且递归的方法会占用栈,所以空间复杂度是O(N)
这是两个非常典型而且常见的链表翻转类题目,在面试中也经常出现作为热身题,所以需要重点关注。
Leetcode中包含翻转链表的题目:
| 序号 | 题目 | 难度 | 代码 |
| — | — | — | — |
| 25 | Reverse Nodes in k-Group | Hard | java |
| 61 | Rotate List | Medium | java |
| 92 | Reverse Linked List II | Medium | java |
| 206 | Reverse Linked List | Easy | java |
合并链表
-
例题:21 Merge Two Sorted Lists 【easy】
-
题意:将两个排序好的链表合并成新的有序链表
-
test case:
Input: 1->2->4, 1->3->4
Output: 1->1->2->3->4->4
-
解题思路: 迭代法和递归法。迭代法是每次比较两个结点,把较小的加到结果链表中,并且这个指针向后移动;递归法即每次比较两个链表的头部,将较小的头部单独取出来,剩下的两个部分继续递归。
-
code:
Java
/迭代法/
class Solution{
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head = new ListNode(0);
ListNode cur = head;
while (l1 != null && l2 != null) {
if (l1.val >= l2.val) {
cur.next = l2;
l2 = l2.next;
} else {
cur.next = l1;
l1 = l1.next;
}
cur = cur.next;
}
if (l1 != null)
cur.next = l1;
if (l2 != null)
cur.next = l2;
return head.next;
}
}
/递归法/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
if (l1.val > l2.val) {
ListNode tmp = l2;
tmp.next = mergeTwoLists(l1, l2.next);
return tmp;
} else {
ListNode tmp = l1;
tmp.next = mergeTwoLists(l1.next, l2);
return tmp;
}
}
}
-
例题:21 Merge k Sorted Lists (Hard)
-
题意:合并k个有序链表
-
解题思路: 分治算法将链表两两合并,最后合并成一个链表,平均一条链表n个节点,合并两条链表为O(n),k条链表合并为O(logk),合并为O(nlogk)
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists == null || lists.length == 0) return null;
return partion(lists, 0, lists.length - 1);
}
private ListNode partion(ListNode[] lists, int start, int end) {
if(start == end) {
return lists[start];
}
else if(start < end) {
int mid = (start + end) / 2;
ListNode l1 = partion(lists, start, mid);
ListNode l2 = partion(lists, mid + 1, end);
return merge(l1, l2);
}
else {
//not gonna happen.
return null;
}
}
private ListNode merge(ListNode l1, ListNode l2) {
if(l1 == null) return l2;
if(l2 == null) return l1;
if(l1.val < l2.val) {
l1.next = merge(l1.next, l2);
return l1;
} else {
l2.next = merge(l1, l2.next);
return l2;
}
}
}
LeetCode中包含 合并链表类的题目:
| 序号 | 题目 | 难度 | 代码 |
| — | — | — | — |
| 2 | Add Two Numbers | medium | java |
| 21 | Merge Two Sorted Lists | Easy | java |
| 23 | Merge k Sorted Lists | Hard | java |
| 445 | Add Two Numbers II | Medium | java |
环形链表
-
例题:141 Linked List Cycle 【easy】
-
题意:判断一个单链表是否存在环
-
test case:
Input : head = [3, 2, 0, -4], pos = 1
Output : true
why:在这个单链表中存在一个环,尾节点指向第二个节点
-
解题思路:双指针法。这道题可以用双指针做,有的也叫快慢指针,或者runner and chaser,意思是从头设置两个指针,一个快指针走2n步(视具体题目而定),慢指针走n步,当快指针走到尾节点时,满指针正好走到链表的一半(视具体题目而定)。在本题中,设置快指针走两步,慢指针一次走一步,如果快指针走到了尽头,则说明链表无环,如果快指针和慢指针相遇就说明链表有环。为什么呢?我们假设一个有环链表,快慢指针最后都会走到环上,而这个环就像一个环形跑道一样,慢指针在后面,快指针在前面,但实际上快指针也在追慢指针,希望能超慢指针一圈。他们在这个跑道上,总会有一天快指针追上了慢指针。我们不用担心快指针跳过了慢指针,因为他们两的速度差是1,所以它们在环上的距离总是每次减1,最后总能减到0。
-
code:
Java
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null) return false;
ListNode slow = head;
ListNode fast = head;
while(fast.next!=null && fast.next.next!=null) {
slow = slow.next;
fast = fast.next.next;
if(slow==fast) return true;
}
return false;
}
}
- 复杂度分析:时间复杂度O(N),空间复杂度 O(1)
LeetCode中含有环形链表的题目:
| 序号 | 题目 | 难度 | 代码 |
| — | — | — | — |
| 141 | Linked List Cycle | Easy | java |
| 142 | Linked List Cycle II | Medium | java |
| 708 | Insert into a Cyclic Sorted List | Medium | java |
拆分链表
-
例题:86 Partition List 【medium】
-
题意:给定一个链表以及一个目标值,把小于该目标值的所有节点都移至链表的前端,大于或等于目标值的节点移至链表的尾端,同时要保持这两部分在原先链表中的相对位置。
-
test case:
-
解题思路: 二分法。设置两个指针left和right,顺序遍历整条链表,left、mid、target三者比较,根据情况left右移或者right左移。关键就在于边界情况和元素有重复。
-
当 nums[mid] = nums[left] 时,这时由于很难判断 target 会落在哪,那么只能采取 left++
-
当 nums[mid] > nums[left] 时,这时可以分为两种情况,判断左半部比较简单
-
当 nums[mid] < nums[left] 时,这时可以分为两种情况,判断右半部比较简单
-
code:
Java
class Solution {
public boolean search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (target == nums[mid]) return true;
if (nums[mid] == nums[left]) left++;
else if (nums[mid] > nums[left]) {
if (nums[left] <= target && nums[mid] > target) right = mid - 1;
else left = mid + 1;
} else {
if (nums[mid] < target && target <= nums[right]) left = mid + 1;
else right = mid - 1;
}
}
return false;
}
}
- 复杂度分析:
LeetCode中含有拆分类的题目:
| 序号 | 题目 | 难度 | 代码 |
| — | — | — | — |
| 725 | Split Linked List in Parts | Medium | java |
| ---- | ------------------------------------------------------------ | ------ | ----------------- |
| 86 | Partition List | Medium | java |
排序链表
-
例题:148 Sort List【medium】
-
题意:在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序
-
test case:
Input: 4->2->1->3
Output: 1->2->3->4
-
解题思路 : 归并排序。这题有很多解法,题目要有时间复杂度是O(nlogn),满足这个条件的有快速排序,堆排序,归并排序,三者的空间复杂度分别为O(1),O(N),)O(N)。对于链表而言,在进行归并操作时并不需要像数组的归并操作那样分配一个临时数组空间,所以是O(1)的空间复杂度,只需要改变节点的next指针的指向,就可以表示新的归并后的顺序。
-
思考:快排和归并排序的时间复杂度都是O(nlogn),实践证明快排的速度比归并排序的速度更快,对于数组排序成立,为什么在链表中归并排序更快呢?
-
code:
Java
public class Solution {
public ListNode sortList(ListNode head) {
if (head == null || head.next == null)
return head;
// step 1.把链表分成两半
ListNode prev = null, slow = head, fast = head;
while (fast != null && fast.next != null) {
prev = slow;
slow = slow.next;
fast = fast.next.next;
}
prev.next = null;
// step 2.对于每一部分的链表进行排序
ListNode l1 = sortList(head);
ListNode l2 = sortList(slow);
// step 3. 合并 l2 和 l2
return merge(l1, l2);
}
ListNode merge(ListNode l1, ListNode l2) {
ListNode l = new ListNode(0), p = l;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
p.next = l1;
l1 = l1.next;
} else {
p.next = l2;
l2 = l2.next;
}
p = p.next;
}
if (l1 != null)
p.next = l1;
if (l2 != null)
p.next = l2;
return l.next;
}
}
- 思考解答:如果待排序的元素存储在数组中,那么快速排序相对归并排序就有两个原因更快。一是,可以很快地进行元素的读取(相对于链表,数组的元素是顺序摆放的,而链表的元素是随机摆放的),数组的partion这步就比链表的partion这步快。二是,归并排序在merge阶段需要辅助数组,需要申请O(N)的空间,申请空间也是需要时间的。而快排不需要额外申请空间。如果待排序的元素存储在链表中,快排的优点就变成了缺点。归并排序于是就速度更优了。
LeetCode中 包含链表排序的题目:
| 序号 | 题目 | 难度 | 代码 |
| — | — | — | — |
| 143 | Reorder List | Medium | java |
| ---- | ------------------------------------------------------------ | ------ | ----------------- |
| 147 | Insertion Sort List | Medium | java |
| 148 | Sort List | Medium | java |
链表的问题是面试当中常常会问到的,比如链表的倒置,删除链表中某个结点,合并两个排序链表,合并 k 个排序链表,排序两个无序链表等。
这些题目一般有一些相应的技巧可以解决。
第一,引入哨兵结点。如我们开头说的 dummy node 结点。在任何时候,不管链表是不是空,head结点都会一直指向这个哨兵结点。我们也把这种有哨兵结点的链表叫做带头链表。
第二,双指针法。这种用法适用于查找链表中某个位置,判断链表是否有环等
第三,分之归并法。这种用法适用于链表的排序处理,如合并 k 个排序链表,排序两个无序链表等。
第四,在解答的过程中,要多考虑边界情况。
-
链表为空
-
链表中只有一个结点
-
链表中只包含两个结点
-
代码在处理头结点跟尾结点是否存在什么问题
参考资料:
1.https://leetcode-cn.com/problems/sort-list/discuss/46714/Java-merge-sort-solution
2.https://leetcode-cn.com/problems/merge-two-sorted-lists/discuss/9735/Python-solutions-(iteratively-recursively-iteratively-in-place).
Android 面试必备 - http 与 https 协议
Android 面试必备 - 计算机网络基本知识(TCP,UDP,Http,https)
题外话
我们见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。
其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。
不断奔跑,你就知道学习的意义所在!
注意:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
Android 面试必备 - http 与 https 协议
Android 面试必备 - 计算机网络基本知识(TCP,UDP,Http,https)
题外话
我们见过很多技术leader在面试的时候,遇到处于迷茫期的大龄程序员,比面试官年龄都大。这些人有一些共同特征:可能工作了7、8年,还是每天重复给业务部门写代码,工作内容的重复性比较高,没有什么技术含量的工作。问到这些人的职业规划时,他们也没有太多想法。
其实30岁到40岁是一个人职业发展的黄金阶段,一定要在业务范围内的扩张,技术广度和深度提升上有自己的计划,才有助于在职业发展上有持续的发展路径,而不至于停滞不前。
不断奔跑,你就知道学习的意义所在!
注意:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-ei9PbnBt-1715797554929)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!