leetcode算法筆記:二叉樹,動態規劃和回溯法

在二叉樹中增長一行

題目描述

給定一個二叉樹,根節點爲第1層,深度爲 1。在其第 d 層追加一行值爲 v 的節點。node

添加規則:給定一個深度值 d (正整數),針對深度爲 d-1 層的每一非空節點 N,爲 N 建立兩個值爲 v 的左子樹和右子樹。 數組

將 N 原先的左子樹,鏈接爲新節點 v 的左子樹;app

將 N 原先的右子樹,鏈接爲新節點 v 的右子樹。 ui

若是 d 的值爲 1,深度 d - 1 不存在,則建立一個新的根節點 v,原先的整棵樹將做爲 v 的左子樹。this

Example
Input: 
A binary tree as following:
       4
     /   \
    2     6
   / \   / 
  3   1 5   

v = 1

d = 2

Output: 
       4
      / \
     1   1
    /     \
   2       6
  / \     / 
 3   1   5  

來源:力扣(LeetCode)
連接:https://leetcode-cn.com/problems/add-one-row-to-tree
 
基本思想
二叉樹的先序遍歷 
 
代碼的基本結構
不是最終結構,而是大致的結構
/**
 * @param {number} cd:current depth,遞歸當前深度
 * @param {number} td:target depth, 目標深度
 */
var traversal = function (node, v, cd, td) {
    // 遞歸到目標深度,建立新節點並返回
  if (cd === td) {
    // return 新節點
  }
  // 向左子樹遞歸
  if (node.left) {
    node.left = traversal (node.left, v, cd + 1, td);
  }
  // 向右子樹遞歸
  if (node.right) {
    node.right = traversal (node.right, v, cd + 1, td);
  }
  // 返回舊節點
  return node;
};
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} v
 * @param {number} d
 * @return {TreeNode}
 */
var addOneRow = function (root, v, td) {
    // 從根節點開始遞歸
  traversal (root, v, 1, td);
  return root;
};

 

 

具體分析
咱們能夠分類討論,分三種狀況處理
 
第1種狀況:目標深度<=當前遞歸路徑的最大深度 
處理方法:val節點替換該目標深度對應的節點,而且
  • 若是目標節點原來是左子樹,那麼重置後目標節點是val節點的左子樹spa

  • 若是目標節點原來是右子樹,那麼重置後目標節點是val節點的右子樹debug

 

第2種狀況:目標深度>當前遞歸路徑的最大深度
閱讀題目發現,有這麼一個描述:「輸入的深度值 d 的範圍是:[1,二叉樹最大深度 + 1]」
因此呢,當目標深度剛好比當前路徑的樹的深度再深一層時,處理方式是:
在最底下那一層節點的左右分支新增val節點

 

第3種狀況:目標深度爲1

咱們再分析題意,題目裏說:「若是 d 的值爲 1,深度 d - 1 不存在,則建立一個新的根節點 v,原先的整棵樹將做爲 v 的左子樹。」3d

這說明當:目標深度爲1時,咱們的處理方式是這樣的 

 

所有代碼

 

/**
 * @param {v} val,插入節點攜帶的值
 * @param {cd} current depth,遞歸當前深度
 * @param {td} target depth, 目標深度
 * @param {isLeft}  判斷原目標深度的節點是在左子樹仍是右子樹
 */
var traversal = function (node, v, cd, td, isLeft) {
  debugger;
  if (cd === td) {
    const newNode = new TreeNode (v);
    // 若是原來是左子樹,重置後目標節點仍是在左子樹上,不然相反
    if (isLeft) {
      newNode.left = node;
    } else {
      newNode.right = node;
    }
    return newNode;
  }
  // 處理上述的第1和第2種狀況
  if (node.left || (node.left === null && cd + 1 === td)) {
    node.left = traversal (node.left, v, cd + 1, td, true);
  }
  if (node.right || (node.right === null && cd + 1 === td)) {
    node.right = traversal (node.right, v, cd + 1, td, false);
  }
  return node;
};
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {number} v
 * @param {number} d
 * @return {TreeNode}
 */
var addOneRow = function (root, v, td) {
  // 處理目標深度爲1的狀況,也就是上述的第3種狀況
  if (td === 1) {
    const n = new TreeNode (v);
    n.left = root;
    return n;
  }
  traversal (root, v, 1, td);
  return root;
};

 

單詞拆分

 

題目描述

 

給定一個非空字符串 s 和一個包含非空單詞列表的字典 wordDict,斷定 s 是否能夠被空格拆分爲一個或多個在字典中出現的單詞。 code

說明:orm

1.拆分時能夠重複使用字典中的單詞。

2.你能夠假設字典中沒有重複的單詞。

 

Example
 

 

example1
輸入: s = "applepenapple", wordDict = ["apple", "pen"]
輸出: true
解釋: 返回 true 由於 "applepenapple" 能夠被拆分紅 "apple pen apple"。
注意: 你能夠重複使用字典中的單詞。

