1.两数之和(哈希表)
给定一个整数数组以及一个目标结果,给出整数数组中两个数加起来等于目标结果的索引
如nums=[2,7,11,15], target=9
return [0,1]
解题思路:使用哈希表的结构,在python即dict的结构,查询时间为O(1),遍历数组,若当前值不在字典中则将下标保存在dict中,键值为target-nums[i],即对于每一项保存的键值为里目标所需的数字,而在后续遍历当中若碰到该数则返回索引
2.加两个数(链表)
两个数以链表的形式存储,如256形式为6->5->2,返回的数字也以逆序列表形式
解题思路:先构建链表头,返回值应为head.next,不断循环,循环条件为l1,l2,c都不存在或为0
3.最长无重复子字符串(双指针)
给定一个字符串求解最长的无重复的子字符串
解题思路:双指针法,一个起始指针指向子字符串的起始位置,只有在碰到重复的字母且重复字母位于起始指针之后时更新起始指针,若当前元素未曾出现,则更新最大长度
5.最长回文子字符串(动态规划)
解体思路:1.动态规划,构建动态规划表,通过dp[j][i]与dp[j+1][i-1]的关系进行求解
2.中心扩展法,对于每个字母,以该字母为中心和以该字母及其后一个字母为偶数中心进行扩展,扩展规则为,首项和尾项相等且不超出边界,返回长度为最大扩展的长度,若最大扩展长度比现存的长度长,则更新现存长度的首尾索引,最终返回s[start:end].
15 3Sum(三指针):
对于数组nums,求所有三个数加起来为0的组合
解题思路:排序后,对于第i个数字,左指针为i+1,右指针为len(nums)-1,
17 电话号码的字母组合(回溯与递归)
给定一串数字电话号码,给出字符的所有可能组合
解题思路:组合问题一般使用回溯算法
22生成对括弧(回溯)
给定数字n,生成所有包含n对括弧的有效排列
解题思路:是一个不断往容器放'('或者')'的问题,放置'('的先决条件为'('个数小于n,而放置')'的先决条件是')'的个数小于'('个数.使用递归的方式实现,当)的个数达到n,将当前结果放入output中.
23合并k个已经排序的链表(分治)
解题思路:1.在合并两个链表的基础上,利用双指针法,l指向链表表头,r指向尾,不断合并两个链表放入新的lists中,说白了就是不断合二为一,直至只剩一个
2.利用堆生成最小堆,不断pop根节点root的元素
31下一个排列(数学理解)
解题思路:1.从后往前遍历,找到第一个下降的数字,该数字应该被后面的第一个大于该数字的数字替换
98有效的二叉搜索树(递归与DFS)
一颗有效的二叉树应该满足左子树的所有节点的值小于父节点而右子树的所有元素大于父节点,所以不仅需要对比left.val,root.val,right.val,还要对比left的最大值和root.val和right的最小值的关系.
解题思路:采用递归的方式不断往下判断,对于每次判断需要给定上下界,如对于左子树,应该有上界限为root.val,而对于右子树应该有下界要求
100.两棵树是否相同(递归与DFS)
两棵树相同的必要条件为root元素相同且左子树相同且右子树相同
解题思路:通过递归判断,需要给定递归结束条件
111和112 求树的最小深度, 求是否一条路径上的数字累加为sum(递归与DFS)
这种问题需要注意一种额外情况,就是若是只有一个儿子的情况,需要分开来注意一下
141 链表有没有环
解题思路:使用快慢指针法,快指针一次走两步,若存在环则快指针总有一天会走到慢指针的后面
136 只出现一次的数
描述:在数组中除了一个只出现一次的数之外,其余数都出现了两次,使用o(n)时间和零额外空间找到这个只出现一次的数
解题思路:1.利用sum和set(),2*(sum(set(nums)))-sum(nums)
2.利用异或操作,两个相同的数异或得到的是零,而零与任何数异或为其本身,且异或操作满足交换律
即4^1^2^1^2 = 4^1^1^2^2=4
137 只出现一次的数II
描述:在数组中除了一个只出现一次的数之外,其余数都出现了三次,使用o(n)时间和零额外空间找到这个只出现一次的数
解题思路:同样使用位操作实现,不过对于三个数需要用两个位来记录
a = 0,b =0 ,假设第一次碰到5,则a=5,b=0,第二次碰到5,a=0,b=5,第三次碰到5,a=0,b=0,根据以上要求a,b需要经过以下操作
a = a^5 & ~b, b = b^5 & ~a
72编辑距离
描述:存在两个单词Word1和word2, 和三种操作,删除,替换和插入字符,求由Word1到word2最少的编辑次数
dp[i][j] 表示 word1 的前 i 个字母和 word2 的前 j 个字母之间的编辑距离
如果word1[i] = word2[j],即两个子串的最后一个字母相同,就不用操作:
dp[i][j] = dp[i-1][j-1]
否则,在插入,替换和删除之中选择当前总编辑距离最小的一个,即
- 在dp[i][j-1]的基础上,在word1后插入一个和word2[j]一样的字符
- 在dp[i-1][j]的基础上,删除word1的第i个字符
- 在dp[i-1][j-1]的基础上,替换word1的最后一个字符和word2[j]相同
dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1]) + 1
动态规划解题思路:1.确认问题确实可以由动态规划解决,往往意味着该问题有子问题,或者是求最优解问题
2.确定状态表达式,即dp[i]或者dp[i][j]分别代表什么意思
3.确定状态表达式的边界
4.确定状态转移方程
128最长连续数字串
描述:在一个数组中找到最长的连续数字串,如[1,4,3,2,6]的最长连续数字串为1,2,3,4,返回长度4
解题思路:通过set来操作,首先去掉重复元素,利用pop()弹出一个元素,对于该元素,遍历与该元素相近的元素是否在set中,在的话,len+=1,且将该元素删除,最终判断最大长度与以该元素扩展的连续数字串长度的最大值
114 把二叉树展开为链表
涉及到树的题目往往和DFS或者BFS有关,根据题意可以很快判断使用DFS更合适
使用递归方式实现DFS,同时需要提供多个边界条件
115生成二叉树的个数(动态规划,分治的思想)
描述:给定n,将1-n个数生成为一颗二叉树,这样的二叉树有多少颗
dp[n] = f(1)+f(2)+....f(n)其中dp[n]表示二叉树的个数,而f(i)表示根节点选择i时,数的个数
选择i为根节点时,左子树个数应该为dp[i-1], 右子树个数为dp[n-i-1]
f(i) = dp[i-1] * dp[n-i]
78 子集(动态规划的思想)
给定一个无重复数的整数集合,输出所有的子集
解题思路:在i-1个元素的集合上,加了一个第i个元素,子集增加了哪些?
当前子集 = 原所有子集 + [原所有子集的每个子集加上当前增加的元素]
dp = dp + [num+i for i in dp]
142 链表环II
描述:找到链表中环的入口对应的节点
解题思路:首先使用快慢指针法确定是否有环,当快指针p1和慢指针p2第一次相遇时表示有环,而设此环入口位置离起点距离为x,相遇位置离入口距离y,即满指针走的长度为x+y,而快指针走的长度为2(x+y),且存在关系2(x+y)-(x+y) =nr,即两指针路程之差为环长整数倍得出x+y=nr,此时需求的时x的长度,将p1移动到起点,使p1,p2同步走,当p1走了x步到入口时,p2离入口的距离为x+y=nr,表示p2也走到入口,所以两者会相遇,所以逆向推到可得p1 p2第二次相遇即为入口
143LRU缓存
实现1.get(key),根据key获取相应的值,同时因为该值刚刚被用过,所以要重新插入dict中,跟换在dict的顺序
2.put(key, value)若key以及存在,将key的值弹出,并重新赋值,这是对key的一次值更换,且更换了在dict中的顺序,表面该值刚刚被使用,若LRU的容量为0了,则将dict中第一个元素弹出,第一个元素意味着很久没用的那个
使用collections中的OrderedDict(),可以记录字典元素的插入顺序,使用pop(key)操作可以弹出key中的值,使用popitem(last=False)可以弹出顺序字典中的头部元素
145Sort List链表排序
使用快慢指针法找到中点,,使用归并排序法实现排序
152最大乘积子数组(动态规划)
使用动态规划的方法,不过需要使用两个动态规划表,分别为dpmax和dpmin,因为当前最大乘积也可能与前一项的最小乘积相关,当出现负数时,状态转移方程dp_max[i] = max(dp_max[i-1]*nums[i], nums[i], dp_min[i-1]*nums[i])
155最小栈
使用列表作为栈的储存空间,但不仅仅保存某个元素,而是保存一个tuple,tuple中的元素为x和当前的最小值,每次push的时候需要进行比较,重新定义当前最小值
160两个链表的交点
先得到两个链表的长度,假设长度差为d,让长链表先走d步,然后同时走,当两个指针相遇时即为交点所在
161求一个数组中的众数,且该众数重复次数大于所有数组的个数的一半
使用投票法,先将nums[0]选为众数,并用cnt记一票,遍历数组,若nums[i]与候选人相同,则加一票,反之减一票,若票数为0,则推选当前nums[i]为候选人输出最后的候选人,此法只适用于该众数重复次数大于所有数组的个数的一半的情况
200岛的数量
首先明白岛的定义:岛可以有两种,一种四周全是水,一种是由大陆延伸出去,但是作为大陆的末端四周除了连接大陆的部分都是水
使用深搜的方法不断搜索岛屿四周,对于i,j若当前为水,则返回,若不为水则将grid[i][j]更新为水,并且对grid四周进行搜索
而岛的数量则是对于满足grid[i][j]==1的进行深搜,且岛屿数量加一,深搜过程中会将同一片大陆区域都设为水域,相当于一片大陆有即产生一个岛,最终得到所有岛的数量
215第k个最大的元素
使用快搜法,对于数组nums,以nums[-1]为枢纽元素,将nums进行分割,前半段大于枢纽元设为s1,后半段小于枢纽元设为s2,若s1长度大于k-1则对s1进行递归搜索,若s1长度为k-1则返回枢纽元,否则对s2进行搜索,第k-len(s1)-1项
221最大的平方
二维动态规划,dp[i][j]表示最大的正方形边长,范围为(n+1)x(n+1)
当matrix[i][j] == '1'时,dp[i][j] = min(dp[i-1][j-1], dp[i][j-1], dp[i-1][j])+1,否则dp[i][j] = 0,返回max(dp)**2
234对偶链表
使用快慢指针确定中点,从中点往后遍历元素放入栈中,另起一个指针从头部开始遍历,与栈的pop()进行对比
236树的最近公共父亲
构建一个字典用于保存各个节点的父亲数
对树进行宽度搜索,直到p,q都在parent字典中,保存方式为parent[child_node] = parent_node
对p进行迭代,把p和p的所有父亲节点放入一个集合中
对q进行遍历,若q在集合中则返回q否则使q = parent[q]往上递归一层
238除了自身外的乘积
对于i位置先将nums[i]左边元素进行累乘,再反向将右边元素进行累乘
279完全平方分解
将一个数分解为完全平方数的累加,返回需要完全平方数的最小个数
使用记忆化搜索方式实现动态规划,这里不使用迭代法的原因是不需要得到所有1-n的结果,dp[i]不与dp[i-1]发生关系
状态转移方程:dp[i] = 1+min(fun(i-j*j) for j in range(1, math.sqrt(i)))
287找重复数字
n+1个数范围都在1-n之间,对于下标为i的位置,对应的值应该为i+1,若结果确实如此,则遍历下一个下标
否则将nums[i]放到nums[i]-1的位置去,即num[i]-1位置的元素和i位置元素互换,这样至少nums[i]-1位置的元素是对上号的,不断这样反复直到当前位置瞒住nums[i]=i+1,当然若中间过程碰到nums[i]==nums[nums[i]-1]则找到了重复数字
300最长递增子序列
这个最长递增子序列无需连续,如数组[10,9,2,5,3,7,101,18]递增序列为2,3,7,101.
这种问题一般最直接想到的就是动态规划的方法.设dp[i]表示第i个位置的最长子序列长度
对于j>i的位置,若nums[j]>nums[i]则dp[j] = max(dp[j], dp[i]+1),这里的状态方程有点特殊,dp[j]和自己也有关,因为这里时对i遍历再对J嵌套循环,所以是不断地更新当前值.