习题

191 阅读10分钟

以下代码全部是AC的代码

3.无重复字符的最长子串 中等

题目

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2: 输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3: 输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。   请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

示例 4: 输入: s = "" 输出: 0   提示:

0 <= s.length <= 5 * 104, s 由英文字母、数字、符号和空格组成

思路

滑动窗口 双指针

/**
 * @param {string} s
 * @return {number}
 */
// 双指针 滑动窗口
var lengthOfLongestSubstring = function(s) {
    var result = 0
    var map = new Map()
    for (var i = 0, j = 0; j < s.length; j++) {
        if (map.has(s.charAt(j))) {
            i = Math.max(map.get(s.charAt(j)) + 1, i) 
        }
        result = Math.max(result, j - i + 1)
        map.set(s.charAt(j), j)
    }
    return result
};

338.比特位计数 中等

题目

给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

示例 1:

输入: 2 输出: [0,1,1] 示例 2:

输入: 5 输出: [0,1,1,2,1,2] 进阶:

给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗? 要求算法的空间复杂度为O(n)。 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。

思路

位运算 + 递推

/**
 * @param {number} num
 * @return {number[]}
 */
var countBits = function(num) {
    const result = new Array(num + 1).fill(0);
    for (let i = 1; i <= num; i++) {
        result[i] += result[i & (i - 1)] + 1;
    }
    return result;
};

231.2的幂 简单

给定一个整数,编写一个函数来判断它是否是 2 的幂次方。

思路

n & (n - 1) 是去掉最后一位1

/**
 * @param {number} n
 * @return {boolean}
 */
var isPowerOfTwo = function(n) {
    return n > 0 && (n & (n - 1)) === 0
};

191.位1的个数 简单

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量)。

/**
 * @param {number} n - a positive integer
 * @return {number}
 */
var hammingWeight = function(n) {
    let result = 0;
    for (; n; n &= n - 1) {
        result += 1;
    }
    return result;
};

110.平衡二叉树 简单

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

思路

从低向上递归 复杂度低 O(n)

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isBalanced = function(root) {
    const height = (root) => {
        if (!root) return 0;
        const leftHeight = height(root.left);
        const rightHeight = height(root.right);
        if (leftHeight === -1 || rightHeight === -1 || Math.abs(leftHeight - rightHeight) > 1) {
            return -1;
        } else {
            return Math.max(leftHeight, rightHeight) + 1;
        }
    }
    return height(root) >= 0;
};

18.四数之和 中等 TODO

15.三数之和 中等

题目

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 示例 2:

输入:nums = [] 输出:[] 示例 3:

输入:nums = [0] 输出:[]  

提示:

0 <= nums.length <= 3000 -105 <= nums[i] <= 105

思路

排序+双指针

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
  const result = [];
  if (nums.length < 3) return result; // 个数小于3直接返回

  const sortedNums = nums.sort((a, b) => a - b);
  let len = sortedNums.length;
  
  if (sortedNums[0] > 0 || sortedNums[len - 1] < 0) return result;

  for (let i = 0; i < len && sortedNums[i] <= 0;) {
    let j = i + 1;
    let k = len - 1;
    while(j < k) {
      const sum = sortedNums[i] + sortedNums[j] + sortedNums[k];
      if (sum > 0) {
        while(j < k && sortedNums[k] === sortedNums[--k]) {}
      } else if (sum < 0) {
        while(j < k && sortedNums[j] === sortedNums[++j]) {}
      } else {
        result.push([sortedNums[i], sortedNums[j], sortedNums[k]]);
        while(j < k && sortedNums[k] === sortedNums[--k]) {}
        while(j < k && sortedNums[j] === sortedNums[++j]) {}
      }
    }
    while (sortedNums[i] === sortedNums[++i]) {}
  }
  return result;
};

