# leetcode部分

# details

lc
// `M 120. 三角形最小路径和`
var minimumTotal = function(triangle) {
    var dp = triangle
    for(var i = dp.length-2; i >= 0; i--) {
        for(var j = 0; j < dp[i].length; j++) {
            dp[i][j] = Math.min(dp[i+1][j], dp[i+1][j+1]) + triangle[i][j];
        }
    }
    return dp[0][0];
};

// - `M 670. 最大交换`
// 将数字从大到小排列,与原数字比较,找出第一位置不一样的数。如8217排序后变为8721,两两对比,第二个数不同,表示7和2交换,得到结果8712
// 对于有重复数字的情况,应该要用位于最后面的去交换
const maximumSwap = function(num) {
    const arr = String(num).split('')
    const temp = arr.slice().sort((a, b) => b - a)
    let flag = -1
    for (let i = 0; i < arr.length; i++) {
        if (arr[i] < temp[i]) {
            flag = i
            break
        }
    }
    if (flag < 0) {
        return num
    }
    for (let i = arr.length - 1; i > flag; i--) {
        if (arr[i] === temp[flag]) {
            [arr[i], arr[flag]] = [arr[flag], arr[i]]
            break
        }
    }
    return parseInt(arr.join(''))
}


// - `E 674. 最长连续递增序列`
var findLengthOfLCIS = function(nums) {
    if (!nums.length) return 0;
    let res = 1, count = 1;

    for (let i = 0; i < nums.length - 1; i++) {
        if (nums[i] < nums[i + 1]) {
            count++;
        } else {
            count = 1;
        }
        res = Math.max(res, count)
    }
    return res;
}


// - `E 扑克牌中的顺子`
var isStraight = function(nums) {
    if (nums.length <= 1) return true

    nums = nums.sort((a, b) => a - b) // 先排序
    let zeroCount = nums.filter(item => !item).length // 找到万能牌 0的个数

    for (let i = zeroCount; i < nums.length - 1; i++) {
        let gap = nums[i+1] - nums[i] // 找到相邻两数间的差值
        if (gap === 1) { // 差值为1继续循环
            continue
        } else if (gap === 0) { // 如果有除0外的重复数字则直接false
            return false
        } else {
            zeroCount = zeroCount - (gap-1) // 拿万能0去补
            if (zeroCount < 0) {
                return false
            }
        }
    }

    return true
};


// H 42. 接雨水
    // for(let i = 0; i < height.length; i++) {
    //     area += (h[i] - height[i]) * 1; // h为下雨之后的水位
    // }
    // h[i] = Math.min(左边柱子最大值, 右边柱子最大值)
var trap = function(height) {
    let volumn = 0;
    const leftMax = [];
    const rightMax = [];

    for(let i = 0, max = 0; i < height.length; i++) { // 注意max赋初值0
        max = Math.max(height[i], max);
        leftMax[i] = max
    }

    for(let i = height.length - 1, max = 0; i >= 0; i--) { // 反向遍历,注意max赋初值0
        max = Math.max(height[i], max);
        rightMax[i] = max
    }

    for(let i = 0; i < height.length; i++) {
        volumn = volumn +  Math.min(leftMax[i], rightMax[i]) - height[i]
    }

    return volumn;
};

// H 25. K 个一组翻转链表
var reverseKGroup = function(head, k) {
  // 标兵
  let dummy = new ListNode()
  dummy.next = head
  let [start, end] = [dummy, dummy.next]
  let count = 0
  while(end) {
    count++
    if (count % k === 0) {
      start = reverseList(start, end.next)
      end = start.next
    } else {
      end = end.next
    }
  }
  return dummy.next

  // 翻转start -> end的链表
  function reverseList(start, end) {
    // 用一个start 记录当前分组的起始节点位置的前一个节点
    // 用一个end 变量记录要翻转的最后一个节点位置
    let [pre, cur] = [start, start.next]
    const first = cur
    while(cur !== end) {
      let next = cur.next
      cur.next = pre
      pre = cur
      cur = next
    }
    start.next = pre
    first.next = cur
    return first
  }
};


// E 1. 两数之和
// 用一个对象注册,类似哈希表
var twoSum = function (nums, target) {
    let res = {}
    for (let i = 0; i < nums.length; i++) { // 每个人报出自己想要配对的人
        if (res[nums[i]] !== undefined) { // 如果有人被登记过
            return [res[nums[i]], i] // 注意返回的是下标
        } else {  // 否则
            res[target - nums[i]] = i // 主持人记住他的需求
        }
    }
}

// M 15. 三数之和
// 双指针法
var threeSum = function(nums) {
    let res = []
    let length = nums.length
    nums.sort((a, b) => a - b) // 先排个队,最左边是最弱(小)的,最右边是最强(大)的
    if (nums[0] <= 0 && nums[length - 1] >= 0) { // 优化1: 整个数组同符号,则无解
        for (let i = 0; i < length - 2;) {
            if (nums[i] > 0) {
                // 优化2: 最左值为正数则一定无解
                break
            }
            let first = i + 1
            let last = length - 1
            do {
                if (first >= last || nums[i] * nums[last] > 0) {
                    // 两人选相遇,或者三人同符号,则退出
                    break
                }
                let result = nums[i] + nums[first] + nums[last]

                if (result === 0) { // 可以组队
                    res.push([nums[i], nums[first], nums[last]])
                }

                if (result <= 0) { // 实力太弱,把中间的菜鸟右移一位
                    while(first < last && nums[first] === nums[++first]) { }
                } else { // 实力太强,把大神那边左移一位
                    while(first < last && nums[last] === nums[--last]) { }
                }
            } while(first < last)

            while(nums[i] === nums[++i]) {}
        }
    }

    return res
};

// - M 43. 字符串相乘(大数相乘)
var multiply = function (num1, num2) {
    let len1 = num1.length,
        len2 = num2.length,
        res = new Array(len1 + len2).fill(0)

    // 结果最多为 m + n 位数
    for (let i = len1 - 1; i >= 0; i--) {
        for (let j = len2 - 1; j >= 0; j--) {
            // 从个位数开始,逐步相乘
            const mul = num1[i] * num2[j]
            // 乘积在结果数组中对应的位置
            const p1 = i + j, p2 = i + j + 1 // p2是当前位,p1是进位(因为最高位索引是0,低位索引更大啊)
            // 对结果进行累加
            const sum = mul + res[p2]
            res[p2] = sum % 10
            res[p1] = res[p1] + Math.floor(sum / 10)
        }
    }

    while (res[0] === 0) {
        res.shift()
    }

    return res.join('') || '0'
};



// - E 415. 字符串相加(处理加法精度)
var addStrings = function(num1, num2) {
    let l1 = num1.length
    let l2 = num2.length

    let str = '' // str是字符串,计算时是字符串拼接
    let add = 0 // add代表进位
    let tmp // 计算临时值
    while (l1 || l2) {
        tmp = (l1 ? +num1[--l1] : 0) + (l2 ? +num2[--l2] : 0) + add
        str = tmp % 10 + str // 拼接,当前计算值拼在前面,想想列竖式
        add = tmp > 9 ? 1 : 0
    }

    if (add) {
        str = 1 + str
    }

    return str
};

// 处理加法精度
var addStrings = function(num1, num2) {
    let xiaoshuLen = Math.max(num1.split('.')[1].length, num2.split('.')[1].length)
    let mult = Math.pow(10, xiaoshuLen)
    num1 = num1 * mult
    num2 = num2 * mult
    num1 = num1.toString()
    num2 = num2.toString()

    let a = num1.length, b = num2.length, sum = '', add = 0
    while(a || b) {
        a ? add += +num1[--a] : ''
        b ? add += +num2[--b] : ''

        sum = add % 10 + sum
        add = add > 9 ? 1 : 0
    }
    if (add) sum = 1 + sum

    return sum / mult
};
addStrings('110.3', '2000.45')

// M 2. 两数相加
// 这个题,链表head是个位:(2 -> 4 -> 3) 342
var addTwoNumbers = function(l1, l2) {
    let head = new ListNode('head');
    let dummy = head; // 哑结点,用于移动操作,最后返回head.next即可
    let add = 0; // 是否进一
    let sum = 0; // 新链表当前未取余的值 = 链表1值 + 链表2值 + add;

    // 遍历,直到最长的都为空
    while(l1 || l2){
        sum = (l1 ? l1.val : 0) + (l2 ? l2.val : 0) + add;
        dummy.next = new ListNode(sum % 10); // 取余则为新链表的值
        dummy = dummy.next;
        add = sum >= 10 ? 1 : 0;
        l1 && (l1 = l1.next);
        l2 && (l2 = l2.next);
    }
    add && (dummy.next = new ListNode(add));
    return head.next;
};


