栈
20 有效括号
使用栈模拟。
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function (s) {
const n = s.length;
if(n%2===1){
return false;
}
const arr = [];
for(let ch of s){
if(ch==='{'||ch==='('||ch==='['){
arr.push(ch);
}else{
if(arr.length===0){
return false;
}else{
const match = getMatch(ch);
const tmp = arr.pop();
if(tmp!==match){
return false;
}
}
}
}
if(arr.length>0){
return false;
}
return true;
};
function getMatch(ch){
if(ch==='}'){
return '{'
}else if(ch===')'){
return '('
}else{
return '['
}
}
链表
创建链表的方法:
function create(arr) {
if (arr.length == 0) {
return null;
}
let head = {
val: arr[0],
next: null
};
let cur = head;
for (let i = 1; i < arr.length; i++) {
cur.next = {
val: arr[i],
next: null
};
cur = cur.next;
}
return head;
}
206 反转链表
定义了三个指针,根据 cur 的值,依次向后移动三个指针。
/**
* 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===null){
return null;
}
let pre = null;
let cur = head;
while(cur!==null){
let next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
};
2 两数相加
方法一:利用数字相加然后转换为链表返回
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function(l1, l2) {
let s1 = JSON.stringify(l1).match(/\d/g).reverse().join(''),
s2 = JSON.stringify(l2).match(/\d/g).reverse().join('')
sum = BigInt(s1)+BigInt(s2)
return [...sum.toString()].reduce((acc,v)=>{return {val: v, next: acc}}, null)
};
方法二:遍历 l1 和 l2, 生成新的链表。
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function (l1, l2) {
let sum = 0;
let index = 0;
let head = null;
let cur = {
val: 0,
next: null
};
while (l1 != null || l2 != null || sum > 0) {
sum += (l1 != null ? l1.val : 0) + (l2 != null ? l2.val : 0);
let curVal = 0;
if (sum >= 10) {
curVal = sum % 10;
sum = parseInt(sum / 10);
} else {
curVal = sum;
sum = 0;
}
cur.val = curVal;
cur.next = {
val: 0,
next: null
}
if (index === 0) {
head = cur;
}
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
if(l1 != null || l2 != null || sum > 0){
cur = cur.next;
}
index++;
}
cur.next = null;
return head;
};
203 移除链表中的元素
虚拟节点法:
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
let pirot = {val: 0, next: head};
let pre = pirot;
while(head!=null){
if(head.val===val){
pre.next = head.next;
head = head.next;
}else{
pre = head;
head = head.next;
}
}
return pirot.next;
};
数组
704 二分查找
var search = function(nums, target) {
let start = 0;
let end = nums.length-1;
while(end>=start){
const pirot = start + parseInt((end - start)/2);
if(nums[pirot]>target){
end = pirot-1;
}else if(nums[pirot]<target){
start = pirot+1;
}else{
return pirot;
}
}
return -1;
};
283 移动零
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var moveZeroes = function(nums) {
let key = 0;
for(let i=0;i<nums.length;i++){
if(nums[i]!=0){
if( i!= key ){
let tmp = nums[key];
nums[key] = nums[i];
nums[i] = tmp;
}
key++;
}
}
};
27 移除元素
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) {
let i = 0;
while(i<nums.length){
const tmp = nums[i];
if(tmp==val){
nums.splice(i, 1);
}else{
i++;
}
}
return nums.length;
};
75 颜色分类
//方法一
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var sortColors = function(nums) {
let count0 = 0;
let count1 = 0;
let count2 = 0;
for(let i=0;i<nums.length;i++){
const tmp = nums[i];
if(tmp==0){
count0++;
}else if(tmp==1){
count1++;
}else{
count2++;
}
}
for(let i=0;i<count0;i++){
nums[i] = 0;
}
for(let i=count0;i<count0+count1;i++){
nums[i] = 1;
}
for(let i=count0+count1;i<count0+count1+count2;i++){
nums[i] = 2;
}
};
11 盛最多水的容器
可以使用双指针的解法。使用两个指针,一个在数组的最左边,一个在最右边。对于这道题目而言,怎么移动指针呢?只要移动数值最小的那个指针即可。为什么要移动最小的指针呢?因为题目要找的是盛最多水的容器。移动较大的指针就会发现之后的容器只会变小不会变大,所以要移动值较小的那个指针哦。
/**
* @param {number[]} height
* @return {number}
*/
var maxArea = function(height) {
let l = 0;
let r = height.length-1;
let res = 0;
while(l<r){
let tmpl = height[l];
let tmpr = height[r];
let area = (r-l)*Math.min(tmpl, tmpr);
if(tmpr<tmpl){
r--;
}else{
l++;
}
res = Math.max(res, area);
}
return res;
};
88 215 167
3 无重复字符的最长子串
这里使用了滑动窗口的思想。因为题目要找的是连续子串,所以可以使用滑动窗口的方法。窗口左边一个变量右边一个变量。set 用来记录已经有的字符。首先,我们不断移动右边的指针,直到遇到set中已经存在的字符串或者到达最右边。这个时候要移动左边的指针;每次移动左边或者右边的时候,更新变量 res,保证它能及时更新到最大值。
滑动窗口的关键在于什么时候移动窗口的左边指针和右边指针。
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
let l = 0;
let r = -1;
let res = 0;
let set = new Set();
while(l<s.length){
if(r+1<s.length && !set.has(s.charAt(r+1))){
r++;
set.add(s.charAt(r));
}else{
set.delete(s.charAt(l));
l++;
}
res = Math.max(res, r-l+1);
}
return res;
}
动态规划
将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案。
题目
70 爬楼梯
//搜索记忆法,自顶向下解决问题
/**
* @param {number} n
* @return {number}
*/
let arr;
var climbStairs = function(n) {
arr = new Array(n+1);
return calc(n);
};
function calc(n){
if(n==1){
arr[1] = 1;
return 1;
}
if(n==2){
arr[2] = 2;
return 2;
}
if(arr[n]==undefined){
arr[n] = calc(n-1) + calc(n-2);
}
return arr[n];
}
//动态规划自底向上
/**
* @param {number} n
* @return {number}
*/
var climbStairs = function(n) {
let arr = new Array(n+1);
arr[1] = 1;
arr[2] = 2;
for(let i=3;i<=n;i++){
arr[i] = arr[i-1] + arr[i-2];
}
return arr[n];
};
343 整数拆分
将任一整数 n 拆分成 i 和 n-i; 保持 i 不变,动态去找 n-i 的最大值。
/**
* @param {number} n
* @return {number}
*/
let arr;
function calc(n){
if(n==1){
return 1;
}
if(arr[n]!=undefined){
return arr[n];
}
let res = -1;
for(let i=1; i<n;i++){
res = Math.max(res, i*(n-i), calc(i) * calc(n-i));
}
arr[n] = res;
return res;
}
var integerBreak = function(n) {
arr = new Array(n+1);
return calc(n);
};
/**
* @param {number} n
* @return {number}
*/
var integerBreak = function(n) {
let arr = new Array(n+1);
arr[1] = 1;
arr[2] = 1;
for(let i=2; i<=n;i++){
if(arr[i]==undefined){
arr[i] = -1;
}
for(let j=1; j<i; j++){
//i = j+ (i-j)
arr[i] = Math.max(arr[i], j*(i-j), j* arr[i-j])
}
}
return arr[n];
};
279
91
62
63
剑指 Offer 42. 连续子数组的最大和
/**
* @param {number[]} nums
* @return {number}
*/
var maxSubArray = function(nums) {
let res = nums[0];
for(let i=1; i< nums.length;i++){
nums[i] += Math.max(nums[i-1], 0);
res = Math.max(res, nums[i]);
}
return res;
};
二叉树
104 二叉树的最大深度
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function(root) {
if(root==null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right))+1;
};
111 二叉树的最低深度
/**
* @param {TreeNode} root
* @return {number}
*/
var minDepth = function(root) {
if(root===null) return 0;
if(root.left===null&&root.right===null) return 1;
let min_depth = Number.MAX_VALUE;
if(root.left){
min_depth = Math.min(min_depth, minDepth(root.left))
}
if(root.right){
min_depth = Math.min(min_depth, minDepth(root.right))
}
return min_depth+1;
};
226 翻转二叉树
/**
* @param {TreeNode} root
* @return {TreeNode}
*/
var invertTree = function(root) {
if(root==null){
return null;
}
let leftTree = invertTree(root.left);
let rightTree = invertTree(root.right);
root.left = rightTree;
root.right = leftTree;
return root;
};
100 相同的树
/**
* @param {TreeNode} p
* @param {TreeNode} q
* @return {boolean}
*/
var isSameTree = function(p, q) {
if(p==null&&q==null){
return true;
}
if(p!=null&&q!=null){
if(p.val==q.val){
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}else{
return false;
}
}
return false;
};
101 对称二叉树
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isSymmetric = function(root) {
return check(root, root);
};
function check(p, q){
if(p==null&&q==null){
return true;
}
if(p==null||q==null){
return false;
}
return p.val==q.val && check(p.left, q.right)&&check(p.right, q.left);
}
222 完全二叉树的节点个数
/**
* @param {TreeNode} root
* @return {number}
*/
var countNodes = function(root) {
if(root==null){
return 0;
}else{
return 1 + countNodes(root.left)+ countNodes(root.right);
}
};
110 平衡二叉树
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isBalanced = function(root) {
if(root==null){
return true;
}
return Math.abs(getHeight(root.left) - getHeight(root.right))<2 &&
isBalanced(root.left) && isBalanced(root.right);
};
function getHeight(root){
if(root==null){
return 0;
}
return Math.max(getHeight(root.left), getHeight(root.right)) +1;
}
112 路径总和
/**
* @param {TreeNode} root
* @param {number} sum
* @return {boolean}
*/
var hasPathSum = function(root, sum) {
if(root==null){
return false;
}
if(root.left==null&&root.right==null){
return sum-root.val ==0;
}
return hasPathSum(root.left, sum-root.val)||hasPathSum(root.right, sum-root.val);
};
404 左叶子之和
var sumOfLeftLeaves = function(root) {
if(root==null){
return 0;
}
let count = 0;
if(root.left){
if(root.left.left==null&&root.left.right==null){
count += root.left.val;
}else{
count += sumOfLeftLeaves(root.left);
}
}
if(root.right){
count += sumOfLeftLeaves(root.right);
}
return count;
};
递归算法实现,需要确定递归的终止条件。
257
113
129
排序
冒泡排序
function bubbleSort(arr){
const l = arr.length;
for(let i=0; i<l; i++){
for(let j=0; j<l-i-1; j++){
//相邻元素两两比较
if(arr[j]>arr[j+1]){
let tmp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tmp;
}
}
}
}
快速排序
function quickSort(arr){
if(arr.length===0) return [];
const tmp = arr.pop();
const left = [];
const right = [];
arr.forEach(item=>{
if(item>tmp){
right.push(item);
}else{
left.push(item);
}
})
return quickSort(left).concat(tmp, quickSort(right));
}
斐波那契数列
什么是斐波那契数列,1,1,2,3,5,8,13...这样一个数列就是斐波那契数列,求第n项的值。变种题目:爬楼梯。
递归
我们很容易写出下面的代码:
function Fibonacci(n){
if(n===1||n===2){
return 1;
}
return Fibonacci(n-1) + Fibonacci(n-2);
}
上面的代码看似完美,在变量 n 比较小的时候,计算还不成问题。但是我的电脑实验的 n >=50
的时候计算时间就很长了(大概是 98秒)。
为什么呢?我们要分析一下上述算法的时间复杂度了。时间复杂度是什么呢?可以简单理解为程序执行的次数。那么,上述算法的时间复杂度(执行次数)怎么计算呢?我们以计算 F(6) 为例分析:
第n层节点个数: 个
前n层节点个数:1+2+4+……+ = 2ⁿ⁺¹-1
F(6)有4层,推理:F(n) 有 n-2层
则 F(n) 一共有节点数:
计算时间复杂度规则:不看常数,不看系数,只看最高次数项
因此:O(F(n)) = O()
可以推算出这种算法的时间复杂度是 O(), 空间复杂度是 O(n) (某一时刻,开辟的空间个数最多为 n )
www.pianshen.com/article/635…
blog.csdn.net/qq_43722079…
优化递归
仅仅使用暴力递归实现的问题是,比如计算 F(4) 要先计算 F(3) 和 F(2) ,而计算 F(3) 又要计算 F(2) 和 F(1),这就出现了重复计算的问题, F(2) 被计算了 2 次。所以效率比较低。那么怎么解决呢?我们可以使用一个数组存储每次计算的结果 F(1)、 F(2)、 F(3)... 这样可以大大减少计算的时间成本。
let arr;
function Fibonacci(n){
arr = new Array(n+1);
return getF(n);
}
function getF(n){
if(n===1||n===2){
arr[n] = 1;
}
if(arr[n]){
return arr[n];
}else{
arr[n] = getF(n-1) + getF(n-2);
}
return arr[n];
}
上述算法的时间复杂度降为 O(n), 同时空间复杂度是 O(n)。到这里继续反问下还可以继续优化吗? 答案是肯定的,请继续向下看。
循环实现
空间复杂度是 O(n) 还是可以继续优化的。那就是不要使用数组来存储所有的中间计算值,我们最后只需要知道 F(n-1) + F(n-2) 的值就可以了。那么代码可以优化如下:
function Fibonacci(n){
if(n===1||n===2){
return 1;
}
let a = 1;
let b = 1;
for(let i=3;i<=n;i++){
let tmp = a+b;
a = b;
b = tmp;
}
return b;
}
上述算法的时间复杂度为 O(n), 同时空间复杂度将为 O(1)。
Better late than never。