zhandand / LeetCode

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

LeetCode

11. 盛最多水的容器

双指针,指向收尾,收缩

收缩时考虑短板

res = height[i] < height[j] ? Math.max(res, (j - i) * height[i++]): Math.max(res, (j - i) * height[j--]);


17. 电话号码的字母组合

循环层数不确定,因此采用DFS来处理循环,到达末尾的条件用index做判断


22. 括号生成

采用DFS处理,若左括号剩余数量>0,直接DFS,加入右括号的条件为左括号数量小于右括号,否则为无效括号序列 与T17的处理方法不同,即未采用for循环是因为,每次不一定加入一个右括号,而T17每次必定加入一个符号


1139. 最大的以 1 为边界的正方形

动态规划,dp[i][j][0]表示左侧连续1的个数,dp[i][j][1]表示上侧连续1的个数,因此可能的最大边长为min(dp[i][j][0],dp[i][j][1]),探测边长,若不满足条件则--后再次探测,探测条件为正方形的左下顶点的上侧连续1的个数和右上顶点的左侧连续1的个数。

dp方程为: dp[i][j][0] = dp[i][j-1][0] dp[i][j][1] = dp[i-1][j][1] ,if(grid[i][j]==1)


面试题 08.11. 硬币

动态规划,完全背包

dp[i][j]表示用前i个硬币,换总价值为j的所有结果,注意,dp[i][0]初始化为1

dp[i][j] = dp[i-1][j],if j<value[i] dp[i-1][j]+dp[i][j-value[i]],if j>=value[i] (不选或选)


1333. 餐厅过滤器

结构体的快排,注意比较函数,小于代表从大到小排,大于代表从小到大排


148. 排序链表

链表排序,O(nlogn) O(1)空间,归并排序。

找到中点的方法和尺取有异曲同工之妙。


324. 摆动排序 II

一开始的思路是从小到大进行排序,后半部分插入到前半部分。(注意vector的变化,size+1,插入后删除) 存在的问题是,前半部分和后半部分的交界处若存在相同的元素,例如[1,2,2,3,3],会存在相等的情况 改变思路,从大到小排序,这样插入尽可能使中间相等的元素分开 算法可以更优...


面试题 16.26. 计算器

操作符栈和操作数栈

扫描到操作符,计算前面所有同等级和更高级的操作符对应的表达式;扫描到操作数,循环扫描(注意不要扩到大循环范围,使用DFA会很麻烦,直接扫描到非数字结束)

简单做法:

只需要一个操作数栈。碰到乘除符号,由于为最高优先级,直接计算出结果;遇到负号,向操作数栈中加入-num。扫描完毕后,使用while循环计算出栈中所有数的和。


1177. 构建回文串检测

任意顺序的回文串,处理类似与“找到唯一一个出现奇数次的数字”,将每一个字符看作一个数字。由于此题需要处理很多遍,选择将扫描的结果记录下来,dp[i][j]表示第i个位置之前第j个字母出现的次数

if s[i-1]-'a' == j s[i][j] = s[i-1][j]+1; else s[i][j] = s[i-1][j];

每次查询的时候,字串的字母表情况为 s[end+1][j]-s[start][j],偶数的消去,奇数的记录下来

累计为奇数,为2k+1,则k=n即可变为回文串;累计为偶数,为2k,则n=k可变为回文串


151. 翻转字符串里的单词

识别字符串压栈,扫描结束后出栈,空间O(n)

不占空间的做法为,先将整个字符串反转,然后将单词反转。空间O(1)


951. 翻转等价二叉树

对于两个指针,有两种情况,指向的值不等,返回false,相等的话,返回叶子结点的情况,翻转和不翻转


1367. 二叉树中的列表

BFS找到所有可能的起点,DFS找到终点。


894. 所有可能的满二叉树

递归处理。左侧i课子树的结果和右侧i颗子树的结果做双重循环,交换左右子树再保存一次,减少循环时间。 另外,可以采用动态规划的**,由于递归过程中有很多相同的结果,将这些结果记录下来,空间换时间


652. 寻找重复的子树

序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。

有了序列化的**之后,这道题目就迎刃而解了。遍历每一颗子树,生成对应的序列,在map中查找,次数为2,则加入ans中


654. 最大二叉树

每次找最大值的索引,左右两边再次递归。tip:为省空间和时间,每次传递left和right,而不是传递一个新的vector(新建对象耗时且占空间)


1104. 二叉树寻路

正常的二叉树的父节点 = 子节点/2. 而Z型的二叉树还存在一个对称的关系,即子节点/2 = sum - 父节点,sum的值为父亲节点所在层的任意两个字节之和。从叶子寻根,递归。


1110. 删点成林