// M 445. 两数相加 II
// 链表尾部是个位:(2 -> 4 -> 3) 243
// 如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。
var addTwoNumbers = function(l1, l2) {
    const stack1 = []
    const stack2 = []
    const stack = []

    let node = l1
    while(node) {
        stack1.push(node.val)
        node = node.next
    }

    node = l2
    while(node) {
        stack2.push(node.val)
        node = node.next
    }

    let a, b, add = 0
    while(stack1.length || stack2.length) {
        a = stack1.pop() || 0
        b = stack2.pop() || 0

        stack.push((a+b+add)%10)
        add = (a+b+add) >= 10 ? 1 : 0
    }

    add && stack.push(1)

    let dummy = {}
    current = dummy
    while(stack.length) {
        current.next = new ListNode(stack.pop())
        current = current.next
    }

    return dummy.next
};






// M 3. 无重复字符的最长子串
// 维护一个数组作为滑动窗口,
// 如果数组中已经有了字符a,则删除a包括自己在内及之前的所有元素
var lengthOfLongestSubstring = function(s) {
    let window = [], max = 0
    for (let i = 0; i < s.length; i++) {
        const index = window.indexOf(s[i])
        if (index !== -1) {
            // 变异方法,删除找到的元素及之前的所有元素
            window.splice(0, index + 1)
        }
        window.push(s[i])
        max = Math.max(window.length, max)
    }

    return max
}


// M 5. 最长回文子串
var longestPalindrome = function (s) {
    let n = s.length
    let res = ''
    // dp[i,j]:字符串s从索引i到j的子串是否是回文串
    let dp = Array.from(new Array(n), () => new Array(n).fill(0))

    for (let i = n - 1; i >= 0; i--) {
        for (let j = i; j < n; j++) {
            if (s[i] == s[j]) {
                if (j - i < 2) {
                    // j - i < 2:意即子串是一个长度为0或1的回文串
                    dp[i][j] = 1
                } else {
                    dp[i][j] = dp[i + 1][j - 1]
                }
            }
            // 如果s[i,j]是回文串,且长度大于现有长度,则更新
            if (dp[i][j] && j - i + 1 > res.length) {
                res = s.substring(i, j + 1)
            }
        }
    }

    return res
};


// E 21. 合并两个有序链表
var mergeTwoLists = function(l1, l2) {
    let head = new ListNode('head')
    let dummy = head // 哑结点便于操作移动
    while (l1 && l2) {
        if (l1.val <= l2.val) {
            dummy.next = l1
            l1 = l1.next
        } else {
            dummy.next = l2
            l2 = l2.next
        }
        dummy = dummy.next
    }


    if (l1) {
        dummy.next = l1
    }

    if (l2) {
        dummy.next = l2
    }

    return head.next
};


// E 53. 最大子序和
// 这道题用动态规划的思路并不难解决,比较难的是后文提出的用分治法求解,但由于其不是最优解法,所以先不列出来
// 动态规划的是首先对数组进行遍历,当前最大连续子序列和为 sum,结果为 ans
// 如果 sum > 0,则说明 sum 对结果有增益效果,则 sum 保留并加上当前遍历数字
// 如果 sum <= 0,则说明 sum 对结果无增益效果,需要舍弃,则 sum 直接更新为当前遍历数字
// 每次比较 sum 和 ans的大小,将最大值置为ans,遍历结束返回结果
var maxSubArray = function(nums) {
    // 这里的遍历,其实是遍历出以某个节点为结束的所有子序列
    let ans = nums[0];
    let sum = 0;
    for(let num of nums) {
        // if(sum > 0) { 可以写成这样
        if(sum + num > num ){
            sum = sum + num;
        } else {
            sum = num;
        }
        ans = Math.max(ans, sum);
    };
    return ans;
};
// 这个方法
var maxSubArray = function(nums) {
    if (nums.length == 1) return nums[0];
    var pre = 0;
    var res = nums[0];
    nums.forEach(item => {
        pre = Math.max(pre + item, item);
        res = Math.max(res, pre);
    });
    return res;
}



// M 11. 盛最多水的容器
// 左右双指针,每次都 向内移动短板,直到相撞
var maxArea = function(height) {
    let i = 0
    let j = height.length - 1
    let area = 0

    while (i < j) {
        if (height[i] < height[j]) {
            area = Math.max(area, height[i] * (j - i))
            i++
        } else {
            area = Math.max(area, height[j] * (j - i))
            j--
        }
    }

    return area
};


// M 146. LRU缓存机制
// Map 中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map 对象是按插入的顺序返回键值
// Map.prototype.keys() 返回一个新的 Iterator 对象。它包含按照顺序插入 Map 对象中每个元素的key值。
// 因此,需要拿到第一个key也就是最少使用的项时,需要使用 next().value 即 Map.keys().next().value
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map/keys
var LRUCache = function(capacity) {
    this.cap = capacity
    this.cache = new Map()
};

LRUCache.prototype.get = function(key) {
    if (this.cache.has(key)) {
        const value = this.cache.get(key)
        this.cache.delete(key)
        this.cache.set(key, value)

        return value
    } else {
        return -1
    }
};