2.两数相加 中等

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    var startNode
    var curNode1 = l1
    var curNode2 = l2
    var curResult
    var top = 0
    
    while(curNode1 && curNode2) {
        var sum = curNode1.val + curNode2.val + top
        var remainder = sum % 10
        if (!startNode) {
            curResult = startNode = new ListNode(remainder)
        } else {
            curResult.next = new ListNode(remainder)
            curResult = curResult.next
        }
        top = (sum - remainder) / 10
        curNode1 = curNode1.next
        curNode2 = curNode2.next
    }
    if (curNode1 || curNode2) {
        var curNode = curNode1 ? curNode1 : curNode2
        while(curNode) {
            curResult.next = new ListNode((top + curNode.val)%10)
            curResult = curResult.next
            top = Math.floor((top + curNode.val)/10)
            curNode = curNode.next
        }
    } 
    if (top) {
        curResult.next = new ListNode(top)
    }
    return startNode
};

1.两数之和 简单

思路 哈希表

我的解答

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(nums, target) {
    var obj = {}
    for(var i = 0, len = nums.length; i < len; i++) {
        if (obj[target - nums[i]] !== undefined) {
            return [obj[target - nums[i]], i]
        } else {
            obj[nums[i]] = i
        }
    }
};

242.有效的字母异位词 简单

题目

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例 1:

输入: s = "anagram", t = "nagaram" 输出: true 示例 2:

输入: s = "rat", t = "car" 输出: false 说明: 你可以假设字符串只包含小写字母。

进阶: 如果输入字符串包含 unicode 字符怎么办?你能否调整你的解法来应对这种情况?

思路一

排序后看是否相等 O(nlogn)

思路二

哈希表 O(n) 注意codePointAt和charCodeAt的区别 my.oschina.net/yqz/blog/89…

var isAnagram = function(s, t) {
    if (s.length !== t.length) {
        return false;
    }
    const table = new Array(26).fill(0);
    for (let i = 0; i < s.length; ++i) {
        table[s.codePointAt(i) - 'a'.codePointAt(0)]++;
    }
    for (let i = 0; i < t.length; ++i) {
        table[t.codePointAt(i) - 'a'.codePointAt(0)]--;
        if (table[t.codePointAt(i) - 'a'.codePointAt(0)] < 0) {
            return false;
        }
    }
    return true;
};

239.滑动窗口最大值 困难 TODO

思路 最小堆 更好的方法:单调队列

为了可以同时弹出队首和队尾的元素,我们需要使用双端队列。满足这种单调性的双端队列deque一般称作「单调队列」。

703.数据流中的第 K 大元素 简单 TODO

题目

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。

请实现 KthLargest 类:

KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。 int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

示例:

输入: ["KthLargest", "add", "add", "add", "add", "add"] [[3, [4, 5, 8, 2]], [3], [5], [10], [9], [4]] 输出: [null, 4, 5, 5, 8, 8]

解释: KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]); kthLargest.add(3); // return 4 kthLargest.add(5); // return 5 kthLargest.add(10); // return 5 kthLargest.add(9); // return 8 kthLargest.add(4); // return 8  

提示: 1 <= k <= 104 0 <= nums.length <= 104 -104 <= nums[i] <= 104 -104 <= val <= 104 最多调用 add 方法 104 次 题目数据保证,在查找第 k 大元素时,数组中至少有 k 个元素

思路 最小堆

225.用队列实现栈 简单 TODO

232.用栈实现队列 简单 TODO

20.有效的括号 简单

题目

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 注意空字符串可被认为是有效字符串。

示例 1:

输入: "()" 输出: true 示例 2:

输入: "()[]{}" 输出: true 示例 3:

输入: "(]" 输出: false 示例 4:

输入: "([)]" 输出: false 示例 5:

输入: "{[]}" 输出: true

思路

堆栈 hash

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function(s) {
    const obj = {
        ')': '(',
        ']': '[',
        '}': '{',
    };
    const len = s.length;
    const stack = [];
    for (let i = 0; i < len; i++) {
        const val = s[i];
        if (!obj[val]) {
            stack.push(val);
        } else if (stack.length === 0 || obj[val] !== stack.pop()) {
            return false;
        }
    }
    return stack.length === 0
};

142. 环形链表II 中等

题目

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

说明:不允许修改给定的链表。

进阶:

你是否可以使用 O(1) 空间解决此题?  