example2
輸入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
輸出: false

來源:力扣(LeetCode)
連接:https://leetcode-cn.com/problems/word-break

 

 

 

基本思想

 

動態規劃

 

具體分析

 

 

動態規劃的關鍵點是:尋找狀態轉移方程

 

 

有了這個狀態轉移方程,咱們就能夠根據上一階段狀態和決策結果,去求出本階段的狀態和結果

 

 

而後,就能夠從初始值,不斷遞推求出最終結果。

 

 

在這個問題裏,咱們使用一個一維數組來存放動態規劃過程的遞推數據

 

 

假設這個數組爲dp,數組元素都爲true或者false,

 

 

dp[N] 存放的是字符串s中從0到N截取的子串是不是「可拆分」的布爾值

 

 

讓咱們從一個具體的中間場景出發來思考計算過程

 

 

假設咱們有

 

wordDict = ['ab','cd','ef']
s ='abcdef'

而且假設目前咱們已經得出了N=1到N=5的狀況,而如今須要計算N=6的狀況

 

或者說,咱們已經求出了dp[1] 到dp[5]的布爾值,如今須要計算dp[6] = ?

 

 

該怎麼計算呢?

 

 

如今新的字符f被加入到序列「abcde」的後面,如此以來,就新增瞭如下幾種6種須要計算的狀況

 

A序列 + B序列
1.abcdef + ""
2.abcde + f
3.abcd + ef
4.abc + def
5.ab + cdef
6.a + bcdef
注意:當A可拆且B可拆時,則A+B也是可拆分的

 

從中咱們不難發現兩點

  1. 當A可拆且B可拆時,則A+B也是可拆分的

  2. 這6種狀況只要有一種組合序列是可拆分的,abcdef就必定是可拆的,也就得出dp[6] = true了

 

下面是根據根據已有的dp[1] 到dp[5]的布爾值,動態計算dp[6] 的過程

 

 

 

 

(注意只要計算到可拆,就能夠break循環了)

 

 

 
具體代碼

 

var initDp = function (len) {
  let dp = new Array (len + 1).fill (false);
  return dp;
};
/**
 * @param {string} s
 * @param {string[]} wordDict
 * @return {boolean}
 */
var wordBreak = function (s, wordDict) {
  // 處理空字符串
  if (s === '' && wordDict.indexOf ('') === -1) {
    return false;
  }
  const len = s.length;
  // 默認初始值所有爲false
  const dp = initDp (len);
  const a = s.charAt (0);
  // 初始化動態規劃的初始值
  dp[0] = wordDict.indexOf (a) === -1 ? false : true;
  dp[1] = wordDict.indexOf (a) === -1 ? false : true;
  // i:end
  // j:start
  for (let i = 1; i < len; i++) {
    for (let j = 0; j <= i; j++) {
      // 序列[0,i] = 序列[0,j] + 序列[j,i]
      // preCanBreak表示序列[0,j]是不是可拆分的
      const preCanBreak = dp[j];
      // 截取序列[j,i]
      const str = s.slice (j, i + 1);
      // curCanBreak表示序列[j,i]是不是可拆分的
      const curCanBreak = wordDict.indexOf (str) !== -1;
      // 狀況1: 序列[0,j]和序列[j,i]均可拆分,那麼序列[0,i]確定也是可拆分的
      const flag1 = preCanBreak && curCanBreak;
      // 狀況2: 序列[0,i]自己就存在於字典中,因此是可拆分的
      const flag2 = curCanBreak && j === 0;
      if (flag1 || flag2) {
        // 設置bool值,本輪計算結束
        dp[i + 1] = true;
        break;
      }
    }
  }
  // 返回最後結果
  return dp[len];
};

 

全排列

 

題目描述

 

給定一個沒有重複數字的序列,返回其全部可能的全排列。

Example

輸入: [1,2,3]
輸出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

來源:力扣(LeetCode)
連接:https://leetcode-cn.com/problems/permutations

 

基本思想

回溯法

 

具體分析

 

  1. 深度優先搜索搞一波,index在遞歸中向前推動

  2. 當index等於數組長度的時候,結束遞歸,收集到results中(數組記得要深拷貝哦)

  3. 兩次數字交換的運用,計算出兩種狀況

 

總結

 

想不通不要緊,套路一波就完事了

 

具體代碼

 

var swap = function (nums, i, j) {
  const temp = nums[i];
  nums[i] = nums[j];
  nums[j] = temp;
};

var recursion = function (nums, results, index) {
  // 剪枝
  if (index >= nums.length) {
    results.push (nums.concat ());
    return;
  }
  // 初始化i爲index
  for (let i = index; i < nums.length; i++) {
    // index 和 i交換??
    // 統計交換和沒交換的兩種狀況
    swap (nums, index, i);
    recursion (nums, results, index + 1);
    swap (nums, index, i);
  }
};
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permute = function (nums) {
  const results = [];
  recursion (nums, results, 0);
  return results;
};
相關文章
相關標籤/搜索