LRUCache.prototype.put = function(key, value) {
    if (this.cache.has(key)) {
        this.cache.delete(key)
    } else {
        if (this.cap === this.cache.size) {
            this.cache.delete(this.cache.keys().next().value)
        }
    }
    this.cache.set(key, value)
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * var obj = new LRUCache(capacity)
 * var param_1 = obj.get(key)
 * obj.put(key,value)
 */


// E 121. 买卖股票的最佳时机(一笔交易)
// 只需要找到最低价格,每次找出最大利润
var maxProfit = (prices) => {
    let profit = 0
    let minPrice = Number.MAX_SAFE_INTEGER // 初始化为最大安全数字

    for(let i = 0; i < prices.length; i++) {
        if (prices[i] < minPrice) {
            minPrice = prices[i]
        } else {
            profit = Math.max(profit, prices[i] - minPrice)
        }
    }

    return profit
}

// E 122. 买卖股票的最佳时机 II(多笔交易)
var maxProfit = (prices) => {
    let profit = 0, gap;
    for (var i = 1; i < prices.length; i++) {
        gap = prices[i] - prices[i-1]
        if (gap > 0) {
            profit += gap;
        }
    }

    return profit
}


// M 33. 搜索旋转排序数组
// 直接用nums.indexOf来做,复杂度是O(n),而题目要求复杂度为O(nlogn),则需要用二分法
// ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )
// 将数组一分为二,其中一定有一个是有序的,另一个可能是有序,也能是部分有序。此时有序部分用二分法查找。无序部分再一分为二,其中一个一定有序,另一个可能有序,可能无序。就这样循环.
// 先找出mid,然后根据mid来判断,mid是在有序的部分还是无序的部分
    // 假如mid小于start,则mid一定在右边有序部分。
    // 假如mid大于等于start, 则mid一定在左边有序部分。
var search = function(nums, target) {
    let start = 0
    let end = nums.length - 1
    while(start <= end) {
        let mid = start + end >> 1

        if (nums[mid] === target) {
            return mid
        }

        // ️⚠️注意这里,mid小于start判断没有等号
        if (nums[start] > nums[mid]) {
            // mid属于右边有序部分,要判断target是否在[mid, end]
            if (target >= nums[mid] && target <= nums[end]) {
                start = mid + 1
            } else {
                end = mid - 1
            }
        } else {
            // mid属于左边有序部分,要判断target是否在[start, mid]
            if (target >= nums[start] && target <= nums[mid]) {
                end = mid - 1
            } else {
                start = mid + 1
            }
        }
    }

    return -1
};


// M 240. 搜索二维矩阵 II
var searchMatrix = function(matrix, target) {
    for(let item of matrix) {
        if (target < item[0]) return false

        let left = 0, right = item.length - 1, mid
        while(left <= right) {
            mid = (left + right) >> 1
            if (target === item[mid]) {
                return true
            }

            if (target < item[mid]) {
                right = mid - 1
            } else {
                left = mid + 1
            }
        }
    }

    return false
};


// M 56. 合并区间
var merge = function (intervals) {
    let length = intervals.length
    if (!length) {
        return []
    }
    let res = []
    // 先按左边界排序
    intervals.sort((a, b) => a[0] - b[0])

    res.push(intervals[0])
    for (let i = 1; i < length; i++) {
        if (intervals[i][0] > res[res.length - 1][1]) {
            // 无交集
            res.push(intervals[i])
        } else {
            // 有交集
            if (intervals[i][1] > res[res.length - 1][1] ) {
                res[res.length - 1][1] = intervals[i][1]
            }
        }
    }

    return res
};


// M 46. 全排列
// 回溯:不停的试探。放一下,尝试一个结果,再撤销,走下一步。
// 回溯的公式:
    // 终止条件
    // 循环
    //     tmpList设置值
    //     backtrack递归,tmpList已经变了,透传参数即可
    //     tmpList撤销上次设置
var permute = function (nums) {
    const res = [] // // 保存所有排列的结果

    const backtrack = (res, tmpList, nums) => {
        if (tmpList.length === nums.length) {
            // 终止条件
            res.push([...tmpList])
        } else {
            for (let i = 0; i < nums.length; i++) {
                if (tmpList.includes(nums[i])) continue
                tmpList.push(nums[i])
                backtrack(res, tmpList, nums)
                tmpList.pop()
            }
        }
    }

    backtrack(res, [], nums) // 执行回溯

    return res
};
// 方法二
var permute = function(nums) {
    const res = []
    const swap = (arr, i, j) => {
        let temp = arr[i]
        arr[i] = arr[j]
        arr[j] = temp
    }

    // 求数组arr从p到q的子数组全排列
    const perm = (arr, p, q) => {
        if (p === q) {
            // 注意!!! 这里不能直接将arr push进数组,因为将原数组传进去,arr之后会改变的,应该push值副本进去
            res.push([...arr])
        } else {
            for(let i = p; i <= q; i++) {
                swap(arr, p, i)
                perm(arr, p+1, q)
                swap(arr, p, i)
            }
        }
    }

    perm(nums, 0, nums.length-1)

    return res
};


// - M 面试题38. 字符串的排列
var permutation = function(s) {
    s = s.split('') // 转成数组
    let res = []
    const swap = (arr, i, j) => {
        let tmp = arr[i]
        arr[i] = arr[j]
        arr[j] = tmp
    }
    const perm = (arr, p, q) => {
        if (p === q) {
            res.push(arr.slice().join('')) // 副本,连成字符串
        } else {
            for(let i = p; i <= q; i++) {
                swap(arr, p, i)
                perm(arr, p+1, q)
                swap(arr, p, i)
            }
        }
    }

    perm(s, 0, s.length - 1)

    return [...new Set(res)] // 去重
};

// E 20. 有效的括号
// 利用栈。
// 遇到左括号,一律推入栈中,
// 遇到右括号,将栈顶部元素拿出,如果不匹配则返回 false,如果匹配则继续循环。
var isValid = function(s) {
    const stack = []
    const len = s.length
    if (len % 2) return false

    for(let i = 0; i < len; i++) {
        let letter = s[i]
        switch(letter) {
            case '(':
            case '{':
            case '[':
                stack.push(letter);
                break;
            case ')':
                if (stack.pop() !== '(') return false;
                break;
            case '}':
                if (stack.pop() !== '{') return false;
                break;
            case ']':
                if (stack.pop() !== '[') return false;
                break;
            default:
                return false;
        }
    }

    return !stack.length
};

// E 7. 整数反转
// 取余数,最后判断正负号及是否越界
var reverse = function(x) {
    let value = Math.abs(x)
    let now = 0
    while(value > 0) {
        now = now * 10 + value % 10
        value = Math.floor(value / 10)
    }
    if (x > 0) {
        return now > Math.pow(2, 31) ? 0 : now
    } else {
        return now > Math.pow(2, 31) ? 0 : -now
    }
};
var reverse = function (x) {
    let fuhao = 1
    if (x < 0) {
        fuhao = 0
    }
    x = Math.abs(x).toString().split('').reverse().join('').valueOf()
    if (x < -Math.pow(2, 31) || x > Math.pow(2, 31) - 1) {
        return 0
    } else {
        return fuhao ? x : -x
    }
};

// M 102. 二叉树的层序遍历
const levelOrder = (root) => {
    if (!root) return []

    const res = []
    const queue = [[root, 0]]
    while(queue.length) {
        let [cur, level] = queue.shift()

        res[level] ? res[level].push(cur.val) : res[level] = [cur.val]

        if (cur.left) queue.push([cur.left, level + 1])
        if (cur.right) queue.push([cur.right, level + 1])
    }

    return res
}

// M 199. 二叉树的右视图
// 层序遍历后,取值
var rightSideView = function (root) {
    if (!root) return []

    const queue = [[root, 0]]
    const res = []

    while (queue.length) {
        let [cur, level] = queue.shift()
        res[level] ? res[level].push(cur.val) : res[level] = [cur.val]

        cur.left && queue.push([cur.left, level + 1])
        cur.right && queue.push([cur.right, level + 1])
    }

    return res.map(arr => arr[arr.length - 1])
};

// M 103. 二叉树的锯齿形层次遍历
// 偶数时,push进队列;奇数时,unshift进队列
var zigzagLevelOrder = function(root) {
    if (!root) return []

    let res = []
    const queue = [[root, 0]]

    while(queue.length) {
        let [cur, level] = queue.shift()

        if (level % 2 === 0) { // 偶数时,push进队列;奇数时,unshift进队列
            res[level] ? res[level].push(cur.val) : res[level] = [cur.val]
        } else {
            res[level] ? res[level].unshift(cur.val) : res[level] = [cur.val]
        }

        if (cur.left) queue.push([cur.left, level + 1])
        if (cur.right) queue.push([cur.right, level + 1])
    }

    return res
};


// M 70. 爬楼梯
var climbStairs = function(n) {
    let dp = []
    dp[0] = 1
    dp[1] = 1
    for (let i = 2; i <= n; i++) {
        dp[i] = dp[i-1] + dp[i-2]
    }

    return dp[n]
};
// 假设要跳3级台阶,只要把最后跳一阶(即前面跳2阶楼梯的方法数) 加上 最后跳2阶(即前面跳1阶楼梯的方法数)
// dp[3] = dp[2] + dp[1]

// 如果每次能跳1阶、2阶、3阶,状态转移方程又是怎样的?
// dp[1] = 1  (1)
// dp[2] = 2  (11 2)
// dp[3] = 4  (3 111 12 21)
// dp[4] = 7  (13 \ 112 22 \ 31 1111 121 211)
// dp[4] = dp[1] + dp[2] + dp[3]
// dp[n] = dp[n-3] + dp[n-2] + dp[n-1]



// M 22. 括号生成
// 递归,记录当前字符以及字符中左右括号的个数
var generateParenthesis = function (n) {
    let res = [];
    // cur :当前字符
    // left:当前字符中左括号个数
    // right:当前字符中右括号个数
    const help = (cur, left, right) => {
        if (cur.length === 2 * n) {
            // 终止条件
            res.push(cur);
        } else {
            if (left < n) {
                // left小于n就能还能继续插入
                help(cur + "(", left + 1, right)
            }
            if (right < left) {
                // 因为先插入左括号,所以右括号个数小于左括号个数就能继续插入
                help(cur + ")", left, right + 1);
            }
        }
    };
    help("", 0, 0);
    return res;
};

// M 93. 复原IP地址
// 递归分成四部分
const restoreIpAddresses = function(str) {
    let result = []
    let helper = (cur, sub) => {
        if (sub.length > 12) {
            return
        }

        if (cur.length === 4 && cur.join('') === str) {
            // 终止条件
            result.push(cur.join('.'))
        } else {
            for(let i = 0, len = Math.min(3, sub.length), temp; i < len; i++) {
                temp = sub.slice(0, i + 1)

                if (temp < 256) {
                    helper(cur.concat([temp * 1]), sub.slice(i + 1)) // 转换下数据类型,如 01为1(LeetCode测试用例)
                }
            }
        }
    }

    helper([], str)

    return result
};
// 这道题不要用正则,因为不容易处理贪婪匹配与非贪婪匹配的问题
// let restoreIpAddresses = function(str) {
//     let res = str.replace(/^(\d{1,3}?)(\d{1,3}?)(\d{1,3}?)(\d{1,3}?)$/g, (match, $0, $1, $2, $3) => {
//         console.log($0, $1, $2, $3)
//     })

//     return res
// };

// restoreIpAddresses("25525511135")




// M 200. 岛屿数量
// 对于一个为 1 且未被访问过的位置,我们递归进入其上下左右位置上为 1 的数,将其 visited 变成 true(即设置为0)
// 重复上述过程,找完相邻区域后,我们将结果 res 自增1,然后我们在继续找下一个为 1 且未被访问过的位置,直至遍历完.
var numIslands = function (grid) {
    // 当前坐标ij,及grid的行列数
    const helper = (grid, i, j, rows, cols) => {
        if (i < 0 || j < 0 || i > rows - 1 || j > cols - 1 || grid[i][j] === "0")
            return;

        grid[i][j] = "0";

        helper(grid, i + 1, j, rows, cols);
        helper(grid, i, j + 1, rows, cols);
        helper(grid, i - 1, j, rows, cols);
        helper(grid, i, j - 1, rows, cols);
    }

    const rows = grid.length
    if (rows === 0) {
        return 0
    }
    const cols = grid[0].length
    let res = 0

    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (grid[i][j] === "1") {
                // 如果是1就未被访问过,递归进入其上下左右位置上为 1 的数,并置为0
                helper(grid, i, j, rows, cols);
                res++;
            }
        }
    }
    return res;
};