遍历候选队列中的根节点,若其值在待删除的vector中则将子树加入候选队列,否则将树根加入结果中,并遍历整个树,不断处理子树


1026. 节点与其祖先之间的最大差值

就一个节点来说的最大差值,就是祖先的最大值或者最小值和自己的val的差值。记录祖先节点的最大最小值,并在调用时传递


剑指 Offer 26. 树的子结构

对于一个结点A来说,满足条件的情况有三种

  • A结点为B的结构的根节点 此种情况递归检查,A空false,B空true,值不等false,值等继续判断孩子
  • A的左孩子为B的结构的根节点
  • A的右孩子为B的结构的根节点

863. 二叉树中所有距离为 K 的结点
  • 方法1:先DFS,map记录所有结点的父节点。从目标节点开始左右孩子,parent进行DFS。层次化遍历有两种方法:记录队列的大小,一个while里遍历某一层的数量,或者在每一层中间插入nullptr,到达相同的效果
  • 方法2:分为两棵树,一个棵树以目标节点为根节点,距离K,一棵树以目标节点的父亲为根节点,距离为K-1

面试题 04.08. 首个共同祖先

考虑三种情况

  • 结点p和结点q分别位于root的两侧,因此left和right均不为空,返回root
  • 结点p和结点q位于root的一侧,因此left和right只有一个不为空,返回非空结点
  • 结点p或结点q为root,直接返回root

865. 具有所有最深结点的最小子树

这道题目与"首个共同祖先"相似 考虑三种情况

  • 此节点的左右深度均等于最大深度,最小子树的祖先为此节点
  • 此节点的左右深度只有一个等于最大深度,假设为左,则最小子树的祖先为最深结点或此结点的祖先节点
  • 此节点的左右深度均不等于最大深度,则最小子树的祖先不为此节点的最深结点

此题与"首个共同祖先"多考虑了最深的条件,dfs返回值为深度,若取消存储答案的全局变量,则返回深度和结点的tuple


116. 填充每个节点的下一个右侧节点指针

先序遍历 每个结点的next有三种情况

  • null
  • 父亲节点的右孩子
  • 父亲节点的兄弟结点的左孩子 每次只关注两个孩子的指针,左孩子next指向右孩子, 右孩子next指向root->next的左孩子 再对左右孩子进行递归

117. 填充每个节点的下一个右侧节点指针II

先序遍历 思路与116大致相同,但是需要考虑情况增加

  • root结点没有左右孩子 (直接返回)
  • root->next结点没有左右孩子 (用while找到第一个有孩子的root->next) 考虑到节点连接方式为DFS,因此在root->next不存在孩子的情况时,root的孩子结点的next无法连接到root->next->next结点的孩子,因此递归时需要先从右孩子开始递归,即先建立右侧的next连接

236. 二叉树的最近公共祖先
  • 方法一:最深祖先结点条件(Fl&&Fr)||((root->val==p->val||root->val==q->val)&&(Fl&&Fr)) 即此节点的左右节点分别包含p和q或者此节点为pq结点中的一个,不妨设为p,左右孩子中包含q. 最终结果保存为全局变量
  • 方法二:DFS,搜索并保存两个变量的路径(可以map也可以栈)

437. 路径总和 III

双重DFS。 不可以在同一个函数里双重。原因如下:sum给root->left一个sum和一个sum-root->val,这个时候root->left会给root->left->left两个sum,因此存在很多重复的部分


501. 二叉搜索树中的众数

此题与98. 验证二叉搜索树类似,运用二叉搜索树的性质,即中序遍历为递增序列。 记录前面的结点的值,将currentTimes和maxTimes进行比较,并更新结果 暴力法:直接map


23. 合并K个升序链表
  • 双链表合并递归
  • 优先队列存储每个链表的head,每次取min延申结果

206. 反转链表
  • 迭代:双指针。在遍历列表时,将当前节点的next指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个元素。
  • 递归:假设此节点后面的链表已经被反转,如何反转前面的部分?依次为基本单位进行递归 改为反转前n个元素(递归法): 与上题的区别在于,直接反转,递归时最后一个元素,即head指向nullptr,而反转前n个元素的递归时最后一个元素指向一个successor,因此在n=1时记录下不需要反转部分的头节点,即successor

92. 反转链表 II

在上面两题的基础上,前m-1个结点不变,即可进行head->next = reverseBetween(head->next,m-1,n-1)的递归,若m=1退化为反转前n个元素


82. 删除排序链表中的重复元素 II

递归,处理成每次去掉头部的连续元素,剩余链表递归


86. 分隔链表

目标结果存在一个分界点,使得左侧链表的值均小于x,右侧链表的值均不小于x.采用双指针法,遍历链表,根据大小增长两个链表的长度。采用dummy结点trick简化


