【LeetCode-TOP100】解题思路-白话文版

目录

128、最长连续序列

283、移动零

3、无重复字符的最长子串

560、和为k的子数组

53、最大子数组和

73、矩阵置零

160、相交链表

49、字母异位词分组

56、合并区间

206、反转链表

124、二叉树中的最大路径和

236、二叉树的最近公共祖先

437、路径总和Ⅲ

105、从前序和中序遍历序列构造二叉树

114、二叉树展开为链表

35、搜索插入位置

74、搜索二维矩阵

20、有效的括号

234、回文链表

23、合并K个升序链表

148、排序链表

994、腐烂的橘子

34、在排序数组中查找元素的第一个位置和最后一个位置

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

347、前K个高频元素

121、买卖股票的最佳时机

55、跳跃游戏

45、跳跃游戏Ⅱ

763、划分字母区间

70、爬楼梯

118、杨辉三角

198、打家劫舍

279、完全平方数

322、零钱兑换

5、最长回文子串

1143、最长公共子序列

72、编辑距离

62、不同路径

64、最小路径和

139、单词拆分

300、最长递增子序列

131、分割回文串

39、组合总和

46、全排列

78、子集

739、每日温度

152、乘积最大子数组

416、分割等和子集

32、最长有效括号

155、最小栈

22、括号生成

141、环形链表

142、环形链表Ⅱ

21、合并两个有序链表

2、两数相加

94、二叉树的中序遍历

104、二叉树的最大深度

226、翻转二叉树

11、盛最多水的容器

15、三数之和

42、接雨水

239、滑动窗口最大值

76、最小覆盖子串

19、删除链表的倒数第N个结点

24、两两交换链表中的节点

25、K个一组翻转链表

101、对称二叉树

543、二叉树的直径

1、两数之和

146、LRU缓存

102、二叉树的层序遍历

138、随机链表的复制

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

98、验证二叉搜索树

230、二叉搜索树中第K小的元素

199、二叉树的右视图

207、课程表

438、找到字符串中所有字母异位词

79、单词搜索

17、电话号码的字母组合

189、轮转数组

238、除自身以外数组的乘积


128、最长连续序列
  • 哈希表(集合),首先将数组中所有的元素以集合的方式去重;
  • 遍历每一个元素,首先判断这个元素能不能称为数组中一个连续序列的首元素(即不存在比它更小的元素);
  • 当遍历到的元素能够称为连续序列的首元素时,循环判断这个元素加1是否仍然存在于集合当中,同时记录序列长度,以此类推求最大的序列长度。
  • 时间复杂度是O(n)
283、移动零
  • 双指针法,初始化两个指针都指向0;
  • 遍历数组,当遇到的数不为0时,交换两个指针所指位置的元素,两个指针都后移一位;否则只有第二个指针后移一位寻找不为0的元素;
  • 时间复杂度O(n)
3、无重复字符的最长子串

方法一:哈希表+滑动窗口

  • 遍历字符串的过程中将每个字符存储在一个哈希表当中,以字符为键,下标为值;
  • 定义一个代表子串起始位置的指针start,初始为0;
  • 当所遇字符已经包含在哈希表中且start指针所指的位置小于已经包含在哈希表中的那个字符的下标时,说明当前子串将要具有重复字符,更新start指针,将其指向那个已经包含在哈希表中的重复字符下标的下一位,即在滑动窗口中去除了重复字符;
  • 当所遇字符不在哈希表中时,或者在哈希表中,但是start指针大于其下标,此时更新子串的最大长度,当前子串的长度为当前的下标i-start+1。
  • 时间复杂度O(n)
560、和为k的子数组
  • 前缀和+哈希表:定义一个哈希表,以每个前缀和的值作为键,出现次数作为值,初始化前缀和0出现的次数为1。
  • 从下标0处开始一直累加到下标i处的值称之为下标i处的前缀和。
  • 遍历数组,计算每一个位置处的前缀和,然后去哈希表中寻找当前的前缀和减去目标值的差值出现的次数就是和为k的子数组出现了多少次(因为一个从下标i开始到下标j结束的子数组的和可以表示为下标j处的前缀和减去下标i处的前缀和,目标值就是我们要找的子数组和)。
  • 每一次得到的结果进行累加。
  • 每一次遍历都需要更新当前前缀和出现的次数。
53、最大子数组和
  • 动态规划:定义一个dp数组,含义是以当前下标i结尾的数组的最大子数组和为dp[i]。
  • dp[i-1]和dp[i]之间的递推关系是:dp[i]的值取决于dp[i-1]对于当前数值来说起到正贡献还是负贡献,即如果当前值加上dp[i-1]之后的结果还不如当前值本身大的话就直接取当前值就可。
  • 最终结果是取dp数组中最大的元素。
73、矩阵置零
  • 数组模拟:主要思路为借助行、列两个辅助布尔数组,先遍历一遍整个矩阵,记录哪些行哪些列含有0,然后第二遍遍历的时候将只要每个位置的数字,只要其行坐标或者列坐标所在行列被标记过就置为0即可。
160、相交链表
  • 双指针:两个初始指针分别指向两个链表的头节点,在两个指针不相等的情况下,依次向前遍历,如果指针已经遍历完了当前链表,就去开始遍历另一个链表。
  • 如果存在相交节点,那么必然存在两个指针相遇的地方,否则,不存在相交节点。
49、字母异位词分组
  • 哈希表+数组:主要思路是对数组中的每一个字符串进行排序,因为由相同字母组成的字符串排序之后的结果肯定相等。
  • 将具有相同排序结果的字符串添加到一个哈希表中,以排序后的结果作为键,注意存储字符串的这个哈希表的values是列表格式的。
  • 最后将哈希表的值转换为列表返回。
56、合并区间
  • 数组+排序:首先根据每个数组元素的第一个数值进行排序,方便合并区间,定义一个结果数组。
  • 遍历数组,如果结果数组为空(目前还未添加任何一个元素)或者结果数组中倒数第一个数组元素的第二个值小于当前所遍历的数组元素的第一个值,说明前后这两个数组元素之间不会有交集,直接将当前遍历的这个数组元素先添加到结果数组当中。
  • 除了上述情况,则要合并结果数组的倒数第一个数组元素和当前正在遍历的数组元素,区间起始值取结果数组倒数第一个数组元素的第一个值,区间结束值取两个数组元素第二个值中的最大值(因为第二个区间可能包含在第一个区间当中)。
206、反转链表
  • 双指针:初始化一个当前指针curr指向头节点,一个前置指针pre(当前指针的前一位置)指向None,只要当前节点不为空就持续遍历链表。
  • 在反转两个链表节点时,首先使用一个临时变量temp存储当前节点的后续节点,再将当前节点curr的指向变换为指向前置节点pre,此时已经完成了链表节点的反转。
  • 最后将当前节点curr和前序节点pre都往后移一位,为下次反转做准备。前置节点移动到当前节点所在位置,当前节点移动到临时变量保存的位置。
  • 最后返回的是前置节点pre,因为最终遍历终止时当前节点为空,前序节点刚好到达最后一个链表节点处。
