code124: Binary Tree Maximum Path Sum
non-empty binary tree, find the maximum path sum.
For this problem, a path is defined as any sequence of nodes from some starting node to any node in the tree along the parent-child connections. The path must contain at least one node and does not need to go through the root.
Example 1:
Input: [1,2,3]
1
/ \
2 3
Output: 6
Example 2:
Input: [-10,9,20,null,null,15,7]
-10
/
9 20
/
15 7
Output: 42
解答:
对于这种比较复杂的问题,思考下能否用动态规划,尤其是树类的题目,一般是可以用到DP的解法。
将之前的某些值保留到后面的递归中并进行处理。
本题中寻找最大的路径经过点的和
一般来说,三点情况:
1 双边三点最大
2 单边两点最大
3 单点最大
继续扩展到更多点:
1 单边存储下其最大值(都要依靠单边或者点,不能是双边),然后整合当前双边的最大值(意思是两边可以无限延长)
2 单边存储最大值,加新增点后的 左右单边的最大值 进行比较选取max
3 对于单点可以归纳进单边最大值里面,比较对象+1 node.val
终止情况:(double_max:双边最大值 single_max: 单边最大值 all_max:目前的最大值)
1 没有左右分支: double_max = node.val single_max = node.val #all_max = node.val
2 左右均有分支:double_max = node.val+left.single_max + right.single_max #single_max = max(node.val+left.single_max,node.val+right.single_max,node,val)
all_max = max(double_max, single_max, left.all_max, right.all_max)
3 左右仅有一个分支:参照 2 的公式。可以将其上级变为 0 。这样可以沿用
4 点不存在时: 全部为0
可优化的部分:
double_max只存在于本层中,所以不需要传递,只需要传递single_max以及all_max即可
带入发现,all_max 初始设置应该为无穷小,因为最大值可能为负数
然后发现1 2 3 因为 4 的存在 都能概括在一起了。
也就是目前两个判断:
1 左右均有分支:double_max = node.val+left.single_max + right.single_max #single_max = max(node.val+left.single_max,node.val+right.single_max,node,val)
all_max = max(double_max, single_max, left.all_max, right.all_max)
2 点不存在时: single_max = 0 all_max = float(’-inf’)
最终代码:
class Solution:
def maxPathSum(self, root) -> int:
def re(node):
if not node:
return [0,float('-inf')]
else:
res_l = re(node.left)
res_r = re(node.right)
double_max = res_l[0]+res_r[0]+node.val
single_max = max(res_l[0]+node.val,res_r[0]+node.val,node.val)
all_max = max(double_max,single_max,res_r[1],res_l[1])
res = [single_max,all_max]
return res
res = re(root)
return res[1]
总结:
1 归纳条件
2 寻找终止点
3 一步步合并条件:
1)single_max 最终包含了当前点的值的比较
2) double_max 无需继续传递,仅存在于本层中比较
3)对于点的判断,最终合并为:点 是否为None
##################################################
code139. Word Break
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
Note:
The same word in the dictionary may be reused multiple times in the segmentation.
You may assume the dictionary does not contain duplicate words.
Example 1:
Input: s = “leetcode”, wordDict = [“leet”, “code”]
Output: true
Explanation: Return true because “leetcode” can be segmented as “leet code”.
Example 2:
Input: s = “applepenapple”, wordDict = [“apple”, “pen”]
Output: true
Explanation: Return true because “applepenapple” can be segmented as “apple pen apple”.
Note that you are allowed to reuse a dictionary word.
Example 3:
Input: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
Output: false
解答:
寻找字符串是否能用数组元素完全分离
1 想到遍历时分割,左边存在则继续后面的检测
wrong:由于数组存在像 a abc ab这样的组合时,可能导致分割后的right不能被继续检测到(abc优先被分裂为a bc)
2 既然这样,我们做回溯,即return A or B,一旦有一个发现没问题,则返回true。
wrong:
TLE,回溯难免会出现这样的问题。如果str本身很长,而且数组中包含的单元数字母太多,会导致分支过多。尤其遇到False的情况会全部走完分支,耗时极大。
3 一般针对回溯的问题,尝试看下是否存在DP的解法:
如果某一段存在于数组中,则看其余段是否也存在。 转变为:如果最后一部分存在于数组中,看前面的是否全部存在。这个是逆向的DP,也就是回溯的思想了。
从正向走的话,我们先判断前面部分是否存在,然后再判断后面的。然后前面部分判断我们要尽量避免产生多余的步骤。 一旦我们发现目前为止到这里是没问题的,那么前面部分的分支那样的步骤就应该马上停下来。
从答案中发现这样操作的:
o(n^2)的遍历操作。i起点,j终点。如果i:j+1的部分存在于数组而且起点i之前就是满足的,则将终点设置到j+1 为True 。最后检查终点处的标识是否为TRUE。
################
字符串常用的DP方法!!!!!!!!
################
代码:
class Solution:
def wordBreak(self, s: str, wordDict) -> bool:
dp = [False]*(len(s)+1)
dp[0] = True
for i in range(len(s)):
for j in range(i,len(s)):
if dp[i] and s[i:j+1] in wordDict:
#print(dp[i],s[i:j+1],i,j)
dp[j+1] = True
#print(dp)
return dp[-1]
使用DP来记录当前位置是否满足条件。(长度为len+1,因为标记的位置是当前数组的末尾+1)
思想:
1 字符串的DP可以多次遍历记录。或者排列式记录(O n方)
2 回溯优化的DP
3 避免重复
简化代码:
class Solution:
def wordBreak(self, s: str, wordDict) -> bool:
dp = [True]
for i in range(1,len(s)+1):
dp += [any(dp[j] and s[j:i] in wordDict for j in range(i))]
#print(dp,s[0:1])
return dp[-1]
理论上是一样的,但是使用了any可以简化代码。
而且并不需要初始化数组,直接进行加和即可。