前言
用非常意识流的语言简述每题的思路,可能只有我读得懂.
分治法
二叉树
宽度优先搜索
岛屿的个数
描述:给一个01矩阵,求不同的岛屿的个数。0代表海,1代表岛,如果两个1相邻,那么这两个1属于同一个岛。我们只考虑上下左右为相邻。
这本质上是图的搜索,所以用BFS。BFS都要用到一个额外的空间来记录是否被访问过,但这里可以直接修改原来的数组,如果不要求保留原来数组的值。从第一个遍历到最后一个,如果是1,那么说明这是个岛屿,然后通过BFS把这个岛屿都标志为已经访问,以后就不会重复计算岛屿的个数了。
不用分层。
|
|
二叉树的层次遍历
描述: 给出一棵二叉树,返回其节点值的层次遍历(逐层从左往右访问)
因为要分层,所以要有循环要有三层:
while
for (i … size)
for (left … right)
记住要在当前点把poll出去的点记录,即当机立断原理。
|
|
安排课程
统计每个课程的入度,构建一个有向图.
然后从入度为0的点进行图的遍历,把入度为0的点加进队列,表示该课程已经上了,然后下一门的入度都减一,把0的点再加进队列里.最后如果上的课没有到达预定的值,表示有死环,返回空数组.
|
|
Knight Shortest Path
通过简单的bfs就行,技巧是设置一个方向数组。
|
|
Zombie in Matrix
遍历一遍数组,把僵尸放进队列,统计人数;
while循环中,首先判断people是否为0(如果是0表示没有人了),分层遍历,僵尸出栈咬人,然后把新僵尸入栈.
|
|
图是否是树
一棵树一定满足边的数目比节点数多一.
如果不符合的先返回false;
构造一个无向图,然后进行遍历,统计遍历到的节点数,看看是否比边数少一;
|
|
克隆图
用hashmap记录每一点,然后bfs遍历图,克隆节点,然后从hashmap中拿出节点,然后克隆原图的的邻居。
要用一个set来记录遍历过的点.
六度问题
做个简单的bfs就行,记住分层的时候不能用queue.size作为结束条件. edges case要注意一个源节点和目的节点相同.
|
|
非递归深搜
二叉树的前序遍历
刚开始我的思路是利用计算机递归的原理,在每个节点进栈的同时,把当前遍历到左节点还是右节点的状态也同时进栈.
当我苦苦思索怎么记录遍历的状态时,九章的答案让我有了醍醐灌顶的感觉,还有这种操作…
二叉树的中序遍历
对于九章的答案我是很服气的..简洁巧妙…
|
|
二叉树的后序遍历
|
|
深度优先搜索
分割回文串
|a|b|c|d|e|f| ,这样分割,然后把分割的位置放到path中做dfs, 递归的参数中的index是index之前的元素都被判断过了,已经切成若干回文串,从index开始看能不能切成若干回文串. 递归结束条件是index == s.length(), 表示整个字符串都完成切割.
记住移除最后一个path元素的写法是: path.remove(path.size() - 1);
|
|
数字组合 II
去重的时候,index之后的不能重复选,意思是在这一层中只能选一个,不能选重复的数字.i > index;
|
|
数字组合
去重 主要不是出现第一次的数字就跳过,因为之前已经有了,而且是不限的,所以要跳过.
|
|
带重复元素的子集
空集是任何集合的子集,所以当集合为空时也要加入.
去重也是i>index,每层选一个不同的加入.
|
|
带重复元素的排列
如果是不带重复元素的排列,那只要在每一层中选一个之前没选过的放进去(path.contians(nums[i])).
如果是带重复元素的排列,用老方法的话,会造成无法把重复元素加进path中.所以要用isNA(不用重复初始化)数组表示哪个位置的元素已经加入了,但是还有一个问题就是用位置不能区分元素是否一样,(在同一层中不能选重复的元素),那就只能sort.那么判断这一层中有没有把和当前数值相同的元素加进path放进递归了,就需要和前面一个元素比较,如果相同,而且可用(或者不可用,这样写是倒过来加,比如[2’, 2’’, 2’’’],结果是[2’’’,2’’,2’]),说明同一种元素已经放过进path里面了,跳过.
所以需要 排序和前置检查, 记录数组.
全排列
每层从0开始选一个之前没选的即可.
|
|
Word Break II
待续,为什么九章用hashmap做就能过
|
|
单词接龙 II
用bfs构建一个图,从start开始,到end结束,同时要记录每个点到start的距离。
找路径用dfs,从end开始,到start结束。
|
|
数组与链表
滑动窗口内数的和
要注意k == nums.length == 0 的情况.
res[]的大小是nums.length - k + 1
res[0] 存初始的值
res[i - k + 1] = res[i - k] - nums[i - k] + nums[i]
|
|
Insert into a Cyclic Sorted List
有四种情况:
- pre <= x <= curr
- x比所有元素都大,那么应该放在最大最小交界处(pre > curr)
- 同上, x比所有元素都小
- 如果把链表都遍历完了还没有完成插入,那么就应该放到最后.(当链表只有一个元素或者链表中的数值大小都一样)
|
|
合并两个排序链表
while 中的条件是 l1 != null && l2 != null (是&& )
|
|
子数组之和
把每个prefixSum都记录下来,map先添加(0, -1).
之后看map有没有相同的prefixSum,如果有就可以返回了.
|
|
最大子数组
minPre = 0;
这样最小前缀不会出现正数.
|
|
最接近零的子数组和
把所有的前缀排序,找出最接近的.开始的下标要加一.
|
|
复制带随机指针的链表
先复制节点
1->1’->2->2’->null
在从头开始复制random
最后把原始链和复制链拆开.
要注意null的问题,当random==null时,cp的random不再是random.next;
同理,拆链的时候,拆到最后,cp节点不用再操作.
|
|
带环链表
设置一个fast指针,一个slow指针,fast每次走两步,slow每次一步.
初始的时候,slow = head, fast = head.next;
while(fast != null && fast.next != null)
while里先判断fast == slow
|
|
链表排序
首先要找到一个链的中点.
1->null,中点是null,findMid返回1;
1->2->null,中点是2,findMid返回1;
sortList(null || 1->null) 返回head(如果1->null不直接返回的话会无穷递归),这函数的作用是二分拆开,最后合并返回.
|
|
两个排序数组的中位数
- i 或者 j 超出范围, 直接选另外一个数组的
- k <= 1, 直接比较A[i] 和 B[j],返回小的
- i + len - 1 或者 j + len - 1 超出范围,扔掉比较长的数组的len个
- 两者比较,扔掉小的
|
|
K组翻转链表
如果head为空或者head.next为空就可以直接返回了.
先统计链表的长度,如果不够k个就返回head.
翻转k个节点要用到4个指针,分别是pre, p, next, 还有head
head最后链接下一个翻转返回的节点.
|
|
双指针
Two Sum - Data structure design
要注意value - i == i的情况.
|
|
去除重复元素
- 可以用Set来记录
- 先排序, 然后insert初始为1,因为第一点肯定不用插入,然后比较后一点和前一点,如果不同(意味着第一次出现)就插入insert处.last之前都是不重复的
- 先排序, 然后insert初始为1,insert记录着上一个不重复数字的值.
|
|
Two Sum - Less than or equal to target
如果nums[start]+nums[end]<=target,那么res += end - start, start这一点统计完了,start++;
如果nums[start]+nums[end]>target,那么end要往回走,直到满足条件.
|
|
Two Sum - Input array is sorted
|
|
Two Sum - Unique pairs
记得要去重,要把end移到和之前数值不一样的点.
|
|
两数和的最接近值
在指针移动的过程中更新diff就行,把diff初始化成最大整型即可.
|
|
颜色分类
判断条件是i <= pr,因为不知pr是什么内容,所以要判断一下.
nums[i]==0,left++,i++;
|
|
排颜色 II
相当于做一次快排,当快排中的while结束的时候,end和start相差1。end最后停留在<=mid的地方,或者越界; start最后停留在>mid的地方,或者越界
|
|
三数之和
最左边的指针位置是i + 2 < numbers.length
记得排序和去重
|
|
数组划分
当left==right时,若nums[left]
|
|
哈希函数与堆
哈希函数
设字符串为abc,那么hash值的计算过程如下:
hash = (hash 31 + ‘a’) % mod
hash = 031 + ‘a’
hash = ‘a’31 + ‘b’
hash = ‘a’31^2 + ‘b’*31 + ‘c’
|
|
优秀成绩
用Arrays排序来解决,按照学号优先再按成绩又高到低排.
|
|
K个最近的点
用优先队列(大根堆)做,把所有的point加进优先队列中,队列大小超过k之后移除队列的头.
|
|
Kth Largest Element II
优先队列(小根堆)
|
|
前K大数
优先队列小根堆
|
|
重哈希
把原来的数组中的链表按新的键值加入到新的hash表中.
先把链表的头结点移到新的hash表中,如果该位置是null的话,可以直接加入,否则找到该位置的链表的尾节点,尾节点链接到链表,这时候头结点顺利移动到新的hash表中,再把头结点的下一个节点移到新Hash表,直到把所有节点移完.
|
|
合并k个排序链表
这有很多方法.
假如有n个节点,m条链表
优先队列: 把每个链表中的头结点放到pq中,然后在队列中选一个最小的头结点,把这个头结点所在的链表poll出来,然后移除头节点后(如果不是null)再放进队列中.
时间复杂度是o(nlogm)(每个节点进队需要logm时间),空间复杂度是o(m);merge two by two
时间复杂度是(nlogm)Divide & Conquer
|
|
丑数 II
每个数都要分别乘以2,3,5.
只有前面的都已经乘以2了,后面的数才可以乘;同理,3,5;
用p2,p3,p5来标识最近哪一个数最后乘了2,3或者5.
如果p2的位置乘以2等于列表中最大的数,说明p2这个位置中的数不必乘以2了,p2++;同理p3,p5;
知道得到k个丑数,那么可以返回了.
|
|
strStr II
用hashcode计算
几个公式:
power=power31^m%mod
targetCode=(targetCode31+target.charAt(i))%mod;做m次
hash=(hash31+source.charAt(i))%mod; 先做m次.
判断两个code是否相等。
当i>=m的时候,hash要减去i-m+1的值乘以power%mod。为了不得到负数,hash = (hash - (source.charAt(i - m) power) % mod + mod) % mod;
|
|
LRU缓存策略
链表用双向链表实现,为了在O(1)时间内删除节点.
用hashMap记录每个节点,可以在set数据的时候用O(1)的时间找到节点.
设置一个head节点,一个tail节点.head的作用是达到容量时把队头移除;tail的作用是把最新的数据放到对尾;
|
|
动态规划
不同的路径
只需要o(m) 或者o(n)的空间
if (i == 0 || j == 0) path[j] = 1; //不要写成0了
|
|
不同的路径 II
先初始化第一行第一列,遇到等于一的情况,就设置一个标志,不再设为1.
path[j] += path[j - 1]
要注意blocki/blockj不要弄反了.
|
|
爬楼梯
因为某个状态只与前两个状态有关,所以只需要两个变量来记录.
dp[i % 2] = dp[0] + dp[1];
|
|
完美平方
定义一个数组dp[]
先把1,4,9,16,25…填上1
然后把从一到n填上
比如12可以分解成dp[11]+1,dp[8]+1,dp[3]+1,选最小的
|
|
最小路径和
四种情况
- i==0&&j==0 sum[j] = grid[0][0];
- i==0&&j!=0 sum[j] = sum[j - 1] + grid[0][j];
- j==0&&i!=0 sum[j] += grid[i][0];
- i!=0&&j!=0 sum[j] = grid[i][j] + Math.min(sum[j], sum[j - 1]);
|
|
Largest Divisible Subset
一个数组用来记录路径
一个数组用来记录整除数的个数.
|
|
跳跃游戏
贪心算法最简单
设置一个farest变量,记录能到达的最远地方
贪心算法
|
|
动态规划
|
|
最长上升子序列
记录每个点之前有多少个比自己小的,最后遍历一遍数组找到最长序列
|
|