牛客算法题

数据结构与算法

合并两个有序数组

思路:A数组有足够的空间。三指针,一个指向A数组元素的末尾i,一个指向B数组元素的末尾j,一个指向A数组的最后。比较i和j指向的元素谁大,大的放在A数组最后K的位置上。然后跟着移动指针。

判断条件: while(k>=0&&i>=0&&j>=0)这里一定要等于0

代码:

public void merge(int A[], int m, int B[], int n) {
        if(m==0){
            for(int i=0; i<n; i++){
                A[i] = B[i];
            }
        }else {
            if(n==0)return;
            else{
                int i = m-1, j=n-1, k=m+n-1;
                while(k>=0 && i>=0 && j>=0){
                    if(A[i]>=B[j]){
                        A[k--] = A[i--];
                    }else{
                        A[k--] = B[j--];
                    }
                }
                //A的数组先结束。 
                if(i==-1){
                    for(int l=0; l<j+1; l++){
                        A[l] = B[l];
                    }
                }else{
                    return;
                } 
            }
        }
    }

合并区间

给出一组区间,请合并所有重叠的区间。请保证合并后的区间按区间起点升序排列。

输入:[[10,30],[20,60],[80,100],[150,180]]

返回:[[10,60],[80,100],[150,180]]

思路:先按照数组起点升序排序,如果第二个数组起点比第一个数组的终点大则合并。

function merge( intervals ) {
    // write code here
    if(intervals.length===0)return [];
    intervals.sort((a, b)=>a.start-b.start);
    let pre = intervals[0]
    let cur;
    let res = [];
    
    for(let i=1; i<intervals.length; i++){
        cur = intervals[i];
        if(cur.start <= pre.end){//如果有交集,合并区间
            pre.end = Math.max(cur.end, pre.end);
        }else{
            res.push(pre);
            pre = cur;
        }
    }
    res.push(pre)//将最后一组推进去
    return res;
}

合法的括号序列

思路:入栈,查看栈顶元素与当前将要入站的元素是否匹配,匹配则将栈顶stack[stack.length-1](JS没有isEmpty()函数,我恨)出栈。

function isValid( s ) {
    // write code here
    if(s.length%2!==0)return false;
    let stack = [];
    var c;
    for(var i=0; i<s.length; i++){
        c = s.charAt(i);
        if((c===')' && stack[stack.length-1]==='(') || (c===']' && stack[stack.length-1]==='[')
           ||(c==='}' && stack[stack.length-1]==='{')){
            stack.pop();
        }else{
            stack.push(c);
        }
    }
    return stack.length===0;
}
module.exports = {
    isValid : isValid
};

链表

链表中环的入口节点

/*
 * function ListNode(x){
 *   this.val = x;
 *   this.next = null;
 * }
 */