124、二叉树中的最大路径和
  • 深度优先搜索+递归:定义一个深度优先搜索函数,定义并初始化一个全局变量self.res=float('-inf')来储存递归遍历过程中的最大路径和。
  • 先递归遍历左子树,后递归遍历右子树;递归终止条件是节点为空;递归过程中更新的最大路径和为当前节点值加上左右子树的返回值;最终递归函数的返回值为当前节点值加上左右子树返回值中较大的值(因为要不断更新最大路径和,所以只能返回最大的那一部分路径)。
  • 每次递归左右子树的返回值为0与函数返回值取最大值,因为负数肯定会导致最大路径和减少。
236、二叉树的最近公共祖先
  • 深度优先搜索+递归:递归的终止条件是当当前遍历节点为空节点或者等于目标节点之一时,返回该节点。
  • 递归地先遍历左子树,后遍历右子树;如果左子树返回值为空,右子树返回值不为空则返回右子树的值;如果右子树返回值为空,左子树返回值不为空则返回左子树的值;如果左右子树的返回值都不为空则返回根节点。
437、路径总和Ⅲ
  • 深度优先搜索+哈希表+前缀和:定义一个哈希表,键为遍历过程中每个节点所在位置的前缀和,值为前缀和出现的次数。
  • 每次遍历到一个非空节点时,先去哈希表中搜索是否存在当前前缀和减去目标路径值(因为当前前缀和减去前序某个前缀和得到的差值刚好就是前后两个节点之间的路径和,这个减法公式左右移位就是去寻找当前前缀和减去目标路径和的差值)的结果对应的键,存在的话结果ans加上对应的值,否则加0。
  • 更新哈希表中前缀和对应键的值,存在就在原来的次数上加1,否则新建一个键,值为1。
  • 先递归遍历左子树,后递归遍历右子树,因为路径要求必须是向下的,所在在遍历到叶子节点之后往回回溯的时候,必须减去叶子节点对应前缀和出现的次数,即前缀和次数减1,回到上一个节点的状态。
105、从前序和中序遍历序列构造二叉树
  • 递归:递归的终止条件就是当前序或中序遍历序列为空时,返回None。
  • 在每一次递归遍历过程中,从前序序列pop出第一个元素就是根节点,然后通过index()方法获取其在中序序列中的位置下标。
  • 根据每次获取的根节点在中序遍历序列中的位置可以将中序遍历序列分割为前后两部分,前半部分是左子树遍历序列、后半部分是右子树遍历序列。
  • 最后返回根节点。
114、二叉树展开为链表
  • 递归:递归终止条件是当前节点为空节点时返回
  • 首先递归左子树,然后递归右子树。
  • 在当前节点node左节点存在时,先将node.left用一个temp变量保存起来;然后通过遍历temp.right找到当前节点左子树中最右边的节点。
  • 然后将当前节点的右子树node.right连接到当前节点左子树中最右边的节点上即temp.right。
  • 最后将当前节点的左子树node.left全部移动到当前节点的右子树上node.right。
  • 因为最终的单链表顺序要按照前序遍历的结果,所以这里是所有左子树的节点都在右子树节点前面。
35、搜索插入位置
  • 二分查找:如果目标值存在于数组当中则返回其索引;如果不存在,则最后left指针所指位置即为目标值将要插入的位置。
  • 因为当最后一步left=right之后,两个指针仍然会移动一步,所以left指针最后所指位置才是要插入的位置。
74、搜索二维矩阵
  • 二分查找:循环遍历,对每一个子数组应用二分查找方法即可。
20、有效的括号
  • 栈:定义一个栈存储所有未匹配的左括号所对应的右括号;定义一个字典,存储着所有的括号对。
  • 遍历字符串,如果字符存在于字典当中,则说明当前字符是左括号,将其对应的右括号加入栈中;如果当前栈为空或者栈顶元素不等于当前字符,则直接返回False;如果栈不为空且栈顶元素等于当前字符则将栈顶元素弹出。
  • 最后返回栈是否为空,为空代表括号都匹配了,非空代表剩余了未匹配的括号。
234、回文链表
  • 方法一:链表转数组,判断数组是否为回文数组(使用头尾双指针方法)
  • 方法二:快慢指针+栈:
    • 首先使用快慢指针找到链表的后半部分的起始位置,同时将链表的前半部分都添加到一个栈中。
    • 特别注意:如果链表节点个数为奇数,则快指针最后指向的位置为末尾节点,此时慢指针指向的位置为中间节点,需要将慢指针再走一步,跳过中间节点。
    • 最后再遍历慢指针,同时与栈中弹出的元素比较。只要出现一个不同即不是回文链表。
23、合并K个升序链表
  • 双指针+归并:按照头尾双指针方法,两两合并链表,将合并之后的链表赋予到头指针所指的位置上,将尾指针所指的链表给pop()出去。
  • 循环合并的终止条件是最终链表数组的长度为1;首先需要定义一个虚拟节点用于两个升序链表的合并。
  • 合并两个链表时,只需要循环遍历比较两个链表节点值的大小,将较小的值首先置于虚拟节点之后,当其中一个链表已经遍历完时,将剩余的另一个链表连接到末尾即可。
148、排序链表
  • 方法一:链表转数组,将数组排序后再转换成链表。
  • 方法二:归并排序链表
    • 首先判断当头节点或者头节点的下一节点为空时直接返回头节点
    • 其次,使用快慢指针的方法找到链表的中间节点,将链表一分为二,前半部分链表以头节点开始,后半部分链表以中间节点开始(慢指针所指的下一节点);需要注意这里切分链表的时候要先保存后半部分的链表(mid=slow.next),再在原来链表的基础上将中间节点的下一节点置空,即断开(slow.next=None)。
    • 然后递归左右两部分链表。
    • 最后进行两个链表的合并排序操作(定义一个虚拟节点),最后返回虚拟节点的下一节点。
994、腐烂的橘子
  • 广度优先搜索:首先遍历一遍统计新鲜橘子的数量,同时存储腐烂橘子的坐标位置。
  • 当存在腐烂橘子且新鲜橘子的数量不为0时
    • 每一轮遍历的长度都是上一轮的腐烂橘子数量,对于已经存储的腐烂橘子坐标,向它的四周遍历,只要坐标不越界且是新鲜橘子,那么就将它变成腐烂橘子且新鲜橘子数量减一,同时将新的腐烂橘子的位置坐标存储起来。
    • 每一轮的腐烂橘子遍历完成之后,分钟加一。
  • 最后返回分钟数,但是当最后剩余的新鲜橘子数量不为0时返回-1。
