Leetcode刷题思路
- 归纳法,如题628
- 利用数学公式推理
- hash表
- 双hash表思想键值对调表查询
- 键值对调
- 利用hash表记录一些位置信息,然后通过if条件更新,比如题目:3
- 链表
- 反转链表"cppc"
- 用哨兵节点方便控制一些逻辑,链表涉及到修改next要保存临时变量
- 利用快慢指针优化链表遍历(快指针到底的时候,慢指针就是答案),比如题目:剑指 Offer 22
- 直接指向下一个数就删除了,当然了也要把val给换了
- 反转链表用到双指针加一个temp,也可以递归用两个参数两边来搞
- 矩阵
- 缓存思想的再可优化,比如题目1380
- 解压压缩思路
- 1572题:逐行算然后减去重叠的,双循环变 O{n} ,小心短路运算的坑,而且可以一次性对结果加两次妙,使用&1当0或者当1来计算妙啊
int diagonalSum(vector<vector<int>>& mat) {
int n = mat.size(), sum = 0, mid = n / 2;
for (int i = 0; i < n; ++i) {
sum += mat[i][i] + mat[i][n - 1 - i];
}
return sum - mat[mid][mid] * (n & 1);
}
-
脑筋急转弯
- 反推思想,既然很多种情况都是为了达成某个目的,那么从这个目的入手,利用数学公式反推解决问题。
- 整体思维法,不要成谜于解题步骤,从全局出发考虑问题。比如 657这道题
- 替换思路,比如用数组模拟栈的一些特点,来解决问题,因为数组比栈直观,或者说更容易理解比如题目:1614
- 反向思考问题,比如1688这道题:共有n个队伍,一个冠军,需要淘汰n-1个 队伍。每一场比赛淘汰一个队伍,因此进行了n-1场比赛。所以共有n-1个配对。
-
回溯
- 求幂集就是选择形成的n叉树
-
贪心算法
- 转换思想,当某中计算过程过于复杂,转化成另外一问题来解决,而另外一种问题解决了就是最优答案了,因为贪心的理念也是当前最优的就可以了。
- 转换思想再转换,把另一个问题转换为另另一个问题来解决,典型题1551.而且不一定非得构建题目条件中的数组,也可以解决问题还省空间
-
位运算
- 异或先求不同,再求解,其实就是一个转换的思想
- i|=32大小写字母互相转换
- 2进制转10进制,可以顺着乘以公式为ans = ans*2+0或1
- 0 ^ x = x ; x ^ x = 0 ; 2x ^ (2x+1) = 1
- a ^ b ^ b = a(这个交换的思路,其中运用到了临时变量)
- a ^ b ^ a = b
- 奇数&(-2)等于-1效果
- 如果(n&1)==1,说明了n的尾巴是为1,如果一个数是2次幂,那么只有一个1其余全是0,形如:1,10,100,1000,10000
- 如果位运算结果是0或1直接用于结算,不用判断了,比如题目:剑指 Offer 15
-
数组
- 把数组看成是一个环形
- 用两个char相减来做数组的索引。
- 两数组各元素进行异或、和,两个条件结合即可判断数组元素是否相同。
- 单独拿一个数当max,用max做循环变量方便计算,比如题目5685
- 用数组代替hash表,其中索引当作是key来对待
- 前缀和思路
- 储存下标的思路,让for循环减少一层,比如题1380
- 遍历时候利用else重置flag状态
- %运算解决循环问题
- 利用求容器的长度,代替求差异,比如题1207
- 1和10划分法,需要11种结果,那么先构造10种,然后以10种的和构造第11种
- 当一个数组需要两种操作的时候,在循环筛选的时候同时做这两种操作,可以节省开销
- 三重for循环,可以按条件提前剪枝
- 数组划分一半
- 数组项用完取负数拿来当标志
- 前缀和思想,求区间之间的和,主要思想是开辟空间,记录这个下标之前的和,然后根据这个空间里面的两个数字相减就是其和,复杂度0{1}
- 先排序再求解,排序后的位置可能还隐藏着更多帮助解题的信息,比如题目:1365
- for迭代的既i++,又让i+=2,关键还对字符串直接修改呢
- 局部判断失败就跳出,变量重置为0思想,这样的话一个变量重置了又可以判断其他的状态了,比如题目1662
- 利用不对等式先化解一部分结论,然后根据这部分再来判断。
- 迭代数目为原数组的一半思路
- 利用一个数组的一些变量比如头节点做标志位,为了简洁
- 题目1464:排序思路先排序就可以很少复杂度求出答案了,或者不排序一次遍历找出第一大和第二大的数(思路是先赋值first,再更新first,这样first就存的是第二大的数) 。下面是反面教材
-
题目1603:让数组项通过自减和多信心存储,使得一个数组做了很多事情,很妙啊
-
利用构造双倍加工集合长度的数组一次遍历思路,并且循环里面走两遍另起的index++
-
构建26个字符数组思路
-
字典
- 利用字典优化双for循环,这代码实在是精妙
Dictionary<int,int> dic=new Dictionary<int,int>();
foreach(int i in nums)
{
if(dic.ContainsKey(i))
{
dic[i]++;
res+=dic[i]-1;
}
else
{
dic.Add(i,1);
}
}
-
递归
- 递归找到节点再添加,在递归中完成添加
- 全排列需要记住三个状态,用过的,第几层了,path.
- 递归中,重复设置容器的思路很赞啊,题目: 剑指 Offer 06
- 位置变量先递进去,再进行运算,比如题目1290,不一定开头就带int计数,特别是有时候不知道计数是多少的时候。
- 利用&&做递归短路出口,先递后归,逐渐完成加法,实在是妙啊
- 只有一个出口不是在开头而是在末尾的写法
- 和链表很配啊
-
队列
- 使用队列来模拟滑动窗口
- 双栈或双队列
-
双指针
- 先排序
- 快慢指针,一个走1步,一个走两步
-
栈
- 利用continue还有相等就push,不相等就pop的解题思路
- 双栈或双队列
- 遍历的过程中判断栈是否为空进行操作,而且if不是连起来的比如题目:1021
-
数学
- 等差数列求和公式:(n^2+n)/2
- 整合思路:把多种步骤简化成一种公式,通过解数学公式,解开编程题如题目LCP17
- 三元符替代Math.Max()
-
字符串
- 跳跃查找法,一次跳多格看看是否满足上下文比如题1309
- KMP算法,1.做一个前缀表,2.在前缀表里面找最长相等前后缀,3.如果不匹配的那个字符查找前缀表,前缀表的值就是字符串要从0索引开始移动的位置。
- 利用字符的asc码之间的计算公式转换成另一种问题来解决
- 通过循环取模运算遍历字符每一个字符串,好处就是不用转换很多次哦。比如题目1281
- 活用交换思想Swap,这样做到不开辟空间
- 原地反转3次的思路,局部两次,整体一次
- 遍历的的时候把i++,单独放在循环内部里
-
滑动窗口
- 只需要3个for循环就实现了,关键是for循环种下标的灵活运用,比如1588
-
矩阵
- 旋转物体中心点,并非坐标系轴哦
-
动态规划
- 分治思想并且把问题先转换成子问题,先求第一个子问题,在求第子问题过程中把真正问题解决了,从右向左递推。 经典题目53.最大子序和
-
分治
- 归并排序就是穿针引线的做法,包括两个排序链表的合并也是同理。
-
复杂度分析
- 最好
- 最坏
- 平均(涉及到概率学)
- 均摊(一种更简单平均复杂度,每一次 O(n) 的插入操作,都会跟着 n-1 次 O(1) 的插入操作,所以把耗时多的那次操作均摊到接下来的 n-1 次耗时少的操作上,均摊下来,这一组连续的操作的均摊时间复杂度就是 O(1))
-
排序算法
- 基数(就是个位放几次,十位放几次,从上放进去,从下向上取出,可以使用队列)、计数(就是在排序磨具里统计数,再还原回去就好了)、桶排序 (类似哈希的策略,每个桶再单独排序)
-
图算法
- 深度优先遍历
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Grap : MonoBehaviour
{
void Start()
{
var g = new Graph(new List<string>() {"1", "2", "3", "4", "5", "6", "7", "8"});
g.SetEdge("1", "2");
g.SetEdge("1", "3");
g.SetEdge("2", "4");
g.SetEdge("2", "5");
g.SetEdge("2", "1");
g.SetEdge("3", "1");
g.SetEdge("3", "6");
g.SetEdge("3", "7");
g.SetEdge("4", "2");
g.SetEdge("4", "8");
g.SetEdge("5", "2");
g.SetEdge("5", "8");
g.SetEdge("6", "3");
g.SetEdge("6", "7");
g.SetEdge("7", "3");
g.SetEdge("7", "6");
g.SetEdge("8", "4");
g.SetEdge("8", "5");
g.ShowGraph();
g.Dfs();
}
void Update()
{
}
}
public class Graph
{
private List<string> Vertexs;
private int[,] Matrix;
private bool[] VisitedVertexIndex;
public Graph(List<string> vertexs)
{
Vertexs = vertexs;
int capacity = Vertexs.Count;
Matrix = new int[capacity, capacity];
VisitedVertexIndex = new bool[capacity];
}
private void VisitedVertex(int i)
{
Debug.Log("访问了:" + GetVertexVal(i));
VisitedVertexIndex[i] = true;
}
private string GetVertexVal(int i)
{
return Vertexs[i];
}
public void Dfs()
{
for (int i = 0; i < Vertexs.Count; i++)
{
if (VisitedVertexIndex[i] == false)
{
dfs(i);
}
}
}
private void dfs(int startIndex)
{
VisitedVertex(startIndex);
int w = FindFirstNeighborVertexIndex(startIndex);
while (w!=-1)
{
if (VisitedVertexIndex[w]==false)
{
dfs(w);
}
w = FindNextNeighborVertexIndex(startIndex, w);
}
}
/// <summary>
/// 这里咱们的是不带权值的无向图
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="val">0表示自己或者无法直接联通</param>
private void SetEdge(int x, int y, int val)
{
Matrix[x, y] = val;
}
public void SetEdge(string self, string neighbor, int val = 1)
{
SetEdge(Vertexs.IndexOf(self), Vertexs.IndexOf(neighbor), val);
}
public void ShowGraph()
{
for (int i = 0; i < Matrix.GetLength(0); i++)
{
string lineStr = "";
for (int j = 0; j < Matrix.GetLength(1); j++)
{
lineStr += Matrix[i, j] + "\t";
}
Debug.Log(lineStr);
}
}
/// <summary>
/// 查找第一个邻接点的,以x为key,y为值
/// </summary>
/// <returns></returns>
public int FindFirstNeighborVertexIndex(int selfIndexX)
{
for (int i = 0; i < VisitedVertexIndex.Length; i++)
{
if (Matrix[selfIndexX, i] >0 )
{
return i;
}
}
return -1;
}
public int FindNextNeighborVertexIndex(int selfIndex, int NextVertexIndexY)
{
for (int i = NextVertexIndexY+1; i < VisitedVertexIndex.Length; i++)
{
if (Matrix[selfIndex, i] > 0)
{
return i;
}
}
return -1;
}
}
* 矩阵模拟的迷宫图算法中有很多bool来做分支判断。
* 最小生成树(要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树)
* 拓扑排序(一个较大的工程往往被划分成许多子工程,我们把这些子工程称作活动(activity)。在整个工程中,有些子工程(活动)必须在其它有关子工程完成之后才能开始,也就是说,一个子工程的开始是以它的所有前序子工程的结束为先决条件的,但有些子工程没有先决条件,可以安排在任何时间开始。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。)
* 求关键路径算法中带出 AOE网和AOV网的区别
AOV网:其顶点用来表示活动。AOE网是用来表示活动之间的制约关系。
AOE网:顶点表示事件,边表示活动,边上的权值用来表示活动持续的时间。AOV网是用来分析工程至少需要花多少时间完成,或是为了缩短时间需要加快哪些活动等问题。
- 堆
- 用数组模拟关键点
- heapfly函数
- S型向上heapfly函数
- 堆排序时候,for循环不断减少做heapfly就完成排序了
- 注意事项
- 编写代码严谨性命名规范性,这些都能减少错误,争取一次bugFree不然浪费很长时间,int max =xx,后面就要用max去搞了
- 索引公式p=(i-1)/2,c1=i2+1,c2=i2+2;
- 用数组模拟关键点
- 二叉排序树
- 中间二分开花,如题目108
- 删除有3种情况
- 中序遍历非递归的实现双重while循环,并且最外面一层while循环带两个bool判断
- 矩阵
- 把矩阵化成字典来求解