function detectCycle( head ) {
    // write code here
    if(head===null || head.next===null)return null;
    var slow = head;
    var fast = findMeet(head);
    if(fast===null){
        return null;
    }else{
        //找到相遇节点后,设置快慢指针都走一步
        while(slow!=fast){
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    } 
}
//找相遇的节点
function findMeet(head){
    if(head===null || head.next===null)return null;
    var slow = head;
    var fast = head;
    while(fast != null && fast.next != null){
        fast = fast.next.next;
        slow = slow.next;
        if(fast===slow)return fast;
    }
    return null;
}
module.exports = {
    detectCycle : detectCycle
};

删除链表倒数第n个结点

思路:双指针,快指针先走n-1步,如果块指针走到空了,说明要删除的是头结点,直接返回head.next

/*
 * function ListNode(x){
 *   this.val = x;
 *   this.next = null;
 * }
 */

/**
  * 
  * @return ListNode类
  */

function removeNthFromEnd( head ,  n ) {
    let slow = head, fast = head;
    while(n>0){
        fast = fast.next;
        n--;
    }
    if(!fast){
        slow = slow.next;
        return slow;
    }
    
    while(fast.next){
        slow = slow.next;
        fast = fast.next;
    }
    slow.next = slow.next.next;
    return head;
}

两个链表的公共结点

思路:A,B指针指向链表1和链表2, A走完链表1再从链表2的头部开始走, B走完链表2再从链表1的头部开始走,如果有相同结点就会相遇。

如果没有相同的结点,a,b走完了两个链表,则a==b==null,退出循环直接返回null。

function FindFirstCommonNode(pHead1, pHead2)
{
    // write code here
    let A = pHead1, B = pHead2;
    //如果没有相同的结点,a,b走完了两个链表,则a==b==null,退出循环直接返回null
    while(A!==B){
        不能用A.next判断,要先判断A不为空才能判断A.next
        A = (A=== null)? pHead2: A.next;
        B = (B=== null)? pHead1: B.next;
    }
    return A;
}

两个链表生成相加链表

9->3->7

6->3

生成:1->0->0->0

思路:先将两个链表进行逆转,再相加。最后生成的链表再逆转一次

/*
 * function ListNode(x){
 *   this.val = x;
 *   this.next = null;
 * }
 */

/**
 * @return ListNode类
 */

function addInList( head1 , head2 ) {
    if(!head1) return head2;
    if(!head2) return head1;

    let head = new ListNode(-1);//这里不要赋值为null,否则在return head.next可能会null.next
    let p = h1, q = h2, cur = head, carry=0,sum=0;
    while(p || q || carry !==0){
        sum = (p?p.val:0) + (q?q.val:0) + carry;
        cur.next = new ListNode(sum%10);
        carry = Math.floor(sum/10);
        
        cur = cur.next;
        p = p?p.next:null;
        q = q?q.next:null;
    }
    return reverse(head.next);
}

function reverse(node){
    if(!node)return node;
    let pre = null, cur = node;
    
    while(cur!==null){
        let temp = cur.next;
         cur.next = pre;
         pre =  cur;
         cur = temp;  
    }
    return pre;
}

单链表的排序

输入:1->3->2->4->5

输出:1->2->3->4->5

思路:先把所有节点放入数组,对数组排序,然后在原来的链表上修改值

function sortInList( head ) {
    // write code here
    let cur = head;
    let arr = []
    while(cur){
        arr.push(cur.val);
        cur = cur.next;
    }
    arr.sort((a,b)=>a-b)
    
    let res = head;
    let dummy = res;
    arr.forEach(item=>{
        res.val = item;
        res = res.next;
    })
    return dummy;
}

 合并两个排序的链表

function Merge(pHead1, pHead2){
    // write code here
    if(!pHead1)return pHead2;
    if(!pHead2)return pHead1;
    if(pHead1.val<=pHead2.val){
        pHead1.next = Merge(pHead1.next, pHead2);
        return pHead1;
    }else{
        pHead2.next = Merge(pHead1, pHead2.next);
        return pHead2;
    }
}

合并k个已排序的链表 

思路:将多个链表两两先合并(合并两个排序链表)。

function mergeKLists( lists ) {
    // write code here
    if(lists.length===0)return null;
    return mergeHelper(lists, 0, lists.length-1);
}

function mergeHelper(lists, lo, hi){
    if(hi===lo)return lists[lo];
    
    let mid = lo + Math.floor((hi - lo)/2);
    let l1 = mergeHelper(lists, lo, mid);
    let l2 = mergeHelper(lists, mid+1, hi);
    return merge2Lists(l1, l2);
}
function merge2Lists(l1, l2){
    if(!l1)return l2;
    if(!l2)return l1;
    if(l1.val <= l2.val){
        l1.next = merge2Lists(l1.next, l2);
        return l1;
    }else{
        l2.next = merge2Lists(l1, l2.next);
        return l2;
    }
}

链表内指定区间翻转 

思路:

  • 先局部翻转

  • 再将已经反转好的局部链表与其他节点建立连接,重构链表。

function reverseBetween( head ,  m ,  n ) {
    // write code here
    let dummy = new ListNode(-1);
    dummy.next = head;
    let pre = dummy, p1, p2;
    //走到m的前一个
    for(let i=0; i<m-1; i++){
        pre = pre.next;
    }
    p1 = pre.next;
    p2 = pre;
    //走到第n个
    for(let i=0; i<n-m+1; i++){
        p2 = p2.next;
    }
    //记下第n+1个用来后续连接。
    let rightNode = p2.next;
    reverse(p1, p2);
    pre.next = p2;
    p1.next = rightNode;
    return dummy.next;
}
function reverse(l1, l2){
    let pre = null;
    let cur = l1
    while(pre!==l2){
        let temp = cur.next;
        cur.next = pre; 
        pre = cur;
        cur = temp;
    }
}

二叉树

重建二叉树

给定前序和中序遍历的数组,重建二叉树

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */
function reConstructBinaryTree(pre, vin)
{
    // write code here
    if(!pre.length || !vin.length)return null;
    let curNode = new TreeNode(pre.shift());
    let index = vin.indexOf(curNode.val);
    curNode.left = reConstructBinaryTree(pre, vin.slice(0, index));
    curNode.right = reConstructBinaryTree(pre, vin.slice(index+1))
    
    return curNode;
}

二叉树的之字形层序遍历

思路:使用两个先进后出的栈,栈1先右子结点进,栈二先左子节点进。返回的是[[], [],[]]类型的数组。

/*
 * function TreeNode(x) {
 *   this.val = x;
 *   this.left = null;
 *   this.right = null;
 * }
 */

/**
  * 
  * @param root TreeNode类 
  * @return int整型二维数组
  */
function zigzagLevelOrder( root ) {
    // write code here
    if(root===null)return [];
    //栈1先右进,栈2先左进
    var stack1 = [root];
    var stack2 = [];
    var res = [];
    while(stack1.length!==0 || stack2.length!==0){
        var row = [];
        if(stack1.length!==0){
            while(stack1.length!=0){
                var cur = stack1.pop();
                row.push(cur.val);
                if(cur.left!=null){
                    stack2.push(cur.left);
                }
                if(cur.right!=null){
                    stack2.push(cur.right);
                }
            }
        }else{
            while(stack2.length>0){
                var cur = stack2.pop();
                row.push(cur.val);
                if(cur.right!=null){
                    stack1.push(cur.right);
                }
                if(cur.left!=null){
                    stack1.push(cur.left);
                }
            }
        }
        res.push(row);
    }
    return res;
}
module.exports = {
    zigzagLevelOrder : zigzagLevelOrder
};

二叉树中和为某个值的路径

function pathSum(root, sum){
    let res = [];
    if(!root)return res;
    let path = [];
    dfs(root, sum, path, res);
    return res;
}

//不用作node空判断退出循环,因为如果node.left和right都为空了就会退出递归。
function dfs(node, sum, res, path){
    path.push(node.val);
    if(node && !node.left && !node.right && node.val===sum){
        res.push(path)
    }
    //    通过slice复制每个支路path的路径
    if(node.left) dfs(node.left, sum - node.val, res, path.slice())
    if(node.right) dfs(node.right, sum - node.val, res, path.slice())
}
function hasPathSum( root ,  sum ) {
    // write code here
    if(!root)return false;
    if(root.val>sum)return false;

    return dfs(root, sum);
}
function dfs(node, sum){
//     if(!node)return false;
    if(node && !node.left && !node.right && sum===node.val)return true;
    return dfs(node.left, sum-node.val)|| dfs(node.right, sum-node.val);
}

平衡二叉树

平衡二叉树:是一颗空树或者它的左右两个子树的高度差不超过1

思路:

helper:

当节点左右子树的深度差<1,则返回当前子树的深度加上1:max(left, right)+1;

当左右子树深度差>2, 则返回-1, 代表此子树不是平衡树

终止条件:当root为空,直接返回0

function isBalanced(pRoot){
    let flag = helper(pRoot);
    if(flag===-1){
        return false;
    }else{
        return true;
    }
}

function helper(root){
    if(!root)return 0;

    let left = helper(root.left)
    if(left===-1)return -1;
    let right = helper(root.right);
    if(right===-1)return -1;
    
    return Math.abs(left-right)>1 ? -1 : Math.max(left, right) +1
    
}

二叉树中两个节点的公共祖先

高度

思路:

1. 先判断root结点是否等于o1或者o2,是的话直接返回root.val,不是则直接继续

2. 遍历二叉树

3.有四种情况:

  • 左右都找到了。说明 o1 和 o2 分居在 root 的两侧,那么最低公共祖先为 root,返回 root
  • 左边没找到右边也没找到。返回 null。
  • 左边没找到右边找到了。说明 p 和 q 全都在 root 的右侧,加上我们在第一步就知道 root 不等于 p 也不等于 q,所以最低公共祖先在 root 的右子树,返回右子树。
  • 左边找到了右边没找到。说明 p 和 q 全都在 root 的左侧,加上我们在第一步就知道 root 不等于 p 也不等于 q,所以最低公共祖先在 root 的左子树,返回左子树。
function lowestCommonAncestor( root ,  o1 ,  o2 ) {
    // write code here
    return helper(root, o1, o2).val; 
}
function helper(root, o1, o2){
    if(root===null || root.val===o1 || root.val===o2)return root;
    
    let leftSon = helper(root.left, o1, o2);
    let rightSon = helper(root.right, o1, o2);
    
    if(leftSon && rightSon){//如果此根节点的左右结点分别等于o1,o2,直接返回此根节点的值
        return root;
    }else if(!leftSon && !rightSon){//如果左边没找到,右边也没找到,返回null
        return null;
    }else if(!leftSon && rightSon){//右边找到了,左边没找到
        return rightSon;
    }else{//左边找到了,右边没找到
        return leftSon;
    }
}

二叉搜索树的最近公共祖先

左子树都比根节点小,右子树都比根节点大

思路://也就是只三种情况:

        //1.root比p,q都大,到root的左子树找

        //2.root比p,q都小,到root的右子树找

        //3.(proot)、(root为根节点,p或q为子节点),返回root

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

 判断对称二叉树

function isSymmetric( root ) {
    if (!root)return true;
    return isSame(root.left, root.right);
}

function isSame(a, b){
    if(!a && !b){
        return true;
    }else {
        if(!a||!b) return false;
    }
    return a.val===b.val && isSame(a.left, b.right) && isSame(a.right, b.left);
}

 二叉树的右视图

站在二叉树右边,能看到的结点

深度遍历,先遍历右子树,如果当前层数=数组长度,说明是这一层需要添加的第一个节点(右结点)。再递归

var rightSideView = function(root) {
    let list = [];
    let deep = 0;
    helper(root, list, deep); 
    return list;
};

function helper(node, list, deep){
    if(!node)return;
    if(list.length===deep){//如果当前层数=数组长度,说明是这一层需要添加的第一个节点。
        list.push(node.val);     
    }
    deep++;

    helper(node.right, list, deep);
    helper(node.left, list, deep);
}

反转字符串

/**
 * 反转字符串
 * @param str string字符串 
 * @return string字符串
 */
function solve( str ) {
    // write code here
    let len = str.length;
    if(len===0)return str;
    let str1 = "";
    let i=0, j=len-1;
    while(j>=0){
        str1 += str[j--];
        
    }
    return str1;
}
module.exports = {
    solve : solve
};
//简洁
function solve( str ) {
    return str.split('').reverse().join("");
}
module.exports = {
    solve : solve
};

顺时针打印数组

function spiralOrder( matrix ) {
    // write code here
    if (!matrix.length) return []
    let left = 0;
    let top = 0;
    let right = matrix[0].length-1;
    let bottom = matrix.length-1;
    let arr = new Array((right+1)*(bottom+1));
    let cnt = 0
    
    while(left<=right && top<=bottom){
        for(let i=left; i<=right; i++){
            arr[cnt++] = matrix[top][i];
        }
        top++;
        for(let i=top; i<=bottom; i++){
            arr[cnt++] = matrix[i][right];
        }
        right--;
        for(let i=right; i>=left&&top<=bottom; i--){//逆着回来的时候记得两个条件都要判断
            arr[cnt++] = matrix[bottom][i];
        }
        bottom--;
        for(let i=bottom; i>=top&&left<=right; i--){
            arr[cnt++] = matrix[i][left];
        }
        left++;
    }
    return arr;
}

fibonacci数列

1.递归

2.动态规划

2.
function Fibonacci(n)
{
    // write code here
    let dp = new Array(n+1);
    dp[0] = 0;
    dp[1] = 1;
    for(let i=2; i<=n; i++){
        dp[i] = dp[i-1]+dp[i-2];
    }
    return dp[n]
}

判断回文

思路:双指针

function judge( str ) {
    // write code here
    let i=0, j=str.length-1;
    while(i<=j){
        if(str[i++]!==str[j--]){
            return false;
        }
    }
    return true;
}

15. 最长回文子串


public class Solution {
    //返回长度
    public int getLongestPalindrome(String A, int n) {
        // write code here
        if(n==0||n==1)return n;
        char[] c = A.toCharArray();
        int len1,len2,len;
        int start = 0, end = 0, maxLen = 0;
        
        for(int i=0; i<n; i++){
            len1 = helper(c, i, i, n);
            len2 = helper(c, i, i+1, n);
            len = Math.max(len1,len2);
            if(len > maxLen){
                maxLen = len;
            }

        }
        return maxLen;
    }

    //返回子串
     public int getLongestPalindrome(String A, int n) {
        // write code here
        if(n==0||n==1)return n;
        char[] c = A.toCharArray();
        int len1,len2,maxLen;
        int start = 0, end = 0;
        
        for(int i=0; i<n; i++){
            len1 = helper(c, i, i, n);
            len2 = helper(c, i, i+1, n);
            maxLen = Math.max(len1,len2);
            if(maxLen > end-start){
                start = i - (maxLen-1)/2;
                end = i + maxLen/2;
            }

        }
        return A.subString(start, end+1);
    }
    public int helper(char[] c, int left, int right, int n){
        int L = left, R = right;
        while(L>=0 && R<n && c[L]==c[R]){
            L--;
            R++;
        }
        return R-L-1;
    }
}

三数之和

思路:先排序,for循环先定位一个num[i],再分别用L,R指针指向i+1和最后一个数,和大于0时R--,小于0时L++

function threeSum( num ) {
    // write code here
    const len = num.length;
    if(len<3)return new Array(0);
    num.sort(function(a,b){return a-b})
    let res = [];
    
    for(let i=0; i< len; i++){
        let L = i+1;
        let R = len-1;
        let sum = 0;
        
        if(i>0 && num[i]===num[i-1])continue;
        
        while(L<R){
            sum = num[i] + num[L] + num[R];
            if(sum===0){
                res.push([num[i], num[L], num[R]]);
                while(L<R && num[L]===num[L+1])L++;//跳过重复的相等的时候才要考虑。
                while(L<R && num[R]===num[R-1])R--;
                L++;
                R--;
            }else if(sum>0){
                R--;
            }else{
                L++;
            }
        }
    }
    return res;
}

最长无重复子数组

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

返回值:3

说明:最长子数组为[1,2,3]  

 思路:因为哈希表可以用键来存储数组元素,用值来存储数组元素的位置,这样碰到重复元素后把j的坐标移到重复的元素后一位。并更新键值

image.png

function maxLength( arr ) {
    // write code here
    if(!arr)return 0;
    if(arr.length===1)return 1;
    let maxLen = 0;
    let map = new Map();
    
    for(let i=0, j=0; i<arr.length; i++){
        if(map.has(arr[i])){
            j = Math.max(j,map.get(arr[i])+1);//把j的坐标移到重复的元素后一位。
        }
        map.set(arr[i], i);
        maxLen = Math.max(maxLen, i-j+1);
    }
    return maxLen;
}

包含min的最小栈

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

1-入栈

2-出栈

3-最小栈

输出:[1,2]

思路:一个主栈,直接作入栈出栈操作;

           一个辅助栈,

            入栈操作:当辅助栈为空直接压入,不为空则判断当前元素与辅助栈栈顶元素大小。比栈顶元素小则入栈,比栈顶元素大则将辅助栈的栈顶元素再压一次。

            出栈操作:和主栈一起将栈头pop出

function getMinStack( op ) {
    // write code here
    let stack1 = [];//主栈
    let stack2 = [];//辅助栈
    let res = [];//结果输出一维数组
    let len = op.length;
    
    for(let i=0; i<len; i++){
//         入栈操作
        if(op[i][0]===1){
            stack1.push(op[i][1]);
            if(!stack2.length){//如果辅助栈为空,直接入栈
                stack2.push(op[i][1]);
            }else{//辅助栈不为空
                if(op[i][1]<stack2[stack2.length-1]){//要入栈的元素比辅助栈栈顶元素小,加入辅助栈
                    stack2.push(op[i][1]);
                }else{//要入栈的元素比辅助站栈顶元素大,将辅助栈栈顶元素再压入一次
                    stack2.push(stack2[stack2.length-1]);
                }
            }
        }else if(op[i][0]===2){//出栈操作
            stack1.pop();
            stack2.pop();
        }else{//最小栈元素
            res.push(stack2[stack2.length-1])
        }
    }
    return res;
}
module.exports = {
    getMinStack : getMinStack
};

数组中的第K大 

有一个整数数组,请你根据快速排序的思路,找出数组中第K大的数。

给定一个整数数组a,同时给定它的大小n和要找的K(1<=K<=n),请返回第K大的数(包括重复的元素,不用去重),保证答案存在。

function findKth( a ,  n ,  K ) {
    // write code here
    if(a===null || K>a.length)return -1;
    helper(a, 0, n-1, n-K);
    return a[n-K];
}

function helper(a, begin, end, k){
    if(begin>=end)return;
    
    let index = quickSort(a, begin, end);
    if(index>k){
        helper(a, begin, index-1, k);
    }else if(index<k){
        helper(a, index+1, end, k);
    }else{
        return;
    }
}

function quickSort(arr, begin, end){
    let flag=arr[begin], i=begin , j=end;

    while(i<j){
        while(i<j && arr[j]>=flag){
            j--;
        }
        if(i<j){
            arr[i] = arr[j];
        }

        while(i<j && arr[i]<=flag){
            i++;
        }
        if(i<j){
            arr[j] = arr[i];
        }
    }
    arr[i] = flag;
    return i;
}

字符串出现次数的 TopK

如果次数相同的则按照字典书序书输出

function topKstrings( strings ,  k ) {
    // write code here
    strings = strings.sort();//按字典排序
    let map = new Map();
    for(let item of strings){
        if(map.has(item)){
            map.set(item, map.get(item)+1);
        }else{
            map.set(item, 1);
        }
    }
    let res = [];
    map.forEach((value,key)=>{
        res.push([key, value]);
    })
    
    res.sort((a, b)=> b[1] - a[1])
    return res.slice(0,k);
}

位运算

数组中出现超过一半的数字

思路:

1. 先排序,再找中位数

2. 投票法:先定一个target和计数器,遍历,如果当前的数和target不一样则计数器减一。计数器为0时,就换当前数为target。因为最终的target是出现最多的,所以计数器不会为0,最后返回的一定是它。

function MoreThanHalfNum_Solution(numbers)
{
    // write code here
// 排序法
//     let len = numbers.length;
//     numbers.sort((a,b)=>a-b)
//     return numbers[Math.floor((len-1)/2)]

//投票法
    if(!Array.isArray(numbers))return -1
    let target = numbers[0];
    let cnt = 1;
    for(let i=1; i<numbers.length; i++){
        if(numbers[i]!==target){
            cnt--
        }else{
            cnt++
        }
        
        if(cnt===0){
            target = numbers[i];
            cnt = 1;
        }
    }
    return target; 
}

 数组中只出现一次的数(其他的出现k次)

思路:因为其余的数都是出现k次,只有一个出现一次。对每一比特位进行加和再除k求余。其他出现k次的数都余0

[5,4, 1,1, 5, 1, 5 ]

101

100

001

001

101

001

101

------对每一位先求和,再除k取余

100

function foundOnceNumber( arr ,  k ) {
    let res = 0;
    for(let i=0; i<32; i++){
        let sum = 0;
        for(let k of arr){
            //依次右移num,同1相与,计算每一位上1的个数
            sum += (k>>i)&1
        }
        if(sum%k!==0){
            res += 1<<i;//因为只出现一次,所以取余的值是1,再位移
        }
    }
    return res;
}

 数组中只出现一次的两个数字(其余都出现两次)

【1,1,3,3,5,5,a,b】

思路1:通过数组和键值进行统计

思路2:

先对整个数组求异或得到c,可以知道c其实就是a^b=c。那么对于c,假如c二进制的第一位是1,其实就代表a二进制的第一位是1(或0),b二进制的第一位是0(或1),总而言之如果第一位的c等于1,那么a和b在第一位肯定不相等。

找到整个数组异或后的数的第一位不为0的位,根据这位对数组分成两组,分别包含a和b。再分别对两个数组分别异或得到a,b

function FindNumsAppearOnce( array ) {
    // write code here
    //先把数组里所有数字进行异或,结果是那两个只出现一次的两个数的异或
    let xor = 0;
    for(let k of array){
        xor ^= k;
    }
    let flag = 0;//用于记录xor第一个是1的位数
    for(let i=0; i<32; i++){
        if(((xor>>i)&1)===1){
            flag = i;
            break;
        }
    }
    let a=0, b=0;
    for(let k of array){
        if(((k>>flag)&1)===1){
            a ^= k;
        }else{
            b ^= k;
        }
    }
    return [a, b].sort((a,b)=>a-b)
}

动态规划

1.01背包问题

0-1 背包问题:给定 n 种物品和一个容量为 c 的背包,物品 i 的重量是 wi,其价值为 vi 。

思路:

声明一个 大小为  m[n][c] 的二维数组,m[ i ][ j ] 表示 在面对第 i 件物品,且背包容量为  j 时所能获得的最大价值 :

(1)j < w[i] 的情况,这时候背包容量不足以放下第 i 件物品,只能选择不拿

(2)j>=w[i] 的情况,这时背包容量可以放下第 i 件物品,我们就要考虑拿这件物品是否能获取更大的价值。

如果拿取,m[ i ][ j ]=m[ i-1 ][ j-w[ i ] ] + v[ i ]。 这里的m[ i-1 ][ j-w[ i ] ]指的就是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,也是相当于为第i件物品腾出了w[i]的空间。

如果不拿,m[ i ][ j ] = m[ i-1 ][ j ] , 同(1)

最后比较这两种情况哪种价值最大:

if(j>=w[i])
    m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
    m[i][j]=m[i-1][j];

//处理输入输出
let line;
line = readline().split(' ');
let N = parseInt(line[0]);
let p = parseInt(line[1]);

let W = [];
let V = [];
for(let i=0;i<N;i++){
    let line=readline().split(" ");
    let v=parseInt(line[0]);
    let w=parseInt(line[1]);
    V.push(v);
    W.push(w);
}

//function
let dp = new Array(N+1);
for(let i=0; i<=N; i++){
    dp[i] = new Array(p+1).fill(0);
}

for(let i=1; i<=N; i++){
    for(let j=1; j<=p; j++){
        if(j>=W[i-1]){
            dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-W[i-1]]+V[i-1]);
        }else{
            dp[i][j] = dp[i-1][j];
        }
    }
}
print(dp[N][p])