34、在排序数组中查找元素的第一个位置和最后一个位置
  • 二分查找:首先利用二分查找方法找到第一个等于目标值的数。
  • 然后以这个数所在下标作为往左右两个方向搜索的起始位置,只要向左或者向右的下一个位置下标不越界且其值等于目标值则边界移动一位。
  • 最后返回左右边界;如果没有找到目标值,返回-1。
215、数组中的第K个最大元素
  • 快速排序+递归:随机选择一个划分数组,将数组划分为small,equal,big三部分。
  • 如果k小于等于big数组的长度,那么第k个最大元素一定是在big数组中,接下来就对(big,k)进行递归。
  • 如果k大于big数组和equal数组长度之和(即nums数组长度减去small数组长度),那么第k个最大元素一定是在small数组当中,接下来就对(small, k-len(big)-len(equal))进行递归。
  • 除过以上两种情况,那么第k个最大元素一定是在equal数组当中。
347、前K个高频元素
  • 方法一:哈希表+排序:首先使用一个哈希表来统计数组中各个元素出现的频率,然后按照频率对哈希表进行从大到小的排序,最后返回前k个最大的键值。
121、买卖股票的最佳时机
  • 动态规划的思想:一次遍历,在每一个位置都更新历史最小价格和最大利润两个变量。
  • 初始化最大利润为0,最小价格为一个无穷大的值,在每个位置都比较一次;最小价格为当前价格与历史最小价格的较小值,最大利润为历史最大利润与当前价格减去历史最小价格的较小值。
  • 最终遍历完返回最大利润。
55、跳跃游戏
  • 动态规划+贪心:初始化最大跳跃距离为0,遍历数组,首先判断已有的最大跳跃距离能不能跳到当前位置,如果已有的最大跳跃距离小于当前位置的下标,那么就无法跳到当前位置,更不会跳到最终位置,直接返回False即可。

  • 如果已有的最大跳跃距离大于等于数组最后一个位置的下标时,代表已经可以跳到数组末尾了,直接返回True即可。
  • 同时判断完之后,每个位置还需要更新已有的最大跳跃距离。
45、跳跃游戏Ⅱ
  • 贪心算法:维护一个最大跳跃距离、最远跳跃下标和跳跃次数。
  • 能否跳到当前位置取决于最大跳跃距离是否大于等于当前位置下标。
  • 最远跳跃下标相当于一个边界情况,当达到最远跳跃下标时跳跃次数必须加一,最远跳跃下标也要更新为当前的最大跳跃距离。
  • 需要注意的是,不遍历最后一个位置,因为在这之前最大跳跃下标已经大于等于最后一个位置下标了(意思是前一步肯定要跳,跳了就能到达最后一个位置了,到了之后不用再跳了)。
763、划分字母区间
  • 贪心算法:既然要让字母都在一个区间之内,那么一个字母的最小出现位置和最大出现位置肯定要在一个区间之内。
  • 首先遍历一遍,记录每个字母出现的最大位置下标;使用ord()函数。
  • 维护start和end下标,遍历过程中不停地更新end位置,当当前遍历位置下标等于end时说明start到end这一部分的字母在end之后的部分中没有出现,就可以把这部分的长度加入到结果列表中去。
  • 然后更新初始位置start为end+1,继续重复上述过程。
70、爬楼梯
  • 动态规划:每次只能爬一阶或者两阶,考虑极端情况,每次爬到终点前的那一步,只有两种可以,终点之前爬了一阶或者爬了两阶到达终点的。
  • 意思是从第n-1阶爬一阶到终点是一条路,从n-2阶一次性爬两阶走到终点也是一条路,那么爬到第n阶的方法总数就是爬到第n-1阶的方法数加上爬到第n-2阶的方法数之和(千万不要认为最后一次爬一阶或者两阶方法数要加一,方法是路径数不是步数,要知道最后的那一步我们已经别无选择了!)
  • 上述即可确定递推公式,初始化时为了方便可以定义爬到0阶有一种方法,爬1阶也是一种方法,后续类推。
118、杨辉三角
  • 动态规划:每一行的第一个元素和最后一个元素都为1,中间的元素为上一行相同下标的元素加上前一个元素的和(由此确定递推公式)。
  • 每次维护一个单行的临时数组,每一行这个数组的长度为遍历下标加一(因为遍历是从0开始的,对应第一行)。
  • 从第二行开始计算,每次遍历的范围从第二个元素到倒数第二个元素。
198、打家劫舍
  • 动态规划:相邻的房子不能连续偷,所以每个房子会有偷或者不偷两种状态,定义dp数组为偷到每个房子时所拥有的最大金额。
  • 偷到某个房子时所能获得的最大金额取决于两种情况:偷了前一个房子(那就不能偷现在这个房子)后拥有的金额;没偷前一个房子,偷了倒数第二个房子(那就可以偷现在这个房子),偷完倒数第二个房子后的金额再加上现在这座房子的金额。以上两者取最大值即为当前的最大金额。
  • 最后返回偷到最后一个房子时所拥有的最大金额。
279、完全平方数
  • 动态规划:定义dp数组为和为i的完全平方数的最少数量。
  • 初始化dp数组中的值为无穷大值,但是dp[0]=0(和为0的完全平方数的最少数量为0)。
  • 遍历过程中dp[i]的值取决于的dp[i]和dp[i-j*j]+1的值中的较少值(对于j,需要从1开始遍历,意思是找到一个最合适的数j,再加上和为i-j*j这个数的最少完全平方数的数量,合起来刚好就是和为i的最少完全平方数的数量),由此可以确定递推公式:dp[i]=min(dp[i], dp[i-j*j]+1)。
322、零钱兑换
  • 动态规划:定义dp数组为凑成金额i所需的最少硬币数量。
  • 初始化一个大小为总金额加一,值为float('inf')的dp数组,且dp[0]=0。
  • 首先遍历硬币,再遍历金额(遍历起始值为coin,因为小于硬币面值的金额无法凑成),dp[i]可以由当前硬币(coin)加上总金额减去当前硬币(i-coin)的值组成,而i-coin的金额值对应的最少硬币数量是多少就又可以继续往前推……,由此便可以确定递推公式:dp[i]=min(dp[i], dp[i-coin]+1)。
5、最长回文子串
  • 方法一:中心扩散法
    • 回文子串字符个数如果是奇数那必然是从中心一个字符往两端扩展的;回文子串字符个数为偶数那必然是从中心两个字符往两端扩展的。
    • 所以通过两次遍历,以每一个字符为中心或者每相邻两个字符为中心向两端扩散,找出其中长度最长的那个回文子串即可。
    • 需要定义一个以左右两个起点开始往外扩散求回文子串的函数,只要左右边界不越界并且左右字符相等,那就是回文子串,直到不符合条件时返回符合的子串部分即可。
    • 一个小的改进tips:辅助函数不要返回每次求得的字符串,而是返回左右两个临界位置,初始化start,end,每次遍历就是去更新start和end,使这两个的长度最大,最后遍历完成后直接返回start-end+1这部分的子串即可。
