Search
当一个大问题是由多个子问题构成时,我们可以通过不断分解问题来最终构建我们想求的大问题,这个过程称为搜索(Search)
搜索空间(Search Space)可以用tree的形式表现出来,便于理解
时间复杂度取决于这棵树的深度和每个节点的children个数
Search最重要的是定义好状态,保证每个子问题都能用一个状态来描述
DP(Dynamic Progamming)
如果我们重复Search Space有重复问题的话,可以记录下这些子问题的答案来保证不会重复计算多次。所有DP也被称为Search + Memorization
如此一来,时间复杂度就取决于子问题的个数
而搜索空间(Search Space)就可以用Tree的形式展现出来,便于理解。
所有DP都可以写成Bottom Up DFS 的形式。
只要定义好状态,可以从一个中间状态出发去思考递归规则
Bottom Up DFS 模版
-
Define STATE of the problem
-
Initialize memo to record calculated subproblems
-
Return dfs(top_level_answer_state)
dfs(state):
- Base case check
- If current problem is calculated, return its answer
- For each subproblem x
a. Ask subproblem for their answers -> call dfs(sub_problem_state)
b. Build up current state problem answer based on subproblem answers
- Store current problem answer
Note: Step 2 & 4 are the difference for DP problems from search questions
例题
思路
对于单个Array或者String来说,一般只有2种定义状态:
- i = index or problem_length -> dp[i] 代表[0,i)的答案
- i,j = indexes -> dp[i][j] 代表array[i] ~ array[j] 这段subarray的答案
我们先从定义1 开始考虑,尝试是否可行
比如我们现在处于状态 i -> if we can break the word.substring(0,i)
记住DP一定是利用子问题的答案构建当前大问题答案
比如我们知道了子问题的答案是true -> we can break the word .substring(0,j)
那么剩下来的部分就是x =word.substring(j,n), 如果x是dictionary里的一个单词,那么整个问题 i 的答案就是true
把所有可能的 j = [0, i) 都试一遍,只要其中一个满足,整个问题的答案就是true
subproblem j + x
High Level:
-
state = (length) -> state[i]: if we can break word.substring(0, length)
-
initialize memo
-
return dfs(n)
Implementation Steps
- Base case: i == 0 -> "" is breakable, return true
- if memo[i] != null -> problem has been calculated -> return memo[i]
- for each subproblem j from [0,i)
a. Ask subproblem for their answers -> call y = dfs(j)
b. if y == true and word.substring(j,n) in dictionary -> current prblem is true
-
memo[i] = answer from step 3
-
return memo[i]
时间复杂度: O(n**2)