2.拼硬币

给你k种面值的硬币,面值分别为c1, c2 ... ck,每种硬币的数量无限,再给一个总金额amount最少需要几枚硬币凑出这个金额,如果不可能凑出,算法返回 -1 。

思路:
dp[i]表示总金额为i时需要的最少的硬币个数。
状态转移方程:假设coins=[1,2,5]
dp[i] = 1 + dp[i-1] # i-1元加上一枚1元总共的硬币数
dp[i] = 1 + dp[i-2] # i-2元加上一枚两元
dp[i] = 1 + dp[i-5] # i-5元加上一枚5元
从中取最小的,dp[i] = min(1 + dp[i-coin] for coin in coins if i >= coin)
或写为:dp[i] = min(dp[i], 1 + dp[i-coin]) ​
初始化状态:dp[0]=0,0分无法用任何面额的硬币来表示
图片

let line = readline().split(' ');
let coins = [];
for(let i=0; i<line.length-1; i++){
    coins.push(parseInt(line[i]));
}
let value = parseInt(line[line.length-1]);
let res;

dp[0] = 0;
for(let i=1; i<=value; i++){
    for(let j=0; j<coins.length; j++){
        if(i>=coins[j]){
            dp[i] = Math.min(dp[i], dp[i-coins[j]]+1)
        }
    }
}
//如果dp[value]等于value+1(value个一元硬币还要再加上一个),
print(dp[value]===value+1 ? -1 : dp[value]);