1143、最长公共子序列
  • 动态规划:定义一个二维dp数组,含义为字符串1的前i个字符和字符串2的前j个字符的最长公共子序列的长度,dp数组大小为n1+1行,n2+1列(因为这里要包含空字符串即字符数为0的情况),初始化大小全为0。
  • 先遍历字符串1后遍历字符串2,遍历的时候都从1开始(代表前1个字符,对应的实际字符下标其实是0),至n1+1或n2+1结束。
  • 判断遍历过程中的字符串1和2当前位置的字符是否相等(因为下标是从0开始的,即当求字符串1的前i个字符和字符串2的前j个字符的最长公共子序列时,要根据i-1和j-1位置处的字符是否相等来进一步求解);如果相等其值那必然是在未拥有这两个字符的序列的基础上加一(即dp[i][j]=dp[i-1][j-1]+1);如果不相等,那么其值则是不算i-1处字符或者不算j-1处字符时的两种序列对的最长公共子序列的长度的最大值(即dp[i][j]=max(dp[i-1][j],dp[i][j-1]))。
  • 最后返回dp[-1][-1]处的值。
72、编辑距离
  • 动态规划:二维dp数据定义为word1的前i个字符转换成word2的前j个字符所需要的最小步数。数组大小为n1+1行,n2+1列;需要将数组的第一行和第一列的值初始化为对应的i和j(因为将一个字符串转换为一个空字符串所需要的最少步数为这个字符串的长度)。
  • 开始遍历,先遍历word1后遍历word2,从位置1处开始遍历;每次的判断条件仍然是字符串word1的第i个字符和字符串word2的第j个字符是否相等(实际写的时候要写下标i-1和j-1,下标是从0开始的)。
  • 如果相等,那不需要进行任何操作,最少步数就等于不包含这两个字符时的操作次数,即dp[i][j]=dp[i-1][j-1]。
  • 如果不相等,可以进行三种操作:删掉word1的第i个字符(dp[i-1][j]+1)或word2的第j个字符(dp[i][j-1]+1)(可能是多余的,删掉就相等了);在word1的前i-1个字符或word2的前j-1个字符的基础上插入一个字符(这里的插入操作和删除操作是等同的,给word1删掉一个字符等同于给word2插入一个字符,例如‘ab’变成‘a’);替换word1的第i个字符或者word2的第j个字符(dp[i-1][j-1]+1),删除和替换操作都需要一步,所以最终递推公式为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1)。
  • 最终返回dp[-1][-1]的值。
62、不同路径
  • 动态规划:可以定义一个大小为m×n的二维dp数组,也可以定义一个长为n的一维dp数组;含义为到达每一个位置的不同路径的数量。
  • 初始化到达第一行和第一列各个位置的路径数为1。
  • 到达每个位置的路径无非就是从上方或者左方走一步来的,所以到达每个位置的最小路径数等于到达其上方的最小路径数加上到达其左方的最小路径数,即dp[i][j]=dp[i-1][j]+dp[i][j-1]或dp[i]=dp[i]+dp[i-1]。
64、最小路径和
  • 动态规划:定义一个大小为m×n的dp数组,含义是到达每个位置时的最小路径和。
  • 初始化第一个位置的值为当前值,第一行和第一列的其他位置的值为累加和,dp[i][0]=dp[i-1][0]+grid[i][0],dp[0][j]=dp[0][j-1]+grid[0][j]。
  • 对于剩余的其他位置,每个位置的最小路径和等于其上方位置和左方位置中较小的那个值加上其本身的值,即dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]。
139、单词拆分
  • 动态规划:定义一个长为n+1的布尔类型的dp数组,含义是字符串的前i个字符能否由worddict中的若干个单词组成,初始化dp[0]=True(空字符串可以被组成),其余所有值为False。
  • 遍历时,对于字符串的前i个字符再次进行逐个遍历,寻找一个分割点,可以将这i个字符分割为前后两部分,其中前j个字符组成的字符串是可以由worddict中的若干个单词组成的(即dp[j]=True),而从j到i部分的字符串[s[j:i]]恰好也包含于worddict中,则说明字符串的前i个字符是可以由worddict中的若干个单词组成的,即dp[i]=True,遍历结束break。
  • 最后返回dp[n]的结果。
300、最长递增子序列
  • 方法一:动态规划
    • 定义一个长为n的dp数组,含义为数组的前i个元素的最长递增子序列的长度为dp[i],初始化所有值为1。
    • 对于前i个元素,都要再次从下标为0的位置进行遍历,以nums[i]为基准,如果nums[i]之前的某个元素小于nums[i],那么说明nums[i]就可以接在这个元素后面成为一个递增的子序列,nums[i]这个位置的值就可以更新为以这个元素为结尾的数组的最长递增子序列长度加一,即dp[i]=max(dp[i], dp[j]+1)(在这个再次遍历的过程当中,dp[i]是在不断地更新过程中的)。
    • 最后返回dp数组中所有元素的最大值(因为最长递增子序列不一定是以最后一个元素结束的)。
  • 方法二:动态规划+二分查找
    • 维护一个最小单调递增的序列,定义这个序列为长为n的dp数组,初始值为0。
    • 定义res指向最小单调递增序列的末尾位置,即为当前最小单调递增序列的长度,初始值为0。
    • 在遍历的过程中,每遇到一个数字,就在当前的最小递增序列中进行二分查找,找到第一个大于等于当前数字的值,将这个值给替换掉,组成新的更小的递增序列。
    • 如果遍历完最小递增序列之后,二分查找的右指针j和res相等了,说明当前值大于这个最小递增序列中的所有值,要新增一个值进来,这个序列的长度就要加一了。
    • 最后返回res即可。
131、分割回文串
  • 回溯法:需要定义一个回溯函数backtrack和一个判断字符串是否为回文串的函数,此外定义res存储最终的结果,path存储每一次分割的结果。
  • 在分割回文串的过程中,因为已经被分割的部分不能再参与接下来的分割,所以需要一个start_index来表示分割的位置,从0开始。
  • 回溯的终止条件是当分割位置(start_index)等于字符串长度时,代表分割已经走到了最后一个位置,此时可以将path添加到res中了。
  • 回溯遍历的过程中,每次都先判断切割位置之前的部分是否是回文串,如果是就将这部分加入path中并进行下一步递归回溯;回溯的过程就是调整切割位置的过程,因为每次都有一个切割起点,所以已经切割过的部分不会再参与到切割中。