109. 有序链表转换二叉搜索树
  • 使用O(n)的额外空间,即用数组存储链表后,O(1)的访问时间,分治构建二叉搜索树
  • 不使用O(1)的额外空间,先序遍历,每次找到根节点后(快慢指针法),两侧递归,时间O(nlogn)
  • 不使用O(1)的额外空间,中序遍历,先构建左子树,再构建根节点和右子树,head使用引用(二叉搜索树的中序遍历即为从小到大)

141. 环形链表
  • 使用set记录下访问过的结点,next碰到即成环
  • 快慢指针,如果成环,slow和fast都困在环中,fast一定会追上slow

142. 环形链表 II
  • 使用set记录下访问过的结点,next碰到即成环
  • 快慢指针。假如成环,假设进环之前的长度为x,相遇点距离环起始点y,环剩余部分z。2(x+y) = x+y+n(y+z) x = n(y+z)-y = (n-1)y+z 因此,从头指针和相遇点同时向后遍历,指针重合的位置即为环的入口

143. 重排链表 234. 回文链表

快慢指针 + 反转链表

滑动窗口

总结

 int left = 0, right = 0;
  while (right < s.size()) 
  {
    window.add(s[right]);
    right++;
    
    while (valid) {
        window.remove(s[left]);
        left++;
    }
  } 

3. 无重复字符的最长子串
window记录已经读入的非重复字符,移动right,指针搜索到下个重复字符,更新前面的指针,随时更新最大宽度

76. 最小覆盖子串
window记录left和right中间的map,与needs进行比较,match后记录长度,固定right,移动left直至不匹配。
面试题 17.18. 最短超串同上

438. 找到字符串中所有字母异位词
window记录left和right中间的map,与needs进行比较,match后验证长度是否匹配,移动left直至不匹配。

424. 替换后的最长重复字符
map记录left和right中间的词频,最大词频+k后与窗口宽度比较,若更窄,则窗口中无法用此词填满,移动left。窗口的大小不减,所求结果为窗口大小
1004. 最大连续1的个数 III同上

992. K 个不同整数的子数组
**固定right**,右移left记录符合条件的结果(之后还原)。若窗口中不同整数的个数大于K,右移K直至等于

1423. 可获得的最大点数
  • DFS,T(n) = 2^k
  • 滑动窗口,由于每次取为最左或最右,问题可转换为取宽度为cardPoints.size()-k的窗口的最小和

1498. 满足条件的子序列数目

由于子序列不要求连续,因此先将子序列排序以便处理。从数组两侧进行处理,左侧为最小值,右侧为最大值,若和小于<=target,则在选定了min的条件下,共有2**(right-left)种可行情况,右移left,否则左移right,直至left > right


375. 猜数字大小 II

dp[i][j] 从数字i猜到数字j的最小花费。dp[i][j] = min(k+max(dp[i][k-1],dp[k+1][j]))这个转移方程与其他的dp不太一样在于填表方式,每个位置的数值需要用到其左侧和下侧的值,所以填表为先从左到右,下从到上


300. 最长上升子序列 面试题 17.08. 马戏团人塔 1218. 最长定差子序列
常规dp[i] = max(dp[i],dp[j+1]),if num[i] > num[j],T(n) = O(n^2)
耐心排序,组间递增,组内递减。lower_bound函数
第二题为第一题的升级版,先按一维排序后,另一维按照LIS排序
第三题,用map存当前数字的目标值的索引,减少一维,O(n)

377. 组合总和 Ⅳ 518. 零钱兑换 II

与换零钱的差别在于换零钱为组合,没有排序。此题有排序

loop 1;
for j in dp[]
  for num in nums[]
      dp[j] + = dp[j-num]
loop2;
for num in nums[]
  for j in dp[]
      dp[j] + = dp[j-num]

第二种方式限制了顺序


1312. 让字符串成为回文串的最少插入次数 516. 最长回文子序列
if(s[i] == s[j]){
  dp[i][j] = dp[i+1][j-1];
}
else{
  dp[i][j] = min(dp[i][j-1],dp[i+1][j])+1;
  #dp[i][j] = min(dp[i][j-1],dp[i+1][j]);
}

注意填表方式


813. 最大平均值和的分组

dp[i][k] = max(dp[i][k],dp[j][k-1]+sum(j,i)/(i-j))


1262. 可被三整除的最大和

dp[i][j]表示nums[0...i]模三余j的最大和,j取0,1,2


983. 最低票价

dp[n]=min( dp[max(n-1,0)] + cost[ 0 ] , dp[max(n-7,0)] + cost[ 1 ] , dp[max(n-30,0)] + cost[ 2 ] )


306. 累加数

回溯+剪枝.使用vector保存切分后的数

About


Languages

Language:C++ 100.0%