// M 695. 岛屿的最大面积
var maxAreaOfIsland = function(grid) {
    // 当前坐标ij,及grid的行列数
    const helper = (grid, i, j, rows, cols) => {
        if (i < 0 || j < 0 || i > rows - 1 || j > cols - 1 || grid[i][j] === 0)
            return 0;

        grid[i][j] = 0;

        // 注意这里要 1 加上 4部分
        return 1 + helper(grid, i + 1, j, rows, cols)
            + helper(grid, i, j + 1, rows, cols)
            + helper(grid, i - 1, j, rows, cols)
            + helper(grid, i, j - 1, rows, cols)
    }

    const rows = grid.length
    if (rows === 0) {
        return 0
    }
    const cols = grid[0].length
    let res = 0

    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (grid[i][j] === 1) {
                // 如果是1就未被访问过,递归进入其上下左右位置上为 1 的数,并置为0
                let area = helper(grid, i, j, rows, cols);
                if (area > res) {
                    res = area
                }
            }
        }
    }
    return res
};



// E 9. 回文数
var isPalindrome = function(x) {
    if (x < 0) return false
    let str = x.toString()
    let left = 0
    let right = str.length - 1

    while(left < right) {
        if (str[left] !== str[right]) {
            return false
        } else {
            left++
            right--
        }
    }

    return true
};


// M 19. 删除链表的倒数第N个节点
var removeNthFromEnd = function(head, n) {
    let dummyHead = new ListNode(0); // 添加哑结点防止head被删掉
    dummyHead.next = head;
    let fast = dummyHead
    let slow = dummyHead
    // 快指针先走 n 步
    while(n--) {
        fast = fast.next
        if (!fast) {
            return null
        }
    }

    while(fast.next) {
        fast = fast.next
        slow = slow.next
    }
    slow.next = slow.next.next

    return dummyHead.next
};




// M 322. 零钱兑换
// dp[n] 为组成n的最少硬币数
var coinChange = function(coins, amount) {
    // 因为每次都要取最小的值,所以一开始用正无穷初始化
    const dp = new Array(amount + 1).fill(Infinity)
    // 组成0元钱只需要0个硬币
    dp[0] = 0
    for (let coin of coins) {
        for (let i = 1; i <= amount; i++) {
            if (i - coin >= 0) {
                dp[i] = Math.min(dp[i], dp[i - coin] + 1)
            }
        }
    }

    return dp[amount] === Infinity ? -1 : dp[amount]
};
// ----------------开始解题,拿实例来说话----------------------

// - 假设给出的不同面额的硬币是[1, 2, 5],目标是 120,问最少需要的硬币个数?

// - 我们要分解子问题,分层级找最优子结构,看到这又要晕了哈,憋急~~ 下面马上举例。

// - 这里我们使用「自顶向下」思想来考虑这个题目,然后用「自底向上」的方法来解题,
//   体验算法的冰火两重天。

// - dp[i]: 表示总金额为 i 的时候最优解法的硬币数

// - 我们想一下:求总金额 120 有几种方法?下面这个思路关键了 !!!
//   一共有 3 种方式,因为我们有 3 种不同面值的硬币。
//   1.拿一枚面值为 1 的硬币 + 总金额为 119 的最优解法的硬币数量
//     这里我们只需要假设总金额为 119 的最优解法的硬币数有人已经帮我们算好了,
//     不需要纠结于此。(虽然一会也是我们自己算,哈哈)
//     即:dp[119] + 1
//   2.拿一枚面值为 2 的硬币 + 总金额为 118 的最优解法的硬币数
//     这里我们只需要假设总金额为 118 的最优解法的硬币数有人已经帮我们算好了
//     即:dp[118] + 1
//   3.拿一枚面值为 5 的硬币 + 总金额为 115 的最优解法的硬币数
//     这里我们只需要假设总金额为 115 的最优解法的硬币数有人已经帮我们算好了
//     即:dp[115] + 1

//   - 所以,总金额为 120 的最优解法就是上面这三种解法中最优的一种,也就是硬币数最少
//     的一种,我们下面试着用代码来表示一下:

//   - dp[120] = Math.min(dp[119] + 1, dp[118] + 1, dp[115] + 1);

//   - 推导出「状态转移方程」:
//     dp[i] = Math.min(dp[i - coin] + 1, dp[i - coin] + 1, ...)
//     其中 coin 有多少种可能,我们就需要比较多少次,那么我们到底需要比较多少次呢?
//     当然是 coins 数组中有几种不同面值的硬币,就是多少次了~ 遍历 coins 数组,
//     分别去对比即可

//   - 上面方程中的 dp[119],dp[118],dp[115] 我们继续用这种思想去分解,
//     这就是动态规划了,把这种思想,思考问题的方式理解了,这一类型的题目
//     问题都不会太大。

// 作者:ignore_express
// 链接:https://leetcode-cn.com/problems/coin-change/solution/js-xiang-jie-dong-tai-gui-hua-de-si-xiang-yi-bu-da/


// E 160. 相交链表
// 与找到两条链表的第一个公共子节点一样
var getIntersectionNode = function(headA, headB) {
    const stack1 = []
    const stack2 = []

    let node = headA
    while(node) {
        stack1.push(node) // 注意比较的是节点,不是值,所以把node push进去
        node = node.next
    }

    node = headB
    while(node) {
        stack2.push(node)
        node = node.next
    }

    node = null
    let top1, top2
    while(stack1.length && stack2.length) {
        top1 = stack1.pop()
        top2 = stack2.pop()
        if (top1 === top2) {
            node = top1
        } else {
            break
        }
    }

    return node
};


// E 88. 合并两个有序数组
// 这个题主要是要注意在nums1上做改动,不用返回任何值
var merge = function(nums1, m, nums2, n) {
    nums1.splice(m, nums1.length - m)
    nums2.splice(n, nums2.length - n)
    Object.assign(nums1, [...nums1,...nums2].sort((a,b) => a - b))
};



// M 8. 字符串转换整数 (atoi)
var myAtoi = function(str) {
    str = str.trim()
    let arr = str.match(/^([+-])?(\d+)/)

    const max = Math.pow(2, 31) - 1
    const min = -Math.pow(2, 31)

    let num = arr ? arr[0] : 0

    if (num < min) {
        return min
    } else if (num > max) {
        return max
    } else {
        return num
    }
};
// myAtoi("   -42")
// myAtoi("4193 with words")
// myAtoi("words and 987")
// myAtoi("-91283472332")


// E 14. 最长公共前缀
var longestCommonPrefix = function(strs) {
    if(!strs || strs.length === 0) return ''

    let ans = strs[0] // 将第一个字符串初始化为所求前缀

    for(let i = 1; i < strs.length; i++) {
        let j = 0 // 注意这里要把j提出for,因为之后还要使用
        for(len = Math.min(ans.length, strs[i].length); j < len; j++) {
            if (ans[j] !== strs[i][j]) {
                break
            }
        }

        ans = ans.substr(0, j)
        if (ans === '') {
            return ans
        }
    }

    return ans
};


// E 141. 环形链表
// 会不会跨过去呢?如果`把慢跑者视作参考系,则意味着慢跑者站着不动,快跑者的速度为1`,而最小间隔是1,因此一定会相遇。
var hasCycle = function(head) {
    let fast = head
    let slow = head
    let firstMeet
    while(fast && fast.next) {
        fast = fast.next.next
        slow = slow.next
        if (fast === slow) {
            firstMeet = fast
            break
        }
    }

    return Boolean(firstMeet)
};

var hasCycle = function (head) {
    const set = new Set();
    while (head) {
        if (set.has(head)) {
            return true;
        }
        set.add(head)
        head = head.next
    }

    return false
};


// E 206. 反转链表
// 每一层干的事情:得到一个指针,判断是否是最后一个,如果是则把指向最后一个数的指针当做新头指针发回去;
// 如果不是,则把从上层得到的新头指针传递下去,并且把当前指针和后一个指针调换指向。
function reverseList(head) {
    if (!head) {
        return null
    }

    if (head && !head.next) {
        return head
    }

    let acc = head.next
    let newHead = reverseList(acc)

    acc.next = head
    head.next = null

    return newHead
}
// 或者不用递归:
var reverseList = (head) => {
    let prev = null
    let cur = head

    while (cur) {
        let next = cur.next
        cur.next = prev

        prev = cur
        cur = next
    }

    return prev
}


