刷题套路总结
很多简单题,看似简单实则不简单,因为面试官会让你多种解法、做出时间复杂度最低的算法。
因此刷题过程一定要记录时间复杂度和空间复杂度!!!
数组专题
分析:
最直接的想法,枚举数组中元素,再遍历数组,计算其枚举元素的个数,时间复杂度是O(n^2)。很明显在面试中不会这么考察。
1如何保存数组中的数字以及出现的个数呢?很容易想到哈希表k-v方法
2在数组中个数超过一半的数字,那么其出现位置必有一个在数组下标为 n/2 位置 (这里是向下取整),为什么?本质上是数学推导,这里就记住吧!
3摩尔投票法,这个算法目前不想学习,感觉普适性不高,后期有遇到再学习,先来记录一下
哈希表法:
要时刻掌握哈希表的常用方法,get(key)、put(key,value)、containsKey(key)、
entrySet()、keySet()、getKey()、getValue()
注:keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry,Map是java中的接口,Map.Entry、HashMap是Map的一个内部接口。
所以我们遍历map的entry类型数据,使用entrySet(),并且返回的数据类型使用Map.Entry
时间复杂度:O(n),遍历数组
空间复杂度:O(n),构建了一个哈希表
class Solution {
// 哈希表记录k-v
public HashMap<Integer,Integer> counts(int[] nums){
HashMap<Integer,Integer> res = new HashMap<>();
for(Integer num:nums){
// 类似打擂台,相同key就+1
if(res.containsKey(num)){
res.put(num,res.get(num) + 1);
}else{
res.put(num,1);
}
}
return res;
}
public int majorityElement(int[] nums) {
int len = nums.length;
// 如何记录多种元素及其个数?使用哈希表
HashMap<Integer,Integer> map = counts(nums);
// 遍历map中的数据
Map.Entry<Integer,Integer> majorityEntry = null;
for(Map.Entry<Integer,Integer> temp:map.entrySet()){
if(majorityEntry == null || temp.getValue() > majorityEntry.getValue()){
majorityEntry = temp;
}
}
return majorityEntry.getKey();
}
}
排序法:
使用java内置的Arrays.sort(nums),默认使用的是快速排序算法。
时间复杂度:O(nlogn),这是数组排序的复杂度
空间复杂度:O(n)
03. 数组中重复的数字
哈希表具有搜索功能。
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public int findRepeatNumber(int[] nums) {
/*
这道题题很容易想到使用哈希表来做,通过比较值是否存在于哈希表里
这里我其实惯性思维使用了k-v方法,实际上使用HashSet即可
*/
Map<Integer,Integer> map = new HashMap<>();
int len = nums.length;
for(int i = 0; i < len; i++){
if(map.containsKey(nums[i])){
return nums[i];
}
map.put(nums[i],i);
}
return -1;
}
}
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
/**
这道题很有意思,三个方面来考虑这个问题。
1.直接暴力搜索,双重for循环来遍历寻找target,时间复杂度O(m*n),肯定不可取
2.优化暴力搜索(一维数组的二分法),每一行的数据都是有序的,每一行可以进行二分法遍历o(mlogn)
3.线性搜索,将整个二维数组逆时针旋转45度,会发现和一个二查搜索树相似,左边小右边大,最后遍历o(m+n)
*/
// 优化暴力搜索
// int m = matrix.length;
// for(int i = 0; i < m; i++){
// int left = 0;
// int right = matrix[i].length - 1;
// while(left <= right){
// int mid = (left + right) / 2;
// if(target == matrix[i][mid]){
// return true;
// }
// else if(target > matrix[i][mid]){
// left = mid + 1;
// }else{
// right = mid - 1;
// }
// }
// }
// return false;
// 线性查找
// 特判!
if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
return false;
}
int row = 0;
int col = matrix[0].length - 1;
while( row < matrix.length && col >= 0){
if(target == matrix[row][col]){
return true;
}
else if(target > matrix[row][col]){
row++;
}else{
col--;
}
}
return false;
}
}
class Solution {
public int minArray(int[] numbers) {
// 这道题居然是一道简单题,难以置信!!!
// 排序数组问题优先想到二分法
// 输出最小值问题实际上就是在寻找旋转点
// 时间复杂度o(logn)
int len = numbers.length;
int left = 0;
int right = len - 1;
// 为什么对number[right]比较而不是对left? 因为right必定是右排序数组,而left却无法确定
while(left < right){
int mid = (left + right) / 2;
if(numbers[mid] > numbers[right]){
// 说明旋转点在右侧
left = mid + 1;
}else if(numbers[mid] < numbers[right]){
// 说明旋转点在左侧
right = mid;
}else{
// 两者相等情况,无法判断旋转点在那一侧,那么就缩小right判断
// 这个需要数学证明~~~
right--;
}
}
// 返回旋转点
return numbers[left];
}
}
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
// 使用哈希表:构建原链表和新链表之间的联系
// 特判
if(head == null){
return null;
}
HashMap<Node,Node> map = new HashMap<>();
// 建立遍历指针
Node cur = head;
while(cur != null){
Node node = new Node(cur.val);
// 储存原节点 和 新节点
map.put(cur,node);
// 遍历指针下移
cur = cur.next;
}
// 已经构造好新链表节点,开始建立next和random引用指向
cur = head;
while(cur != null){
// next引用指向:新节点指向新节点
map.get(cur).next = map.get(cur.next);
// random引用指向
map.get(cur).random = map.get(cur.random);
// cur指针下移
cur = cur.next;
}
// 返回新链表的head节点
return map.get(head);
}
}