39、组合总和
  • 回溯:定义回溯函数backtrack、res存储最终所有的结果,path存储每一个满足条件的结果。
  • 回溯的终止条件是目标值等于0,每固定一个值后,相当于接下来要搜索的值是target减去这个值的数。
  • 先对数组进行排序的目的是为了减少搜索的次数,如果当前元素大于目标值时,搜索提前终止,因为数组已经排过序了,当前值大于目标值那么后续值也会大于目标值,就没有满足条件的数了。
  • 回溯过程中要多增加一个参数start_index,表示每次搜索的起点,只考虑起点之后的数字作为候选数,避免重新开始一轮搜索的时候把前面已经搜索过的结果再加入进来,也是为了避免重复结果的产生。
46、全排列
  • 回溯:定义回溯函数backtrack,res存储最终所有的结果,path存储每一个满足条件的结果。
  • 需要定义一个回溯参数start_index,代表当前正在生成从哪个位置开始的排列。
  • 回溯函数的终止条件是当start_index等于n-1时,说明当前已经生成了一个完整的排列,可以将这个排列添加到最终的结果当中了。
  • 在递归回溯过程当中,对于start_index和它后面的每一个位置,都去尝试将start_index位置的元素和这个位置的元素交换位置,然后递归的生成剩余的排列,最后在回溯的过程中需要将这两个元素位置交换回来。
78、子集
  • 回溯:子集问题需要返回遍历过程当中的每一个节点上的结果。定义回溯函数backtrack,res存储最终所有的结果。
  • 回溯函数具有两个参数,一个是遍历的起点start_index,另一个是当前子集curr,初始化curr=[]。
  • 在递归回溯遍历过程中需要将start_index之后的每一个元素都尝试加入到当前子集中来,递归地生成所有的子集,需要回溯的时候再将这个元素从当前子集中移除。
739、每日温度
  • 单调栈:维护一个单调递增的栈,存储的是尚未找到下一个温度更高的日子的天的索引。
  • 初始化一个和温度数组一样大小的结果数组res用来保存最终的结果。
  • 遍历过程中,如果栈不为空且当前数值大于栈顶元素对应的数值,就弹出栈顶元素,并更新栈顶元素对应的res列表中的值为当前遍历索引减去栈顶元素(栈中保存的是索引),循环这个过程直到栈顶元素对应的数值大于当前数值或者栈为空。
  • 每次都要保存当前遍历索引。
152、乘积最大子数组
  • 动态规划:定义dp数组为前i个元素组成数组中乘积最大的连续子数组的乘积值。为了优化空间复杂度,只需要定义dp_max和dp_min表示以当前位置结束的子数组的最大和最小乘积。再定义一个结果值res,在每一次遍历中都要更新最大结果。所有值都初始化为第一个元素值。
  • 如果当前遍历值为正数,那么当前最大值就取前一个最大值乘以当前值和当前值两者中的较大值(因为前面一个最大值也可能为负数,再乘以当前值只会变得更小),当前最小值就是取前一个最小值乘以当前值和当前值两者中的较小值。
  • 如果当前遍历值为负数,就需要提前将前一个最大最小值交换,因为最小值乘以负数可能变成最大值,而最大值乘以负数可能变成最小值。
  • 最后返回res,记录遍历过程中产生的乘积最大值。
416、分割等和子集
  • 动态规划-背包问题:首先如果这个数组的和不是偶数,那么必然不可能被分割成两个子集且每个子集的和为target。定义一个布尔类型的值为False的dp数组,大小为target+1,表示是否存在一个子集的和为i。初始化dp[0]=True,因为肯定存在子集为空。
  • 正向遍历数据,然后反向遍历背包容量,当然如果当前值大于背包容量,那直接跳过。
  • 每次反向遍历背包容量的原因是我们在每次更新dp[i]时会用到dp[i-num],如果正向遍历那dp[i-num]就会提前被更新,但实际上我们想要使用的是上一个循环中的值,反向遍历可以避免重复计算。
  • 没遇到一个值,考虑要不要加入当前值来凑成和为i的子集,如果dp[i]本身已经为True那便为True,如果dp[i-num]为True,那表明在这个基础上再加入当前值肯定能凑出和为i的子集(只要满足一个条件即可)。
32、最长有效括号
  • 栈:定义一个栈stack来存储从左往右最后一个未匹配的右括号,初始因为没有右括号先加入-1。
  • 定义一个初始的最长有效括号长度max_length在遍历过程中不断更新。
  • 遍历整个字符串,如果遇到左括号就直接将其索引加入到栈中。
  • 如果遇到右括号,先将栈顶元素弹出,如果弹出后栈为空说明弹出的那个索引对应的是从左往右最后一个未匹配的右括号,也就是没有左括号等待匹配,此时继续将目前遍历的这个右括号对应的索引加入栈中,成为新的从左往右最后一个未匹配的右括号;如果弹出后栈不为空,那么说明弹出的是栈中未匹配的左括号,此时有效括号的长度为当前索引减去栈顶元素,更新max_length。
  • 继续上述遍历即可获得最长有效括号的长度。
155、最小栈
  • 最小栈:一种辅助栈,栈顶元素始终是当前栈的最小元素。
  • 首先定义一个主栈stack和一个辅助栈(也就是最小栈)min_stack。
  • 往栈中加入元素:将元素加入主栈的同时如果最小栈为空或或者最小栈的栈顶元素大于等于加入元素,那么该元素同时也要加入到最小栈中。
  • 删除主栈栈顶元素:要判断所删除的主栈栈顶元素是否等于最小栈的栈顶元素,如果相等,则同时要pop出最小栈的栈顶元素。
  • 获取主栈栈顶元素及获取最小栈栈顶元素,直接返回即可。
22、括号生成
  • 动态规划+递归回溯:以'({}){}'的形式返回递归过程中的每一个结果。
  • 回溯的终止条件:如果n等于0,直接返回['']。
  • 遍历n,将n对括号拆分成left和right对两部分,当left为i时,right就为n-1-i。
  • 将所有的left对括号和right对括号的两两组合添加到最终的结果中。
141、环形链表
  • 快慢指针法:如果头节点或头节点的下一个节点为空,直接返回False;定义快慢指针low,fast,初始时都指向头节点(也可以初始时慢指针指向头节点,快指针指向头节点的下一个节点)。
  • 在fast和fast.next不为空的情况下,慢指针每次走一步,快指针每次走两步,如果快指针和慢指针又相等了则说明链表存在环;如果遍历结束都没有相等则说明不存在环。
142、环形链表Ⅱ
  • 方法一:快慢指针法
    • 首先使用与第141题相同的方法判断链表是否存在环,然后如果slow等于fast存在环的情况下,让slow或fast指向头节点,两者接下来同时走,每次走一步,当两者再度相遇的时候就会同时指向入环的第一个节点。
  • 方法二:集合
    • 使用一个集合存储遍历过的节点,当再次遇到一个存在于集合中的节点时,这个节点就是入环后的第一个节点。如果遍历完都不存在这样一个节点则说明没有环。