// M 92. 反转链表 II
var reverseBetween = function(head, m, n) {
    let dummyHead = new ListNode(0)
    dummyHead.next = head
    let tmpHead = dummyHead

    let pos = 0;
    while(pos++ < m-1) {
        tmpHead = tmpHead.next
    }

    let prev = null
    let cur = tmpHead.next

    while(pos++ <= n) {
        let next = cur.next
        cur.next = prev

        prev = cur
        cur = next
    }

    // 将原链表与区间反转的链表拼接
    // console.log(cur.val, prev.val, tmpHead.val, tmpHead.next.val)
    // cur指向后半截的头
    // prev指向反转后区间头
    // tmpHead指向前半截尾
    // tmpHead.next指向反转后区间尾
    // ---tmpHead tmpHead.next---prev cur---
    tmpHead.next.next = cur // 反转后区间尾.next = 后半截的头
    tmpHead.next = prev // 前半截尾.next = 反转后区间头

    return dummyHead.next
};


// M 143. 重排链表
// 给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.
// 步骤一: 找到链表中点后分割其为 left 链表、right 链表两部分;
// 步骤二: 翻转 right 链表, 翻转链表思路同 206.Reverse_Linked_List;
// 步骤三: 接着从 left 链表的左侧, 翻转后的 right 链表的左侧各取一个值进行交替拼接;
var reorderList = function (head) {
    const dummy = new ListNode(0)
    dummy.next = head // 头结点前增加哑结点

    let slow = dummy
    let fast = dummy

    // 用快慢指针,二分链表
    while (fast && fast.next) {
        slow = slow.next
        fast = fast.next.next
    }

    let right = slow.next
    slow.next = null // 注意,这里很重要,只有这里断链,才能使left只保留链表前半部分
    let left = dummy.next


    const reverseList = (list) => {
        let prev = null
        let cur = list

        while (cur) {
            let next = cur.next
            cur.next = prev

            prev = cur
            cur = next
        }

        return prev
    }

    right = reverseList(right)

    while (left && right) {
        let lNext = left.next
        let rNext = right.next

        right.next = left.next
        left.next = right

        left = lNext
        right = rNext
    }

    return dummy.next
}

// 排序链表
const sortList = function(head) {
	// 保持链表结构不改变,只交换值
	const swap = (p, q) => {
		const val = p.val
		p.val = q.val
		q.val = val
	}

	// 寻找基准元素的节点
	const partion = (begin, end = null) => { // ————————————————————————————————————————————》好好理解下
		const val = begin.val
		let p = begin
		let q = begin.next
		// 保证 p左小,p右大
		while (q !== end) {
			if (q.val < val) {
				// 遍历小于基准时,要跟p的下一个节点交换
				p = p.next
				swap(p, q)
			}
			q = q.next
		}
		// 让基准元素跑到中间去
		swap(p, begin)

		return p // 返回得到的基准元素
	}

	const sort = (begin, end = null) => {
		if (begin !== end && begin !== null) {
			const part = partion(begin, end)
			sort(begin, part)
			sort(part.next, end)
		}
		return begin
	}

	return sort(head)
}


// E 198. 打家劫舍
// 设f(x)为打劫前x家房子所能得到的最大的资金
// f(n)=max(nums[n]+f(n-2),f(n-1))
var rob = function (nums) {
    const dp = Array.from(nums.length + 1)
    dp[0] = 0
    dp[1] = nums[0]
    // dp[2] = Math.max(dp[1], dp[0] + nums[1])
    // dp[3] = Math.max(dp[2], dp[1] + nums[2])
    for (let i = 2; i <= nums.length; i++) {
        dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1])
    }

    return dp[nums.length]
};


// E 111. 二叉树的最小深度
const minDepth = (root) => {
    if (!root) {
        return 0;
    }
    // 注意如果根节点的左或右子树为空的话是构不成子树的。
    // 而最小深度是要求从根节点到子树的。当左或右子树为空时,不符合要求。
    if (!root.left) {
        return 1 + minDepth(root.right);
    }
    if (!root.right) {
        return 1 + minDepth(root.left);
    }
    return 1 + Math.min(minDepth(root.left), minDepth(root.right))
};


// E 104. 二叉树的最大深度
var maxDepth = (root) => {
    if (!root) return 0

    return 1 + Math.max(maxDepth(root.left), maxDepth(root.right))
}

// E 543. 二叉树的直径(利用最大深度)
// 二叉树的直径不一定过根节点,因此需要去搜一遍所有子树(例如以root,root.left, root.right...为根节点的树)对应的直径,取最大值。
// root的直径 = root左子树高度 + root右子树高度
// root的高度 = max {root左子树高度, root右子树高度} + 1
// 这里的maxDepth比起上面来说,只是需要用lNum与rNum来更新全局max
var diameterOfBinaryTree = function(root) {
    let max = 0
    const maxDepth = (root) => {
        if (!root) return 0

        const lNum = maxDepth(root.left)
        const rNum = maxDepth(root.right)

        max = Math.max(lNum + rNum, max)

        return 1 + Math.max(lNum, rNum)
    }

    maxDepth(root)

    return max
};


// 判断树相同
const isSameTree = (left, right) => {
    if (!left && !right) {
        return true
    }

    if (!left || !right) {
        return false
    }

    if (left.val !== right.val) {
        return false
    }

    return isSameTree(left.left, right.left) && isSameTree(left.right, right.right)
}

// E 101. 对称二叉树
var isSymmetric = (root) => {
    if (!root) {
        return true;
    }
    let walk = (left, right) => {
        if (!left && !right) { // 先判断 都不存在的情况
            return true;
        }

        if (!left || !right) {
            return false;
        }

        if (left.val !== right.val) {
            return false;
        }

        return walk(left.left, right.right) && walk(left.right, right.left);
    }

    return walk(root.left, root.right)
}

// M 236. 二叉树的最近公共祖先
// 递归查找当前root中的左右子树是否有p节点或者q节点,有则返回p或q,没有返回null
var lowestCommonAncestor = function(root, p, q) {
    // 递归终止条件,查找的临界条件
    if(root === null || root === p || root === q) { //  没找到 或 找到 p 或 找到 q ,则终止递归
        return root
    }
    // 没到临界条件时,递归查找左右子树
    // 从左右子树分别递归查找,看是否找到
    let left = lowestCommonAncestor(root.left, p, q) // 所有节点的值都是唯一的
    let right = lowestCommonAncestor(root.right, p, q)

    if(left && right){
        // p 和 q 分别在 root 的 左子节点 和 右子节点,root是最近根节点
        return root
    } else {
        // p 和 q 在同一侧子树中,或 p 和 q 都不在这两个子树中,返回调用结果即可
        return left || right
    }
};
// 如果当前遍历的节点 root,不是 p 或 q 或 null,则我们要递归搜寻左右子树:
// - 如果左右子树递归调用,都有结果,说明 p 和 q 分居 root 的左右子树,返回 root。
// - 如果只是其中一个子树递归调用有结果,说明 p 和 q 都在这个子树,则返回该子树递归调用的结果。
// - 如果两个子树递归调用的结果都为 null,说明 p 和 q 都不在这俩子树中,返回 null。


// E 112. 路径总和
// 此题必须从根节点到叶子节点,判断是否存在和
var hasPathSum = function (root, sum) {
    if (!root) return false

    if (!root.left && !root.right && root.val === sum) {
        // 终止条件
        return true
    } else {
        // 递归,递归查找左右子树
        let left = hasPathSum(root.left, sum - root.val)
        let right = hasPathSum(root.right, sum - root.val)

        // 返回能够把sum最终减完的子树结果
        return left || right
    }
};

// E 437. 路径总和 III
// 此题不需要从根节点到叶子节点,但必须是向下遍历求和,返回路径数
var pathSum = function(root, sum) {
    const pathSumRoot = (root, cur) => {
        if(!root) return 0;
        let ret = 0
        if(root.val === cur){
            // 如果求和成功,则返回结果加1且继续递归左右子树
            ret = 1 + pathSumRoot(root.left, cur - root.val) + pathSumRoot(root.right, cur - root.val)
        }else{
            // 没求和成功,也继续递归左右子树
            ret = pathSumRoot(root.left, cur - root.val) + pathSumRoot(root.right, cur - root.val)
        }
        return ret
    }

    let num = 0

    if (root) {
        // 遍历每个节点
        num = pathSumRoot(root, sum)
        if (root.left) {
            num = num + pathSum(root.left, sum)
        }
        if (root.right) {
            num = num + pathSum(root.right, sum)
        }
    }

    return num
};

