LeetCode刷题笔记
- 最近在学习
JavaScript
,刷题也用顺便巩固,两全其美。
简单题
罗马数字转整数
- 思路
- 通常情况下,罗马数字中小的数字在大的数字的右边。若输入的字符串满足该情况,那么可以将每个字符视作一个单独的值,累加每个字符对应的数值即可。
- 若存在小的数字在大的数字的左边的情况,根据规则需要减去小的数字。对于这种情况,我们也可以将每个字符视作一个单独的值,若一个数字右侧的数字比它大,则将该数字的符号取反。
1 | /** |
最长公共前缀
- 描述
- 编写一个函数来查找字符串数组中的最长公共前缀。
- 如果不存在公共前缀,返回空字符串
""
。
- 思路
- 纵向扫描
1 | /** |
- 改进版本
1 | var longestCommonPrefix = function(strs) { |
有效的括号
- 描述
- 给定一个只包括
'('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s ,判断字符串是否有效。 - 有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 给定一个只包括
- 思路
- 栈
- 遍历字符串,遇左括号入栈,遇右括号进行匹配校验,匹配成功出栈,失败返回
false
。 - 遍历完毕,栈为空(说明全部匹配)则返回
true
,栈不空(说明有括号”落单“)返回false
。
1 | /** |
- 改进(哈希)
1 | var isValid = function(s) { |
移除元素
- 描述
- 给你一个数组
nums
和一个值val
,你需要原地移除所有数值等于val
的元素,并返回移除后数组的新长度。 - 不要使用额外的数组空间,你必须仅使用
O(1)
额外空间并 原地 修改输入数组。 - 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
- 给你一个数组
- 思路
- 遍历 +
splice()
,此法有点流氓,我猜不是题目考察本意。
- 遍历 +
1 | /** |
- 官方解法
- 思路(双指针)
- 右指针
right
指向当前将要处理的元素,左指针left
指向下一个将要赋值的位置。 - 如果右指针指向的元素不等于
val
,它一定是输出数组的一个元素,我们就将右指针指向的元素复制到左指针位置,然后将左右指针同时右移; - 如果右指针指向的元素等于
val
,它不能在输出数组里,此时左指针不动,右指针右移一位。 - 最后返回
left
即是数组应当输出得到长度。
- 右指针
1 | var removeElement = function(nums, val) { |
搜索插入位置
- 描述
- 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
- 请必须使用时间复杂度为$O(log_n)$的算法。
- 思路
- 有序且要求时间复杂度
->
二分查找。 - 需要注意的是,查找失败时应该返回的下标是
right + 1
或left
(循环结束条件为left <= right
时)。
- 有序且要求时间复杂度
1 | /** |
最大子数组和
- 描述
- 给你一个整数数组
nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 - 子数组是数组中的一个连续部分。
- 给你一个整数数组
- 思路(贪心)
- 从左向右迭代,一个个数字加过去如果
tempSum < 0
, 那说明加上它只会变得越来越小, 所以我们将tempSum
置零后重新开始找子序串。 - 在迭代的过程中要注意,我们需要用
maxSum
来不断维持当前的最大子序和, 因为maxSum
的值是在不断更新的, 所以我们要及时记录下它的最大值。 - 有一个注意点是: 当数组全是负数的时候其实是没有问题的。因为在
tempSum
不断遍历的过程中, 早已将最大值不断传给maxSum
,即使tempSum
一直是负数被不断置零也不用担心,maxSum
还是会记录下最大的那个负数。
- 从左向右迭代,一个个数字加过去如果
1 | /** |
- 思路(动态规划)
- 略
最后一个单词的长度
- 描述
- 给你一个字符串
s
,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中最后一个单词的长度。 - 单词是指仅由字母组成、不包含任何空格字符的最大子字符串。
- 给你一个字符串
- 思路
- 反向遍历,忽略尾部的空格,找到最后一个单词。
1 | /** |
- 简化(实际上
lastWLen
本身就可以作为flag
)
1 | var lengthOfLastWord = function(s) { |
- 思路(内置方法)
1 | var lengthOfLastWord = function(s) { |
二进制求和
- 描述
- 给你两个二进制字符串,返回它们的和(用二进制表示)。
- 输入为非空字符串且只包含数字
1
和0
。
- 思路(模拟)
- 长度不同则补零
1 | /** |
x 的平方根
- 描述
- 给你一个非负整数
x
,计算并返回x
的 算术平方根 。 - 由于返回类型是整数,结果只保留整数部分 ,小数部分将被舍去。
- 给你一个非负整数
- 思路
- 找出刚好小于或等于
x
的i*i
。 - 穷举
- 二分查找
- 查找范围为
[0,x]
的一个单调递增数组,找出第一个小于等于x的值。
- 查找范围为
- 找出刚好小于或等于
1 | /** |
1 | var mySqrt = function(x) { |
爬楼梯
- 描述
- 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
- 每次你可以爬
1
或2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
- 思路
- 一般递归(超时)
- 带记录的递归(保存已计算过的阶)
- 动态规划
1 | /** |
1 | // 带记录的递归 |
1 | // 动态规划 |
删除排序链表中的重复元素
- 描述
- 给定一个已排序的链表的头
head
, 删除所有重复的元素,使每个元素只出现一次。返回已排序的链表。
- 给定一个已排序的链表的头
- 思路
- 双指针(其实可以只用一个指针)
1 | /** |
合并两个有序数组
- 描述
- 给你两个按 非递减顺序 排列的整数数组
nums1
和nums2
,另有两个整数m
和n
,分别表示nums1
和nums2
中的元素数目。 - 请你合并
nums2
到nums1
中,使合并后的数组同样按非递减顺序排列。 - 注意:最终,合并后数组不应由函数返回,而是存储在数组
nums1
中。为了应对这种情况,nums1
的初始长度为m + n
,其中前m
个元素表示应合并的元素,后n
个元素为0
,应忽略。nums2
的长度为n
。
- 给你两个按 非递减顺序 排列的整数数组
- 思路
- 双指针 + 辅助数组
- 先合并再排序
- 逆向双指针
1 | /** |
1 | var merge = function(nums1, m, nums2, n) { |
1 | var merge = function(nums1, m, nums2, n) { |
二叉树的中序遍历
- 描述
- 给定一个二叉树的根节点
root
,返回它的中序遍历。
- 给定一个二叉树的根节点
- 思路(递归)
1 | /** |
相同的树
- 描述
- 给你两棵二叉树的根节点
p
和q
,编写一个函数来检验这两棵树是否相同。 - 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
- 给你两棵二叉树的根节点
- 思路
- DFS
- BFS
- 骚操作
1 | // DFS |
1 | // 骚操作 判断两个 js 对象是否相等 |
中等题
无重复字符的最长子串
- 描述
- 给定一个字符串
s
,请你找出其中不含有重复字符的最长子串的长度。
- 给定一个字符串
- 思路(双指针 + Set)
- 一个指针遍历,另一个指针记录回溯位置。
1 | /** |
- 思路(滑动窗口)
1 | var lengthOfLongestSubstring = function (s) { |
最长回文子串
- 描述
- 给你一个字符串
s
,找到s
中最长的回文子串。]
- 给你一个字符串
- 思路
- 首先,找到源串中的所有回文串,然后,截取最长的即可。
- 如何找:
- 找到源串中头尾相同的子串。
- 用判断回文串的方法(双指针)判断该子串和是否为回文串。
- 若是,则与之前找到的回文串比较长度,更长则更新
maxStr
和maxLen
。 - 否则,换下一子串继续第二步。
1 | /** |
Z 字形变换
- 描述
- 将一个给定字符串
s
根据给定的行数numRows
,以从上往下、从左到右进行Z
字形排列。
- 将一个给定字符串
- 思路
- 模拟
- 数学规律
1 | /** |
- 改进(模拟)
- 妙用
flag
- 妙用
1 | var convert = function (s, numRows) { |
盛最多水的容器
- 描述
- 给定一个长度为
n
的整数数组height
。有n
条垂线,第i
条线的两个端点是(i, 0)
和(i, height[i])
。 - 找出其中的两条线,使得它们与
x
轴共同构成的容器可以容纳最多的水。
- 给定一个长度为
- 思路
- 暴力搜索(超时)
- 双指针
- 每次移动指向高度较小的指针,面积才有可能大于之前的。
1 | /** |
1 | /** |
整数转罗马数字
- 描述
- 给你一个整数,将其转为罗马数字。
- 思路
- 模拟(模拟)
- 根据罗马数字的唯一表示法,为了表示一个给定的整数
num
,我们寻找不超过num
的最大符号值,将num
减去该符号值,然后继续寻找不超过num
的最大符号值,将该符号拼接在上一个找到的符号之后,循环直至num
为0
。最后得到的字符串即为num
的罗马数字表示。
1 | /** |
三数之和
- 描述
- 给你一个包含
n
个整数的数组nums
,判断nums
中是否存在三个元素a
,b
,c
,使得a + b + c = 0
?请你找出所有和为0
且不重复的三元组。
- 给你一个包含
- 思路
- 暴力搜索
- 略
- 排序 + 双指针
- 将排好序的数组,依次以某一元素为基准(并去除重复),剩下的元素进行
two sum
操作(也要去重)。 - 有序的
tow sum
:相加之和小于目标值,左指针left++
,相加之和大于于目标值右指针right--
。 - 其实相当于三指针,只是指针
i
是依次从左向右移动而已。
- 将排好序的数组,依次以某一元素为基准(并去除重复),剩下的元素进行
- 暴力搜索
1 | /** |
最接近的三数之和
- 描述
- 给你一个长度为
n
的整数数组nums
和一个目标值target
。请你从nums
中选出三个整数,使它们的和与target
最接近。 - 返回这三个数的和。
- 给你一个长度为
- 思路
- 排序 + 双指针
- 三之和稍微变化
- 排序 + 双指针
1 | /** |
电话号码的字母组合
- 描述
- 给定一个仅包含数字
2-9
的字符串,返回所有它能表示的字母组合。答案可以按任意顺序返回。 - 给出数字到字母的映射如下(与电话按键相同)。
- 注意
1
不对应任何字母。
- 给定一个仅包含数字
- 思路
- 递推组合(广度优先-非标准)
- 例如:输入
"23456"
,可先将2
和3
进行组合,然后保存这个中间结果res
。 - 然后,再将
res
与4
进行组合,再保存至res
… - 直到把最后一个
6
组合完毕,则res
即最终结果。
- 例如:输入
- 递归回溯(深度优先)
- 递推组合(广度优先-非标准)
1 | var letterCombinations = function (digits) { |
1 | /** |
四数之和
- 描述
- 给你一个由
n
个整数组成的数组nums
,和一个目标值target
。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复)
- 给你一个由
- 思路(降维)
- 将四数之和降为两数之和,套两层循环即可(三数之和套一层)
- 注意:
- 判断重复
target !== 0
时,不能使用nums[0](nums[n-1]) <(>) target
直接返回。
1 | /** |
- 优化
1 | var fourSum = function (nums, target) { |
- 代码随想录
1 | var fourSum = function(nums, target) { |
括号生成
- 描述
- 数字
n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且有效的括号组合。
- 数字
- 思路
- 首先,可以知道的是,在“拼凑”每一组有效括号的过程中,一定满足:
- 左括号数小于等于括号的对数((左括号 + 右括号)/2)
- 右括号小于等于左括号数(当然也小于等于括号对数)
- 题目要求生成
n
对的合法括号序列组合,因此可以考虑使用深度优先搜索,将搜索顺序定义为枚举序列的每一位填什么,那么最终的答案一定是由n个左括号和n个右括号组成。
- 首先,可以知道的是,在“拼凑”每一组有效括号的过程中,一定满足:
1 | var generateParenthesis = function (n) { |
两两交换链表中的节点
- 描述
- 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题。
思路
- 迭代
- 递归
迭代
1 | /** |
- 递归
1 | ``` |
- 官方解法
- 思路
- 由于给定的数组nums是有序的,因此对于任意$i<j$,如果$nums[i]=nums[j]$,则对任意$i≤k≤j$必有$nums[i]=nums[k]=nums[j]$,即相等的元素在数组中的下标一定是连续的。利用数组有序的点,可以通过双指针的方法刪除重复元素。
- 思路
1 | var removeDuplicates = function(nums) { |
买卖股票的最佳时机 II
- 思路(贪心)
- 股票买卖策略
- 单独交易日:设今天价格$p_1$、明天价格$p_2$,则今天买入、明天卖出可赚取金额$p_2-p_1$(负值代表亏损)。
- 连续上涨交易日:
- 设此上张交易日股票价格分別为$p_1,p_2,…,p_n$,则第一天买最后一天卖收益最大,即$p_n-p_1$。
- 等价于每天都买卖。
- 连续下降交易日:则不买卖收益最大,即不会亏钱。
- 故只需将相邻两天价格差为正的差价加起来,即为最大收益。
- 股票买卖策略
1 | /** |
轮转数组
- 描述
- 给你一个数组,将数组中的元素向右轮转
k
个位置,其中k
是非负数。
- 给你一个数组,将数组中的元素向右轮转
- 思路
- 切片连接
1 | /** |
- 改进版本
1 | var rotate = function (nums, k) { |
存在重复元素
描述
- 给你一个整数数组
nums
。如果任一值在数组中出现至少两次,返回true
;如果数组中每个元素互不相同,返回false
。
- 给你一个整数数组
思路(超时)
- 遍历数组后删除对应元素
- 使用
includes()
判断是否任然存在该元素
1 | /** |
- 思路(排序)
- 在对数字从小到大排序之后,数组的重复元素一定出现在相邻位置中。
1 | var containsDuplicate = function(nums) { |
- 思路(哈希表)
- 对于数组中每个元素,将它插入到哈希表中。如果插入一个元素时发现该元素已经存在于哈希表中,则说明存在重复的元素。
1 | var containsDuplicate = function(nums) { |
只出现一次的数字
- 描述
- 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
- 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
- 思路(空间复杂度:$O(n)$)
- 使用集合存储数字。遍历数组中的每个数字,如果集合中没有该数字,则将该数字加入集合,如果集合中已经有该数字,则将该数字从集合中删除,最后剩下的数字就是只出现一次的数字。
- 使用哈希表存储每个数字和该数字出现的次数。遍历数组即可得到每个数字出现的次数,并更新哈希表,最后遍历哈希表,得到只出现一次的数字。
- 使用集合存储数组中出现的所有数字,并计算数组中的元素之和。由于集合保证元素无重复,因此计算集合中的所有元素之和的两倍,即为每个元素出现两次的情况下的元素之和。由于数组中只有一个元素出现一次,其余元素都出现两次,因此用集合中的元素之和的两倍减去数组中的元素之和,剩下的数就是数组中只出现一次的数字。
- 思路(位运算)
- 任何数和$0$做异或运算,结果仍然是原来的数,即$a⊕0=a$。
- 任何数和其自身做异或运算,结果是$0$,即$a⊕a=0$。
- 异或运算满足交换律和结合律
- $a⊕b⊕a=b⊕(a⊕a)$
- $b⊕(a⊕a)=b⊕0=b$
- 数组中的全部元素的异或运算结果即为数组中只出现一次的数字。
1 | /** |
1 | // 哈希 |
两个数组的交集 II
- 描述
- 给你两个整数数组
nums1
和nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
- 给你两个整数数组
- 思路
- 分别以两个数组对的数组元素为键,出现次数为值,创建哈希表。然后遍历两个哈希表找出共同元素,插入
n
次入数组(n
为较小值)。
- 分别以两个数组对的数组元素为键,出现次数为值,创建哈希表。然后遍历两个哈希表找出共同元素,插入
1 | /** |
- 官方解法
- 思路(一个哈希表)
- 首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。
- 为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。
1 | var intersect = function(nums1, nums2) { |
- 思路(排序+双指针)
- 首先对两个数组进行排序,然后使用两个指针遍历两个数组。
- 初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
1 | var intersect = function(nums1, nums2) { |
加一
- 描述
- 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。
- 最高位数字存放在数组的首位,数组中每个元素只存储单个数字。
- 你可以假设除了整数
0
之外,这个整数不会以零开头。
- 思路(js方法)
- 数组 -> 大整数 -> +1 -> 数组(->表示转换类型)
- 基于js是一门非常高级的语言,已经封装好了很多方法,直接调用方法完成,虽然方便但忽略了算法。
1 | /** |
- 官方解法
- 思路(找出最长的后缀
9
)- 如果
digits
的末尾没有9
,例如[1,2,3]
,那么我们直接将末尾的数加一,得到[1,2,4]
并返回; - 如果
digits
的末尾有若干个9
,例如[1,2,3,9,9]
,那么我们只需要找出从末尾开始的第一个不为9
的元素,即3
,将该元素加一,得到[1,2,4,9,9]
。随后将末尾的9
全部置零,得到[1,2,4,0,0]
并返回。 - 如果
digits
的所有元素都是9
,例如[9,9,9,9,9]
,那么答案为[1,0,0,0,0,0]
。我们只需要构造一个长度比digits
多1
的新数组,将首元素置为1
,其余元素置为0
即可。
- 如果
1 | var plusOne = function(digits) { |
移动零
- 描述
- 给定一个数组
nums
,编写一个函数将所有0
移动到数组的末尾,同时保持非零元素的相对顺序。 - 请注意 ,必须在不复制数组的情况下原地对数组进行操作。
- 给定一个数组
- 思路
- 考虑到js对数组封装的方法丰富,就不使用找零移动法了
- 将数组中为
0
的元素删除,并计数count
,删除全部0
后,在数组末尾插入count
个0
。
1 | /** |
- 官方解法
- 思路(双指针 类似冒泡排序)
- 使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。
- 右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。
- 注意到以下性质:
- 左指针左边均为非零数
- 右指针左边直到左指针处均为零。
- 因此每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变。
- 思路(双指针 类似冒泡排序)
1 | // 还是写复杂了呀 |
1 | // 这个好点 |
有效的数独
- 描述
- 请你判断一个
9 x 9
的数独是否有效。只需要 根据以下规则 ,验证已经填入的数字是否有效即可。- 数字
1-9
在每一行只能出现一次。 - 数字
1-9
在每一列只能出现一次。 - 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。 - 空白格用’.’表示。
- 数字
- 请你判断一个
- 思路(三次遍历)
- 利用哈希唯一性,按规则扫描表格内的数,插入哈希表,若已存则在返回
false
;若扫描完成未发现重复则返回true
。 - 分别按行、列、
3 × 3
块扫描。 - 其中块扫描按块进行列扫面,即先扫描左边
3 × 9
组成的3个列块,然后向右依次扫描。
- 利用哈希唯一性,按规则扫描表格内的数,插入哈希表,若已存则在返回
1 | /** |
- 官方解法
- 思路(一次遍历)
- 创建二维数组
rows
和columns
分别记录数独的每一行和每一列中的每个数字的出现次数,创建三维数组subbozes
记录数独的每一个小九官格中的每个数字的出现次数。 - 其中$rows[i][index]、\textit{columns}[j][\textit{index}]$
- $columns[j][index]$和$subboxes[⌊\frac{i}{3}⌋][⌊\frac{j}{3}⌋][index]$
- 分别表示数独的第
i
行第j
列的单元格所在的行、列和小九宫格中,数字index+1
出现的次数,其中0≤index<9
,对应的数字index+1
满足1≤inde+1≤9
- 创建二维数组
1 | var isValidSudoku = function(board) { |
旋转图像
- 描述
- 给定一个
n×n
的二维矩阵matrix
表示一个图像。请你将图像顺时针旋转90
度。 - 你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
- 给定一个
- 思路
- 找出旋转前后下标变化规律
- 关键等式:
matrixNew[col][n−row−1]=matrix[row][col]
1 | /** |
反转字符串
- 描述
- 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
- 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
- 思路
- 调用内建方法
reverse()
- 双指针
- 解构语法
- 位运算
- 调用内建方法
1 | /** |
整数反转
- 描述
- 你一个
32
位的有符号整数x
,返回将x
中的数字部分反转后的结果。 - 如果反转后整数超过
32
位的有符号整数的范围[−2^31, 2^31 − 1]
,就返回0
。 - 假设环境不允许存储
64
位整数(有符号或无符号)。
- 你一个
- 思路
- 先将输入整数的位数计算出来,以便计算每一位应对应的权重(转化后在什么位上)。
- 通过整除、取余获取尾部数字,乘以转化后对应的位的权重,然后相加。
1 | /** |
- 官方解法
1 | var reverse = function(x) { |
字符串中的第一个唯一字符
- 描述
- 给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。
- 思路
- 遍历字符串利用hashmap以字符为
key
、{index:null,count:null}
为value
,存储字符的下标index
和出现次数count
。 - 遍历哈希表找到第一个
count === 1
的映射,并返回其下标。
- 遍历字符串利用hashmap以字符为
1 | /** |
- 官方解法
- 思路
- 我们可以对字符串进行两次遍历。
- 在第一次遍历时,我们使用哈希映射统计出字符串中每个字符出现的次数。
- 在第二次遍历时,我们只要遍历到了一个只出现一次的字符,那么就返回它的索引,否则在遍历结束后返回
-1
。
1 | var firstUniqChar = function(s) { |
有效的字母异位词
- 描述
- 给定两个字符串
s
和t
,编写一个函数来判断t
是否是s
的字母异位词。 - 注意:若
s
和t
中每个字符出现的次数都相同,则称s
和t
互为字母异位词。
- 给定两个字符串
- 思路
- 首先判断字符串长度是否相等,不相等直接
return false
。 - 利用hashmap,扫描字符串字符存入map并记录出现次数。
- 扫描第二个字符串判断map中是否已经存在:
- 若存在则次数减
1
,若次数减至< 0
则return false
; - 若不存在
return false
(不存在说明第二个串用了第一个没有使用的字符) 。
- 若存在则次数减
- 首先判断字符串长度是否相等,不相等直接
1 | /** |
- 官方解法
- 思路
- 排序
- 判断字符串是否相等
1 | var isAnagram = function(s, t) { |
验证回文串
- 描述
- 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
- 说明:本题中,我们将空字符串定义为有效的回文串。
- 思路
- 双指针
- 正则表达式(两年前学过忘记了,用得很笨拙)
1 | /** |
- 其他解法
- 思路
- 和我一样,但是正则比我用的好
1 | var isPalindrome = function(s) { |
实现 strStr()
- 描述
- 实现
strStr()
函数。 - 给你两个字符串
haystack
和needle
,请你在haystack
字符串中找出needle
字符串出现的第一个位置(下标从0
开始)。如果不存在,则返回-1
。 - 说明:
- 当
needle
是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。 - 对于本题而言,当
needle
是空字符串时我们应当返回0
。这与C语言的strstr()
以及Java的indexOf()
定义相符。
- 当
- 实现
- 思路(暴力搜索)
- 双指针
- 分别指向源串和子串,比较、回溯(源串回溯至第一个匹配位置的下一位,模式串回溯至
0
) - 模式串指针指向最后一位字符的下一位,则代表匹配成功。
- 源串指针指向最后一位字符的下一位,且此时模式串没有指向最后一位字符的下一位则匹配失败。
1 | /** |
- 其他思路
- KMP(略)
- 滑动窗口(略)
删除链表中的节点
- 描述
- 请编写一个函数,用于删除单链表中某个特定节点。在设计函数时需要注意,你无法访问链表的头节点
head
,只能直接访问要被删除的节点 。 - 题目数据保证需要删除的节点不是末尾节点。
- 请编写一个函数,用于删除单链表中某个特定节点。在设计函数时需要注意,你无法访问链表的头节点
- 思路
- 复制下一个结点的值 -> 删除下一个节点。
- (我觉得我又行了!)
1 | /** |
反转链表
- 描述
- 给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。
- 给你单链表的头节点
- 思路
- 略
1 | /** |
删除链表的倒数第 N 个结点
- 描述
- 给你一个链表,删除链表的倒数第
n
个结点,并且返回链表的头结点。
- 给你一个链表,删除链表的倒数第
- 思路
- 计算链表长度
- 首先从头节点开始对链表进行一次遍历,得到链表的长度
L
。随后我们再从头节点开始对链表进行一次遍历,当遍历到第L-n+1
个节点时,它就是我们需要删除的节点。 - 注意
- 没有头结点的链表注意表头元素的删除方法需要单独编写
- 若使用复制删除法,则尾部结点的删除方法也需要单独编写
- 其他
- 双指针
- 由于我们需要找到倒数第
n
个节点,因此我们可以使用两个指针first
和second
同时对链表进行遍历,并且first
比second
超前n
个节点。当first
遍历到链表的末尾时,second
就恰好处于倒数第n
个节点。
- 由于我们需要找到倒数第
- 栈
- 双指针
1 | /** |
合并两个有序链表
- 描述
- 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
- 思路
- 迭代
- 递归
1 | /** |
1 | var mergeTwoLists = function(l1, l2) { |
回文链表
- 描述
- 给你一个单链表的头节点
head
,请你判断该链表是否为回文链表。如果是,返回true
;否则,返回false
。
- 给你一个单链表的头节点
- 思路
- 将值复制到数组中后用双指针法
- 递归
- 快慢指针
1 | /** |
环形链表
- 描述
- 给你一个链表的头节点
head
,判断链表中是否有环。
- 给你一个链表的头节点
- 思路
- 利用数据结构集合(或哈希表)的唯一性,也可以用数组的
includes()
方法- 遍历链表加入集合,重复则
true
,反之true
。
- 遍历链表加入集合,重复则
- 快慢指针
- 定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置
head
,而快指针在位置head.next
。 - 这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
- 定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置
- 利用数据结构集合(或哈希表)的唯一性,也可以用数组的
1 | /** |
1 | const hasCycle = function(head) { |