示例 1: 输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。

示例 2: 输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。

示例 3: 输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。  

提示:

链表中节点的数目范围在范围 [0, 104] 内, -105 <= Node.val <= 105, pos 的值为 -1 或者链表中的一个有效索引。

思路1

哈希表

思路2

快慢指针:在head处和相遇点同时释放相同速度且速度为1的指针,两指针必会在环入口处相遇

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    if (head === null || head.next === null) return null;
    let slow = head;
    let fast = head;
    do{
        if (fast === null || fast.next === null) return null;
        slow = slow.next;
        fast = fast.next.next;
    }while(slow !== fast);
    let p = head; // 在head处和相遇点同时释放相同速度且速度为1的指针,两指针必会在环入口处相遇
    while (p !== slow) {
        p = p.next;
        slow = slow.next;
    }
    return p;
};

141. 环形链表 简单

给定一个链表,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

如果链表中存在环,则返回 true 。 否则,返回 false 。

进阶:

你能用 O(1)(即,常量)内存解决此问题吗?

示例 1: 输入:head = [3,2,0,-4], pos = 1 输出:true 解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0 输出:true 解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1 输出:false 解释:链表中没有环。  

提示:

链表中节点的数目范围是 [0, 104], -105 <= Node.val <= 105, pos 为 -1 或者链表中的一个 有效索引 。

思路

哈希表:时间复杂度O(n),空间复杂度O(n)

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    const map = new Map();
    let node = head;
    while(node) {
        if (map.has(node)) return true;
        map.set(node, true);
        node = node.next;
    }
    return false;
};

参考的思路:快慢指针:时间复杂度O(n),空间复杂度O(1)

本方法需要读者对「Floyd 判圈算法」(又称龟兔赛跑算法)有所了解。

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function(head) {
    if (head === null || head.next === null) return false;
    let slow = head;
    let fast = head.next;
    while(slow !== fast) {
        if (fast === null || fast.next === null) return false;
        slow = slow.next;
        fast = fast.next.next;
    }
    return true;
};

24. 两两交换链表中的节点 中等

题目

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1: 输入:head = [1,2,3,4] 输出:[2,1,4,3] 示例 2:

输入:head = [] 输出:[] 示例 3:

输入:head = [1] 输出:[1]  

提示:

链表中节点的数目在范围 [0, 100] 内 0 <= Node.val <= 100  

进阶:你能在不修改链表节点值的情况下解决这个问题吗?(也就是说,仅修改节点本身。)

思路

迭代

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function(head) {
    let prev = new ListNode(0);
    prev.next = head;
    let cur = prev;
    // cur cur.next cur.next.next
    // cur cur.next.next cur.next
    while(cur.next && cur.next.next) {
        const next = cur.next;
        const nextNext = cur.next.next;
        cur.next = nextNext;
        next.next = nextNext.next;
        nextNext.next = next;
        cur = next;
    }
    return prev.next;
};

递归

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function(head) {
    if (!head || !head.next) return head;
    const newHead = head.next;
    head.next = swapPairs(newHead.next);
    newHead.next = head;
    return newHead;
};

206.反转链表 简单

题目

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 进阶: 你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

思路

迭代:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    if (!head || !head.next) return head;
    let pre = null;
    let cur = head; 
    while(cur) {
        const next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    return pre;
};

递归:略复杂

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    if (!head || !head.next) return head;
    const newHead = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return newHead;
};

212.单词搜索 II 困难

给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words,找出所有同时在二维网格和字典中出现的单词。

单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。

示例 1: 输入:board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"] 输出:["eat","oath"]

示例 2:

输入:board = [["a","b"],["c","d"]], words = ["abcb"] 输出:[]  

提示:

m == board.length n == board[i].length 1 <= m, n <= 12 board[i][j] 是一个小写英文字母 1 <= words.length <= 3 * 104 1 <= words[i].length <= 10 words[i] 由小写英文字母组成 words 中的所有字符串互不相同

思路

方法一:dfs 可以对比(79. 单词搜索 中等)

方法二:Trie树 先把要搜索的数组放在一个Trie树里,然后再进行dfs(此版代码效率比较低,待优化)