// M 24. 两两交换链表中的节点
// 使用四指针
var swapPairs = function(head) {
    const dummy = new ListNode(0)
    dummy.next = head
    let prev = dummy
    let first = prev.next

    while(first && first.next) {
        let second = first.next
        let next = second.next

        second.next = first
        first.next = next
        prev.next = second

        prev = first
        first = prev.next
    }

    return dummy.next
};


// H 41. 缺失的第一个正数
var firstMissingPositive = function(arr) {
    let newArr = []
    for(let i = 0, len = arr.length; i < len; i++) {
        let value = arr[i]
        if (value > 0 && value <= len) {
            newArr[value - 1] = value
        }
    }

    for(let i = 0, len = newArr.length; i < len; i++) {
        // if (newArr[i] === undefined) 也可以
        if (newArr[i] !== i + 1) {
            return i + 1
        }
    }

    return newArr.length + 1
};


// M 300. 最长上升子序列
// 由于一个子序列一定会以一个数结尾,于是将状态定义成:dp[i] 表示以 nums[i] 结尾的「上升子序列」的长度
var lengthOfLIS = function (nums) {
    if (!nums.length) return 0

    const len = nums.length
    const dp = new Array(len + 1).fill(1) // 初始化为1,因为子序列最少包含自己,即1

    for (let i = 1; i < len; i++) {
        for (let j = 0; j < i; j++) {
            if (nums[j] < nums[i]) {
                // 加1为在nums[j]的最长递增子序列dp[j]基础上加上当前元素nums[i]所得的最长递增子序列
                dp[i] = Math.max(dp[i], dp[j] + 1)
            }
        }
    }

    dp.sort((a, b) => b - a)

    return dp[0]
};


// M 98. 验证二叉搜索树
// 传递上下界
var isValidBST = (node) => {
    const walk = (node, lower, upper) => {
        if (!node) return true

        let value = node.val

        if (lower !== null && value <= lower) return false // 注意这里有0的情况,所以需要与null判断
        if (upper !== null && value >= upper) return false

        if (!walk(node.left, lower, value)) return false
        if (!walk(node.right, value, upper)) return false

        return true
    }

    return walk(node, null, null)
}

// M 105. 从前序与中序遍历序列构造二叉树
// 注意一开始的边界判断
var buildTree = (preorder, inorder) => {
    if(preorder.length === 0){
        return null;
    }
    if(preorder.length === 1){
        return new TreeNode(preorder[0]);
    }
    const value = preorder[0];
    const root = new TreeNode(value);
    const index = inorder.indexOf(value);

    const inLeft = inorder.slice(0,index);
    const inRight = inorder.slice(index+1);
    const preLeft = preorder.slice(1,index+1);
    const preRight = preorder.slice(index+1);

    root.left = buildTree(preLeft, inLeft);
    root.right = buildTree(preRight, inRight);

    return root;
}


// M 221. 最大正方形
// 若某格子值为 1,则以此为右下角的正方形的、最大边长为:上面的正方形、左面的正方形或左上的正方形中,最小的那个,再加上此格。
// 状态定义:dp[i][j] 以 matrix[i][j] 为右下角的正方形的最大边长
// dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1;
var maximalSquare = function(matrix) {
    if (matrix.length === 0) return 0;
    const rows = matrix.length;
    const cols = matrix[0].length;
    let max = Number.MIN_VALUE;

    const dp = Array.from(new Array(rows + 1), () => new Array(cols + 1))

    // 为了代码简便,在matrix的最左和最上补0
    for(let j = 0; j < cols + 1; j++) {
        dp[0][j] = 0
    }
    for(let i = 0; i < rows + 1; i++) {
        dp[i][0] = 0
    }

    for (let i = 1; i < rows + 1; i++) {
        for (let j = 1; j < cols + 1; j++) {
            if (matrix[i - 1][j - 1] === "1") {
                dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]) + 1;
                max = Math.max(max, dp[i][j]);
            } else {
                dp[i][j] = 0;
            }
        }
    }

    // max是边长,求面积
    return max * max;
};


// E 26. 删除排序数组中的重复项(返回移除后数组的新长度)
// 需要在原数组上操作
// 当且仅当遇到下一个不相同即不重复的元素时,更新指针位置为下一个元素
var removeDuplicates = function(nums) {
    const n = nums.length
    let j = 0

    for(let i = 1; i < n; i++) {
        if (nums[i] !== nums[i-1]) {
            j++
            nums[j] = nums[i]
        }
    }

    return j + 1 // 求长度,索引加1
};



// M 17. 电话号码的字母组合
function letterCombinations(str) {
    if (!str) return []
    let nums = str.split('')
    let map = ['', 1, 'abc', 'def', 'ghi', 'jkl', 'mno', 'pqrs', 'tuv', 'wxyz'];
    // 数字所对字母映射数组
    let codes = nums.map(i => map[i])

    const combine = (arr) => {
        if (arr.length < 2) return arr[0].split('')

        let tmp = []
        for(let i = 0; i < arr[0].length; i++) {
            for(let j = 0; j < arr[1].length; j++) {
                tmp.push(`${arr[0][i]}${arr[1][j]}`)
            }
        }
        // 合并后使用 tmp 替换已经合并完成的元素
        arr.splice(0, 2, tmp)

        if (arr.length > 1) {
            return combine(arr)
        } else {
            return arr[0]
        }
    }

    return combine(codes)
}

// 求组合:从n个数组中各选一个元素,有多少种组合
const combine = (arr) => {
    if (arr.length < 2) return arr[0].split('')

    let tmp = []
    for(let i = 0; i < arr[0].length; i++) {
        for(let j = 0; j < arr[1].length; j++) {
            tmp.push(`${arr[0][i]}${arr[1][j]}`)
        }
    }

    arr.splice(0, 2, tmp)

    if (arr.length > 1) {
        return combine(arr)
    } else {
        return arr[0]
    }
}
// combine([[1,3,4], [5,6,7]])
// ["15", "16", "17", "35", "36", "37", "45", "46", "47"]
// combine(['abc', 'def'])
// ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]
// combine(['abc', 'def', 'ghig'])
// ["adg", "adh", "adi", "adg", "aeg", "aeh", "aei", "aeg", "afg", "afh", "afi", "afg", "bdg", "bdh", "bdi", "bdg", "beg", "beh", "bei", "beg", "bfg", "bfh", "bfi", "bfg", "cdg", "cdh", "cdi", "cdg", "ceg", "ceh", "cei", "ceg", "cfg", "cfh", "cfi", "cfg"]

// M 71. 简化路径
    // 遇到正常的字母时,推入 stack 中
    // 遇到 .. 时,stack 弹出最近一个路径
    // 遇到 . 或者为空时,不修改当前 stack。
    // 最后返回 '/' + stack.join('/') 为新的路径
var simplifyPath = function(path) {
    const arr = path.split('/')
    const stack = []

    for(let item of arr) {
        if (item === '' || item === '.') {
            continue
        } else if (item === '..') {
            stack.pop()
        } else {
            stack.push(item)
        }
    }

    return '/' + stack.join('/')
};


// E 169. 多数元素
// 因为大于一半, 所以排序后的 中间那个数必是所求
var majorityElement = function(nums) {
    nums.sort((a, b) => a - b)
    return nums[Math.floor(nums.length / 2)]
};
// 如果不用排序,可以使用 ”投票算法“,当投票计数为0时,替换为当前数值
var majorityElement = function(nums) {
    let count = 1
    let more = nums[0]
    for(let i = 1; i < nums.length; i++) {
        if (count === 0) {
            more = nums[i]
        }

        if (nums[i] === more) {
            count++
        } else {
            count--
        }
    }

    return more
};


// M 6. Z 字形变换
var convert = function(s, numRows) {
    if (numRows === 1) return s // 如果只有1行,则直接返回字符串

    let len = Math.min(s.length, numRows)
    let rows = []
    // 初始化 len 行
    for(let i = 0; i < len; i++) {
        rows[i] = ''
    }

    let index = 0    // 行索引
    let down = false // 移动方向

    for(let c of s) {
        rows[index] += c

        if (index === 0 || index === numRows - 1) {
            down = !down
        }
        index += down ? 1 : -1
    }

    // 最后遍历把多行拼接起来
    let res = ''
    for(let r of rows) {
        res += r
    }

    return res
};


// 455. 分发饼干
    // - 使用双指针
