分析:简单题(lc572)
递归 解法:深度优先搜索枚举 s 中的每一个节点,判断这个点的子树是否和 t 相等。如何判断一个节点的子树是否和 t 相等呢,我们又需要做一次深度优先搜索来检查,即让两个指针一开始先指向该节点和 t 的根,然后「同步移动」两根指针来「同步遍历」这两棵树,判断对应位置是否相等。
先序 遍历:将t1和t2通过先序遍历序列化成为一个字符串,然后再判断t1序列化后的字符串是否包含t2序列化后的字符串。
需要注意的点是:当前节点的左右节点要是为空的时候,需要加入占位符表示为空。
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
#
#
# @param root1 TreeNode类
# @param root2 TreeNode类
# @return bool布尔型
#
class Solution_dfs:
def isContains(self , root1 , root2 ):
# write code here
if not root1 and not root2:
return True
if not root1 or not root2:
return False
ans1,ans2=False,False
if root1.val==root2.val:
ans1=self.isContains(root1.left, root2.left) and self.isContains(root1.right, root2.right)
ans2=self.isContains(root1.left, root2) or self.isContains(root1.right, root2)
return ans1 or ans2
#只可使用先序遍历,而非中/后序遍历
class Solution_preorder:
def isContains(self , root1 , root2 ):
if not root1 and not root2:
return True
if not root1 or not root2:
return False
li_1 = []
li_2 = []
preorder(root1,li_1)
preorder(root2,li_2)
str1 = ''.join(li_1)
str2 = ''.join(li_2)
return str2 in str1
def preorder(root,li):
if root:
li.append(str(root.val))
preorder(root.left,li)
preorder(root.right,li)
else:
li.append('-')
# write code here
分析:middle(lc347)
利用堆的思想 :建立一个 小顶堆,然后遍历「出现次数数组」:
如果堆的元素个数小于 k,就可以直接插入堆中。
如果堆的元素个数等于 k,则检查堆顶与当前出现次数的大小。如果堆顶更大,说明至少有 k 个数字的出现次数比当前值大,故舍弃当前值;否则,就弹出堆顶,并将当前值插入堆中。
遍历完成后,堆中的元素就代表了「出现次数数组」中前 k 大的值。基于快速排序的方法,求出「出现次数数组」的前 k 大的值。
将数组用快排从大到小排序,取数组的第一个数a[start]为pivot,那么经过一轮调整之后,数组左边的所有值大于或等于temp,数组右边的所有值都小于或等于temp,假设此时temp是数组第i个数。
如果i正好等于K,那么temp就是第K大值
如果i大于K,那么说明第K大值在数组左边,则继续在左边查找
如果i小于K,那么说明第K大值在数组的右边,继续在右边查找
class Solution_minHeap:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
count = collections.Counter(nums)
heap = []
for key, val in count.items():
if len(heap) >= k:
if val > heap[0][0]:
heapq.heapreplace(heap, (val, key))
else:
heapq.heappush(heap, (val, key))
return [item[1] for item in heap]
class Solution_quickSort:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
count = collections.Counter(nums)
num_cnt = list(count.items())
topKs = self.findTopK(num_cnt, k, 0, len(num_cnt) - 1)
return [item[0] for item in topKs]
def findTopK(self, num_cnt, k, low, high):
pivot = random.randint(low, high)
num_cnt[low], num_cnt[pivot] = num_cnt[pivot], num_cnt[low]
base = num_cnt[low][1]
i = low
for j in range(low + 1, high + 1):
if num_cnt[j][1] > base:
num_cnt[i + 1], num_cnt[j] = num_cnt[j], num_cnt[i + 1]
i += 1
num_cnt[low], num_cnt[i] = num_cnt[i], num_cnt[low]
if i == k - 1:
return num_cnt[:k]
elif i > k - 1:
return self.findTopK(num_cnt, k, low, i - 1)
else:
return self.findTopK(num_cnt, k, i + 1, high)
分析:hard(lc84)
第 i 位置最大面积:以i 为中心,向左找第一个小于 heights[i] 的位置 lefti;向右找第一个小于 heights[i] 的位置 righti,即最大面积为 heights[i] * (righti - lefti -1)思路1:维护一个 单调递增的栈,就可以找到 lefti 和 righti;
思路2:当我们找 i 左边第一个小于 heights[i] 如果 heights[i-1] >= heights[i] 其实就是和 heights[i-1] 左边第一个小于 heights[i-1] 一样。依次类推,右边同理
class Solution_monoStack:
def largestRectangleArea(self, heights: List[int]) -> int:
stack = []
heights = [0] + heights + [0] #左右添加两个虚拟哨兵, 方便处理
res = 0
for i in range(len(heights)):
while stack and heights[stack[-1]] > heights[i]:
tmp = stack.pop()
res = max(res, (i - stack[-1] - 1) * heights[tmp])
stack.append(i)
return res
class Solution_dp:
def largestRectangleArea(self, heights: List[int]) -> int:
if not heights:
return 0
n = len(heights)
left_i = [0] * n
right_i = [0] * n
left_i[0] = -1
right_i[-1] = n
for i in range(1, n):
tmp = i - 1
while tmp >= 0 and heights[tmp] >= heights[i]:
tmp = left_i[tmp]
left_i[i] = tmp
for i in range(n - 2, -1, -1):
tmp = i + 1
while tmp < n and heights[tmp] >= heights[i]:
tmp = right_i[tmp]
right_i[i] = tmp
res = 0
for i in range(n):
res = max(res, (right_i[i] - left_i[i] - 1) * heights[i])
return res
#双指针
class Solution:
def maxLength(self , arr ):
if len(arr) == 0:
return 0
d = {}
startpos = 0
endpos = 0
ans = []
while endpos <= len(arr) - 1:
if arr[endpos] in d:
startpos =max(startpos,d[arr[endpos]]+1) #若出现与d中重复元素,判断重复元素位置与startpos的大小,在startpos前则不管,否则更新startpos
if endpos - startpos + 1 > len(ans): ans = arr[startpos:endpos+1] #更新当前最大长度
d[arr[endpos]] = endpos #记录扫描过的元素,值-索引
endpos += 1
return ans
#队列
class betterSolution:
def maxLength(self , arr: List[int]) -> int:
queue = []
ans = []
for num in arr:
while num in queue: queue.pop(0)
queue.append(num)
if len(queue) > len(ans): ans = queue
return ans
分析:middle(lc437)
我们定义节点的前缀和为:由根结点到当前结点的路径上所有节点的和。我们利用先序遍历二叉树,记录下根节点 root 到当前节点 p 的路径上除当前节点以外所有节点的前缀和,在已保存的路径前缀和中查找是否存在前缀和刚好等于当前节点到根节点的前缀和 curr 减去 targetSum
class Solution:
def pathSum(self, root: TreeNode, targetSum: int) -> int:
prefix = collections.defaultdict(int)
prefix[0] = 1
def dfs(root, curr):
if not root:
return 0
ret = 0
curr += root.val
ret += prefix[curr - targetSum]
prefix[curr] += 1
ret += dfs(root.left, curr)
ret += dfs(root.right, curr)
prefix[curr] -= 1
return ret
return dfs(root, 0)
分析:middle(nc17)
思路一——中心扩散法:我们遍历字符串的每一个字符,然后以当前字符为中心往两边扩散,查找最长的回文子串
思路二——动态规划:定义二维数组dp[length][length],如果dp[left][right]为true,则表示字符串从left到right是回文子串,如果dp[left][right]为false,则表示字符串从left到right不是回文子串。
如果dp[left+1][right-1]为true,我们判断s[left]和s[right]是否相等,如果相等,那么dp[left][right]肯定也是回文子串,否则dp[left][right]一定不是回文子串
class Solution:
def getLongestPalindrome(self , A: str) -> int:
n = len(A)
temp = 0
for i in range(n):
temp = max(temp,spread(A,i, i, n),spread(A,i, i+1, n)) #注意奇长度回文串与偶长度回文串的参数略有不同
return temp
def spread(A,l,r,n): #以A[left]和A[right]为中心向左右两边扩散,返回扩散的最大长度
while l >= 0 and r <= n-1 and A[l] == A[r]: #利用回文子串正反一样的特点进行扩散
l = l - 1
r = r + 1
return r-l-1
# write code here
class Solution {
public:
int getLongestPalindrome(string A, int n) {
if(n <= 1) return n;
int longest = 1;
bool dp[n][n];
for(int i = 0; i < n; i++) { //从短到长对每种长度分别判断,可以这么做是因为判断较长的需要利用较短的
for(int j = 0; j< n - i; j++) { //从头开始对长度i+1的子字符串判断
if(i == 0) dp[j][j] = 1; //长度1一定为回文
else if(i == 1) dp[j][j + 1] = (A[j] == A[j + 1]); //长度2判断头尾是否相等
else if(A[j] == A[j + i]) dp[j][j + i] = dp[j + 1][j + i - 1]; //长度大于等于3,判断两头是否相等,若相等则同去两头的bool值一样
else dp[j][j + i] = 0; //否则为0
if(dp[j][j + i]) longest = max(longest, i + 1); //更新最大值
}
}
return longest;
}
};
分析
乘积的最后三位的值只与乘数和被乘数的后三位有关,与乘数和被乘数的高位无关
def theLastThreeNumber(base,N):
ans = 1
for i in range(N):
ans = (ans * base) % 1000
return ans
分析:middle(nc91)
两步走:
第一步——求最长递增子序列长度
第二步——求字典序靠前的子序列对于第一步:贪心+二分,时间复杂度为O(nlogn)
(动态规划,时间复杂度为O(n^2),会超
对于第二步,假设我们原始数组是arr1,得到的maxLen为[1,2,3,1,3],最终输出结果为res(字典序最小的最长递增子序列),res的最后一个元素在arr1中位置无庸置疑是maxLen[i]==3对应的下标,那么到底是arr1[2]还是arr1[4]呢?如果是arr1[2],那么arr1[2]
#
# retrun the longest increasing subsequence
# @param arr int整型一维数组 the array
# @return int整型一维数组
#
class Solution:
def LIS(self , arr ):
if not arr: return 0
monoList = [arr[0]] #长度为 i+1 的子序列的最后一位的最小值(不是解,只是长度关联),单调递增
maxlen = [1] #maxlen[i]记录以arr[i]结尾的最长递增子列长度
longestLenth = 1 #记录最长递增子序列长度
for i in arr[1:]:
if i > monoList[-1]:
monoList.append(i)
longestLenth = len(monoList)
maxlen.append(len(monoList))
else:
pos = findPosition(monoList, i) #二分查找恰好合适的位置
monoList[pos] = i #对该位置数字进行更新
maxlen.append(pos+1)
length = len(monoList)
ans = [0]*length
for j in range(-1,-len(arr)-1,-1): #倒着遍历arr,找到满足长度的maxlen就记录,然后更新(即同样值的maxlen,选尽量靠右边的)
if length > 0:
if maxlen[j] == length:
ans[length-1] = arr[j]
length -= 1
else:break
return ans, longestLenth
def findPosition(li, t):
if li[-1] < t: return None
l = 0
r = len(li) - 1
while l < r:
mid = (l + r) >> 1
if li[mid] >= t:
r = mid
else:
l = mid + 1
return r
# write code here
分析:hard(lc124)
首先,考虑实现一个简化的函数 maxGain(node),该函数计算二叉树中的一个节点的最大贡献值,具体而言,就是在以该节点为根节点的子树中寻找以该节点为起点的一条路径,使得该路径上的节点值之和最大。具体而言,该函数的计算如下:
空节点的最大贡献值等于 0。
非空节点的最大贡献值等于节点值与其子节点中的最大贡献值之和(对于叶节点而言,最大贡献值等于节点值)
根据函数 maxGain 得到每个节点的最大贡献值之后,计算二叉树的最大路径和:对于二叉树中的一个节点,该节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值,如果子节点的最大贡献值为正,则计入该节点的最大路径和,否则不计入该节点的最大路径和。维护一个全局变量 maxSum 存储最大路径和,在递归过程中更新 maxSum 的值,最后得到的 maxSum 的值即为二叉树中的最大路径和
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
# @param root TreeNode类
# @return int整型
#
class Solution:
def maxPathSum(self , root ):
res = [-float('inf') #此处使用列表可变类型,可起到全局变量的作用
_ = maxGain(root, res)
return res[0]
def maxGain(root,a):
if not root: return 0
#递归计算左右子节点的最大贡献值
#只有在最大贡献值大于 0 时,才会选取对应子节点
leftG, rightG = maxGain(root.left,a), maxGain(root.right,a)
#节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
a[0] = max(a[0], root.val + max(leftG,0) + max(rightG, 0))
#返回节点的最大贡献值
gain = max(leftG, rightG, 0) + root.val
return gain #注意此处并不是返回a[0](最大路径和)
# write code here
分析:middle(lc135)
贪心 :左右各遍历一次
把所有孩子的糖果数初始化为 1;
先从左往右遍历一遍,如果右边孩子的评分比左边的高,则右边孩子的糖果数更新为左边孩子的 糖果数加 1;
再从右往左遍历一遍,如果左边孩子的评分比右边的高,且左边孩子当前的糖果数不大于右边孩子的糖果数;
则左边孩子的糖果数更新为右边孩子的糖果数加 1
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# pick candy
# @param arr int整型一维数组 the array
# @return int整型
#注意到第i个孩子所需的最少糖果数由以下两者较大者决定:
#以i结尾的最长严格递增得分序列长度;
#以i开头的最长严格递减得分序列长度决定,可递推求
class Solution:
def candy(self , arr: List[int]) -> int:
lenth = len(arr)
afterless = [1] * lenth
for i in range(lenth-2, -1, -1):
if arr[i] > arr[i+1]:
afterless[i] = afterless[i+1] + 1
frontless = [1] * lenth
for i in range(1, lenth):
if arr[i] > arr[i-1]:
frontless[i] = frontless[i-1] + 1
res = 0
for i in range(lenth):
if max(afterless[i], frontless[i]) == 1:
#此时i+1,i-1位置都至少大于等于i位置
res += 1 #知i位置给1个糖果不影响其他位置
else:
#得分趋势为/*\ *代表第i个位置
res += max(afterless[i], frontless[i])
return res
# write code here
分析:hard(lc51)
基于集合的回溯 :
1.设置三个集合分别记录不能再被选中的的列col,正斜线pos,反斜线neg
2.规律:行号i - 列号j 可确定唯一正斜线,行号i + 列号j 可确定唯一反斜线
3.符合要求的点记录当前点并递归下一个皇后,最后一个皇后成功安置后将res+1,然后需回溯回初始点将初始点删除,将初始点的皇后放置其他位置进行判断
4.不符合要求的需要进行循环
# @param n int整型 the n
# @return int整型
#
class Solution:
def Nqueen(self , n ):
# write code here
col=set()
dia1=set()
dia2=set()
self.ans=0
def dfs(num,col,dia1,dia2):
if num==n:
self.ans+=1
else:
for j in range(n):
if j in col or num-j in dia1 or j+num in dia2:
continue
col.add(j)
dia1.add(num-j)
dia2.add(j+num)
dfs(num+1,col,dia1,dia2)
col.remove(j)
dia1.remove(num-j)
dia2.remove(j+num)
dfs(0,col,dia1,dia2)
return self.ans