刷题过程中对于一系列技巧知晓了,但是拿过来题目 却不知道用什么对了……
或者说 对应的题目看过、刷过、实现过 却……具体细节又不会了……(特别是 魔鬼细节二分搜索哇!)
再者说 有些题目的“奇技淫巧”不是那么容易在短时间内想到的,所以想到了要针对面试厂家进行速刷……并相应的梳理刷题过程中 自己知识框架/思维 中短缺、不熟练的部分再进行二刷、三刷……
另外很重要的一点: 对刷过的题目 要尝试进行分类、可以快速索引到……
以上就是本篇的核心思想和目前刷题中遇到的问题。针对加粗重点部分进行重点突破……
更新:剑指Offer需要需要进一步由重点到细节——逐渐刷完!
当遇到一个比较复杂的问题时,可以通过画图、举例或者分解来考虑,从具体的实例中总结普遍规律。
注重效率的考量,这66道题用蛮力法解决可能都不是很难,但是我们需要考虑的是时间效率和空间效率的平衡,以空间换时间有时候是一个不错的选择。
常用数据结构和数据操作是基础,要重点掌握,如树的遍历,排序,查找,递归等操作,在题目中反复用到,要深刻理解算法思想。
算法题主要分成数据结构和具体算法部分,简单归类如下。
LinkedList
003-从尾到头打印链表
014-链表中倒数第k个结点
015-反转链表
016-合并两个或k个有序链表
025-复杂链表的复制
036-两个链表的第一个公共结点
055-链表中环的入口结点
056-删除链表中重复的结点
Tree
004-重建二叉树
017-树的子结构
018-二叉树的镜像
022-从上往下打印二叉树
023-二叉搜索树的后序遍历序列
024-二叉树中和为某一值的路径
026-二叉搜索树与双向链表
038-二叉树的深度
039-平衡二叉树
057-二叉树的下一个结点
058-对称的二叉树
059-按之字形顺序打印二叉树
060-把二叉树打印成多行
061-序列化二叉树
062-二叉搜索树的第k个结点
Stack & Queue
005-用两个栈实现队列
020-包含min函数的栈
021-栈的压入、弹出序列
044-翻转单词顺序列(栈)
064-滑动窗口的最大值(双端队列)
Heap
029-最小的K个数
Hash Table
034-第一个只出现一次的字符
图
065-矩阵中的路径(BFS)
066-机器人的运动范围(DFS)
斐波那契数列
007-斐波拉契数列
008-跳台阶
009-变态跳台阶
010-矩形覆盖
搜索算法
001-二维数组查找
006-旋转数组的最小数字(二分查找)
037-数字在排序数组中出现的次数(二分查找)
全排列
027-字符串的排列
动态规划
030-连续子数组的最大和
052-正则表达式匹配(我用的暴力)
回溯
065-矩阵中的路径(BFS)
066-机器人的运动范围(DFS)
排序
035-数组中的逆序对(归并排序)
029-最小的K个数(堆排序)
029-最小的K个数(快速排序)
位运算
011-二进制中1的个数
012-数值的整数次方
040-数组中只出现一次的数字
其他算法
002-替换空格
013-调整数组顺序使奇数位于偶数前面
028-数组中出现次数超过一半的数字
031-整数中1出现的次数(从1到n整数中1出现的次数)
032-把数组排成最小的数
033-丑数
041-和为S的连续正数序列(滑动窗口思想)
042-和为S的两个数字(双指针思想)
043-左旋转字符串(矩阵翻转)
046-孩子们的游戏-圆圈中最后剩下的数(约瑟夫环)
051-构建乘积数组
我个人觉得数据结构和DP在面试中手写代码的几率比较高,因此笔者目前的刷题节奏主要是:
剑指offer->Leetcode动态规划->面试前再过一遍剑指offer
有个重要的点是:每道题做完一定要去讨论区!
讨论区有非常精简的大神级代码,你好不容易AC了一道题准备去讨论区吹(装)水(逼),点开一看,“握草,还可以这样”。
思考为什么他可以写出这么好的代码,把每道题的思路理解后用笔记本记录下来,争取刷到融会贯通,即看见有个题能自动归类到某个方面,这样有一定好处。面试最重要的是让面试官日后能愿意与你以后一起工作,因此沟通交流非常重要。比如有时候面试需要交流,看着像是一道排序的题做不出来,就可以跟面试官交流:“我有几个不成熟的想法,一排序,二动态规划,三是直接搜索算法”,面试官可能就给个提示:“你先用排序试试吧“。
我的题解:
#define For(x,y,z) for(int x=y; x
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
/**
* @Description: 首先快慢指针 找中点。
* @param {*}
* @return {*}
* @notes: 一前一后往后插
*/
void reorderList(ListNode* head) {
ListNode *slow=head, *fast=head;
while(fast->next!=nullptr && fast->next->next!=nullptr){
fast = fast->next->next;
slow = slow->next;
}
ListNode *l1=head, *l2=slow->next;
slow->next = nullptr; // 这个指针游离了! 就会错误!!!!
insertTwoReveList(l1, l2);
}
// helper 后序递归插入
void insertTwoReveList(ListNode* &l1, ListNode *l2){
if(l2 == nullptr){
return ;
}
insertTwoReveList(l1, l2->next);
ListNode *tmp1 = l1;
l1->next = l2;
l2->next = tmp1;
l1 = tmp1;
}
};
#define For(x,y,z) for(int x=y; x
class Solution {
public:
/**
* @Description: 技巧:先 上下翻转,然后再对角翻转即可
* @param {*}
* @return {*}
* @notes:
*/
void rotate(vector<vector<int>>& matrix) {
int m = matrix.size(), n = matrix[0].size();
For(i,0,m/2){
matrix[i].swap(matrix[m-i-1]);
}
// 对角元素 互换
For(i, 0, m){
For(j, i+1, n){
swap(matrix[i][j], matrix[j][i]);
}
}
}
};
#define For(x,y,z) for(ll x = y; x < z;++x)
typedef long long ll;
class Solution {
public:
/**
* @Description: 大于目标值 最小子数组和
* @param {int} target
* @return {*}
* @notes:
*/
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size();
int left=0, right=0;
int minArr = INT_MAX;
int sum=0;
while(right<n){
sum+=nums[right];
right++;
while( target >= sum ){
// 查看是否最小
int len = right-left + 1;
minArr = minArr>len?len:minArr;
// shrink windows
sum -= nums[left];
left++;
}
}
return minArr;
}
};
解题思路
因为空间复杂度要求 O(1),不能使用 除法,因此一定需要在 乘法 过程中得到所有答案;
由于输出数组不算在空间复杂度内,那么我们可以将 L 或 R 数组用输出数组来计算。先把输出数组当作 L 数组来计算,然后再动态构造 R 数组得到结果。 让我们来看看基于这个思想的算法。
L
数组。res[i]=res[i]∗R
。然后 R 更新为 R=R∗nums[i]
,其中变量 R
表示的就是索引右侧数字的乘积。我的题解:
#define For(x,y,z) for(ll x = y; x < z;++x)
typedef long long ll;
class Solution {
public:
/**
* @Description: 关键是 不能用除法+还要原地或者用res 进行O(1)的算法题解。
* @param {*}
* @return {*}
* @notes: 使用res 计算元素左侧的乘积; 再进行一次从右到左 计算元素右边的乘积,对应乘res 即可得到结果。
*/
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<int> res(n, 1); // 首先代表 i左侧的元素乘积
For(i, 1, n){
res[i] = res[i-1]*nums[i-1];
}
// 右边有个tmp
int tmp = 1; //动态右边的累乘 除去自身
for(int j = n-2; j >= 0;j--){
tmp *= nums[j+1];
res[j] *= tmp;
}
return res;
}
};
我的题解
min_num 记录以 nums[i-1] 结尾的乘积最小值,max_num 记录以 nums[i-1] 结尾的乘积最大值。
#define For(x,y,z) for(ll x = y; x < z;++x)
typedef long long ll;
class Solution {
public:
/**
* @Description: 最大乘积 有负数,所以可以记录最大和最小值。
* @param {*}
* @return {*}
* @notes:
*/
int maxProduct(vector<int>& nums) {
int min_num = 1, max_num = 1; // 代表 以 nums[i-1]为结尾的最小和最大的值
int maxNow = INT_MIN;
For(i, 0, nums.size()){
if(nums[i] < 0) {
// 最大最小互换
int tmp = min_num;
min_num = max_num;
max_num = tmp;
}
min_num = min( min_num*nums[i], nums[i]);
max_num = max( max_num*nums[i], nums[i]); // 要么自己开始,要么和之前一样开始。
maxNow = max_num>maxNow?max_num:maxNow;
}
return maxNow;
}
};