var findContentChildren = function (g, s) {
    g = g.sort((a, b) => a - b);
    s = s.sort((a, b) => a - b);
    var gLen = g.length;
    var sLen = s.length;
    var i = 0;
    var j = 0;
    var maxNum = 0;
    while (i < gLen && j < sLen) {
        if (s[j] >= g[i]) {
            i++;
            j++;
            maxNum++;
        } else {
            j++;
        }
    }
    return maxNum;
};


// M 442. 数组中重复的数据(不用空间,原地哈希)
    // 这种题,其实看见不用空间、o(n)时间,通常潜台词就是,用原地哈希来做。
    // [4,3,2,7,8,2,3,1],均为正整数
    // 当i = 1时,此时n=3,把nums[3-1] *= -1 变成负数,结果 [4,3,-2,7,8,2,3,1]
    // 当i = 6时,此时n=3,发现nums[3-1]这个位置已经为负数说明之前已经被改过,也就是n=3这个数字出现过,就把3数字添加到arr里
    // 这里i=1改的是nums[2], i=6改的也是nums[2],这里nums[2]只是用来记录状态
var findDuplicates = function (nums) {
    let arr = []
    for (let i = 0; i < nums.length; i++) {
        let n = Math.abs(nums[i]) // 在原数组nums上改变符号,所以要取绝对值
        if (nums[n - 1] < 0) {
            arr.push(n)
        } else {
            // 出现一次,就把坐标对应的数字改成负数,这里索引对应的数字符号只是记录状态
            nums[n - 1] *= -1
        }
    }
    return arr;
};



// - E 696. 计数二进制子串
const countBinarySubstrings = (s) => {
    let result = []
    let helper = (str) => { // ————————————————————————————————————————————》好好理解下
        let left = str.match(/^(0+|1+)/)[0]
        let right = (str[0]^1).toString().repeat((left.length))

        let reg = new RegExp(`^(${left}${right})`)
        if (reg.test(str)) {
            return RegExp.$1
        } else {
            return ''
        }
    }

    for(let i = 0; i < s.length - 1; i++) {
        let sub = helper(s.slice(i))
        if (sub) {
            result.push(sub)
        }
    }

    return result.length
}
var countBinarySubstrings = function(s) {
    // pre 前一个数字连续出现的次数,cur 当前数字连续出现的次数,result 结果子串个数
    let pre = 0, cur = 1, res = 0
    for (let i = 0, len = s.length - 1; i < len; i++) {
        // 判断当前数字是否与后一个数字相同
        if (s[i] === s[i+1]) { // 相同,则当前数字出现的次数cur加1
            cur++
        } else { // 不同,则当前数字的次数,事实上变成了前一个数字的次数,当前数字的次数重置为1
            pre = cur
            cur = 1
        }
        if (pre >= cur) { // 前一个数字出现的次数 >= 后一个数字出现的次数,则一定包含满足条件的子串
            res++
        }
    }
    return res
};


// E 557. 反转字符串中的单词 III
function revertByWord(str) {
    // 对字符串方法可以使用正则匹配空格
    let arr = str.split(/\s/g);
    // 注意item需要先转成数组再使用reverse方法
    let res = arr.map(item =>
        item.split('').reverse().join('')
    )

    return res.join(' ');
}


// E 459. 重复的子字符串
var repeatedSubstringPattern = function(s) {
    if (s.length > 10000) return false;

    return /^(\w+)\1+$/.test(s)
};


// E 414. 第三大的数
var thirdMax = function(nums) {
    var sets = new Set(nums)
    // 注意这里要将 set类型转回 array
    nums = Array.from(sets).sort((a, b) => b - a)
    if (nums.length < 3) {
        return Math.max(...nums)
    }

    return nums[2]
}

// 不用set,空间O(1)复杂度
var thirdMax = function(nums) {
    if (nums.length < 3) return Math.max(...nums);
    let max1 = -Infinity; // 存储最大 置为最小值
    let max2 = -Infinity; // 存储第二大 置为最小值
    let max3 = -Infinity; // 存储第三大 置为最小值
    for (let n of nums) {
        if (n > max1) { // 先比较最大的,成功就把值向后传递,把第三大的丢掉
            max3 = max2;
            max2 = max1;
            max1 = n;
            continue;
        }
        if (n !== max1 && n > max2) { // 第一个判断没中,判断是不是第二大的,注意值不能等于最大,这是为了防止重复
            max3 = max2;
            max2 = n;
            continue;
        }
        if (n !== max1 && n !== max2 && n > max3) { // 同上,多了个判断条件
            max3 = n;
            continue;
        }
    }

    if (max3 === -Infinity || max2 === -Infinity || max1 === -Infinity) {
        return Math.max(max1, max2, max3); // 这里其实就是判断,在去重后的长度是不是小于3,不是的话三个max肯定都不是-Infinity
    }

    return max3; //直接返回正确答案
};


// M 1143. 最长公共子序列
var longestCommonSubsequence = function(str1, str2) {
    let m = str1.length,
        n = str2.length

    let dp = Array.from(new Array(m + 1), () => new Array(n + 1).fill(0))

    for (let i = 1; i <= n; i++) {
        for (let j = 1; j <= m; j++) {
            if (str[i-1] === str[j-1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) // i是str1长度,j是str2长度,
            }
        }
    }

    return dp[m][n]
}

// E 62. 不同路径
var uniquePaths = function(m, n) {
    let dp = Array.from(new Array(m), () => new Array(n).fill(0))

    for(let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (i == 0) {
                // 确定上边界
                dp[0][j] = 1
            } else if (j == 0) {
                // 确定左边界
                dp[i][0] = 1
            } else {
                dp[i][j] = dp[i-1][j] + dp[i][j-1]
            }
        }
    }

    return dp[m-1][n-1]
};



// M 63. 不同路径 II
var uniquePathsWithObstacles = function(arr) {
    let m = arr.length, n = arr[0].length

    // 初始化为不可达
    let dp = Array.from(new Array(m), () => new Array(n).fill(0))

    // 检查起始或者目标元素是不是1(被占用了),如果起始或者最后那个格就是1,说明怎么都怎么不到那,直接返回0
    if (arr[0][0] == 1 || arr[m-1][n-1] == 1) return 0

    // 确定初始边界
    dp[0][0] = 1

    // 由初始边界确定 左边界(第一列)
    for (let i = 1; i < m; i++) {
        if (arr[i][0] != 1) {
            dp[i][0] = dp[i-1][0];
        }
    }

    // 由初始边界确定 上边界(第一行)
    for (let j = 1; j < n; j++) {
        if (arr[0][j] != 1) {
            dp[0][j] = dp[0][j-1];
        }
    }

    // 动态规划推导
    for (let i = 1; i < m ; i++) {
        for (let j = 1; j < n; j++) {
            if (arr[i][j] != 1) {
                dp[i][j] = dp[i-1][j] + dp[i][j-1]
            }
        }
    }

    return dp[m-1][n-1]
};


// 面试题 08.06. 汉诺塔问题
let hanota = function(A, B, C) {
    let move = (A, B, C, n) => {
        if (n === 0) return
        move(A, C, B, n-1)  // n-1从A到B
        C.push(A.pop())     // 第n从A到C
        move(B, A, C, n-1)  // n-1从B到C

        return C
    }

    return move(A, B, C, A.length)
};


// M 958. 二叉树的完全性检验
var isCompleteTree = function(root) {
    if (!root) {
        return true
    }

    const queue = [[root, 1]]
    let count = 0

    while (queue.length) {
        let [cur, index] = queue.shift()
        // 用来判断索引位置节点是否存在,如果index !== ++count代表左右子树层级相差超过1层,或者最后一层没有左节点却有右节点
        if (index !== ++count) {
            return false
        }
        // 层序遍历过程中,用index来维护节点索引,如果根节点index为1,那么一个节点索引是index,那他的左孩子索引是index * 2,右孩子索引是index * 2 +1
        cur.left && queue.push([cur.left, index * 2])
        cur.right && queue.push([cur.right, index * 2 + 1])
    }

    return true
};


// E 110. 平衡二叉树
const isBalanced = function(root) {
    const depth = (root) => {
        return !root ? 0 : Math.max(depth(root.left), depth(root.right)) + 1
    }

    if (!root) {
        return true
    }

    return (Math.abs(depth(root.left) - depth(root.right)) <= 1) && isBalanced(root.left) && isBalanced(root.right)
};

// E 统计节点个数
const sizeOfTree = (root) => {
    if (!root) {
        return 0
    }

    return 1 + sizeOfTree(root.left) + sizeOfTree(root.right)
}


// - 二叉树的镜像
var invertTree = function(root) {
    if (!root) {
        return null
	}

	[root.left, root.right] = [invertTree(root.right), invertTree(root.left)]

	return root
};