最长公共子序列(LCS)

\begin{Bmatrix} res[i][j]=0,i=0 or j=0 & \\ res[i][j]=res[i-1][j-1]+1,A[i]=B[j]& \\ res[i][j]=max(res[i-1][j],res[i][j-1]),A[i]\neq B[j]& \end{Bmatrix}

如果出现c[i-1][j]等于c[i][j-1]的情况,说明最长公共子序列有多个,故两边都要进行递归

返回LCS:递归过程,从最后往前。链接

/**
 * longest common subsequence
 * @param s1 string字符串 the string
 * @param s2 string字符串 the string
 * @return string字符串,如果没有公共子序列则返回-1
 */
function LCS( s1 ,  s2 ) {
    // write code here
    if(s1.length===0 || s2.length===0)return '-1';
    let m = s1.length, n = s2.length;
    let res = '';
    var dp = new Array(m+1);
    for(let i=0; i<=m; i++){
        dp[i] = new Array(n+1).fill(0);
    }
    for(let i=1; i<m+1; i++){
        for(let j=1; j<n+1; j++){
             if(s1.charAt(i-1)==s2.charAt(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]);
            }
        }
    }
    //从后往前找子序列
    while(m>0 && n>0){
        if(s1[m-1]===s2[n-1]){
            res = s1[m-1]+res;
            m--;
            n--;
        }else{
            if(dp[m][n-1]>=dp[m-1][n]){
                n--;
            }else{
                m--;
            }
        }
    }
    return res==="" ? -1:res;
}
module.exports = {
    LCS : LCS
};

 最长公共子串