/**
 * @param {character[][]} board
 * @param {string[]} words
 * @return {string[]}
 */
var findWords = function(board, words) {
    var Trie = function() {
        this.root = {};
    };
    Trie.prototype.insert = function(word) {
        let node = this.root;
        for (const c of word) {
            if (!node[c]) node[c] = {};
            node = node[c];
        }
        node.isWord = true; // 记录到该节点是否是一个word
    };
    var trie = new Trie();
    for (const word of words) {
        trie.insert(word);
    }

    const result = [];
    const resultObj = {};
    const m = board.length, n = board[0].length;
    const dx = [-1, 1, 0, 0];
    const dy = [0, 0, -1, 1];
    const dfs = (board, i, j, curWord, curDict) => {
        curWord += board[i][j];
        curDict = curDict[board[i][j]];
        if (curDict.isWord) {
            if (!resultObj[curWord]) { // 去重
                result.push(curWord);
                resultObj[curWord] = true;
            }
        }

        const tmp = board[i][j];
        board[i][j] = '@';  // 暂存
        for (let k = 0; k < 4; k++) {
            const x = i + dx[k];
            const y = j + dy[k];
            if (x >= 0 && x < m && y >= 0 && y < n && board[x][y] !== '@' && curDict[board[x][y]]) {
                dfs(board, x, y, curWord, curDict);
            }
        }
        board[i][j] = tmp; // 恢复
    }
    
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (trie.root[board[i][j]]){
                dfs(board, i, j, '', trie.root);
            }
        }
    }

    return result;
};

208.实现 Trie (前缀树) 中等

实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

示例: Trie trie = new Trie(); trie.insert("apple"); trie.search("apple"); // 返回 true trie.search("app"); // 返回 false trie.startsWith("app"); // 返回 true trie.insert("app");
trie.search("app"); // 返回 true

说明:

你可以假设所有的输入都是由小写字母 a-z 构成的。 保证所有输入均为非空字符串。

思路

Trie树:根节点不包含任何字符,除根节点外每一个节点都只包含一个字符;从根节点到某一节点,路径上经过的字符拼接起来,为该节点对应的字符串;每个节点的所有子节点包含的字符都不相同。

/**
 * Initialize your data structure here.
 */
var Trie = function() {
    this.root = {};
};

/**
 * Inserts a word into the trie. 
 * @param {string} word
 * @return {void}
 */
Trie.prototype.insert = function(word) {
    let node = this.root;
    for (const c of word) {
        if (!node[c]) node[c] = {};
        node = node[c];
    }
    node.isWord = true; // 记录到该节点是否是一个word
};

/**
 * Returns if the word is in the trie. 
 * @param {string} word
 * @return {boolean}
 */
Trie.prototype.search = function(word) {
    let node = this.root;
    for (const c of word) {
        if (!node[c]) return false;
        node = node[c];
    }
    return !!node.isWord;
};

/**
 * Returns if there is any word in the trie that starts with the given prefix. 
 * @param {string} prefix
 * @return {boolean}
 */
Trie.prototype.startsWith = function(prefix) {
    let node = this.root;
    for (const c of prefix) {
        if (!node[c]) return false;
        node = node[c];
    }
    return true;
};

/**
 * Your Trie object will be instantiated and called as such:
 * var obj = new Trie()
 * obj.insert(word)
 * var param_2 = obj.search(word)
 * var param_3 = obj.startsWith(prefix)
 */

69.x的平方根 简单

实现 int sqrt(int x) 函数。

计算并返回 x 的平方根,其中 x 是非负整数。

由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

示例 1:

输入: 4 输出: 2 示例 2:

输入: 8 输出: 2 说明: 8 的平方根是 2.82842...,   由于返回类型是整数,小数部分将被舍去。