21、合并两个有序链表
  • 方法一:双指针法
    • 如果链表1为空,返回链表2;反之一样。
    • 定义两个指针分别指向两个链表的头节点;定义一个虚拟节点,定义当前节点为虚拟节点的下一个节点。
    • 在两个链表都不为空的情况下同时遍历两个链表,每一次遍历都将具有较小值的那个节点接在当前节点后面,同时当前节点后移一位。
    • 最后当某一个链表已经遍历完时将另一个未遍历完的链表接在当前节点后面。
    • 最后返回虚拟节点的下一个节点也就是合并之后的头节点。
2、两数相加
  • 链表:定义一个虚拟节点,当前节点等于虚拟节点;定义一个进位,代表每一位要加上的进位数。
  • 在链表1或者链表2或者进位存在的情况下,当前位的总和就等于三者之和(如果当前位置的某个链表节点为空则加0)。
  • 总和整除以10就等于该位置产生的进位数;总和取余就是该位置产生的新的数字,创造一个值等于该值的新的节点跟在当前节点的后面。
  • 当前节点后移一位,两个链表的节点(开始是头节点)也跟着后移一位开始下一轮遍历。
94、二叉树的中序遍历
  • 方法一:递归
    • 按照左、中、右的顺序递归遍历,终止条件是所遇节点为空,返回[];每次都返回左子树的值+根节点的值+右子树的值即可。
104、二叉树的最大深度
  • 二叉树的最大深度指的是二叉树有多少层节点,这个问题拆解到一个最小单元就是求以这个二叉树中的每一个节点为根节点的二叉树的深度,从最底层往上不断地返回。
  • 递归:终止条件是所遇节点是空,返回0。
  • 递归地遍历左子树和右子树,从底向上每次都返回当前节点下左子树的深度和右子树的深度的最大值加一到上一层节点上(+1的意思是每次加上当前节点那一层,比如当当前节点的左右子树返回结果都是0的时候,加上1代表以当前节点为根节点的二叉树的最大深度为1)。
226、翻转二叉树
  • 递归:这个问题可以拆解为把每一个节点下面的左右子树都交换位置,从上往下地递归遍历即可完成翻转二叉树。
  • 终止条件是当前节点为空,直接返回当前节点。
  • 节点不为空时,然后首先交换当前节点地左右子树,然后递归地遍历当前节点地左子树和右子树。
  • 最后返回根节点即可。
11、盛最多水的容器
  • 双指针+贪心:定义两个指针从两边往中间移动,求左右两边的最大高度,同时为了求最大体积,每次要移动的必须是较小的那个值。
  • 在这个移动的过程当中更新最大体积,体积就等于左右两边的最大高度中较小的那个值与横轴下标之差的乘积(能盛多少水总是由较小的高度来决定的)。
15、三数之和
  • 排序+双指针:为了能够更好地去除结果集当中的重复组合,首先要对数组进行排序。
  • 遍历过程中,首先固定一个元素值,从后面部分的数组中通过移动左右指针(左指针指向当前元素的后一个位置,右指针指向末尾位置)的方法来找到两个数字,使三者相加为0。
  • 在遍历刚开始的时候,当下标大于0时首先应该判断当前值是否等于上一个值,以此来跳过重复元素值。
  • 当三者和为0时,将三个数值加入结果集中,此时要同时移动左右指针,且要跳过重复元素,即在保证左指针始终小于右指针的情况下,持续跳过下一个相等元素,直至遇到一个不相等的元素时再将指针移动一位。
  • 当三者和小于0时应该移动左指针使总和增大靠近0;当三者和大于0时应该移动右指针使总和减小靠近0。
42、接雨水
  • 方法一:动态规划
    • 定义两个数组,分别记录每个位置从左往右看和从右往左看的最大高度。
    • 然后再遍历一遍数组,取每个位置的左右最大高度中的那个较小值,减去本位置的高度就是本位置所能接的雨水的体积(因为每个位置的宽为1),将所有位置能接到的雨水的体积加起来就是最终的答案。
  • 方法二:双指针
    • 定义左右两个指针代替了动态规划方法中的两个数组,在遍历的过程当中分别更新从左往右和从右往左的最大高度。
    • 每次都移动左右最大高度中指向较小值的那个指针,如果左边的最大值小于右边的最大值,就先计算左边指针所指位置能接雨水的体积;否则计算右边指针位置能接雨水的体积。
    • 最终相加所有位置的雨水体积就是最终结果。
239、滑动窗口最大值
  • 双端队列:导入collections.deque(),利用双端队列可以左出右进的特点,可以保证滑动窗口的有效范围。
  • 双端队列存储的是数值的索引而不是数值本身。
  • 双端队列的左边第一个值始终是当前滑动窗口的最大值,如果遍历时遇到的值大于双端队列从右往左的第一个值,那就要将这个值从右边移除出队列,循环这个判断操作直到遇到双端队列中大于该值的数或者队列为空时。最后将这个值的索引也加入到队列中。
  • 当双端队列左边的第一个索引小于当前遍历索引i-k+1(刚好是滑动窗口的左边界)时,就要将左边的这个值移除队列,因为此时这个值已经不在滑动窗口的范围内了。
  • 从左往右遍历数组时,因为当遍历的数值数量还不够滑动窗口的长度时是无法得到第一个滑动窗口的最大值的,所以只有当遍历索引i>k-1时,才能将当前双端队列的左边第一个值加入到结果列表中,往后每移动一次都要加入一次。
76、最小覆盖子串
  • 滑动窗口:从左往右遍历,在当前已遍历子串完全包括了目标子串的所有字符时保存当前部分的起始位置和结束位置,然后起始位置往后移一位,继续遍历。
  • 首先定义一个字典,遍历一遍目标字符串来记录目标字符串中的字符及其数量。
  • 初始化一个元组,遍历过程中重复更新记录包括目标子串且长度最小的子串的起始索引和结束索引。
  • 在遍历过程中,当遍历到的字符在字典中且值大于0时,目标字符数量减一(表示找到了一个还有多少没找到),同时字典中该字符对应的值也要减一(这个操作要在判断条件之外进行,这是为了下面判断起始位置处字符是否是目标字符考虑,如果只有目标字符在字典中对应的值才减一,那么最后在判断起始位置处的字符是否是目标字符时就不能以字典中对应值为0来判断,因为非目标字符的值也可能为0)。
  • 当目标字符数量等于0时代表所有的字符都已经找到了,这个时候就要记录这部分子串的起始位置和结束位置然后更新结果,如果结束位置减去起始位置的长度小于结果元组中的两个值之差就更新。
  • 接下来就是要将起始位置后移一位,但是要先判断起始位置是不是目标字符,如果在字典中该字符对应的值为0则说明它是目标字符,那么移除了这个字符之后,对应的目标字符数量就要加一,同时字典中的该字符对应的值也要加一,起始位置后移一位。