\begin{Bmatrix} 0, A[i]!=B[j]& \\ 1, A[i]==B[j] and ( i==0 || j==0)& \\ dp[i-1][j-1]+1, A[i]==B[j] and (i>0 || j>0)& \end{Bmatrix}

思路:用一个maxLen记录最长的dp[][], 用index记录下标,随时更新。

/**
 * @return string字符串
 */
function LCS( str1 ,  str2 ) {
    // write code here
    if(str1.length===0||str2===0)return "-1";
    let len1 = str1.length, len2 = str2.length;
    let dp = new Array(len1);
    let maxLen = 0, endIndex=0;
    for(let i=0; i<len1; i++){
        dp[i] = new Array(len1).fill(0);
    }
    
    for(let i=0; i<len1; i++){
        for(let j=0; j<len2; j++){
            if(str1.charAt(i)===str2.charAt(j)){
                if(i===0 || j===0){
                    dp[i][j] = 1;
                }else{
                    dp[i][j] = dp[i-1][j-1] + 1;
                }
            }
            if(dp[i][j]>maxLen){
                maxLen = dp[i][j];
                endIndex = i;
            }
        }
    }
    return str1.substring(endIndex-maxLen+1, endIndex+1);
}
module.exports = {
    LCS : LCS
};

链表

3. 最长递增子序列

输入:[2,1,5,3,6,4,8,9,7]

返回值:[1,3,4,8,9]

动态规划解法o(N*N)

function LIS( arr ) {

    let dp = new Array(arr.length).fill(1);
    let maxLen = 0;
     //计算长度
    for(let i=1; i<arr.length; i++){
        for(let j=0; j<i; j++){
            if(arr[i] > arr[[j]){
                dp[i] = Math.max(dp[i], dp[j]+1);
                maxLen = math.max(maxLen, dp[i]);
            }
        }
    }

    //拼成子串
    let res = new Array(maxLen);
    for(let i=arr.length-1; i>=0; i++){
        if(arr[i]===maxLen)res[--maxLen] = arr[i];
    }
    return res;

}

 贪心+二分搜索o(NlogN)

function LIS( arr ) {
   // write code here
    let dp = [arr[0]];//用于记录子序列的
    let len = [1];//用于记录长度的
    let curIndex = 0;//记录当前dp里面有多少个数字(是索引,真实长度要+1)
    
    for(let i=1; i<arr.length; i++){
        // 如果数组当前元素大于dp数组的最后一个数,直接添加到dp数组后面
        //第二种情况时,curIndex没有变化,是在这个范围内的某个位置插入变化的值,所以不用管curIndex
        if(arr[i] > dp[curIndex]){
            //记录序列
            dp[++curIndex] = arr[i];
            //记录长度
            len[i] = curIndex + 1;
        }else{
            let l = 0, r = curIndex;
            while(l<r){
                let mid = Math.floor((l + r) / 2);//这里一定要floor
                if(dp[mid] > arr[i]){
                    r = mid;//这里不能mid-1,要不然会有r===l的情况使l更新不了
                }else{
                    l = mid + 1;
                }
            }
            //curIndex没有变化,是在这个范围内的某个位置插入变化的值,所以不用管curIndex
            dp[l] = arr[i];
            len[i] = l+1;
        }
    }
    
    //从后往前找到递增子串
    let res = new Array(curIndex+1);
    for(let i=len.length-1; i>=0; i--){
        if(len[i] === curIndex+1){
            res[curIndex] = arr[i];
            curIndex--;
        }
        
    }
    return res;
}

4. 高楼扔鸡蛋

f(n,k)表示n层楼,手里还有k个鸡蛋时,至少需要试多少次才能找到临界楼层。

双蛋:

 先假设,最小的次数为x次。
  首先在x层摔,那么会出现两个结果:
  1、碎了,为了找出那一层碎了,第二个鸡蛋必须从1~x-1进行遍历的摔
  2、没碎,那么第二次就在x+(x-1)楼层摔。

为什么是x+x-1楼层呢?
首先我们已经假设了通过x步我们就能得到答案,现在我们在x层已经用了一次了,那么就只剩下x-1步了。所以我们选择x+(x-1)层,如果碎了,我们就能通过x-2步,遍历x+1~x+(x-1)-1的所有楼层。

     3、如果在x+(x-1)楼碎了,那么同1,遍历x+1~x+(x-1)-1
  4、没碎,那么同2,就在x+(x-1)+(x-2)层摔

x+(x-1)+(x-2)+...+1>=100===>(x+1)x/2>=100

//o(k*N*N) 超时解法
public int superEggDrop(int k, int n) {
        int[][] dp = new int[k+1][n+1];

        for(int i=1; i<=n; i++){
            dp[1][i] = i;//只有一个鸡蛋时,有多少层就试多少层
            dp[0][i] = 0;//一个鸡蛋都没有,全为0
        }
        for(int i=1; i<=k; i++){
            dp[i][0] = 0;
        }
        for(int i=2; i<=k; i++){//从第二个鸡蛋开始试
            for(int j=1; j<=n; j++){
                int res = Integer.MAX_VALUE;
                for(int x=1; x<=j; x++){//x小于当前的层数
                    res = Math.min(res, Math.max(dp[i-1][x-1], dp[i][j-x])+1);
                }
                dp[i][j] = res;
            }
        }
        return dp[k][n];
    }

另一个思路 :(救命我真的看不懂)

假设dp[k][j] =N ---代表k个鸡蛋,操作数为j时,包含F楼层(目标层)的最大层数为N。按照层数j=1,2,3,4...不断迭代,第一个>=N的dp[k][j]的j就是所求的最小移动次数。

状态转移方程:

设dp[k][j] = NN,dp[k][j]可以由两个状态转换过来。在在[1,NN]中随便选1层 X 扔鸡蛋,有两种情况:

1. 碎了:那么F一定在[0, x-1]中。这时dp[k][j]的状态就退化成了dp[k-1][j-1] , 即k-1个鸡蛋,操作数为j-1, 即 dp[k-1][j-1] 代表 [0,X-1];

2. 没碎:F一定在[X,NN]中,这时dp[k][j]的状态就退化成dp[k][j-1].

在回顾一下dp[k][j]的含义, dp[k][j] 代表了 [0,N]这样一个区间
dp[k][j-1]代表[X,NN] - > [0,NN-X] -> dp[k][j-1] = NN - X
dp[k-1][j-1] 代表[0,X-1] -> dp[k-1][j-1] = X - 1
但是dp[k][j] = NN = (X - 1) + (NN - X) + 1 = dp[k][j-1] + dp[k-1][j-1] + 1

这就是为啥有这个1

var superEggDrop = function(k, n) {
   let dp = new Array(k+1).fill(0).map(()=>new Array(n+1).fill(0));
   for(let i=1; i<=n; i++){
       dp[0][i] = 0;
       for(let j=1; j<=k; j++){
           dp[j][i] = dp[j][i-1] + dp[j-1][i-1] +1;
           if(dp[j][i] >= n) return i;
       }
   }
    return n
};

 5.编辑距离

s1字符串经过多少次变换变成s2字符串

let s1 = readline()
let s2 = readline()
let len1 = s1.length
let len2 = s2.length
let dp = new Array(len2+1).fill(0).map(()=>new Array(len1+1).fill(0))

for(let i=0; i<=len1; i++){
    dp[0][i] = i;
}
for(let j=0; j<=len2; j++){
    dp[j][0] = j;
}

for(let i=1; i<=len1; i++){
    for(let j=1; j<=len2; j++){
        if(s1[i-1]===s2[j-1]){
            dp[i][j] = dp[i-1][j-1]
        }else{
            dp[i][j] = Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1])) + 1
        }
    }
}
print(dp[len1][len2])