思路 二分查找

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function(x) {
    if (x === 0 || x === 1) return x;
    let left = 1, right = (x >> 1) + 1;
    while(left <= right) {
        const mid = parseInt(left + (right - left) / 2);
        const square = mid ** 2;
        if (x === square) {
            return mid;
        } else if (square < x) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return right;
};

参考的思路

/**
 * @param {number} x
 * @return {number}
 */
var mySqrt = function(x) {
    // 解法二 数学方法 牛顿迭代 公式 r = ( r + x / r ) / 2
    let r = x

    while (r ** 2 > x) r = ((r + x / r) / 2) | 0

    return r
};

37.解数独 困难

题目

编写一个程序,通过填充空格来解决数独问题。

一个数独的解法需遵循如下规则:

数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 空白格用 '.' 表示。

一个数独

答案被标成红色。

提示:

给定的数独序列只包含数字 1-9 和字符 '.' 。 你可以假设给定的数独只有唯一解。 给定数独永远是 9x9 形式的。

思路

递归 还没有写代码

36.有效的数独 中等

题目

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。 数字 1-9 在每一列只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

上图是一个部分填充的有效的数独。

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入: [ ["5","3",".",".","7",".",".",".","."], ["6",".",".","1","9","5",".",".","."], [".","9","8",".",".",".",".","6","."], ["8",".",".",".","6",".",".",".","3"], ["4",".",".","8",".","3",".",".","1"], ["7",".",".",".","2",".",".",".","6"], [".","6",".",".",".",".","2","8","."], [".",".",".","4","1","9",".",".","5"], [".",".",".",".","8",".",".","7","9"] ] 输出: true

示例 2: 输入: [   ["8","3",".",".","7",".",".",".","."],   ["6",".",".","1","9","5",".",".","."],   [".","9","8",".",".",".",".","6","."],   ["8",".",".",".","6",".",".",".","3"],   ["4",".",".","8",".","3",".",".","1"],   ["7",".",".",".","2",".",".",".","6"],   [".","6",".",".",".",".","2","8","."],   [".",".",".","4","1","9",".",".","5"],   [".",".",".",".","8",".",".","7","9"] ] 输出: false 解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。 但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。

说明: 一个有效的数独(部分已被填充)不一定是可解的。 只需要根据以上规则,验证已经填入的数字是否有效即可。 给定数独序列只包含数字 1-9 和字符 '.' 。 给定数独永远是 9x9 形式的。

思路

遍历三层

/**
 * @param {character[][]} board
 * @return {boolean}
 */
var isValidSudoku = function(board) {
    const isValid = (row, col, val) => {
        for (let i = 0; i < 9; i++) {
            if (board[row][i] === val && i !== col) return false;
            if (board[i][col] === val && i !== row) return false;
            const curSquareRow = parseInt(row / 3) * 3 + parseInt(i / 3);
            const curSquareCol = parseInt(col / 3) * 3 + parseInt(i % 3);
            if (board[curSquareRow][curSquareCol] === val && (curSquareRow !== row || curSquareCol !== col)) {
                return false;
            } 
        }
        return true;
    };
    for (let row = 0; row < 9; row++) {
        for (let col = 0; col < 9; col++) {
            const val = board[row][col];
            if (val !== '.') {
                const isValValid = isValid(row, col, val);
                if (!isValValid) return false;
            }
        }
    }
    return true;
};

遍历一遍 时间复杂度:O(1),因为我们只对 81 个单元格进行了一次迭代。 空间复杂度:O(1)。

/**
 * @param {character[][]} board
 * @return {boolean}
 */
var isValidSudoku = function(board) {
    const rows = [];
    const columns = [];
    const boxes = [];

    for (let row = 0; row < 9; row++) {
        for (let col = 0; col < 9; col++) {
            const val = board[row][col];
            if (val === '.') continue;
            // console.log(val, row, col, rows, columns, boxes)

            if (!rows[row]) rows[row] = {};
            if (rows[row][val]) return false;
            rows[row][val] = 1;

            if (!columns[col]) columns[col] = {};
            if (columns[col][val]) return false;
            columns[col][val] = 1;
            
            const boxIndex = parseInt(row / 3) * 3 + parseInt(col / 3);
            if (!boxes[boxIndex]) boxes[boxIndex] = {};
            if (boxes[boxIndex][val]) return false;
            boxes[boxIndex][val] = 1;
        }
    } 
    return true;
};

52.N皇后II 困难 TODO

基于回溯 基于位运算

51.N皇后 困难

题目

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

输入:n = 4 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。

思路: 回溯 剪枝

时间复杂度:O(N!) ,空间复杂度:O(N)

/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function(n) {
    if (n < 1) return [];
    const col = new Map(); // 列是否会被攻击
    const pie = new Map(); // 撇是否会被攻击
    const na = new Map(); // 捺是否会被攻击
    const result = [];
    const dfs = (row, curResult) => {
        if (row === n) {
            result.push(curResult);
            return;
        }
        for (let j = 0; j < n; j++) {
            if (col.has(j) || pie.has(row + j) || na.has(row - j) ) {
                continue;
            }
            col.set(j, 1);
            pie.set(row + j, 1);
            na.set(row - j, 1);
            dfs(row + 1, curResult.concat(j)); // 递归
            col.delete(j); // 记得删除
            pie.delete(row + j);
            na.delete(row - j);
        }
    };
    dfs(0, []);
    for (let i = 0, len = result.length; i < len; i++) {
        for(let j = 0; j < n; j++) {
            const val = result[i][j];
            result[i][j] = '.'.repeat(val) + 'Q' + '.'.repeat(n - val - 1);
        }
    }
    return result;

};

思路: 位运算 TODO

22.括号生成 中等

题目

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 示例: 输入:n = 3 输出:[ "((()))", "(()())", "(())()", "()(())", "()()()" ]

解题

/**
 * @param {number} n
 * @return {string[]}
 */
var generateParenthesis = function(n) {
    const result = [];
    const helper = (str, startnum, endnum) => {
        // console.log(startnum, endnum, str);
        if (endnum === 0) {
            result.push(str);
            return;
        }
        if (startnum === 0) {
            result.push(str + ')'.repeat(endnum));
            return;
        }
        if (startnum === endnum) {
            helper(`${str}(`, startnum - 1, endnum);
            return;
        }
        helper(`${str}(`, startnum - 1, endnum);
        helper(`${str})`, startnum, endnum - 1);
    }
    helper('', n, n);
    return result;
};

111.二叉树的最小深度 简单

题目

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明:叶子节点是指没有子节点的节点。

思路

BFS(如果是叶子节点就更新最小深度)

var minDepth = function(root) {
    if (!root) return 0;
    const queue = [root];
    let depth = 1;
    while(queue.length) {
        const len = queue.length;
        for (let i = 0; i < len; i++) {
            const node = queue.shift();
            if (!node.left && !node.right) return depth;
            node.left && queue.push(node.left);
            node.right && queue.push(node.right);
        }
        depth += 1;
    }
    return depth;
};

DFS

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function(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));
};

