leetcode做题笔记备份

本来两个三月前打算搞个博客园美化,结果跳票了(

来混更个leetcode做题笔记。

11月开始做题,至今做了100题。作为一个基础贼差的菜鸡,一开始毫无章法地瞎鸡儿做,直到半个月前才明白菜鸡做题的终极奥义是看书+按tag做以及滚动复习。

做题时会对一些自认为比较经典/不好想/解法巧妙的题写写笔记,记录下思路方法。

以及做题时最好遵循以下三个步骤:

  1. 明确输入输出
  2. 选择/思考要使用的算法
  3. 考虑复杂度和corner case

2. 两数相加

最高位在链表末端

两数相加,每位相加所产生的进位最多只进一位,所以无需追究位数更多的那个数比位数更少的数多出来的若干位

新数复写在l1中

13. 罗马数字转整数

表驱动法;如果右边比当前大,减掉,否则加上

19. 删除链表的倒数第N个节点

迭代:

变量 a b c,先令 a 指向第 n 个节点,再令 b 指向第一个节点,c 指向 null,然后一起向后运动,直到 a 指向最后一个节点,此时删除 b

递归:

定义计数变量 cur = 0,链表 head 的 next 指向 子链表的 head,当 cur === n 时,令当前链表的母链表的 head.next 赋值为当前链表的 head.next

let cur = 0
var removeNthFromEnd = function(head, n) {
if (!head) return null
head.next = removeNthFromEnd(head.next, n)
++cur
if (cur === n) return head.next
return head
};

22. 括号生成

如果 n 大于零,可以添加左括号,并记录未闭合的左括号数为 m;如果 m 大于零,则可以添加右括号

26. 删除排序数组中的重复项

思路:只要把所有重复元素中后面那个放在第i个位置就可以了,最后返回i

28. 实现 strStr()

这题不是easy = =
避免误区:除单个字符外,长字符串直接比较;使用语言内置库
最坏情况O(mn)的解:Sunday算法
只做了滑动窗口常规解

33. 搜索旋转排序数组

将数组从中间分开,至少有一个部分是有序的,对有序部分二分,对无序部分一分为二,以此类推

35. 搜索插入位置

左闭右开

37. 解数独

注意空里是字符串不是数字

39. 组合总和

重要提醒:一旦遇到“解集不能包含重复的组合”,先想到排序

递归思路:求cand中的ksum,就是求cand[i]与cand中的(k-1)sum
递归优化:传下标传下标传下标!!
dfs思路:经典dfs做法

46. 全排列

求abcd的全排列,即为求a[bcd的全排列] + b[acd的全排列] + c[abd的全排列] + d[abc的全排列],以此类推。容易写出递归解

51. N 皇后

使用递归 用栈。下标表示行,元素本身表示列。 每次递归结束需要pop

54. 螺旋矩阵

按顺序删除四边,然后将剩下的部分的矩阵继续进行这个过程

56. 合并区间

经典贪心?每一步都得到最合适的解,解下一步时就不用再考虑之前做过的解了(大概是这么理解的)

60. 排列序列

很朴素的思路:

求1~n的第k个排列,就是求以index = ceil(k / (n - 1)!) 为开头的剩下的数的第 k - (index - 1) * (n - 1)! 个排列
因此edge case:剩下的数长度为0(不太确定,可以再想想)

62. 不同路径

二维dp

63. 不同路径 II

@MonsterSamarua:
何时使用【回溯】,何时使用【动态规划】,用大白话说,就是:
首先看取值范围,递归回溯一维数组,100就是深度的极限了(何况本题是100²) 2.如果是求走迷宫的【路径】,必然是回溯;如果是走迷宫的【路径
的条数】,必然是dp--------(这个竟然屡试不爽!!!!)

77. 组合

递归思路:

求1~n中k个数的组合就是求(Numi)与i~n中k-1个数的组合

dfs思路:

对于每个侯选位置i,尝试放入每个候选数i~n

dfs优化点:尝试放入每个候选数i~(n-(k-已被填入空的个数)+1)

78. 子集

递归:

前缀 + 前缀最后一个数字后面的数字集合中的一个数字,初始前缀为[]

位运算:

答案中子集的个数是 2^n 个,因此使用 [0, n) 的二进制表示,取位数为1的位置的原数组上的元素进行组合

90. 子集 II

优化提示:用下标代替真实的子数组

94. 二叉树的中序遍历

迭代写法为模拟递归写法。用栈模拟函数调用栈,用一个变量模拟当前实参。

var inorderTraversal = function(root) {
const ans = []
const dfs = function (node) {
if (!node) {
return
}
dfs(node.left)
ans.push(node.val)
dfs(node.right)
}
dfs(root)
return ans
};
var inorderTraversal = function(root) {
const ans = []
const stack = []
let node = root
while (node || stack.length !== 0) {
if (node) {
stack.push(node)
node = node.left
} else {
node = stack.pop()
ans.push(node.val)
node = node.right
}
}
return ans
};

95. 不同的二叉搜索树 II

力求通过本题学习分析递归解法的复杂度。

求 [1, n] 所有的可能就是求 [1, i) 所有的可能叉乘 (i, n] 所有的可能,以此类推
时间复杂度:取决于生成的bst数量。设可生成的bst数量为Gn,则时间复杂度为 n*Gn
空间复杂度:取决于生成的bst数量。设可生成的bst数量为Gn,每棵树有n个节点,递归函数最多需要O(n)的栈空间,所有空间复杂度是 nGn + n

96. 不同的二叉搜索树

递归思路:求 [1, n] 的解即求 [1, i) * (i, n] 的解。由于bst的种类只与节点个数有关,所以做记忆化处理时的空间复杂度是 n 的
dp思路:定义dp[i]:用连着的i个数所构建出的bst种类数

98. 验证二叉搜索树

中序遍历为升序

99. 恢复二叉搜索树

思路历程:

  1. 最朴素的思路是求中序遍历,然后从前到后遍历中序遍历结果找出错误的一个数,再从后向前找出另一个,最后再遍历bst修改两个相应节点
  2. 优化:求中序遍历记录节点而非节点的值(代码变短了点但并没有快多少)
  3. 优化:参考98题(验证bst),设置一个pre变量记录当前节点的中序顺序的上一个节点,在遍历过程中找出两个节点(但其实也并没有快多少)
    空间O(1)的解法是Morris算法,还没有看

102. 二叉树的层序遍历

dfs和bfs都可以。bfs用队列,dfs用先序遍历

103. 二叉树的锯齿形层序遍历

关键思路:根据奇偶数行决定每层的元素在数组中从前往后放还是从后往前放

105. 从前序与中序遍历序列构造二叉树

先序第一个是root,然后在中序中找这个root,root左边是左子树右边是右子树,中序中可以得到子树的长度。递归进行上述操作
优化:传下标。左闭右开,右减左等于长度

106. 从中序与后序遍历序列构造二叉树

找root -> 找左右子树的长度

108. 将有序数组转换为二叉搜索树

思路:求[1,2,3,4,5]的bst即为求以3为root、left为[1,2]的bst、right为[4,5]的bst的树,以此类推,易写出递归解
热知识:bst的中序遍历是升序序列

112. 路径总和

edge case 一定要提前想好测试好后再提交
对叶子节点的检测一定是左右都是null
这题nmd错了4次

120. 三角形最小路径和

自底向上:以Pij为开头的最小路径就是Pij+Math.min{ P(i+1)j为开头的最小路径, P(i+1)(j+1)为开头的最小路径 } 自顶向下:以Pij为结尾的最小路径就
是Pij+Math.min{ P(i-1)j为结尾的最小路径, P(i - 1)(j - 1)为结尾的最小路径 }

121. 买卖股票的最佳时机

前i天的最大利润 = max{ 前 i - 1 天的最大利润,当天的价格 - 前 i - 1 天的最小价格 }

122. 买卖股票的最佳时机 II

贪心:根据分析,只要今天的价格比昨天大,昨天就买入今天就卖出

136. 只出现一次的数字

重要提示:不需要额外空间的方法:1. 位运算;2. 原地哈希

144. 二叉树的前序遍历

与中序差不多,模拟递归函数调用栈

var preorderTraversal = function (root) {
const stack = []
const ans = []
let node = root
while (node || stack.length !== 0) {
if (node) {
stack.push(node)
ans.push(node.val)
node = node.left
} else {
node = stack.pop()
node = node.right
}
}
return ans
}

146. LRU 缓存机制

思路: 数据结构记录数据的“频率”属性,说明数据结构具有线性顺序,因此可以想到队列、链表、数组 根据需求设计接口,新增数据 -> 频率最高;

当前被访问的数据 -> 频率最高;容量超出 -> 末尾删除 说明能数据结构需要能够动态增长缩短,且了解其前驱后继 所以认为链表最合适,且由于需要

同时知道前驱和后继,所以选择双向链表 由于需要能根据 key 找到 value,所以还需要哈希
本题需要map+双向链表实现
map负责记录k-v关系,双向链表负责记录数据被读写历史
双向链表须实现的接口:
length: number
addToHead(val: number): Node cache未满,新增数据
moveToHead(node: Node): void 读写已存在数据
removeTail(): val cache已满,写入新数据前移除旧数据
双向链表中的值须存key,因为map中元素的删除需要已知key

169. 多数元素

摩尔投票法

198. 打家劫舍

前 i 个的最大值 = max{ 前 i - 1 个的最大值,前 i - 2 个的最大值 + 第 i 个 }

206. 反转链表

反转链表 a->b->c 相当于反转链表 b->c 后最后连接上a,于是容易写出递归解

215. 数组中的第K个最大元素

快排方法找第k大。注意当lo===hi时需要判断lo是否等于k-1,从而返回相应的值。

226. 翻转二叉树

思路:翻转二叉树的意思:对于每一个节点,交换它的左边和右边

290. 单词规律

双向映射

322. 零钱兑换

动态规划思路:求组成钱数n的最小硬币数就是组成钱数n-ci的最小硬币数+1

354. 俄罗斯套娃信封问题

同最长上升子序列问题,只不过要先排序。因为不排序的话在使用“以信封i为最外层信封”的思路时,有可能出现一条路径上某个地方产生更新(新增了一个节点,路径长+1),但这个更新不会通知到已经算好的部分这样的问题。如果排序后就不会产生这个问题,因为越大的信封总是越在后面进行计算的。最长上升子序列问题本身已经隐含了“越大的总是越放在后面进行计算”这样的条件,所以无需关心顺序。简单地说排一下序之后就是经典的dag上的dp问题
下次再看到这条笔记,如果忘了当时的思路一定要仔细看上面的笔记然后结合提交代码想一想哦。

389. 找不同

哈希和位运算

925. 长按键入

第一个字母必须相同

当两个字母不同时再做去掉相同字母的处理

原文地址:https://www.cnblogs.com/sevenskey/p/14224352.html