最小编辑代价 

如果两个字符串的当前字符相等,则什么都不做dp[i][j]=dp[i-1][j-1]

function minEditCost( str1 ,  str2 ,  ic ,  dc ,  rc ) {
    // write code here
    let len1 = str1.length, len2 = str2.length;
    let dp = new Array(len1+1).fill(0).map(x=>new Array(len2+1).fill(0))
    //由str1变为空字符串
    for(let i=0; i<=len1; i++){
        dp[i][0] = i * dc;
    }
    //由空字符串变为str2
    for(let i=0; i<len2; i++){
        dp[0][i] = i * ic;
    }
    
    for(let i=1; i<=len1; i++){
        for(let j=1; j<=len2; j++){
            if(str1[i-1]===str2[j-1]){
                dp[i][j] = dp[i-1][j-1];
            }else{
                dp[i][j] = Math.min(dp[i-1][j-1]+rc, Math.min(dp[i][j-1]+ic, dp[i-1][j]+dc));
            }
        }
    }
    return dp[len1][len2];
}

6. 打家劫舍

6.1 打家劫舍1

var rob = function(nums) {
    let len = nums.length;
    if(len<=1) return len === 0 ? 0 : nums[0];
    let dp = new Array(len+1);
    dp[0] = 0;
    dp[1] = nums[0]

    for(let i=2; i<=len; i++){
        //抢(i-1)以及抢(i-2)+(i)中选最大的
        dp[i] = Math.max(dp[i-1], dp[i-2]+nums[i-1]);
    }

    return dp[len]
};

 6.2 打家劫舍2

地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。

环状排列意味着第一个房子和最后一个房子中只能选择一个偷窃,因此可以把此环状排列房间问题约化为两个单排排列房间子问题:

1. 在不偷窃第一个房子的情况下(即 nums[1:]nums[1:]),最大金额是 p_1p 
2. 在不偷窃最后一个房子的情况下(即 nums[:n-1]nums[:n−1]),最大金额是 p_2p 

综合偷窃最大金额: 为以上两种情况的较大值,即 max(p1,p2)。

var rob = function(nums) {
    let len = nums.length;
    if(len<=1) return len === 0 ? 0 : nums[0];
    let dp1 = new Array(len+1);//不偷第一家
    let dp2 = new Array(len+1);//不偷最后一家
    //分为两个dp,一个是不偷第一家,一个是不偷最后一家的情况
    dp1[0] = dp2[0] = 0;
    dp1[1] = 0;
    dp2[1] = nums[0];
    for(let i=2; i<=len; i++){
        dp1[i] = Math.max(dp1[i-1], dp1[i-2]+nums[i-1]);
        if(i<len){
            dp2[i] = Math.max(dp2[i-1], dp2[i-2]+nums[i-1]);
        }else{
            dp2[i] = dp2[i-1];
        }
    }
    return Math.max(dp1[len], dp2[len])
};

接雨水问题

输入:[3,1,2,5,2,4]

返回值:5

说明:数组 [3,1,2,5,2,4] 表示柱子高度图,在这种情况下,可以接 5个单位的雨水,蓝色的为雨水

思路:

1. 找到数组中从下标 i 到最左端最高的条形块高度 left_max。

leftMax = Math.max(arr[i], leftMax[i-1])

2. 找到数组中从下标 i 到最右端最高的条形块高度 right_max。

rightMax[i] = Math.max(arr[i], rightMax[i+1]);

扫描数组arr 并更新答案:
累加 min(left_max[i],right_max[i]) - arr[i]到 ans 上

function maxWater(arr){
    let len = arr.length;
    if(len<3)return 0;
    let leftMax = new Array(len);
    let rightMax = new Array(len);
    let res = 0;

    leftMax[0] = arr[0];
    for(let i=1; i<len; i++){
        leftMax[i] = Math.max(arr[i], leftMax[i-1]);
    }
    rightMax[len-1] = arr[len-1];
    for(let i=len-2; i>=0; i--){
        rightMax[i] = Math.max(arr[i], rightMax[i+1]);
    }

    for(let i=0; i<len; i++){
        res += (Math.min(leftMax, rightMax)-arr[i] )
    }

    return res;
}

 岛屿数量

岛屿问题示例

 思路:

dfs()

  • 用深度搜索.从(i, j)向上下左右(i+1,j),(i-1,j),(i,j+1),(i,j-1) 做深度搜索
  • 终止条件:
    1. (i, j) 越过矩阵边界;
    2. grid[i][j] == 0,代表此分支已越过岛屿边界。

  • 搜索岛屿的同时,执行 grid[i][j] = '0',即将岛屿所有节点删除,以免之后重复搜索相同岛屿。

主循环

  • 遍历整个矩阵,当遇到 grid[i][j] == '1' 时,从此点开始做深度优先搜索 dfs,岛屿数 count + 1 且在深度优先搜索中删除此岛屿。
function solve( grid ) {
    let cnt = 0;
    for(let i=0; i<grid.length; i++){
        for(let j=0; j<grid[0].length; j++){
            if(grid[i][j]==='1'){
                dfs(i,j,grid);
                cnt++;
            }
        }
    }
    return cnt
}    
function dfs(x, y, grid){
    if(x<0 || x>=grid.length || y<0 || y>=grid[0].length || grid[x][y]==='0')return;
    grid[x][y] = '0'
    dfs(x-1, y, grid);
    dfs(x+1, y, grid);
    dfs(x, y-1, grid);
    dfs(x, y+1, grid);
}

买卖股票的最佳时机

输入:[7,1,5,3,6,4]
输出:5
function maxProfit( prices ) {
    let minPrice = Number.MAX_VALUE;
    let maxProfit = 0;

    for(let i=0; i<prices.length; i++){
        minPrice = Math.min(price[i], minPrice);
        maxProfit = Math.max(maxProfit, price[i]-minPrice);
    }
    return maxProfit;
}

击鼓传花

K(K>=3)个人在玩一个击鼓传花的小游戏。每击一次鼓,拿着花的要将花交给别人,不能留在自己手中。游戏开始前花在小猿手中,求击了N次鼓后,这朵花又回到小猿手中的方案数,请输出这个数模1000000007后的结果。

思路:

f(i,p)表示击鼓i次后是不是在自己手上。p===0表示在自己手上,p===1不在手上。

f(i,0) = f(i-1, 1)

f(i,1) = f(i-1, 0)*(k-1) + f(i-1, 1)*(k-2)

当f ( i , 0 )表示第i个状态的时候在手里,那么i − 1状态的时候一定不在手里。

如果f ( i , 1 ) 表示第i个状态不在手里,那么i − 1状态的时候可以在手里,也可以不在手里,在手里有k − 1种情况(即,将自己手里的花传给其他人),不在手里有k − 2 种情况(即,既不在自己手里也不在上一轮的人手里)

边界条件f(0,0)=1(即游戏开始前在自己手中)。f(0,1)=0

//只通过50%
let input = readline().split(' ');
let k = parseInt(input[0]),n = parseInt(input[1]);
let dp = new Array(n+1).fill(0).map(()=>new Array(2).fill(0))
dp[0][1] = 0;
dp[0][0] = 1;
for(let i=1; i<=n; i++){
    dp[i][0] = dp[i-1][1]%1000000007;
    dp[i][1] = dp[i-1][0]*(k-1)%1000000007+dp[i-1][1]*(k-2)%1000000007;
}
print (dp[n][0]);

 矩阵的最小路径和