19、删除链表的倒数第N个结点
  • 双指针法:如果头节点为空直接返回头节点;定义一个虚拟节点,虚拟节点的下一个节点是头节点,初始化令第一个指针指向头节点,第二个指针指向虚拟节点(如果第二个指针也指向头节点那么最后到达的位置将是倒数第n个节点)。
  • 先让第一个指针移动n步,再同时移动两个指针,当第一个指针到达链表末尾节点为空时第二个指针恰好指在链表的倒数第n+1个节点处。
  • 利用second.next=second.next.next的方法删除倒数第n个节点即可。
24、两两交换链表中的节点
  • 双指针法:如果头节点或者head.next为空直接返回头节点。初始化一个虚拟节点指向头节点,一个前序节点pre指向虚拟节点。
  • 在head 和 head.next存在的情况下,第一个节点first指向head,第二个节点second指向head.next,接下来的步骤就是要交换这两个节点。
  • 交换节点:首先将前序节点pre指向第二个节点second,再令first.next=second.next,意味着将第二个节点的后续部分先接到第一个节点后面,最后再令second.next=first,从而改变了两个节点的顺序(注意此刻交换完之后,原本的前一个节点first已经成为了现在的第二个节点)。
  • 最后要往后移动head和pre的位置,准备下一次交换;令head=first.next、pre=first即可。
25、K个一组翻转链表
  • 每次先确定K个链表节点的头节点和尾节点,对这K个链表节点进行反转,如果不足K个节点就直接返回;
  • 首先定义一个指向头节点的虚拟节点dummy,初始时尾节点tail指向虚拟节点,在头节点不为空的情况下开始遍历,先将尾节点移动K个节点到达第一部分K个节点的末尾位置(如果这个移动途中不足k个节点就为空了那就直接返回dummy.next)。
  • 定义一个链表反转函数,输入当前K个节点链表的头节点head和尾节点tail,将其反转,输出是反转之后的head,tail。
  • 链表反转:如果头节点为空,直接返回头节点;令前序节点pre指向tail.next(这是完成反转之后链表要指向的位置),当前节点cur指向头节点head;在前序节点pre不等于尾节点tail时(因为pre一直是当前节点cur的前一个节点,当pre等于tail时说明cur已经到了tail.next位置,此时反转已经完成了)进行反转;首先使用一个临时节点temp=cur.next,然后通过令cur.next=pre反转cur节点,最后前移pre和cur节点,通过pre=cur,cur=temp;最后返回tail和head,就是新的head和tail。
  • 在链表反转之前要先保存头节点之前的部分pre和尾节点之后的部分temp,得到反转结果之后要将其连接到原本的链表上,头尾都和之前的部分相连,即pre.next=head,tail.next=temp,最后移动pre=tail,head=tail.next至新的位置。
101、对称二叉树
  • 递归+深度优先搜索:首先当根节点为空时,直接返回True。需要定义一个辅助函数(函数的输入为左右两个节点,初始为root.left,root.right)接下来来递归地遍历比较左右两个子树。
  • 终止条件是当两个节点都为空时,返回Ture;如果两个节点中有一个节点为空或者两个节点的值不相等时,返回False。
  • 接下来递归地遍历(left.left,right.right)和(left.right,right.left)两个组合。只有当两种情况都返回True时才是对称二叉树。
543、二叉树的直径
  • 递归+深度优先搜索:需要定义一个全局变量res(初始值为0)来更新树的直径,定义一个辅助函数来递归地计算每个子树的直径,从而求得最大直径。
  • 在辅助函数中,递归的终止条件是如果节点为空返回0;递归地求左右子树的直径,并更新直径res=max(res,left+right),其中left+right求得是当前节点为根节点时左右子树的长度之和(也就是直径),而每一次递归返回时要返回的是当前节点为根节点的树的深度,为max(left,right)+1。
1、两数之和
  • 哈希表:定义一个哈希表存储遍历过程中每个元素及其索引。
  • 遍历过程中如果哈希表不为空且目标值减去当前值已经存在于哈希表中则直接返回当前值的索引和哈希表中存在的差值的索引即为结果;否则将当前值和索引加入哈希表中继续遍历。
146、LRU缓存
  • LRU缓存的定义:意思是最近最少使用,是一种淘汰策略。当缓存满了需要腾出空间时,优先移除那些最近最少被访问的数据。
  • 哈希表+双向链表:使用哈希表可以在常数时间内快速查找对应的节点,键是一个整数,用来唯一标识缓存中的每一项数据,值是缓存中存储的节点,每个节点对象都是Node类的实例(双向链表Node类的定义需要包括:key,value,prev,next四个值)。
  • LRU类初始化:定义最大缓存容量capacity,定义一个哈希表hash_table,定义一个头节点head和尾节点tail作为头尾的哨兵节点并将两者连接在一起形成一个双向链表的初始框架。
  • LRU类的remove方法:从链表中移除一个节点,输入一个节点node,首先找到这个节点的前一个节点prev和后一个节点next,然后将这两个节点连接在一起即删除掉了节点node。
  • LRU类的add方法:将一个节点添加到链表的头部(紧接着head节点),为了保证最近使用的节点总是在最前面。首先找到头节点prev=self.head和头节点的下一个节点next=self.head.next;然后将节点node添加在这两个节点之间,使prev.next=node and node.prev=prev(用于连接前面的节点),node.next=next and next.prev=node(用于连接后面的节点)。
  • LRU类的get方法:用于获取缓存中的数据,首先判断如果数据的键存在于哈希表中,直接获取哈希表中存储的对应的节点node,然后同时先使用remove方法删除链表中的节点node,再使用add方法将node添加到链表的头部,如果不存在直接返回-1。
  • LRU类的put方法:用于向缓存中添加数据,首先判断如果数据的键已经存在于哈希表中,则先使用remove方法从链表中删除对应的节点,再使用类Node生成要加入的节点,再将这个节点加入到哈希表中,最后如果哈希表的长度已经大于最大缓存容量,则获取链表尾部的节点remove_node=self.tail.prev,将这个节点从链表中删除掉,同时删除掉对应哈希表中的值。
102、二叉树的层序遍历
  • 广度优先搜索+双端队列:使用一个双端队列,按二叉树的每一层来遍历所有的节点,每遍历完一层就要将这一层的节点以数组形式加入到最终的结果中去。
  • 初始队列值为[根节点],当队列不为空时,首先计算当前队列的长度(也就是上一层的节点数量,每次循环遍历完为止),初始化当前层的节点列表。
  • 在每一层的遍历过程中,从队列左边pop出节点,并将该节点的值加入当当前层的列表中,如果该节点存在左节点或者右节点就依序将它们加入到队列的右边尾部。每遍历完一次就将所有节点值加入到结果中。