104.二叉树的最大深度 简单

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

示例: 给定二叉树 [3,9,20,null,null,15,7],返回它的最大深度 3 。

思路

方法1 递归

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    if (!root) return 0;
    return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
};

方法2 BFS

var maxDepth = function(root) { // BFS
    if (!root) return 0;
    let depth = 0;
    const nodes = [root]; // 队列
    while(nodes.length > 0) {
        const len = nodes.length;
        for (let i = 0; i < len; i++) {
            const node = nodes.shift(); // 获取节点
            node.left && nodes.push(node.left);
            node.right && nodes.push(node.right);
        }
        depth += 1;
    }
    return depth;
};

方法3 DFS

102.二叉树的层序遍历 中等

题目

给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 示例: 二叉树:[3,9,20,null,null,15,7],

返回其层序遍历结果:

[ [3], [9,20], [15,7] ]

我的思路 BFS 广度优先搜索

我的第一次提交,用时只超过了33%

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function(root) {
    if (!root) return [];
    const result = [];
    let n = 0;
    let nodes = [root];
    while(nodes.length > 0) {
        result.push([]);
        const len = nodes.length;
        for(let i = 0; i < len; i++) {
            const node = nodes.shift();
            result[n].push(node.val);
            node.left && nodes.push(node.left);
            node.right && nodes.push(node.right);
        }
        n += 1;
    }
    return result;
};

