以下代码全部是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;
}
};