给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和

dp[i][j] = matrix[i][j]+min(dp[i-1][j],dp[i][j-1])

function minPathSum( matrix ) {
    // write code here
    let n = matrix.length;
    let m = matrix[0].length;
    if(!matrix || n<=0 || m<=0){
        return 0;
    }
    let dp = new Array(n).fill(0).map(x=>new Array(m).fill(0));
    dp[0][0] = matrix[0][0];
    for(let i=1; i<n; i++){//边界:只能竖着走
        dp[i][0] = dp[i-1][0] + matrix[i][0];
    }
    for(let j=1; j<m; j++){//边界:只能横着走
        dp[0][j] = dp[0][j-1] + matrix[0][j];
    }
    
    for(let i=1; i<n; i++){
        for(let j=1; j<m; j++){
            dp[i][j] = matrix[i][j] + Math.min(dp[i-1][j], dp[i][j-1]);
        }
    }
    return dp[n-1][m-1];
}

正则表达式

1.判断是否符合格式:XXX-XXX-XXXX

function matchesPattern(str) {
    let reg = /^\d{3}-\d{3}-\d{4}$/
    return reg.test(str)   
}

2.返回字符串中连续的三个数字

function captureThreeNumbers(str) {
    let reg = /\d{3}/;
    let res = str.match(reg);
    if(res){
        return res[0];
    }
    return false;
     
     
}

3.给千分位加上逗号

function addComma(num){
  //没有小数
  if(num.toString().indexOf('.')===-1){
     return num.toLocaleString();
  }else{
    // 如果是小数,则保留两位小数
    //\d{3}+\.表示3个数字或者3的倍数个数字,后面跟着.
    return num.toFixed(2).replace(/(\d{1,3})(?=(\d{3})+\.)/g, '$1,')
  }
}

字符串转驼峰

var str = 'test_align_center'
var res; 
var reg = /_(\w)/g
if (reg.test(str)) {
    res = str.toLowerCase().replace(reg, function (match, p1) {
        return p1.toString().toUpperCase()
    })
} else {
    reg2 = /^[A-Z]/g;
    res = str.replace(reg2, function (match) {
        return match.toLowerCase()
    })
}
console.log(res)

字符串去重 

var str = "aaaabbbbbccccc";
var reg = /(\w)\1*/g;
console.log(str.replace(reg,"$1"));//abc

JS基础

1. 将函数 fn 的执行上下文改为 obj,返回 fn 执行后的值

输入:alterContext(function() {return this.greeting + ', ' + this.name + '!'; }, {name: 'Rebecca', greeting: 'Yo' })

输出:Yo, Rebecca!

function alterContext(fn, obj) {
   return fn.call(obj)
}

2. 保留精度的乘法

 输入:3     0.00001

 输出:0.00003

//1.先转换成字符串
//2.计算小数后面有几位
//3.最大的小数位
//4.乘
function multiply(a, b){
    let str1 = a.toString();
    let str2 = b.toString();

    let len1 = (str1.indexOf('.')==-1)? 0 : (str1.length-str1.indexOf('.')-1);
    let len2 = (str2.indexOf('.')==-1)? 0 : (str2.length-str2.indexOf('.')-1);

    var maxLen = Math.max(len1, len2);
    return parseFloat(a*b).toFixed(maxLen);
} 

大数加法

function solve( s ,  t ) {
    let len1 = s.length - 1, len2 = t.length - 1;
    var carry = 0, sum = 0;
    let res = '';
    let c1, c2;
    while (len1 >= 0 || len2 >= 0 || carry !== 0) {
      c1 = len1>=0 ? Number(s.charAt(len1--)) : 0;
      c2 = len2>=0 ? Number(t.charAt(len2--)) : 0;
      sum = c1 + c2 + carry;
      carry = Math.floor(sum / 10);
      res = "" + Math.floor(sum % 10) + res;
    }
    return res;
}


3. 二进制转换

输入:65

输出:01000001

function convertToBinary(num) {
    let left;
    let res = [];
    while(num){
        left = num % 2;
        num = Math.floor(num/2);
        res.unshift(left);
    }
    while(res.length<8){
        res.unshift(0);
    }
    return res.join("");
}

4.多进制转换

function solve( M ,  N ) {
    // write code here
    return M.toString(N).toUpperCase()
}

柯里化 

实现:add(1,2,3), add(1)(2)(3)

function curryAdd(){
    // 将参入的类数组转为数组
    let arg = Array.prototype.slice.call(arguments);
    
    //在函数内部声明一个函数,利用闭包特性保存arg并收集所有的参数值
    let add = function(){
        arg.push(...arguments);
        return add;
    }

    add.toString = function(){
        return arg.reduce((a,b)=>{return a+b})
    }

    return add;
}

使用闭包

实现函数 makeClosures,调用之后满足如下条件:
1、返回一个函数数组 result,长度与 arr 相同
2、运行 result 中第 i 个函数,即 result[i](),结果与 fn(arr[i]) 相同

function makeClosures(arr, fn){
    let res = [];
    for(let i=0; i<arr.length; i++){
        res[i] = function(){
            return fn(arr[i])
        }
    }
}

//利用bind方法
function makeClosures(arr, fn){
    let res = [];
    for(let i=0; i<arr.length; i++){
        fn.bind(null, arr[i])
    }
}

验证IP地址

/**
 * 验证IP地址
 * @param IP string字符串 一个IP地址字符串
 * @return string字符串
 */
function solve( IP ) {
    // write code here
    if(IP.split('.').length>1){
        let arr = IP.split('.');
        if(arr.length===4){
            for(let i=0; i<arr.length; i++){
                if(parseInt(arr[i])<0 || parseInt(arr[i])>255 || arr[i].indexOf(0)===0){
                    return "Neither";
                }
            }
        }
        return "IPv4";
    }else if(IP.split(':').length > 1){
        let arr = IP.split(':');
            for(let i=0; i<arr.length; i++){
                if(arr[i].length>4 || !arr[i].length){
                    return "Neither";
                }
            }
        return "IPv6"
        }
}

 移除数组中的元素 

 直接在原数组

function removeWithoutCopy(arr, item) {
    let len = arr.length;
    for(let i=0; i<len; i++){
        if(arr[i]===item){
            arr.splice(i,1)
            i--
            len--;
        }
    }
    return arr;
}

 重新返回新数组

function remove(arr, item) {
     return arr.filter(p=>{
         return p!==item;
     })
}

 统计字符串

统计字符串中每个字符的出现频率,返回一个 Object,key 为统计字符,value 为出现频率
1. 不限制 key 的顺序
2. 输入的字符串参数不会为空
3. 忽略空白字符

输入:'hello world'

输出:{h: 1, e: 1, l: 3, o: 2, w: 1, r: 1, d: 1}

function count(str) {
    let res = {}
    str = str.replace('/\s/g', '');
    
    for(let i=0; i<str.length; i++){
        res[str[i]] = res[str[i]]  ? ++res[str[i]] : 1;
    }
    return res;
}

扁平化数组 (二维转一维,多维转一维)

 扁平化去重排序数组

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];

编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
var res = [...new Set(arr.join().split(',').map(Number))];
res.sort((a,b)=>a-b)
console.log(res)

如何实现一个new

1. 首先创建一个空对象,空对象_proto_属性指向构造函数的原型对象

[Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。]

2. 把新创建的空对象的this指向构造函数,并给空对象添加属性。