进行了一点修改后, 超过了93%

var levelOrder = function(root) {
    if (!root) return [];
    const result = [];
    let nodes = [root];
    while(nodes.length > 0) {
        const currentLevel = []; // 定义currentLevel
        const len = nodes.length;
        for(let i = 0; i < len; i++) {
            const node = nodes.shift();
            currentLevel.push(node.val);
            node.left && nodes.push(node.left);
            node.right && nodes.push(node.right);
        }
        result.push(currentLevel);
    }
    return result;
};

参考的思路

深度优先搜索DFS

var levelOrder = function(root) {
    if (!root) return [];
    const result = [];
    const helper = (root, level) => {
        if (!root) return;
        // if (!result[level]) result[level] = []; // 这行击败15%
        if (result.length < level + 1) result.push([]); // 这行击败97%
        result[level].push(root.val);
        helper(root.left, level + 1);
        helper(root.right, level + 1);
    }
    helper(root, 0);
    return result;
};

122.买卖股票的最佳时机II 简单

题目

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [7,1,5,3,6,4] 输出: 7 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。   随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 示例 2:

输入: [1,2,3,4,5] 输出: 4 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。   注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。   因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 示例 3:

输入: [7,6,4,3,1] 输出: 0 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。  

提示:

1 <= prices.length <= 3 * 10 ^ 4 0 <= prices[i] <= 10 ^ 4

参考的思路

贪心法: 时间复杂度O(n),空间复杂度O(1)

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function(prices) {
    const len = prices.length;
    let result = 0;
    for(let i = 1; i < len; i++) {
        const subtract = prices[i] - prices[i - 1];
        
        if (subtract > 0) {
            result += subtract;
        }
    }
    return result;
};

动态规划:(0表示当天手里没有股票,1表示当天手里有股票),时间复杂度O(n),空间复杂度O(n)

var maxProfit = function(prices) {
    const n = prices.length;
    let dp0 = 0, dp1 = -prices[0];
    for (let i = 1; i < n; ++i) {
        let newDp0 = Math.max(dp0, dp1 + prices[i]);
        let newDp1 = Math.max(dp1, dp0 - prices[i]);
        dp0 = newDp0;
        dp1 = newDp1;
    }
    return dp0;
};

169.多数元素/求众数 Majority 简单

题目

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入:[3,2,3] 输出:3 示例 2:

输入:[2,2,1,1,1,2,2] 输出:2

进阶:

尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

我的思路

遍历一遍,记录在hash表里,时间复杂度为 O(n)、空间复杂度为 O(n)

/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function(nums) {
    const obj = {};
    for (let i = 0, len = nums.length; i < len; i++) {
        obj[nums[i]] = obj[nums[i]] ? obj[nums[i]] + 1 : 1;
        if (obj[nums[i]] > len / 2) {
            return nums[i];
        }
    }
    return
};

参考的思路

Boyer-Moore 投票算法,时间复杂度为 O(n)、空间复杂度为 O(1)

/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function(nums) {
    let x = 0
    let m = 0
    for(let n of nums){
        if(m === 0) x = n
        m += (x === n ? 1 : -1)
    }
    return x
};

其它思路

排序以后取中间的数 O(nlogn)

分治O(nlogn)

50.Pow(x, n) 中等

1.题目

实现 pow(x, n) ,即计算 x 的 n 次幂函数。

示例 1:

输入: 2.00000, 10 输出: 1024.00000

示例 2:

输入: 2.10000, 3 输出: 9.26100

示例 3:

输入: 2.00000, -2 输出: 0.25000 解释: 2-2 = 1/22 = 1/4 = 0.25

说明:

-100.0 < x < 100.0 n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。

2.参考的思路

方法1:使用递归,时间复杂度O(logn)

/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
var myPow = function(x, n) {
    if(!n) return 1;
    if (n < 0) {
        return 1 / myPow(x, -n);
    }
    if (n % 2) {
        return x * myPow(x, n - 1);
    }
    return myPow(x*x, n / 2);
};

方法二: 使用迭代,时间复杂度O(logn)