// 递归的方式
var invertTree = function(root) {
    if (!root) {
        return null
	}

	[root.left, root.right] = [invertTree(root.right), invertTree(root.left)]

	return root
};

// 遍历的方式
var invertTree = function(root) {
    if (!root) {
        return null
    }

    const stack = [root]
    let current = null
    while(current = stack.shift()) {
        const left = current.left
        const right = current.right
        current.left = right
        current.right = left

      if (left) {
        stack.push(left)
      }

      if (right) {
        stack.push(right)
      }
    }

    return root
}



// - 二叉树的层序遍历
const levelOrder = function(root) {
    if (!root) return []

    let res = []
        queue = [[root, 0]]
    while (queue.length) {
        let [cur, level] = queue.shift()
        res[level] ? res[level].push(cur.val) : res[level] = [cur.val]

        if (cur.left) queue.push([cur.left, level + 1])
        if (cur.right) queue.push([cur.right, level + 1])
    }

    return res
}

// - 二叉树的中序遍历
const inorderTraversal = (root) => {
    const res = [];
    const stack = [];
    let current = root;

    while (current || stack.length > 0) {
        while (current) {
            stack.push(current);
            current = current.left;
        }

        current = stack.pop();
        res.push(current.val); // 前序入栈就输出,中序出栈再输出,后续是对前序的修改

        current = current.right;
    }

    return res;
};



// - 二叉树的前序遍历
const preorderTraversal = (root) => {
    const res = [];
    const stack = [];
    let current = root;

    while (current || stack.length > 0) {
        while (current) {
            res.push(current.val); // 前序入栈就输出,中序出栈再输出,后续是对前序的修改
            stack.push(current);
            current = current.left;
        }

        current = stack.pop();

        current = current.right;
    }
    return res;
};



// - 二叉树的后序遍历
const postorderTraversal = (root) => {
    const res = [];
    const stack = [];
    let current = root;

    while (current || stack.length > 0) {
        while (current) {
            res.push(current.val);
            stack.push(current);
            current = current.right; // 相比前序遍历,修改,先遍历右子树
        }

        current = stack.pop();

        current = current.left; // 相比前序遍历,修改,后遍历左子树
    }
    return res.reverse(); // 因为是 根右左,所以需要翻转成 左右根
}



// - 重建二叉树
function reConstruct(preorder, inorder) {
    if(preorder.length === 0){
        return null;
    }
    if(preorder.length === 1){
        return new Node(preorder[0]);
    }
    const value = preorder[0];
    const root = new Node(value);
    const index = inorder.indexOf(value);

    // slice 包含从 start 到 end(不包括该元素)
    const inLeft = inorder.slice(0, index);
    const inRight = inorder.slice(index + 1); // 去掉根
    const preLeft = preorder.slice(1, index + 1); // 去掉根
    const preRight = preorder.slice(index + 1);

    root.left = reConstruct(preLeft, inLeft);
    root.right = reConstruct(preRight, inRight);

    return root;
}



// - 求二叉树的后序遍历
var getHRD = () => {
    let preorder, inorder;
    let rebulidPostTree = (preorder, inorder) => {
        if (!preorder.length) {
            return null;
        }

        if (preorder.length === 1) {
            return preorder[0];
        }

        const root = preorder[0];
        const index = inorder.findIndex(root);

        const inLeft = inorder.slice(0, index);
        const inRight = inorder.slice(index + 1);
        const preLeft = preorder.slice(1, index + 1);
        const preRight = preorder.slice(index + 1);

        return rebulidPostTree(preLeft, inLeft) + rebulidPostTree(preRight, inRight) + root;
    }

    while(preorder = readline()) {
        inorder = readline();
        console.log(rebulidPostTree(preorder, inorder))
    }
}



// - 生成二叉搜索树
const generateBST = (data) => { // ————————————————————————————————————————————》好好理解下
    let root = new Node(data.shift())

    let insert = (node, data) => {
        if (node.val > data) {
            if (!node.left) {
                node.left = new Node(data)
            } else {
                insert(node.left, data)
            }
        } else {
            if (!node.right) {
                node.right = new Node(data)
            } else {
                insert(node.right, data)
            }
        }
    }

    // 遍历所有的数据,逐渐插入到当前这颗搜索树中
    data.forEach(item => insert(root, item))

    return root
}



// - 二叉搜索树的第k个节点(BST的中序遍历是升序)
// 利用BST的中序遍历是升序 // ————————————————————————————————————————————》好好理解下
const kthSmallest = (root, k) => {
    const res = []
    const stack = []
    let current = root

    while(current || stack.length > 0) {
        while(current) {
            stack.push(current)
            current = current.left
        }
        current = stack.pop()
        res.push(current.val)

        current = current.right
    }

    return res[k - 1]
}



// - 二叉搜索树的后序遍历
const verifyPostorder = (postorder) => {
    const len = postorder.length
    if (len < 2) {
        return true
    }

    const root = postorder[len -1];
    const index = postorder.findIndex(item => item > root)

    const leftTree = postorder.slice(0, index)
    const rightTree = postorder.slice(index, len - 1)

    const res = rightTree.some(item => item < root)

    return res ? false : verifyPostorder(leftTree) && verifyPostorder(rightTree)
}


// - E 605. 种花问题
const canPlaceFlowers = (flowerbed = [], num) => {
    // 为了方便处理边界问题,前后补0
    flowerbed.push(0)
    flowerbed.unshift(0)
    let result = 0
    for (let i = 1; i < flowerbed.length; i++) {
        if (flowerbed[i] === 0 && flowerbed[i - 1] === 0 && flowerbed[i + 1] === 0) {
            flowerbed[i] = 1
            result++
        }
    }
    return result >= num
}





// - 链表中环的入口结点
    // 环形链表的入口节点
    // 方法:分两步
    // 阶段一 快慢指针判断是否成环,相遇必定成环,快指针走到链尾指向null则无环;
    // 阶段二 如果成环,记录第一次相遇的节点firstMeet,使用两个慢指针(即步频为1的)一个从head,一个从firstMeet出发,相遇时从head出发的指针则为入环点
function detectCycle(head) {
  // 阶段1:快慢指针,速度fast步频2,slow步频1,试图找出第一次相遇的点,判断是否成环
  let fast = head, slow = head, firstMeet = null
  while(slow && fast && fast.next) {
      slow = slow.next
      fast = fast.next.next
      if(slow === fast) {
          // 相遇时,慢指针走的路程就是 相遇点firstMeet
          firstMeet = slow
          break
      }
  }

  if(!firstMeet) {
      // 没有相遇,不成环
      return null
  }

  // 阶段2:用 阶段1中找到的 相遇点firstMeet 来找到环的入口
  // 设定head到入环点为n,假设环路大于n,环内路被分为b与n。
  // 慢指针从入环处开始跑b步,距离入环处就剩下了n。
  // 此时,快指针则是从距离入环处n步远的位置开始跑了2b步,距离入环处也是剩下了n。
  // 慢指针在圈内b的位置,快指针在圈内n+2b(减去圈长n+b也是b的位置),位置相同
  // 所以他们相遇了,距离入环处都是n,这个点就是firstMeet
  // 所以最后,使用两个slow指针,一个从head出发,一个从firstMeet出发,都走n步,相遇,返回从head出发的slow即可
  // 另外,对于环路小于n的情况,则可将多个小环展开视为一个大环,情况是一样的
  while(firstMeet && head) {
      if(firstMeet === head) {
          return head
      }
      firstMeet = firstMeet.next
      head = head.next
  }
}



// - 从尾到头打印链表
function printFromTailToHead(node) {
  node.next && printFromTailToHead(node.next)
  node.value && console.log(node.value)
}




// - 设计循环队列
class MyCircularQueue {
    constructor(k) {
        // 用来保存数据长度为k的数据结构
        this.list = Array(k)
        // 队收尾指针
        this.front = 0
        this.rear = 0
        // 队列长度
        this.len = k
    }
    // 入队
    enQueue(num) {
        if (this.isFull()) {
            return false
        } else {
            this.list[this.rear] = num
            this.rear = [this.rear + 1] % this.len

            return true
        }
    }
    // 出队
    deQueue() {
        let num = this.list[this.front]

        this.list[this.front] = ''
        this.front = [this.front + 1] % this.len

        return num
    }
    isEmpty() {
        return this.front === this.rear && !this.list[this.front]
    }
    isFull() {
        return this.front === this.rear && !!this.list[this.front]
    }
    Front() {
        return isEmpty() ? -1 : this.list[this.front]
    }
    Rear() {
        let rear = this.rear - 1
        return isEmpty()
            ? -1
            : this.list[rear < 0 ? this.len - 1 : rear]
    }
}








Last Updated: 11/3/2020, 2:47:39 PM