3. 如果构造函数返回一个引用类型的值,则返回这个值;否则返回新创建的对象。

function _new(fn, ...arg){
    var obj = Object.create(fn.prototype);
    const result = fn.apply(obj, ...args);
    return result instanceof Object ? result : obj;
}

a==1 && a==2 && a==3为true成立的情况

var a = ?;
if(a == 1 && a == 2 && a == 3){
 	conso.log(1);
}

 1、当执行a == 1 && a == 2 && a == 3 时,会从左到右一步一步解析,首先 a == 1,会进行上面转换,每一步都调用toString方法

所以只要改原形的toString()方法

const a = {
    i:1,
    toString(){
        return a.i++;
    }
}
if(a == 1 && a == 2 && a == 3){
 	conso.log(1);
}

判断两个对象的内容是否相同

let obj1 = {
  a: 1,
  b: {
   c: 2
  }
 }
 let obj2 = {
  b: {
   c: 3
  },
  a: 1
 }
 console.log(isObjectValueEqual(obj1, obj2))//false

function isEqual(a, b){
    if(a===b)return true;//如果引用对象地址相同,返回true

    //获取a、b的键值数组
    let propA = Object.getOwnPropertyNames(a);
    let propB = Object.getOwnPropertyNames(b);
    if(propA.length!==propB.length)return false;
    
    for(let key in a){
        if(b.hasOwnProperty(a[key])){//判断b中是否有a的键,没有则返回false
            if(typeof a[key] === 'Object'){
                if(!isEqual(a[key], b[key]))return false;
            }else if(a[key] !== b[key]){
                return false;
            }
        }else{
            return false;
        }
    }    
    return true;
}

实现sleep函数

//用回调
function sleep(callback,time){
    setTimeout(callback, time)
}

//用promise
const sleep = function(time){
    return new Promise(resolve=>setTimeout(resolve, time))
}

sleep().then(()=>{
    console.log(1)
})

 给定两个数组,计算他们的交集

let map = {}
let res= [];

for(let item of nums1){
    if(map[item]){
        map[item]++;
    }else{
        map[item] = 1;
    }
}

for(let item of nums2){
    if(map[item]>0){
        res.push(item);
        map[item]--;
    }
}

扁平化数组 (二维转一维,多维转一维)

[1, [2, 3, [4, 5]]]  ------>    [1, 2, 3, 4, 5]

 思路:遍历数组每一项,若值为数组则递归遍历,否则concat

//利用reduce+递归
function flatten(arr){
    return arr.reduce((result, item)=>{
        return result.concat(Array.isArray(item)? flatten(item) : item);
    }, []);
}
//join+split+map
function flatten(arr){
    return arr.join().split(",").map(item=>{return parseInt(item)})
}
//扩展运算符:若arr中含有数组则使用一次扩展运算符,直至没有为止(不是很懂这个方法)
function flatten(arr){
    while(arr.some(item=>Array.isArray(item))){
        arr = [].concat(...arr)
    }
    return arr;
}

21. 数组去重

//1.扩展运算符+set
function unique(arr){
    return [...new Set(arr)]//通过展开操作符将set转为数组
}

//2.创建一个新数组,利用indexOf(arr[i])是否存在于新数组中,不存在则添加
function unique(arr){
    let res = [];
    for(let i=0; i<arr.length; i++){
        if(res.indexOf(arr[i])===-1){
            res.push(arr[i]);
        }
    }
}

//3.先对数组排序,如果判断前后的元素是否相同,不同则加到res里面
function unique(arr){
    if(!arr.length)return 
    arr.sort();
    let res = [arr[0]];
    for(let i=0; i<arr.length; i++){
        if(arr[i] !== arr[i-1]){
            res.push(arr[i]);
        }
    }
    return res;
}

//4.通过filter
function unique(arr){
    retrun arr.filter((item, index)=>{
      //indexOf(item)会返回多个含有item的下标,如果等于第一个返回的下标==当前索引就返回这个元素
        return arr.indexOf(item, 0)===index;
    })
}

实现简单的迭代器 

const iter = function(arr){
    let nextIndex  = 0;
    return {
        next:()=>{
            return nextIndex<arr.length? {value:arr[nextIndex], done: false}:
                    {value:undefined, done: true}
        }
    }
}

const it = iter(['aaa', 'ccc']);
console.log(it.next()); //{value:"aaa",done:false}
console.log(it.next()); //{value:"ccc",done:false}
console.log(it.next()); //{value:undefined,done:true}

 实现一个once函数,传入函数参数只执行一次

function once(func){
    var tag = true;
    return function(){
        if(tag===true){
            func.apply(null, arguments);
            tag = false;
        }
        return undefined;
    }
}

将原生的Ajax封装成promise 

var myAjax = function(url){
    return new Promise((resolve, reject)=>{
        var xhr = new XMLHttpRequest();
        xhr.open('get', url);
        xhr.send(data);
        xhr.onreadystatechange = function(){
            if(xhr.status===200 && readyState==4){
                var json = JSON.parse(xhr.responseText);
                resolve(json);
            }else if(readyState==4 && xhr.status!==200){
                reject('error');
            }
        };            
    })
}

lazyMan

  function lazyMan(name) {
    this.taskList = [];
    this.introduce(name);
  }

  lazyMan.prototype = {
    _bind: function (method, fn) {
      if (method === 'sleepFirst') {
        this.taskList.unshift(fn);
      } else {
        this.taskList.push(fn);
      }
    },

    next: function () {
      if (this.taskList.length > 0) {
        var fn = this.taskList.shift();
        fn && fn();
      }
    },

    introduce: function (name) {
      var me = this;
      this._bind('introduce', function () {
        me.log('Hi! This is ' + name + '!');
        me.next();
      });

      setTimeout(function () {
        me.next();
      }, 0);
      return this;
    },

    sleep: function (sec) {
      var me = this;
      this._bind('sleep', function () {
        me.log('Wake up after' + sec);
        setTimeout(() => {
          me.next();
        }, sec * 1000);
      });
      return this;
    },

    eat: function (str) {
      var me = this;
      this._bind('eat', function () {
        me.log('Eat' + str + '~');
        me.next();
      });
      return this;
    },

    sleepFirst: function (sec) {
      var me = this;
      this._bind('sleepFirst', function () {
        me.log('Wake up after' + sec);
        setTimeout(() => {
          me.next();
        }, sec * 1000);
      });
      return this;
    },

    log: function (str) {
      console.log(str);
    }
  };

  var lm = function (name) {
    return new lazyMan(name);
  };

  lm('Hank').sleepFirst(2).eat('lunch').sleep(3).eat('dinner')

 promise方法

    class Lazy {
        constructor(name) {
            this.sleepFirstTime = 0;
            this.promise = Promise.resolve().then(
                () => this.sleepFirstTime && this._sleep(this.sleepFirstTime)
            ).then(() => {
                console.log(`Hi! This is ${name}!`);
            });
        }
        sleepFirst(time) {
            this.sleepFirstTime = time;
            return this;
        }
        eat(food) {
            this.promise = this.promise.then(() => {
                console.log(`Eat ${food}~`);
            });
            return this;
        }
        sleep(time) {
            this.promise = this.promise.then(() => this._sleep(time));
            return this;
        }
        _sleep(time) {
            return new Promise((next) => {
                setTimeout(() => {
                    console.log(`Wake up after ${time}`);
                    next();
                }, time);
            });
        }
    }

    function LazyMan(name) {
        return new Lazy(name);
    }

LazyMan(“Hank”).sleepFirst(2000).sleep(2000).eat(“supper”)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值