/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
var myPow = function(x, n) {
    if (n < 0) {
        x = 1 / x;
        n = -n;
    }
    let pow = 1;
    while (n) {
        if (n & 1) {
            pow *= x;
        }
        x *= x;
        n = n >>> 1; // n为2147483648的情况下不能直接有符号右移,因为右移会将操作数置成32位,越界。可用无符号右移动,对于非负数,有符号右移和无符号右移总是返回相同的结果。
    }
    return pow;
};

98. 验证二叉搜索树 中等

1.题目:

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

节点的左子树只包含小于当前节点的数。 节点的右子树只包含大于当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。

2.自己的思路:

使用中序遍历,为了提高内存利用率,可以只保存prev的值(最近访问的节点的值),时间复杂度O(n)

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isValidBST = function(root) {
    let prev = -Infinity
    function isValid(root) {
        if (!root) return true
        if (!isValid(root.left)) {
            return false
        }
        if (root.val <= prev) {
            return false
        }
        prev = root.val
        return isValid(root.right)
    }
    return isValid(root)
};

3.参考的思路:

使用递归, ,时间复杂度O(n)

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isValidBST = function(root) {
    function isValid(root, min, max) {
        if (!root) return true;
        if (root.val <= min || root.val >= max) return false;
        return isValid(root.left, min, root.val) && isValid(root.right, root.val, max);
    }
    return isValid(root, -Infinity, Infinity)
};

236.二叉树的最近公共祖先(中等)Lowest Common Ancestor of a Binary Tree (LAC)

1.题目:

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]

2.自己的思路:

遍历一边二叉树,得到所有节点的父节点;找到p和q的所有父节点;看父节点的交叉。未进行编码

3.参考的思路:

使用递归,时间复杂度O(N),找到左右子树里是否有p或q,只有一个有,就等于该子树的LCA,如果两个都有,则LCA等于root

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function(root, p, q) {
    if (!root) return null;
    if (root === p || root === q) return root; // 如果root是p或者q,说明当前树的LCA就是root,返回root
    const left = lowestCommonAncestor(root.left, p, q); // 找到左子数的LCA(是否为null等价于是否包含P或Q)
    const right = lowestCommonAncestor(root.right, p, q); // 找到右子树的LCA(是否为null等价于是否包含P或Q)
    if (!left) return right; // 如果左子树LCA为null(没有p且没有q),那就等价于右子树的LCA
    if (!right) return left; // 如果左子树LCA非null(有p或者Q),右子树LCA为null(没有p且没有q),那就等价于左子树的LCA
    return root // 如果左右子树都非null(有p或者Q),则LCA为root
};

235.二叉搜索树的最近公共祖先(简单)

1.题目:

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]

2.自己的思路:

根据大小值递归,已进行编码

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function(root, p, q) {
    let smaller = p.val > q.val ? q.val : p.val;
    let bigger =  p.val > q.val ? p.val : q.val;
    if (root.val >= smaller && root.val <= bigger) return root; // 如果跟root比一个大一个小,那LCA就是root
    if (root.val > bigger) return lowestCommonAncestor(root.left, p, q);
    if (root.val < smaller) return lowestCommonAncestor(root.right, p, q);
};

3.参考的思路:

第一种方法是236题的递归,与我自己的实现基本一致

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function(root, p, q) {
    if (p.val < root.val && q.val < root.val) 
        return lowestCommonAncestor(root.left, p, q);
    if (p.val > root.val && q.val > root.val)
        return lowestCommonAncestor(root.right, p, q);
    return root;
};

第二种方法是不用递归,用while循环,也可以实现

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function(root, p, q) {
    while (root) {
        if (p.val < root.val && q.val < root.val) {
            root = root.left;
        } else if (p.val > root.val && q.val > root.val) {
            root = root.right;
        } else {
            return root;
        }
    }
};

使用continue:

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function(root, p, q) {
    while (root) {
        if (p.val < root.val && q.val < root.val) {
            root = root.left;
            continue;
        }
        if (p.val > root.val && q.val > root.val) {
            root = root.right;
            continue;
        } 
        return root;
    }
};