138、随机链表的复制
  • 哈希表:使用一个哈希表来复制链表的节点,键为原链表节点,值为新链表节点。
  • 首先遍历一遍链表,复制所有的链表节点;第二遍从头开始遍历链表时候,对于哈希表中存储的每一个新链表节点,使用hash_table.get()方法找到这个新链表节点的next指向的节点以及random指向的节点。
  • 最后返回新链表的头节点,即hash_table[head]
108、将有序数组转换为二叉搜索树
  • 二叉搜索树的定义:每个节点的值都大于其左子树所有节点的值,都小于其右子树所有节点的值,左右子树本身也必须是二叉搜索树。
  • 高度平衡的二叉搜索树:任意节点的左子树和右子树的高度差不超过1。
  • 如果这个升序数组为空,直接返回;否则先找到这个数组最中间的数(nums[mid])作为根节点,再从数组的左半部分(nums[:mid])和右半部分nums[mid+1:]中采用同样的方法找到根节点的左节点和右节点,以此方法递归最后返回根节点。
98、验证二叉搜索树
  • 递归:定义一个辅助函数来递归地验证是否二叉搜索树。这个辅助函数地参数包括当前节点,节点值地下限和上限三个参数,初始化上限和下限为正无穷和负无穷。
  • 如果节点为空,直接返回True;如果当前节点值小于等于下限或者大于等于上限,直接返回False;每次递归地判断该节点节点和右节点地值是否也满足上述条件(必须都满足才行)。
  • 递归地过程中必须更新上下限的值;比如当前节点地值是其左节点的上限,是其右节点的下限。
230、二叉搜索树中第K小的元素
  • 方法一:结合二叉搜索树的性质和中序遍历
    • 中序遍历二叉搜索树的结果就是一个递增的数组,最后直接返回这个递增数组的第k-1个元素即可。
  • 方法二:中序遍历的过程中记录遍历节点数量
    • 当k减小到0时直接返回当前节点的值,后续遍历不用再进行,提前返回。
    • 首先持续递归遍历左节点,直到找到最左边的节点也就是中序遍历的起点,然后接下来每递归一次,k的值先减小1,再去递归右节点,直到k等于0。
199、二叉树的右视图
  • 层序遍历:存储层序遍历过程中二叉树每一层节点的最后一个节点值就是二叉树的右视图(层序遍历的详细方法见102题)。
207、课程表
  • 图+深度优先搜索:定义一个邻接表graph,第一次遍历存储所有课程的先修课程,其键为课程,值为一个包含该课程所有先修课程的列表。
  • 定义一个长为numCourses的数组,记录每个课程节点的状态,初始状态0为未访问,-1为正在访问,1为已访问。
  • 遍历所有课程,调用辅助函数dfs来判断以每个课程为起点是否会存在环,如果图中存在环则说明无法完成所有课程,直接返回False。
  • 定义辅助函数递归地检查每个节点是否形成环。如果当前节点状态为-1,则说明存在环,直接返回False;如果当前节点状态为1,表示该节点已经被访问过了,直接返回True;将当前节点状态设置为-1表示正在访问;然后遍历当前节点地所有邻接节点,递归调用辅助函数,如果任何一个邻接节点返回False,则直接返回False;最后将当前节点状态设置为1,表示访问完毕。
438、找到字符串中所有字母异位词
  • 哈希表+字符计数:定义两个数组(长度为26)来统计当前窗口(窗口的大小就是目标字符串p的长度)两个字符串的字符及其数量。
  • 首先如果字符串s的长度小于字符串p的长度,那么s中必定不会存在p的异位词,直接返回空数组。
  • 先遍历字符串p,同时遍历字符串s的前len(p)个字符,遍历完之后也就固定了滑动窗口的长度,因为字符串p的异位词肯定和字符串p的长度一样。然后先判断两个数组是否相等,如果相等则说明字符串p和字符串s的前面部分的字符和数量完全相等,则就是一个异位词,直接将索引0加入结果中。
  • 接下来继续从索引0处开始遍历,但是遍历终点为len(s)-len(p),因为要考虑到滑动窗口的右端刚好到达字符串s的最后一个字符时,滑动窗口的最左端位置就在len(s)-len(p)-1处。
  • 在遍历过程中,先从滑动窗口左边移除字符,也就是对应的数组中对应字符数量减去1;再加入滑动窗口右边的第一个值,也就是s[i+len(p)],对应数组中的字符数量加一;每一次遍历都判断一下当前数组是否和目标字符串的数组相等,如果相等的话就将当前索引加一加入到结果当中。
79、单词搜索
  • 深度优先搜索+回溯:遍历二维字符网格的每一个位置,然后从这个位置开始进行深度搜索,沿着这个位置的四个方向进行搜索,如果找到了对应单词就直接返回True,直到最后都没找到就返回False。
  • 深度优先搜索函数的参数包括:二维字符网格board,单词word,位置索引i,j,单词下标索引k。
  • 对于每个位置,首先判断位置索引i,j是否在有效范围内,以及当前位置的字符是否和单词对应位置的字符相等,如果上述情况有一个不满足就直接返回False。
  • 如果k等于单词长度减一,代表此时以及找到了单词的所有字符,直接返回True即可。
  • 为了避免下次搜索时会重复搜索该位置,先将该位置置为空,再对该位置的四个方向进行递归搜索。搜索完毕后再将该位置的字符还原。
  • 每一次搜索返回对四个方向搜索的结果,只要有一个方向是True,最后返回就是True。
17、电话号码的字母组合
  • 回溯:定义一个数组res存储最终的结果,定义一个数组path存储过程中的每一个结果,定义一个map存储所有数字对应的字母,定义一个回溯函数进行回溯(需要有一个起始下标index参数,对应每次遍历的起点)。
  • 回溯的终止条件是起始下标index等于输入数字的长度,代表已经遍历到了最后一个数字,可以将path中的结果添加到res中了(要使用''.join()方法)。
  • 先根据起始下标得到对应的数字,再根据数字获取对应的字母,对字母字符串进行遍历,先添加至path中,再进行下一个数字的递归遍历,递归之后要进行回溯。
  • 最后返回结果res即可。
189、轮转数组
  • 数组:将数组中的元素向右移动k次,当k小于数组长度时,相当于将数组的最后k个元素移动到数组最前面来;当k大于数组长度时,则相当于将数组最后的k%n个元素移动到数组最前面来(因为将一个元素恰好移动数组长度的次数后会回到原位)。
  • 使用负索引切片的操作一次性完成,即nums[:]=nums[-k:]+nums[:-k]。
238、除自身以外数组的乘积
  • 数组:第一遍正向遍历时先求得当前索引左侧所有数字的乘积,第二遍反向遍历再求得当前索引右侧所有数字的乘积,然后将左右两侧的结果相乘。
  • 需要定义一个结果数组(存储第一遍遍历过程中当前索引左侧的乘积及最终结果)和一个额外的临时变量(用来存储第二遍反向遍历过程中的每一个右侧乘积)。

持续更新中……

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值