javascript算法+手写js面试题

链表

  function ListNode(val, next) {
      this.val = (val===undefined ? 0 : val)
      this.next = (next===undefined ? null : next)
  }

双指针秒杀7道链表题目

1. 合并两个有序链表

leetcode_21_合并两个有序链表

方法一


function mergeTwoLists(head1, head2) {
    let newHead = new ListNode(0), p = newHead;
    while(head1 && head2) {
        if(head1.val <= head2.val) {
            p.next = head1;
            head1 = head1.next;
        } else {
            p.next = head2;
            head2 = head2.next;
        }
        p = p.next;
    }
    // 没遍历完的接上即可
    p.next = head1 ? head1 : head2;
    return newHead.next;
}

方法二

var mergeTwoLists = function(l1, l2) {
    if(!l1) return l2;
    if(!l2) return l1;
    if (l1.val < l2.val) {
        l1.next = mergeTwoLists(l1.next, l2);
        return l1;
    } else {
        l2.next = mergeTwoLists(l1, l2.next);
        return l2;
    }
};

2. 合并 k 个有序链表

在这里插入图片描述

var mergeKLists = function(lists) {
    if(!lists.length) return null; // 空的直接返回,不然 lists.length 是负数
    return mergeList(lists, 0, lists.length - 1);
};

// 归并排序进行不断的分割,return 时的 merge 进行合并排序
function mergeList(lists, start, end) {
    // 如果 start === end 说明分治的分到头了,只剩一个元素了,直接返回即可
    if(start === end) {
        return lists[start];
    }
    const mid = start + ((end - start) >> 1); // 找到中点,然后下面继续进行递归分割成左右两部分
    const leftList = mergeList(lists, start, mid);
    const rightList = mergeList(lists, mid + 1, end);
    return merge(leftList, rightList); // 进行合并
}

// 这里就是基本算法,合并两个有序链表
function merge(head1, head2) {
    let newHead = new ListNode(0), p = newHead;
    while(head1 && head2) {
        if(head1.val <= head2.val) {
            p.next = head1;
            head1 = head1.next;
        } else {
            p.next = head2;
            head2 = head2.next;
        }
        p = p.next;
    }
    // 没遍历完的接上即可
    p.next = head1 ? head1 : head2;
    return newHead.next;
}

3. 寻找单链表的倒数第 k 个节点

// 返回链表的倒数第 k 个节点
function findFromEnd(head, k) {
    let p1 = head;
    // p1 先走 k 步
    for (lett i = 0; i < k; i++) {
        p1 = p1.next;
    }
    let p2 = head;
    // p1 和 p2 同时走 n - k 步
    while (p1 != null) {
        p2 = p2.next;
        p1 = p1.next;
    }
    // p2 现在指向第 n - k 个节点
    return p2;
}

4. 删除单链表的倒数第k个节点

function removeNthFromEnd(head, n) {
    // 虚拟头结点
   let dummy = new ListNode(-1);
    dummy.next = head;
    // 删除倒数第 n 个,要先找倒数第 n + 1 个节点
    let x = findFromEnd(dummy, n + 1);
    // 删掉倒数第 n 个节点
    x.next = x.next.next;
    return dummy.next;
}
// 返回链表的倒数第 k 个节点
function findFromEnd(head, k) {
    let p1 = head;
    // p1 先走 k 步
    for (lett i = 0; i < k; i++) {
        p1 = p1.next;
    }
    let p2 = head;
    // p1 和 p2 同时走 n - k 步
    while (p1 != null) {
        p2 = p2.next;
        p1 = p1.next;
    }
    // p2 现在指向第 n - k 个节点
    return p2;
}

5. 寻找单链表的中点

function middleNode(head) {
    // 快慢指针初始化指向 head
    let slow = head, fast = head;
    // 快指针走到末尾时停止
    while (fast != null && fast.next != null) {
        // 慢指针走一步,快指针走两步
        slow = slow.next;
        fast = fast.next.next;
    }
    // 慢指针指向中点
    return slow;
}

6. 判断单链表是否包含环并找出环起点

// 判断是否包含环
function hasCycle( head) {
    // 快慢指针初始化指向 head
    let slow = head, fast = head;
    // 快指针走到末尾时停止
    while (fast != null && fast.next != null) {
        // 慢指针走一步,快指针走两步
        slow = slow.next;
        fast = fast.next.next;
        // 快慢指针相遇,说明含有环
        if (slow == fast) {
            return true;
        }
    }
    // 不包含环
    return false;
}
// 包含环,找出环的起点
ListNode detectCycle(ListNode head) {
    ListNode fast, slow;
    fast = slow = head;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) break;
    }
    // 上面的代码类似 hasCycle 函数
    if (fast == null || fast.next == null) {
        // fast 遇到空指针说明没有环
        return null;
    }

    // 重新指向头结点
    slow = head;
    // 快慢指针同步前进,相交点就是环起点
    while (slow != fast) {
        fast = fast.next;
        slow = slow.next;
    }
    return slow;
}

7. 判断两个单链表是否相交并找出交点

ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    // p1 指向 A 链表头结点,p2 指向 B 链表头结点
    ListNode p1 = headA, p2 = headB;
    while (p1 != p2) {
        // p1 走一步,如果走到 A 链表末尾,转到 B 链表
        if (p1 == null) p1 = headB;
        else            p1 = p1.next;
        // p2 走一步,如果走到 B 链表末尾,转到 A 链表
        if (p2 == null) p2 = headA;
        else            p2 = p2.next;
    }
    return p1;
}

反转链表

反转整个单链表

// 定义:输入一个单链表头结点,将该链表反转,返回新的头结点
ListNode reverse(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    ListNode last = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return last;
}

反转链表的前n个节点

ListNode successor = null; // 后驱节点

// 反转以 head 为起点的 n 个节点,返回新的头结点
ListNode reverseN(ListNode head, int n) {
    if (n == 1) {
        // 记录第 n + 1 个节点
        successor = head.next;
        return head;
    }
    // 以 head.next 为起点,需要反转前 n - 1 个节点
    ListNode last = reverseN(head.next, n - 1);

    head.next.next = head;
    // 让反转之后的 head 节点和后面的节点连起来
    head.next = successor;
    return last;
}

反转给定链表索引区间 [m, n]的一部分

ListNode reverseBetween(ListNode head, int m, int n) {
    // base case
    if (m == 1) {
        return reverseN(head, n);
    }
    // 前进到反转的起点触发 base case
    head.next = reverseBetween(head.next, m - 1, n - 1);
    return head;
}

数组

快慢指针技巧——原地修改数组

1. 删除有序数组中的重复项

在这里插入图片描述

int removeDuplicates(int[] nums) {
    if (nums.length == 0) {
        return 0;
    }
    int slow = 0, fast = 0;
    while (fast < nums.length) {
        if (nums[fast] != nums[slow]) {
            slow++;
            // 维护 nums[0..slow] 无重复
            nums[slow] = nums[fast];
        }
        fast++;
    }
    // 数组长度为索引 + 1
    return slow + 1;
}

2. 移除元素

在这里插入图片描述

int removeElement(int[] nums, int val) {
    int fast = 0, slow = 0;
    while (fast < nums.length) {
        if (nums[fast] != val) {
            nums[slow] = nums[fast];
            slow++;
        }
        fast++;
    }
    return slow;
}

3. 移动零

在这里插入图片描述

void moveZeroes(int[] nums) {
    // 去除 nums 中的所有 0,返回不含 0 的数组长度
    int p = removeElement(nums, 0);
    // 将 nums[p..] 的元素赋值为 0
    for (; p < nums.length; p++) {
        nums[p] = 0;
    }
}

// 见上文代码实现
int removeElement(int[] nums, int val);

快慢指针技巧——滑动窗口

1. 最小覆盖字串

在这里插入图片描述

string minWindow(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    // 记录最小覆盖子串的起始索引及长度
    int start = 0, len = INT_MAX;
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 扩大窗口
        right++;
        // 进行窗口内数据的一系列更新
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c])
                valid++;
        }

        // 判断左侧窗口是否要收缩
        while (valid == need.size()) {
            // 在这里更新最小覆盖子串
            if (right - left < len) {
                start = left;
                len = right - left;
            }
            // d 是将移出窗口的字符
            char d = s[left];
            // 缩小窗口
            left++;
            // 进行窗口内数据的一系列更新
            if (need.count(d)) {
                if (window[d] == need[d])
                    valid--;
                window[d]--;
            }                    
        }
    }
    // 返回最小覆盖子串
    return len == INT_MAX ?
        "" : s.substr(start, len);
}

2. 字符串排列

在这里插入图片描述

// 判断 s 中是否存在 t 的排列
bool checkInclusion(string t, string s) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    while (right < s.size()) {
        char c = s[right];
        right++;
        // 进行窗口内数据的一系列更新
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c])
                valid++;
        }

        // 判断左侧窗口是否要收缩
        while (right - left >= t.size()) {
            // 在这里判断是否找到了合法的子串
            if (valid == need.size())
                return true;
            char d = s[left];
            left++;
            // 进行窗口内数据的一系列更新
            if (need.count(d)) {
                if (window[d] == need[d])
                    valid--;
                window[d]--;
            }
        }
    }
    // 未找到符合条件的子串
    return false;
}

3. 找出所有字母异位词

**加粗样式**

vector<int> findAnagrams(string s, string t) {
    unordered_map<char, int> need, window;
    for (char c : t) need[c]++;

    int left = 0, right = 0;
    int valid = 0;
    vector<int> res; // 记录结果
    while (right < s.size()) {
        char c = s[right];
        right++;
        // 进行窗口内数据的一系列更新
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c]) 
                valid++;
        }
        // 判断左侧窗口是否要收缩
        while (right - left >= t.size()) {
            // 当窗口符合条件时,把起始索引加入 res
            if (valid == need.size())
                res.push_back(left);
            char d = s[left];
            left++;
            // 进行窗口内数据的一系列更新
            if (need.count(d)) {
                if (window[d] == need[d])
                    valid--;
                window[d]--;
            }
        }
    }
    return res;
}

4. 最长无重复子串

在这里插入图片描述

int lengthOfLongestSubstring(string s) {
    unordered_map<char, int> window;

    int left = 0, right = 0;
    int res = 0; // 记录结果
    while (right < s.size()) {
        char c = s[right];
        right++;
        // 进行窗口内数据的一系列更新
        window[c]++;
        // 判断左侧窗口是否要收缩
        while (window[c] > 1) {
            char d = s[left];
            left++;
            // 进行窗口内数据的一系列更新
            window[d]--;
        }
        // 在这里更新答案
        res = max(res, right - left);
    }
    return res;
}

左右指针——(由外向内)

1. 求和

1.1 两数之和

在这里插入图片描述

int[] twoSum(int[] nums, int target) {
    // 一左一右两个指针相向而行
    int left = 0, right = nums.length - 1;
    while (left < right) {
        int sum = nums[left] + nums[right];
        if (sum == target) {
            // 题目要求的索引是从 1 开始的
            return new int[]{left + 1, right + 1};
        } else if (sum < target) {
            left++; // 让 sum 大一点
        } else if (sum > target) {
            right--; // 让 sum 小一点
        }
    }
    return new int[]{-1, -1};
}

1.2 两数之和——进阶

nums 中可能有多对儿元素之和都等于 target,请你的算法返回所有和为 target 的元素对儿,其中不能出现重复。

过程:可以实现,但是会重复

vector<vector<int>> twoSumTarget(vector<int>& nums, int target {
    // 先对数组排序
    sort(nums.begin(), nums.end());
    vector<vector<int>> res;
    int lo = 0, hi = nums.size() - 1;
    while (lo < hi) {
        int sum = nums[lo] + nums[hi];
        // 根据 sum 和 target 的比较,移动左右指针
        if      (sum < target) lo++;
        else if (sum > target) hi--;
        else {
            res.push_back({lo, hi});
            lo++; hi--;
        }
    }
    return res;
}

结果:跳过重复的元素

 // 先对数组排序
    sort(nums.begin(), nums.end());
    vector<vector<int>> res;
    int lo = 0, hi = nums.size() - 1;
while (lo < hi) {
    int sum = nums[lo] + nums[hi];
    // 记录索引 lo 和 hi 最初对应的值
    int left = nums[lo], right = nums[hi];
    if (sum < target)      lo++;
    else if (sum > target) hi--;
    else {
        res.push_back({left, right});
        // 跳过所有重复的元素
        while (lo < hi && nums[lo] == left) lo++;
        while (lo < hi && nums[hi] == right) hi--;
    }
}

1.3 三数之和

在这里插入图片描述

/* 从 nums[start] 开始,计算有序数组
 * nums 中所有和为 target 的二元组 */
vector<vector<int>> twoSumTarget(
    vector<int>& nums, int start, int target) {
    // 左指针改为从 start 开始,其他不变
    int lo = start, hi = nums.size() - 1;
    vector<vector<int>> res;
    while (lo < hi) {
        ...
    }
    return res;
}

/* 计算数组 nums 中所有和为 target 的三元组 */
vector<vector<int>> threeSumTarget(vector<int>& nums, int target) {
    // 数组得排个序
    sort(nums.begin(), nums.end());
    int n = nums.size();
    vector<vector<int>> res;
    // 穷举 threeSum 的第一个数
    for (int i = 0; i < n; i++) {
        // 对 target - nums[i] 计算 twoSum
        vector<vector<int>> 
            tuples = twoSumTarget(nums, i + 1, target - nums[i]);
        // 如果存在满足条件的二元组,再加上 nums[i] 就是结果三元组
        for (vector<int>& tuple : tuples) {
            tuple.push_back(nums[i]);
            res.push_back(tuple);
        }
        // 跳过第一个数字重复的情况,否则会出现重复结果
        while (i < n - 1 && nums[i] == nums[i + 1]) i++;
    }
    return res;
}

1.4 四数之和

在这里插入图片描述

vector<vector<int>> fourSum(vector<int>& nums, int target) {
    // 数组需要排序
    sort(nums.begin(), nums.end());
    int n = nums.size();
    vector<vector<int>> res;
    // 穷举 fourSum 的第一个数
    for (int i = 0; i < n; i++) {
        // 对 target - nums[i] 计算 threeSum
        vector<vector<int>> 
            triples = threeSumTarget(nums, i + 1, target - nums[i]);
        // 如果存在满足条件的三元组,再加上 nums[i] 就是结果四元组
        for (vector<int>& triple : triples) {
            triple.push_back(nums[i]);
            res.push_back(triple);
        }
        // fourSum 的第一个数不能重复
        while (i < n - 1 && nums[i] == nums[i + 1]) i++;
    }
    return res;
}

/* 从 nums[start] 开始,计算有序数组
 * nums 中所有和为 target 的三元组 */
vector<vector<int>> 
    threeSumTarget(vector<int>& nums, int start, int target) {
        int n = nums.size();
        vector<vector<int>> res;
        // i 从 start 开始穷举,其他都不变
        for (int i = start; i < n; i++) {
            ...
        }
        return res;

1.5 n数之和

/* 注意:调用这个函数之前一定要先给 nums 排序 */
vector<vector<int>> nSumTarget(
    vector<int>& nums, int n, int start, int target) {

    int sz = nums.size();
    vector<vector<int>> res;
    // 至少是 2Sum,且数组大小不应该小于 n
    if (n < 2 || sz < n) return res;
    // 2Sum 是 base case
    if (n == 2) {
        // 双指针那一套操作
        int lo = start, hi = sz - 1;
        while (lo < hi) {
            int sum = nums[lo] + nums[hi];
            int left = nums[lo], right = nums[hi];
            if (sum < target) {
                while (lo < hi && nums[lo] == left) lo++;
            } else if (sum > target) {
                while (lo < hi && nums[hi] == right) hi--;
            } else {
                res.push_back({left, right});
                while (lo < hi && nums[lo] == left) lo++;
                while (lo < hi && nums[hi] == right) hi--;
            }
        }
    } else {
        // n > 2 时,递归计算 (n-1)Sum 的结果
        for (int i = start; i < sz; i++) {
            vector<vector<int>> 
                sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
            for (vector<int>& arr : sub) {
                // (n-1)Sum 加上 nums[i] 就是 nSum
                arr.push_back(nums[i]);
                res.push_back(arr);
            }
            while (i < sz - 1 && nums[i] == nums[i + 1]) i++;
        }
    }
    return res;
}

在这里插入图片描述

2. 反转数组

在这里插入图片描述

void reverseString(char[] s) {
    // 一左一右两个指针相向而行
    int left = 0, right = s.length - 1;
    while (left < right) {
        // 交换 s[left] 和 s[right]
        char temp = s[left];
        s[left] = s[right];
        s[right] = temp;
        left++;
        right--;
    }
}

3. 回文串判断

boolean isPalindrome(String s) {
    // 一左一右两个指针相向而行
    int left = 0, right = s.length() - 1;
    while (left < right) {
        if (s.charAt(left) != s.charAt(right)) {
            return false;
        }
        left++;
        right--;
    }
    return true;
}

4. 寻找最长回文子串——双指针(由内向外)

在这里插入图片描述

// 在 s 中寻找以 s[l] 和 s[r] 为中心的最长回文串
String palindrome(String s, int l, int r) {
    // 防止索引越界
    while (l >= 0 && r < s.length()
            && s.charAt(l) == s.charAt(r)) {
        // 双指针,向两边展开
        l--; r++;
    }
    // 返回以 s[l] 和 s[r] 为中心的最长回文串
    return s.substring(l + 1, r);
}
String longestPalindrome(String s) {
    String res = "";
    for (int i = 0; i < s.length(); i++) {
        // 以 s[i] 为中心的最长回文子串
        String s1 = palindrome(s, i, i);
        // 以 s[i] 和 s[i+1] 为中心的最长回文子串
        String s2 = palindrome(s, i, i + 1);
        // res = longest(res, s1, s2)
        res = res.length() > s1.length() ? res : s1;
        res = res.length() > s2.length() ? res : s2;
    }
    return res;
}

二叉树

1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现,这叫「遍历」的思维模式。
2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。

纲领篇

1. 二叉树的最大深度

在这里插入图片描述
方法一

// 记录最大深度
int res = 0;
// 记录遍历到的节点的深度
int depth = 0;

// 主函数
int maxDepth(TreeNode root) {
	traverse(root);
	return res;
}

// 二叉树遍历框架
void traverse(TreeNode root) {
	if (root == null) {
		return;
	}
	// 前序位置
	depth++;
    if (root.left == null && root.right == null) {
        // 到达叶子节点,更新最大深度
		res = Math.max(res, depth);
    }
	traverse(root.left);
	traverse(root.right);
	// 后序位置
	depth--;
}

方法二

分解问题计算答案

// 定义:输入根节点,返回这棵二叉树的最大深度
int maxDepth(TreeNode root) {
	if (root == null) {
		return 0;
	}
	// 利用定义,计算左右子树的最大深度
	int leftMax = maxDepth(root.left);
	int rightMax = maxDepth(root.right);
	// 整棵树的最大深度等于左右子树的最大深度取最大值,
    // 然后再加上根节点自己
	int res = Math.max(leftMax, rightMax) + 1;

	return res;
}

2. 二叉树的直径

在这里插入图片描述
方法一:最坏时间复杂度是 O(N^2)

// 记录最大直径的长度
int maxDiameter = 0;

public int diameterOfBinaryTree(TreeNode root) {
    // 对每个节点计算直径,求最大直径
    traverse(root);
    return maxDiameter;
}

// 遍历二叉树
void traverse(TreeNode root) {
    if (root == null) {
        return;
    }
    // 对每个节点计算直径
    int leftMax = maxDepth(root.left);
    int rightMax = maxDepth(root.right);
    int myDiameter = leftMax + rightMax;
    // 更新全局最大直径
    maxDiameter = Math.max(maxDiameter, myDiameter);
    
    traverse(root.left);
    traverse(root.right);
}

// 计算二叉树的最大深度
int maxDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    int leftMax = maxDepth(root.left);
    int rightMax = maxDepth(root.right);
    return 1 + Math.max(leftMax, rightMax);
}

方法二:时间复杂度只有 O(N)

// 记录最大直径的长度
int maxDiameter = 0;

public int diameterOfBinaryTree(TreeNode root) {
    maxDepth(root);
    return maxDiameter;
}

int maxDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    int leftMax = maxDepth(root.left);
    int rightMax = maxDepth(root.right);
    // 后序位置,顺便计算最大直径
    int myDiameter = leftMax + rightMax;
    maxDiameter = Math.max(maxDiameter, myDiameter);

    return 1 + Math.max(leftMax, rightMax);
}

3. 层序遍历

// 输入一棵二叉树的根节点,层序遍历这棵二叉树
void levelTraverse(TreeNode root) {
    if (root == null) return;
    Queue<TreeNode> q = new LinkedList<>();
    q.offer(root);

    // 从上到下遍历二叉树的每一层
    while (!q.isEmpty()) {
        int sz = q.size();
        // 从左到右遍历每一层的每个节点
        for (int i = 0; i < sz; i++) {
            TreeNode cur = q.poll();
            // 将下一层节点放入队列
            if (cur.left != null) {
                q.offer(cur.left);
            }
            if (cur.right != null) {
                q.offer(cur.right);
            }
        }
    }
}

思路篇

1. 翻转二叉树

在这里插入图片描述
方法一:遍历的方式

// 主函数
TreeNode invertTree(TreeNode root) {
    // 遍历二叉树,交换每个节点的子节点
    traverse(root);
    return root;
}

// 二叉树遍历函数
void traverse(TreeNode root) {
    if (root == null) {
        return;
    }

    /**** 前序位置 ****/
    // 每一个节点需要做的事就是交换它的左右子节点
    TreeNode tmp = root.left;
    root.left = root.right;
    root.right = tmp;

    // 遍历框架,去遍历左右子树的节点
    traverse(root.left);
    traverse(root.right);
}

方法二: 分解问题

// 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点
TreeNode invertTree(TreeNode root) {
    if (root == null) {
        return null;
    }
    // 利用函数定义,先翻转左右子树
    TreeNode left = invertTree(root.left);
    TreeNode right = invertTree(root.right);

    // 然后交换左右子节点
    root.left = right;
    root.right = left;

    // 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root
    return root;
}

2. 填充每个二叉树节点的右侧指针

在这里插入图片描述在这里插入图片描述

// 主函数
Node connect(Node root) {
    if (root == null) return null;
    // 遍历「三叉树」,连接相邻节点
    traverse(root.left, root.right);
    return root;
}

// 三叉树遍历框架
void traverse(Node node1, Node node2) {
    if (node1 == null || node2 == null) {
        return;
    }
    /**** 前序位置 ****/
    // 将传入的两个节点穿起来
    node1.next = node2;
    
    // 连接相同父节点的两个子节点
    traverse(node1.left, node1.right);
    traverse(node2.left, node2.right);
    // 连接跨越父节点的两个子节点
    traverse(node1.right, node2.left);
}

3. 将二叉树展开为列表

在这里插入图片描述

// 定义:将以 root 为根的树拉平为链表
void flatten(TreeNode root) {
    // base case
    if (root == null) return;
    
    // 利用定义,把左右子树拉平
    flatten(root.left);
    flatten(root.right);

    /**** 后序遍历位置 ****/
    // 1、左右子树已经被拉平成一条链表
    TreeNode left = root.left;
    TreeNode right = root.right;
    
    // 2、将左子树作为右子树
    root.left = null;
    root.right = left;

    // 3、将原先的右子树接到当前右子树的末端
    TreeNode p = root;
    while (p.right != null) {
        p = p.right;
    }
    p.right = right;
}

构造篇

1. 最大二叉树

在这里插入图片描述

/* 主函数 */
TreeNode constructMaximumBinaryTree(int[] nums) {
    return build(nums, 0, nums.length - 1);
}

// 定义:将 nums[lo..hi] 构造成符合条件的树,返回根节点
TreeNode build(int[] nums, int lo, int hi) {
    // base case
    if (lo > hi) {
        return null;
    }

    // 找到数组中的最大值和对应的索引
    int index = -1, maxVal = Integer.MIN_VALUE;
    for (int i = lo; i <= hi; i++) {
        if (maxVal < nums[i]) {
            index = i;
            maxVal = nums[i];
        }
    }

    // 先构造出根节点
    TreeNode root = new TreeNode(maxVal);
    // 递归调用构造左右子树
    root.left = build(nums, lo, index - 1);
    root.right = build(nums, index + 1, hi);
    
    return root;
}

2. 通过前序和中序遍历结果构造二叉树

在这里插入图片描述
在这里插入图片描述

TreeNode build(int[] preorder, int preStart, int preEnd, 
               int[] inorder, int inStart, int inEnd) {
        
    if (preStart > preEnd) {
        return null;
    }

    // root 节点对应的值就是前序遍历数组的第一个元素
    int rootVal = preorder[preStart];
    // rootVal 在中序遍历数组中的索引
    int index = valToIndex.get(rootVal);

    int leftSize = index - inStart;

    // 先构造出当前根节点
    TreeNode root = new TreeNode(rootVal);
    // 递归构造左右子树
    root.left = build(preorder, preStart + 1, preStart + leftSize,
                      inorder, inStart, index - 1);

    root.right = build(preorder, preStart + leftSize + 1, preEnd,
                       inorder, index + 1, inEnd);
    return root;
}

3. 通过前序和中序遍历结果构造二叉树

在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/d6dd19d7ca80464eada28f374ebcb657.png在这里插入图片描述

// 存储 inorder 中值到索引的映射
HashMap<Integer, Integer> valToIndex = new HashMap<>();

TreeNode buildTree(int[] inorder, int[] postorder) {
    for (int i = 0; i < inorder.length; i++) {
        valToIndex.put(inorder[i], i);
    }
    return build(inorder, 0, inorder.length - 1,
                 postorder, 0, postorder.length - 1);
}

/* 
    build 函数的定义:
    后序遍历数组为 postorder[postStart..postEnd],
    中序遍历数组为 inorder[inStart..inEnd],
    构造二叉树,返回该二叉树的根节点 
*/
TreeNode build(int[] inorder, int inStart, int inEnd,
               int[] postorder, int postStart, int postEnd) {
    // root 节点对应的值就是后序遍历数组的最后一个元素
    int rootVal = postorder[postEnd];
    // rootVal 在中序遍历数组中的索引
    int index = valToIndex.get(rootVal);

    TreeNode root = new TreeNode(rootVal);
    // 递归构造左右子树
    root.left = build(inorder, inStart, index - 1,
                        postorder, postStart, postStart + leftSize - 1);
    
    root.right = build(inorder, index + 1, inEnd,
                        postorder, postStart + leftSize, postEnd - 1);
    return root;
}

4. 通过后序和前序遍历结果构造二叉树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution {
    // 存储 postorder 中值到索引的映射
    HashMap<Integer, Integer> valToIndex = new HashMap<>();

    public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {
        for (int i = 0; i < postorder.length; i++) {
            valToIndex.put(postorder[i], i);
        }
        return build(preorder, 0, preorder.length - 1,
                    postorder, 0, postorder.length - 1);
    }

    // 定义:根据 preorder[preStart..preEnd] 和 postorder[postStart..postEnd]
    // 构建二叉树,并返回根节点。
    TreeNode build(int[] preorder, int preStart, int preEnd,
                   int[] postorder, int postStart, int postEnd) {
        if (preStart > preEnd) {
            return null;
        }
        if (preStart == preEnd) {
            return new TreeNode(preorder[preStart]);
        }

        // root 节点对应的值就是前序遍历数组的第一个元素
        int rootVal = preorder[preStart];
        // root.left 的值是前序遍历第二个元素
        // 通过前序和后序遍历构造二叉树的关键在于通过左子树的根节点
        // 确定 preorder 和 postorder 中左右子树的元素区间
        int leftRootVal = preorder[preStart + 1];
        // leftRootVal 在后序遍历数组中的索引
        int index = valToIndex.get(leftRootVal);
        // 左子树的元素个数
        int leftSize = index - postStart + 1;

        // 先构造出当前根节点
        TreeNode root = new TreeNode(rootVal);
        // 递归构造左右子树
        // 根据左子树的根节点索引和元素个数推导左右子树的索引边界
        root.left = build(preorder, preStart + 1, preStart + leftSize,
                postorder, postStart, index);
        root.right = build(preorder, preStart + leftSize + 1, preEnd,
                postorder, index + 1, postEnd - 1);

        return root;
    }
}

在这里插入图片描述

基本操作

二叉搜索树操作的代码框架

void BST(TreeNode root, int target) {
    if (root.val == target)
        // 找到目标,做点什么
    if (root.val < target) 
        BST(root.right, target);
    if (root.val > target)
        BST(root.left, target);
}

1. 验证二叉搜索树

在这里插入图片描述

const helper = (root, lower, upper) => {
    if (root === null) {
        return true;
    }
    if (root.val <= lower || root.val >= upper) {
        return false;
    }
    return helper(root.left, lower, root.val) && helper(root.right, root.val, upper);
}
var isValidBST = function(root) {
    return helper(root, -Infinity, Infinity);
};

2. 普通树中搜索值为target的节点

TreeNode searchBST(TreeNode root, int target);
    if (root == null) return null;
    if (root.val == target) return root;
    // 当前节点没找到就递归地去左右子树寻找
    TreeNode left = searchBST(root.left, target);
    TreeNode right = searchBST(root.right, target);

    return left != null ? left : right;
}

3. 二叉搜索树中搜索

在这里插入图片描述

TreeNode searchBST(TreeNode root, int target) {
    if (root == null) {
        return null;
    }
    // 去左子树搜索
    if (root.val > target) {
        return searchBST(root.left, target);
    }
    // 去右子树搜索
    if (root.val < target) {
        return searchBST(root.right, target);
    }
    return root;
}

4. 二叉搜索树中插入一个数

TreeNode searchBST(TreeNode root, int target) {
    if (root == null) {
        return null;
    }
    // 去左子树搜索
    if (root.val > target) {
        return searchBST(root.left, target);
    }
    // 去右子树搜索
    if (root.val < target) {
        return searchBST(root.right, target);
    }
    return root;
}

5. 二叉搜索树中删除一个数

需要考虑三种情况
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

TreeNode deleteNode(TreeNode root, int key) {
    if (root == null) return null;
    if (root.val == key) {
        // 这两个 if 把情况 1 和 2 都正确处理了
        if (root.left == null) return root.right;
        if (root.right == null) return root.left;
        // 处理情况 3
        // 获得右子树最小的节点
        TreeNode minNode = getMin(root.right);
        // 删除右子树最小的节点
        root.right = deleteNode(root.right, minNode.val);
        // 用右子树最小的节点替换 root 节点
        minNode.left = root.left;
        minNode.right = root.right;
        root = minNode;
    } else if (root.val > key) {
        root.left = deleteNode(root.left, key);
    } else if (root.val < key) {
        root.right = deleteNode(root.right, key);
    }
    return root;
}

TreeNode getMin(TreeNode node) {
    // BST 最左边的就是最小的
    while (node.left != null) node = node.left;
    return node;
}

动态规划

套路框架

1. 斐波那契数列

在这里插入图片描述

int fib(int N) {
    // 备忘录全初始化为 0
    int[] memo = new int[N + 1];
    // 进行带备忘录的递归
    return helper(memo, N);
}

int helper(int[] memo, int n) {
    // base case
    if (n == 0 || n == 1) return n;
    // 已经计算过,不用再计算了
    if (memo[n] != 0) return memo[n];
    memo[n] = helper(memo, n - 1) + helper(memo, n - 2);
    return memo[n];
}

优化

int fib(int n) {
    if (n == 0 || n == 1) {
        // base case
        return n;
    }
    // 分别代表 dp[i - 1] 和 dp[i - 2]
    int dp_i_1 = 1, dp_i_2 = 0;
    for (int i = 2; i <= n; i++) {
        // dp[i] = dp[i - 1] + dp[i - 2];
        int dp_i = dp_i_1 + dp_i_2;
        // 滚动更新
        dp_i_2 = dp_i_1;
        dp_i_1 = dp_i;
    }
    return dp_i_1;
}

2. 零钱兑换

在这里插入图片描述

int[] memo;

int coinChange(int[] coins, int amount) {
    memo = new int[amount + 1];
    // 备忘录初始化为一个不会被取到的特殊值,代表还未被计算
    Arrays.fill(memo, -666);

    return dp(coins, amount);
}
// 定义:要凑出金额 n,至少要 dp(coins, n) 个硬币
int dp(int[] coins, int amount) {
    if (amount == 0) return 0;
    if (amount < 0) return -1;
    // 查备忘录,防止重复计算
    if (memo[amount] != -666)
        return memo[amount];

    int res = Integer.MAX_VALUE;
    for (int coin : coins) {
        // 计算子问题的结果
        int subProblem = dp(coins, amount - coin);
        // 子问题无解则跳过
        if (subProblem == -1) continue;
        // 在子问题中选择最优解,然后加一
        res = Math.min(res, subProblem + 1);
    }
    // 把计算结果存入备忘录
    memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res;
    return memo[amount];
}

在这里插入图片描述

子序列类型问题

1. 最长递增子序列

在这里插入图片描述

int lengthOfLIS(int[] nums) {
    // 定义:dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度
    int[] dp = new int[nums.length];
    // base case:dp 数组全都初始化为 1
    Arrays.fill(dp, 1);
    for (int i = 0; i < nums.length; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) 
                dp[i] = Math.max(dp[i], dp[j] + 1);
        }
    }
    
    int res = 0;
    for (int i = 0; i < dp.length; i++) {
        res = Math.max(res, dp[i]);
    }
    return res;
}

2. 俄罗斯套娃信封问题

在这里插入图片描述
先对宽度 w 进行升序排序,如果遇到 w 相同的情况,则按照高度 h 降序排序;之后把所有的 h 作为一个数组,在这个数组上计算 LIS 的长度就是答案。
在这里插入图片描述

// envelopes = [[w, h], [w, h]...]
public int maxEnvelopes(int[][] envelopes) {
    int n = envelopes.length;
    // 按宽度升序排列,如果宽度一样,则按高度降序排列
    Arrays.sort(envelopes, new Comparator<int[]>() 
    {
        public int compare(int[] a, int[] b) {
            return a[0] == b[0] ? 
                b[1] - a[1] : a[0] - b[0];
        }
    });
    // 对高度数组寻找 LIS
    int[] height = new int[n];
    for (int i = 0; i < n; i++)
        height[i] = envelopes[i][1];

    return lengthOfLIS(height);
}

int lengthOfLIS(int[] nums) {
    // 定义:dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度
    int[] dp = new int[nums.length];
    // base case:dp 数组全都初始化为 1
    Arrays.fill(dp, 1);
    for (int i = 0; i < nums.length; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) 
                dp[i] = Math.max(dp[i], dp[j] + 1);
        }
    }
    
    int res = 0;
    for (int i = 0; i < dp.length; i++) {
        res = Math.max(res, dp[i]);
    }
    return res;
}

3. 最大子数组和

在这里插入图片描述

int maxSubArray(int[] nums) {
    int n = nums.length;
    if (n == 0) return 0;
    // 定义:dp[i] 记录以 nums[i] 为结尾的「最大子数组和」
    int[] dp = new int[n];
    // base case
    // 第一个元素前面没有子数组
    dp[0] = nums[0];
    // 状态转移方程
    for (int i = 1; i < n; i++) {
    	// 要么自成一派,要么和前面的子数组合并
        dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]);
    }
    // 得到 nums 的最大子数组
    int res = Integer.MIN_VALUE;
    for (int i = 0; i < n; i++) {
        res = Math.max(res, dp[i]);
    }
    return res;
}

优化–降低空间复杂度

int maxSubArray(int[] nums) {
    int n = nums.length;
    if (n == 0) return 0;
    // base case
    int dp_0 = nums[0];
    int dp_1 = 0, res = dp_0;

    for (int i = 1; i < n; i++) {
        // dp[i] = max(nums[i], nums[i] + dp[i-1])
        dp_1 = Math.max(nums[i], nums[i] + dp_0);
        dp_0 = dp_1;
        // 顺便计算最大的结果
        res = Math.max(res, dp_1);
    }
    
    return res;
}

4. 最长公共子序列类型

4.1 最长公共子序列

在这里插入图片描述

// 备忘录,消除重叠子问题
int[][] memo;

/* 主函数 */
int longestCommonSubsequence(String s1, String s2) {
    int m = s1.length(), n = s2.length();
    // 备忘录值为 -1 代表未曾计算
    memo = new int[m][n];
    for (int[] row : memo) 
        Arrays.fill(row, -1);
    // 计算 s1[0..] 和 s2[0..] 的 lcs 长度
    return dp(s1, 0, s2, 0);
}

// 定义:计算 s1[i..] 和 s2[j..] 的最长公共子序列长度
int dp(String s1, int i, String s2, int j) {
    // base case
    if (i == s1.length() || j == s2.length()) {
        return 0;
    }
    // 如果之前计算过,则直接返回备忘录中的答案
    if (memo[i][j] != -1) {
        return memo[i][j];
    }
    // 根据 s1[i] 和 s2[j] 的情况做选择
    if (s1.charAt(i) == s2.charAt(j)) {
        // s1[i] 和 s2[j] 必然在 lcs 中
        memo[i][j] = 1 + dp(s1, i + 1, s2, j + 1);
    } else {
        // s1[i] 和 s2[j] 至少有一个不在 lcs 中
        memo[i][j] = Math.max(
            dp(s1, i + 1, s2, j),
            dp(s1, i, s2, j + 1)
        );
    }
    return memo[i][j];
}

4.2 两个字符串的删除操作

在这里插入图片描述
那么,要计算删除的次数,就可以通过最长公共子序列的长度推导出来

int minDistance(String s1, String s2) {
    int m = s1.length(), n = s2.length();
    // 复用前文计算 lcs 长度的函数
    int lcs = longestCommonSubsequence(s1, s2);
    return m - lcs + n - lcs;
}

4.3 最小ascii删除和

在这里插入图片描述

// 备忘录
int memo[][];
/* 主函数 */    
int minimumDeleteSum(String s1, String s2) {
    int m = s1.length(), n = s2.length();
    // 备忘录值为 -1 代表未曾计算
    memo = new int[m][n];
    for (int[] row : memo) 
        Arrays.fill(row, -1);

    return dp(s1, 0, s2, 0);
}

// 定义:将 s1[i..] 和 s2[j..] 删除成相同字符串,
// 最小的 ASCII 码之和为 dp(s1, i, s2, j)。
int dp(String s1, int i, String s2, int j) {
    int res = 0;
    // base case
    if (i == s1.length()) {
        // 如果 s1 到头了,那么 s2 剩下的都得删除
        for (; j < s2.length(); j++)
            res += s2.charAt(j);
        return res;
    }
    if (j == s2.length()) {
        // 如果 s2 到头了,那么 s1 剩下的都得删除
        for (; i < s1.length(); i++)
            res += s1.charAt(i);
        return res;
    }

    if (memo[i][j] != -1) {
        return memo[i][j];
    }

    if (s1.charAt(i) == s2.charAt(j)) {
        // s1[i] 和 s2[j] 都是在 lcs 中的,不用删除
        memo[i][j] = dp(s1, i + 1, s2, j + 1);
    } else {
        // s1[i] 和 s2[j] 至少有一个不在 lcs 中,删一个
        memo[i][j] = Math.min(
            s1.charAt(i) + dp(s1, i + 1, s2, j),
            s2.charAt(j) + dp(s1, i, s2, j + 1)
        );
    }
    return memo[i][j];
}

背包类型问题

1. 0-1背包问题

在这里插入图片描述
dp[i][w] 表示:对于前 i 个物品(从 1 开始计数),当前背包的容量为 w 时,这种情况下可以装下的最大价值是 dp[i][w]。

如果你没有把这第 i 个物品装入背包,那么很显然,最大价值 dp[i][w] 应该等于 dp[i-1][w],继承之前的结果。

如果你把这第 i 个物品装入了背包,那么 dp[i][w] 应该等于 val[i-1] + dp[i-1][w - wt[i-1]]。

首先,由于数组索引从 0 开始,而我们定义中的 i 是从 1 开始计数的,所以 val[i-1] 和 wt[i-1] 表示第 i 个物品的价值和重量。

你如果选择将第 i 个物品装进背包,那么第 i 个物品的价值 val[i-1] 肯定就到手了,接下来你就要在剩余容量 w - wt[i-1] 的限制下,在前 i - 1 个物品中挑选,求最大价值,即 dp[i-1][w - wt[i-1]]。

int knapsack(int W, int N, int[] wt, int[] val) {
    // base case 已初始化
    int[][] dp = new int[N + 1][W + 1];
    for (int i = 1; i <= N; i++) {
        for (int w = 1; w <= W; w++) {
            if (w - wt[i - 1] < 0) {
                // 这种情况下只能选择不装入背包
                dp[i][w] = dp[i - 1][w];
            } else {
                // 装入或者不装入背包,择优
                dp[i][w] = Math.max(
                    dp[i - 1][w - wt[i-1]] + val[i-1], 
                    dp[i - 1][w]
                );
            }
        }
    }
    
    return dp[N][W];
}

2. 分割等和子集

在这里插入图片描述
我们可以先对集合求和,得出 sum,把问题转化为背包问题:

给一个可装载重量为 sum / 2 的背包和 N 个物品,每个物品的重量为 nums[i]。现在让你装物品,是否存在一种装法,能够恰好将背包装满?

dp[i][j] = x 表示,对于前 i 个物品(i 从 1 开始计数),当前背包的容量为 j 时,若 x 为 true,则说明可以恰好将背包装满,若 x 为 false,则说明不能恰好将背包装满。

boolean canPartition(int[] nums) {
    int sum = 0;
    for (int num : nums) sum += num;
    // 和为奇数时,不可能划分成两个和相等的集合
    if (sum % 2 != 0) return false;
    int n = nums.length;
    sum = sum / 2;
    boolean[][] dp = new boolean[n + 1][sum + 1];
    // base case
    for (int i = 0; i <= n; i++)
        dp[i][0] = true;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= sum; j++) {
            if (j - nums[i - 1] < 0) {
                // 背包容量不足,不能装入第 i 个物品
                dp[i][j] = dp[i - 1][j];
            } else {
                // 装入或不装入背包
                dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
            }
        }
    }
    return dp[n][sum];
}

3. 零钱兑换

在这里插入图片描述
首先看看刚才找到的「状态」,有两个,也就是说我们需要一个二维 dp 数组。

dp[i][j] 的定义如下:

若只使用前 i 个物品(可以重复使用),当背包容量为 j 时,有 dp[i][j] 种方法可以装满背包。

换句话说,翻译回我们题目的意思就是:

若只使用 coins 中的前 i 个(i 从 1 开始计数)硬币的面值,若想凑出金额 j,有 dp[i][j] 种凑法。

经过以上的定义,可以得到:

base case 为 dp[0][…] = 0, dp[…][0] = 1。因为如果不使用任何硬币面值,就无法凑出任何金额;如果凑出的目标金额为 0,那么“无为而治”就是唯一的一种凑法。

我们最终想得到的答案就是 dp[N][amount],其中 N 为 coins 数组的大小。

如果你不把这第 i 个物品装入背包,也就是说你不使用 coins[i-1] 这个面值的硬币,那么凑出面额 j 的方法数 dp[i][j] 应该等于 dp[i-1][j],继承之前的结果。

如果你把这第 i 个物品装入了背包,也就是说你使用 coins[i-1] 这个面值的硬币,那么 dp[i][j] 应该等于 dp[i][j-coins[i-1]]。

int change(int amount, int[] coins) {
    int n = coins.length;
    int[][] dp = int[n + 1][amount + 1];
    // base case
    for (int i = 0; i <= n; i++) 
        dp[i][0] = 1;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= amount; j++)
            if (j - coins[i-1] >= 0)
                dp[i][j] = dp[i - 1][j] 
                         + dp[i][j - coins[i-1]];
            else 
                dp[i][j] = dp[i - 1][j];
    }
    return dp[n][amount];
}

最小路径和

在这里插入图片描述
在这里插入图片描述

int[][] memo;

int minPathSum(int[][] grid) {
    int m = grid.length;
    int n = grid[0].length;
    // 构造备忘录,初始值全部设为 -1
    memo = new int[m][n];
    for (int[] row : memo)
        Arrays.fill(row, -1);
    
    return dp(grid, m - 1, n - 1);
}

int dp(int[][] grid, int i, int j) {
    // base case
    if (i == 0 && j == 0) {
        return grid[0][0];
    }
    if (i < 0 || j < 0) {
        return Integer.MAX_VALUE;
    }
    // 避免重复计算
    if (memo[i][j] != -1) {
        return memo[i][j];
    }
    // 将计算结果记入备忘录
    memo[i][j] = Math.min(
        dp(grid, i - 1, j),
        dp(grid, i, j - 1)
    ) + grid[i][j];

    return memo[i][j];
}

一个方法团灭 LEETCODE 打家劫舍问题

一个方法团灭 LEETCODE 股票买卖问题

排序类

1. 冒泡排序类

1. 冒泡排序+优化

// 第一版
let bubbleSort = arr => {
	for (let i = 0; i < arr.length; i++) {
		for (let j = 0; j < arr.length - 1 - i; j++) {
			let tmp
            // 容易忘记写if判断语句
            // if判断语句容易写成arr[i] > arr[j]
			if (arr[j] > arr[j + 1]) {
				tmp = arr[j]
				arr[j] = arr[j + 1]
				arr[j + 1] = tmp
			}
		}
	}
	return arr
}
console.log(bubbleSort([2, 3, 4, 1, 2, 3, 2, 1, 9, 4, 6, 9, 6]))

// 第二版(优化1)
// 第二版的优化之处在于使用一个标志位判断相邻两个数之间是否有数据交换
// 如果有数据交换,则将标志位设置为false
// 如果没有数据交换,则表明前一个数比后一个数小,不需要执行,跳出大的循环,节省时间
let bubbleSort = arr => {
	for (let i = 0; i < arr.length; i++) {
		// 设置一个标志位,默认正序排列
		let isTrue = true
		for (let j = 0; j < arr.length - 1 - i; j++) {
			let tmp
			if (arr[j] > arr[j + 1]) {
				tmp = arr[j]
				arr[j] = arr[j + 1]
				arr[j + 1] = tmp
				// 如果交换元素,将标志位设置为false,表明是乱序排列
				isTrue = false
			}
		}
        // 这个地方容易错写成isTrue = true
		// 如果没有交换元素表明是正序排列,跳出大循环
		if (isTrue) {
			break
		}
	}
	return arr
}
console.log(bubbleSort([2, 3, 4, 1, 2, 3, 2, 1, 9, 4, 6, 9, 6]))

// 第三版(优化2)
// 第三版相比较第二版的优化之处在于增加了有效长度
	// 假如说排序之后最后面的几个元素是已经排好序的56789,那么我们在下面的排序过程中便不需要遍历到后面的5个元素了,只需要遍历到有效长度的位置即可,而这个有效长度便是最后交换元素的索引
let bubbleSort = arr => {
	for (let i = 0; i < arr.length; i++) {
		let isTrue = true
		// 默认最后交换元素的索引为0
		let lastChangeIndex = 0
		// 默认初始有效长度为数组总长度
		let bubbleLength = arr.length - 1
        // 上面一行bubbleLength的初始长度容易写错
        //下面一行j < bubbleLength容易写错
		for (let j = 0; j < bubbleLength; j++) {
			let tmp
			if (arr[j] > arr[j + 1]) {
				tmp = arr[j]
				arr[j] = arr[j + 1]
				arr[j + 1] = tmp
				isTrue = false
				// 最后交换元素的索引
				lastChangeIndex = j
			}
		}
		// 将最后交换元素的索引赋值给有效数组长度
		bubbleLength = lastChangeIndex
		if (isTrue) {
			break
		}
	}
	return arr
}
console.log(bubbleSort([2, 3, 4, 1, 2, 3, 2, 1, 9, 4, 6, 9, 6]))

2. 数组中的第K个最大元素

// 方法一:使用sort
export default (arr, k) => {
  // arr.sort((a, b) => b - a)返回的是一个数组,选取数组的下标的k-1个元素,因为索引下标是从0开始的
  return arr.sort((a, b) => b - a)[k - 1]
}
// 方法二:面试官肯定会让你使用优化的方法来解决的,这个该怎么解决呢?
// 第一种方法使用sort()是按照顺序将整个数组全部循环一遍,这个样子会使得时间复杂度和空间复杂度都比较大
// 而优化的方法如下:
// 因为我们要求的是第K个元素的最大值,所以我们使用冒泡排序,直接直接冒泡到第K个元素即可,这样时间复杂度和空间复杂度都会节省很多
let findK = (arr, k) => {
	for (let i = 0; i < k + 1; i ++) {
		for (let j = 0; j < arr.length - 1 - i; j ++) {
			let tmp
			if (arr[j] > arr[j + 1]) {
				tmp = arr[j]
				arr[j] = arr[j + 1]
				arr[j + 1] = tmp
			}
		}
	}
	return arr[arr.length - k]
}
console.log(findK([1,4,5,2,7,9,2,3,7,6], 6))

3. 最大间距

在这里插入图片描述

// 第一版
function selectionSort(arr, k) {
	let max = 0
	let space
	for (let i = 0; i < arr.length; i++) {
		for (let j = 0; j < arr.length - 1 - i; j++) {
			if (arr[j] > arr[j + 1]) {
				let tmp = arr[j]
				arr[j] = arr[j + 1]
				arr[j + 1] = tmp
			}
		}
		// 从第二轮循环开始,因为第一轮只有一个排序完毕
		// 已经排好序的两个元素的差值
		if (i > 0) {
			space = arr[arr.length - i] - arr[arr.length - 1 - i]
			if (space > max) {
				max = space
			}
		}
	}
	return max
}
console.log(selectionSort([6, 4, 2, 9, 5, 3], 2))

2. 选择排序

在这里插入图片描述

// 第一版
function selectionSort(arr) {
	let len = arr.length
	for (let i = 0; i < len; i++) {
		// 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
		let minIndex = i
		for (let j = i + 1; j < len; j++) {
			if (arr[j] < arr[minIndex]) { // 寻找最小的数
				minIndex = j // 保存最小数的索引
			}
		}
		// 将最小数与第一个数交换,放在起始位置(已排序元素的最后,未排序元素的最前面)
		let tmp = arr[i]
        // 容易将minIndex写成
		arr[i] = arr[minIndex]
		arr[minIndex] = tmp
	}
	return arr;
}
console.log(selectionSort([2, 3, 4, 1, 2, 3, 2, 1, 9, 4, 6, 9, 6]))

3. 奇偶排序

// 第一版
function selectionSort(arr) {
	let even = 1
	let odd = 0
	let r = []
	for (let i = 0; i < arr.length; i++) {
		if (arr[i] % 2 === 0) {
			r[odd] = arr[i]
			odd += 2
		} else {
			r[even] = arr[i]
			even += 2
		}
	}
	return r
}
console.log(selectionSort([4, 2, 5, 7]))

4. 选择排序

在这里插入图片描述
基础版本

// 方法一:基础算法
export default (arr) => {
  let quickSort = (arr) => {
    let len = arr.length
    // 容易忘记写递归终止的条件
    if (len < 2) {
      return arr
    } else {
      // 选标尺元素
      let flag = arr[0]
      let left = []
      let right = []
      // 把剩余的元素遍历下,比标尺元素小的放左边,大的放右边
      for (let i = 1, tmp; i < len; i++) {
        tmp = arr[i]
        if (tmp < flag) {
          left.push(tmp)
        } else {
          right.push(tmp)
        }
      }
      // 进行递归操作(左边数组+标尺元素+右边元素)
      return quickSort(left).concat(flag, quickSort(right))
    }
  }
  return quickSort(arr)
}

进阶版本

function quickSort(arr) {
            // 寻找中点
            function findCenter(arr, left, right) {
                let idx = left + 1
                // 小于等于
                for (let i = idx; i <= right; i++) {
                    if (arr[left] > arr[i]) {
                        swap(arr, idx, i)
                        idx++ 
                    }
                }
                //idx - 1容易写错
                swap(arr, left, idx - 1)
                return idx - 1
            }
            // 交换两个数
            function swap(arr, left, right) {
                let tmp = arr[left]
                arr[left] = arr[right]
                arr[right] = tmp 
            }
            // 递归调用
            function sort(arr, left, right) {
                // 容易忘记写这个if的判断语句
                if (left < right) {
                // 容易将这一句话写在if语句上面 
                    let center = findCenter(arr, left, right)
                    sort(arr, left, center - 1)
                    sort(arr, center + 1, right)
                }
            }
            // 调用执行该函数
            sort(arr, 0, arr.length - 1)
            return arr
        }

5. 插入排序

插入排序,一般也被称为直接插入排序。对于少量元素的排序,它是一个有效的算法 [1] 。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增1的有序表。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动 [2] 。

时间复杂度O(n*2)
空间复杂度O(1)

 function insertSort(arr) {
            let len = arr.length
            let preIndex,current
            // 像玩纸牌那样插入
            for (let i = 1; i < len; i++) {
                preIndex = i - 1
                // 一定要将arr[i]赋值给current,否则结果不如人意
                current = arr[i]
                // 这部分容易将current写成arr[i],既然前面将arr[i]赋值给current,那么后面的arr[i]就没用了,要用current
                while (preIndex > 0 && arr[preIndex] > current) {
                    arr[preIndex + 1] = arr[preIndex]
                    preIndex--
                }
                arr[preIndex + 1] = current
            }
            return arr
        }

6. 希尔排序

在这里插入图片描述

7. 并归排序

在这里插入图片描述

在这里插入图片描述

function mergeSort(arr) {
            let len = arr.length 
            // 递归终止的条件
            if (len < 2) return arr 
            let middle = Math.floor(len / 2)
            // 拆分两个子数组
            let left = arr.slice(0, middle)
            let right = arr.slice(middle) 
            // 递归拆分
            let mergeSortLeft = mergeSort(left)
            let mergeSortRight = mergeSort(right)
            // 合并两个子数组
            return merge(mergeSortLeft, mergeSortRight)
        }
        function merge(left, right) {
            let res = []
           while(left.length && right.length) {
               // 注意: 判断的条件是小于或等于,如果只是小于,那么排序将不稳定.
            if (left[0] <= right[0]) {
                //每次都要删除left或者right的第一个元素,将其加入result中
                res.push(left.shift())
            } else {
                res.push(right.shift())
            }
           }
           //将剩下的元素加上
           while(left.length) {
               res.push(left.shift())
           }
           while(right.length) {
               res.push(right.shift())
           }
           return res
        }
        console.log(mergeSort([2,4,7,5,0,1,6,9]))

8.堆排序

 // 排序的过程
        function sort(arr) {
            let n = arr.length 
            if (n < 2) return arr 
            // 一次整的构建最大堆的过程
            // i = Math.floor(n / 2)是最后一个父节点的索引,从最后一个父节点开始倒叙构建最大堆
            for(let i = Math.floor(n / 2); i>= 0; i--) {
                maxHeap(arr, i, n)
            }
            // n个元素构建最大堆
            for(let j = 0; j < n; j++) {
                // 第一个元素与最后一个元素交换 n - 1 - j是有效长度(n从0-n-1的)
                swap(arr, 0, n - 1 - j)
                // 交换一个,扔掉最后一个元素,有效长度要小一
                // 之所以用0交换是因为索引为0的值与最后一个值交换之后,最大堆从0的地方被破坏了,所以从0开始交换
                maxHeap(arr, 0, n - 1 - j - 1)
            }
            return arr
        }
        // 两个数据的交换
        function swap(arr, i, j) {
            let tmp = arr[i]
            arr[i] = arr[j]
            arr[j] = tmp 
        }
        // 构建一个最大堆的过程(i是(每个节点)父元素的索引,size是有效长度)
        function maxHeap(arr, i, size) {
            // i 是父节点索引,l是左子节点的索引
            let l = i * 2 + 1
            // i 是父节点索引,r是右子节点的索引
            let r = i * 2 + 2
            // 使largest指向左节点右节点父节点中的最大值
            // 默认largest指向父节点
            let largest = i
            // 每次构建一次最大堆,第一个与最后一个节点互换位置,那么下次构建最大堆的时候,最后一个节点便不参与其中,所以有效长度要减少一个,数组的长度是不变的,变化的是参与构建最大堆的有效长度
            if (l <= size && arr[largest] < arr[l]) largest = l
            if (r <= size && arr[largest] < arr[r]) largest = r
            // 如果largest 与 i 不相同,那么将值做交换使得父节点为最大值
            if (largest !== i) {
               swap(arr, i, largest) 
            //  交换完成之后可能会影响到子节点的最大堆的顺序,所以对子节点继续递归做构建最大堆
            // 交换完成之后,largest相当于子节点中的父节点,有效长度不变,因为我们没有将顶元素与最后一个元素交换来将最后一个元素从堆中移出
               maxHeap(arr, largest, size)
            }
        }
        console.log(sort([3,2,4,1,7,5,9]))
function shellSort(arr) {
            let len = arr.length 
            let gap = Math.floor(len / 2)
            while (gap !== 0) {
                // 下面完全就是插入排序,只是插入排序的起始值1变为gap了
                for (let i = gap; i < len; i++) {
                    let preIndex = i - gap 
                    let current = arr[i]
                    //preIndex = i - gap >= 0-->i >= gap
                    while(i >= gap && arr[preIndex] > current) {
                        arr[preIndex + gap] = arr[preIndex]
                        // 这个地方容易错写成 preIndex - gap
                        preIndex -= gap 
                    }
                    arr[preIndex + gap] = current
                }
                gap = Math.floor(gap / 2)
            }
            return arr
        }
        console.log(shellSort([2,4,7,5,0,1,6,9]))

手写js面试题

1. 手写Promise

分步骤参考链接

const PENDING = 1
        const FULFILLED = 2
        const REJECTED = 3
        class JPromise {
            constructor(excutor) {
                const that = this 
                that.status = PENDING 
                that.value = null 
                that.error = null 
                that.resolveCallback = []
                that.rejectCallback = []
                function resolve(value) {
                    if (value instanceof JPromise) {
                        value.then(resolve,reject)
                    }
                    if (that.status === PENDING) {
                        that.status = FULFILLED
                        that.value = value
                        that.resolveCallback.forEach(fn => fn(value))
                    }
                }
                function reject(error) {
                    if (error instanceof JPromise) {
                        error.then(resolve,reject)
                    }
                    if (that.status === PENDING) {
                        that.status = REJECTED
                        that.error = error
                        that.rejectCallback.forEach(fn => fn(error))
                    }
                }
                try {
                    excutor(resolve, reject)
                } catch(e) {
                    reject(e)
                }
            }
            then(onResolved, onRejected) {
                let that = this
                if (that.status === PENDING) {
                    return new JPromise((resolve, reject) => {
                        setTimeout(() => {
                            that.resolveCallback.push(() => {
                                try {
                                    resolvePromise(onResolved(that.value), resolve, reject)
                                } catch(e) {
                                    reject(e)
                                }
                            }
                            )
                        })
                        that.rejectCallback.push(() => {
                            try {
                                    resolvePromise(onRejected(that.error), resolve, reject)
                                } catch(e) {
                                    reject(e)
                                }
                        })
                        
                    })
                }
                if (that.status === FULFILLED) {
                    return new JPromise((resolve, reject) => {
                        setTimeout(() => {
                        try {
                            resolvePromise(onResolved(that.value), resolve, reject)
                        } catch(e) {
                            reject(e)
                        }
                    })
                    })
                    
                }
                if (that.status === REJECTED) {
                    return new JPromise((resolve, reject) => {
                        setTimeout(() => {
                        try {
                            resolvePromise(onRejected(that.error), resolve, reject)
                        } catch(e) {
                            reject(e)
                        }
                    })
                    })
                }
            }
            catch(onRejected) {
                return this.then(null, onRejected)
            }
            finally(callback) {
                return this.then(callback, callback)
            }
            static resolve(value) {
                return value instanceof JPromise ? value : new JPromise(resolve => resolve(value))
            }
            static reject(error) {
                return error instanceof JPromise ? error : new JPromise(null, reject => {reject(error)})
            }
            static all(promises) {
                promises = Array.isArray(promises) ? promises : [promises]
                return new JPromise((resolve, reject) => {
                    const length = promises.length 
                    let values = []
                    let counter = 0
                    promises.forEach((singlePromise, index) => {
                        if (!(singlePromise instanceof JPromise)) {
                            singlePromise = JPromise.resolve(singlePromise)
                        }
                        singlePromise.then(ret => {
                            values[index] = ret
                            if (++counter === length) resolve(values)
                        },reject)
                    }) 
                })
            }
            static race(promises) {
                promises = Array.isArray(promises) ? promises : [promises]
                return new JPromise((resolve, reject) => {
                    promises.forEach(singlePromise => {
                        if (!(singlePromise instanceof JPromise)) {
                            singlePromise = JPromise.resolve(singlePromise)
                        }
                        singlePromise.then(resolve, reject)
                    })
                })
            }
        }
        function resolvePromise(retValue, resolve, reject) {
            if (retValue instanceof JPromise) {
                if (retVaule.status === PENDING) {
                    retVaule.then(ret => {
                        resolvePromise(ret, resolve, reject)
                    })
                } else {
                    retVaule.then(resolve, reject)
                }
            } else {
                resolve(retValue)
            }
        }
        let p1 = new JPromise((resolve, reject) => {
            setTimeout(() => {
                resolve(1)
            },2000)
        })
        let p2 = new JPromise((resolve, reject) => {
            setTimeout(() => {
                resolve(2)
            },1000)
        })
        let p3 = new JPromise((resolve, reject) => {
            setTimeout(() => {
                reject('error')
            },2000)
        })
        let p4 = new JPromise((resolve, reject) => {
            setTimeout(() => {
                resolve(5)
            },1000)
        })
        JPromise.race([p1,p3,p4,]).then(res => {
            console.log(res)
        }, err => {
            console.log(err)
        })

2. 手写一个简单的双向绑定(Object.defineProperty)

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>双向绑定</title>
</head>
<body>
	<input type="text" id="model">
	<div id="modelText"></div>
    <button id="btn"></button>
</body>
<script>
	let input = document.querySelector('#model')
	let text = document.querySelector('#modelText')
    let btn = document.querySelector('#btn')

	let data = {value: ''}
	// 三个参数:操作的对象,改变的对象的属性,执行的逻辑
    // 数据-->视图  model--> view
	Object.defineProperty(data, 'value', {
		get: function() {
			return 'input.value'
		},
		// 是赋值的过程,将值展示到我们想要展示在页面中的位置
		set: function(val) {
			input.value = val
			text.innerText = val
			ok.innerText = val
		}
	})
	// 触发的事件,触发该事件,将我们改变的值赋值给data中的value属性
	// 视图-->数据  view-->model
 input.addEventListener('keyup',function(val){
      data.value = input.value;
    })
 // 点击按钮,清空div中的内容   
    btn.onclick = function() {
			text.innerText = ''
		}
</script>
</body>
</html>

3. 手写一个简单的双向绑定(Proxy)

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>双向绑定</title>
</head>
<body>
    <input type="text" id="input">
    <span id="p"></span>
</body>
<script>
let input = document.getElementById('input')
        let span = document.getElementById('p')
        let obj = {}
        // 数据  到  视图
        const newObj = new Proxy(obj, {
            get: function(target, key, receiver) {
                
                return Reflect.get(target, key, receiver)
            },
            // 容易忘记这四个参数
            // target目标对象,对应obj
            // key 对应obj的key,此处为text
            // value 对应obj的value,此处为 输入的值
            // receiver 对应接收者,此处对应为Proxy{}对象
            set: function(target, key, value, receiver) {
                input.value = value 
                span.innerText = value
                //可以打印出来看他们分别是什么
                // console.log('target',target)
                // console.log('key',key)
                // console.log('value',value)
                // console.log('receiver',receiver)
                return Reflect.set(target, key, value, receiver)
            }
        })
        // 视图  到  数据
        input.onkeyup = function() {
            newObj.text = input.value
        }
</script>
</body>
</html>

4. 实现一个sleep函数

// Promise
const sleep = time => {
	return new Promise(resolve => setTimeout(resolve, time))
}
sleep(1000).then(() => {
	console.log(1)
})

// generator
function* sleep(time) {
	yield new Promise(resolve => setTimeout(resolve, time))
}
sleep(1000).next().value.then(() => {
	console.log(1)
})

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

async function output() {
	let out = await sleep(1000)
	console.log(1)
	return out
}
output()

// ES5
function sleep(fn, time) {
	if (typeof fn === 'function') {
		setTimeout(fn, 1000)
	}
}

function output() {
	console.log(1)
}
sleep(output, 1000)

5. 实现(5).add(3).minus(2)

Number.prototype.add = function(n) {
	return this.valueOf() + n
}
Number.prototype.minus = function(n) {
	return this.valueOf() - n
}
console.log((5).add(3).minus(2))

6. 手写观察者模式

const Subject = (() => {
            const observers = []
            const addOb = (ob) => {
                observers.push(ob)
            }
            const notify = () => {
                for (let ob of observers) {
                    if (typeof ob.update === 'function') {
                        ob.update()
                    }
                }
            }
            return {addOb, notify}
        })()
        let subA = {
            update: () => {
                console.log('subA')
            }
        }
        let subB = {
            update: () => {
                console.log('subB')
            }
        }
        Subject.addOb(subA) //添加观察者subA
        Subject.addOb(subB) //添加观察者subB
        Subject.notify() //通知所有的观察者

7. 手写发布订阅模式

const PubSub = (() => {
            const topics = {} // 保存订阅主题
            //订阅某类型的主题
            const subscribe = (type, fn) => {
                if (!topics[type]) topics[type] = []
                topics[type].push(fn)
            }
            // 发布某类型的主题
            const publish = type => {
                if (!topics[type]) return 
                for (let fn of topics[type]) {
                    fn()
                }
            }
            return {subscribe, publish}
        })()
        let subA = {type: '1'}
        let subB = {type: '2'}
        let subC = {type: '3'};
        PubSub.subscribe(subA.type, () => {console.log(`A:${subA.type}`)})
        PubSub.subscribe(subB.type, () => {console.log(`B:${subB.type}`)})
        PubSub.subscribe(subC.type, () => {console.log(`C:${subC.type}`)})
        PubSub.publish(subB.type)

8. 实现Promise.prototype.finally

Promise.prototype.finally = (callback) => {
            return this.then(
                value => this.constructor.resolve(callback).then(() => value),
                err => this.constructor.resolve(callback).then(() => {throw err})
            )
        }

9. 手写Ajax

// 封装ajax
function ajax({
	url,
	type,
	data
}) {
	// 创建异步请求对象
	let xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP')
	// 处理get请求
	if (type.toLowerCase() === 'get' && data !== undefined) {
		url += "?" + data
		xhr.open(type, url, true)
		xhr.send()
	}
	// 处理post请求
	if (type.toLowerCase() === 'post') {
		xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
		xhr.open(type, url, true)
		data !== undefined ? xhr.send(data) : xhr.send(null)

	}
	// 绑定监听事件
	xhr.onreadystatechange = function() {
		if (xhr.readyState === 4 && xhr.status === 200) {
			let res = xhr.responseText
			if (typeof res === 'string') {
				res = JSON.parse(res)
			}
		} else {
			console.log(xhr.responseText)
		}
	}
}

10. Promise封装ajax

function ajax({type, url, data}) {
            return new Promise((resolve, reject) => {
                let xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP')
                if (type.toLowerCase() === 'get' && data) {
                    url += '?' + data
                    xhr.open(type, url, true)
                    xhr.send()
                }
                if (type.toLowerCase() === 'post') {
                    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
                    xhr.open(type, url, true)
                    data ? xhr.send(data) : xhr.send(null)
                }
                if (xhr.onreadyState === 4 && xhr.status === 200) {
                    resovle(xhr.responseText)
                } else {
                    reject('error')
                }
            })            
        }
        ajax(type, url, data)
        .then(res => {
            console.log(res)
        }).catch(e => {
            throw error(e)
        })

11. 手写百度搜索框

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        fieldset, img, input, button {
            border: none;
            padding: 0;
            margin: 0;
            outline-style: none;
        }


        ul, ol {
            list-style: none;
            margin: 0px;
            padding: 0px;
        }


        #box {
            width: 405px;
            margin: 200px auto;
            position: relative;
        }


        #txtSearch {
            float: left;
            width: 300px;
            height: 32px;
            padding-left: 4px;
            border: 1px solid #b6b6b6;
            border-right: 0;
        }


        #btnSearch {
            float: left;
            width: 100px;
            height: 34px;
            font: 400 14px/34px "microsoft yahei";
            color: white;
            background: #3385ff;
            cursor: pointer;
        }


        #btnSearch:hover {
            background: #317ef3;
        }


        #pop {
            width: 303px;
            border: 1px solid #ccc;
            padding: 0px;
            position: absolute;
            top: 34px;
        }


        #pop ul li {
            padding-left: 5px;
        }


        #pop ul li:hover {
            background-color: #CCC;
        }
    </style>
</head>
<body>
<div id="box">
    <input type="text" id="txtSearch">
    <input type="button" value="百度一下" id="btnSearch">
</div>
<script>
     var box = document.getElementById("box");
    var txt = document.getElementById("txtSearch");
    //模拟从后台获取到的数据
    var data = ["a", "abc", "abbbb", "abxxxx", "xyz", "abcdef", "abzzzz",'中国','中央'];
    //1.用户输入的同时 从datas中 寻找符合要求的 项目 放到一个新数组中
    
    txt.onkeyup = function() {
         var val = this.value;//获取当前文本框中的内容
        //1.1从datas中 寻找符合要求的 项目 放到一个新数组中
        var arr = [];
        //1.2遍历datas里面的每一项 判断是否符合要求 如果符合要求 就放到新数组中
        for (var i = 0; i < data.length; i++) {
            //原数组的每一项数据
            //判断每一个data是否以当前的val开头
            if (data[i].indexOf(val) === 0) {//符合要求
                arr.push(data[i]);
            }
        }
  		 var popDiv = document.getElementById("pop");
        if (popDiv) {
            //如果进来了 说明已经有了 就要把他干掉
            box.removeChild(popDiv);
        }
        // 如果后台中没有对应的数据,不创建DOM
        if (arr.length === 0) {
            return;
        }
        // 如果输入内容为空,不创建DOM
          if (val === "") {
            return;
        }
        // 创建一个div,设置div的id,将div加到box中
  	  var popDiv = document.createElement('div')
    	 popDiv.id = 'pop'
        box.appendChild(popDiv)
        // 创建一个ul,将ul加到div中
        let ul = document.createElement('ul')
            popDiv.appendChild(ul)
        // 遍历符合规则的数据,每遍历一个数据动态创建一个li,将数据的内容添加到li中
        for (let i = 0; i < arr.length; i++) {
            let li = document.createElement('li')
            ul.appendChild(li)
            li.innerText = arr[i]
            // 关键词高亮显示
            //split(val):将字符串以val为分隔符,转换为数组,例如:‘abcd’.split('a')为['','bcd'].
            //join(val):将数组以val为分隔符转换为字符串,例如['','bcd'].join('a') 为‘abcd’,例如['b','c','d'].join('a')为‘abcad’
            li.innerHTML = li.innerHTML.split(val).join('<span style="color:red;">' + val + '</span> ')
            // 点击li,将li中的内容添加到input中,将pop隐藏
            li.onclick = function() {
                txt.value = this.innerText
                popDiv.style.display = "none"
            }
            // 鼠标移入高亮显示
            li.onmousemove = function() {
            	this.style.backgroundColor = "red"
            }
            li.onmouseout = function() {
            	this.style.backgroundColor = ""
            }
            // 点击空白,pop隐藏
            document.onclick = function() {
       			popDiv.style.display = "none"
    		}

        }
    }
// 页面跳转
    btn.onclick = function() {
			window.location.href = 'https://www.baidu.com'
		}
    //todo:用上下键选择内容(涉及到事件对象)(有兴趣可以研究一下)


</script>
</body>
</html>

12. 原生js实现耳机下拉框联动

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <div id='container'>
        <select id="line" onchange="getStation()">
            <option value="0">请选择</option>
        </select>
        <select id="station">
            <option value="0">请选择</option>
        </select>
    </div>
</head>
<body>
    <script>
        let lines = ['102','103']
        let stationName = [["百万庄西口", "百万庄中街", "百万庄东口", "展览路", "阜成门外", "阜成门", "阜成门内", "白塔寺", "西四路口东", "西安门", "北海", "故宫", "沙滩路口西", "美术馆东", "东四路口东", "朝内小街", "朝阳门内", "朝阳门外", "神路街", "东大桥路口东", "关东店", "呼家楼西", "小庄路口东", "红庙路口西", "红庙路口东"],             ["动物园枢纽站", "二里沟", "百万庄", "甘家口大厦", "甘家口东", "阜外西口", "展览路", "阜成门外", "阜成门", "阜成门内", "白塔寺", "西四路口西", "缸瓦市", "甘石桥", "西单商场", "西单路口南", "宣武门内", "校场口", "菜市口北", "果子巷", "虎坊桥路口南", "太平街北口", "太平街", "陶然桥北", "永定门长途汽车站", "北京南站"]         ]
        let line = document.getElementById('line')
        // 将数据渲染到第一个下拉框中
        function aa() {
            for(let i = 0; i < lines.length; i++) {
                line.innerHTML += `<option>${lines[i]}</option>`
            }
        }
        aa()
        // 实现二级下拉框等级联动并将联动的内容渲染到二级下拉框的下面
        function getStation() {
            let station = document.getElementById('station')
            // 筛选出一级下拉框中选中的标签索引中对应的stationName数组中的内容筛
            // selectedIndex容易写成selectIndex
            let stations = stationName[line.selectedIndex - 1]
            for (let j = 0; j < stations.length; j++) {
                // 如果直接写成station[j],那么会将下拉框中的第一个元素无法展示出来
                // 容易错写成station[j + 1].setOption()
                station[j + 1] = new Option(stations[j],stations[j])
            }
        }
    </script>
</body>
</html>

13. 手写防抖和节流

13.1 防抖

let p = document.getElementById('p')
        function debounce(fn, wait) {
            let timeout 
            return function() {
                let that = this 
                let arg = arguments 
                clearTimeout(timeout) 
                timeout = setTimeout(function() {
                    fn.apply(that, arg)
                }, wait)
            }
        }
        p.onmousemove = debounce(func,1000)
        function func() {
            console.log(1)
        }

13.2 节流

使用时间戳

 let p = document.getElementById('p')
        function throttle(fn, wait) {
            let timeout, pre = 0
            return function() {
                let that = this 
                let args = arguments 
                // new Date()这种形式是时间格式字符,不是数字
                let now = +new Date()
               // let now = new Date().getTimeout()
                if (now - pre > wait) {
                    fn.apply(that, args)
                    pre = now
                }
            }
        }
        p.onmousemove = throttle(func,1000)
        function func() {
            console.log(1)
        }

使用定时器

let p = document.getElementById('p')
        function throttle(fn, wait) {
            let timeout
            return function() {
                let that = this 
                let args = arguments 
                if(!timeout) {
                    timeout = setTimeout(function() {
                    timeout = null
                    fn.apply(that, args)
                },1000)
                }
                
            }
        }
        p.onmousemove = throttle(func,1000)
        function func() {
            console.log(1)
        }

使用定时器实现和时间戳一样的效果

function throttle(fn, wait) {
            let timeout
            return function() {
                let that = this 
                let args = arguments 
                if(!timeout) {
                    fn.apply(that, args)
                    timeout = setTimeout(function() {
                    timeout = null
                    
                },1000)
                }
            }
        }

14. 手写深拷贝

//乞丐版
        // JSON.parse(JSON.stringify())
        // 升级版
        function clone(target, map = new WeakMap()) { //WeakMap替换Map,实现不用的内存,系统自动收回
            //克隆基本类型的值
            if (!isObject(target)) {
                return target 
            } else {
                // 克隆数组和对象
                let cloneTarget = Array.isArray(target) ? [] : {}
                // 解决循环引用的问题
                if (map.get(target)) return map.get(target)
                map.set(target, cloneTarget)
                // 克隆数组和对象
                for (let key in target) {
                    if (isObject(target[key])) {
                        cloneTarget[key] = clone(target[key], map)
                    } else {
                        cloneTarget[key] = target[key]
                    }
                }
                // 克隆Symbol
                let symbols = Object.getOwnPropertySymbols(target)
                if (symbols.length) {
                    symbols.forEach(symbol => {
                        if (isObject(target[symbol])) {
                            cloneTarget[symbol] = clone(target[symbol], map)
                        } else {
                            cloneTarget[symbol] = target[symbol]
                        }
                    })
                }
                // 克隆函数
                if (typeof target === 'function') return target
                return cloneTarget
            }
        } 
        // 判断是否是object
        function isObject(target) {
            let type = typeof target 
            return type === 'object' && type !== null 
        }

15. 手写懒加载

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <div id='container'>
        <img src="images/loading.gif" data-src="images/1.png">
        <img src="images/loading.gif" data-src="images/2.png">
        <img src="images/loading.gif" data-src="images/3.png">
        <img src="images/loading.gif" data-src="images/4.png">
        <img src="images/loading.gif" data-src="images/5.png">
        <img src="images/loading.gif" data-src="images/6.png">
        <img src="images/loading.gif" data-src="images/7.png">
        <img src="images/loading.gif" data-src="images/8.png">
        <img src="images/loading.gif" data-src="images/9.png">
        <img src="images/loading.gif" data-src="images/10.png">
    </div>
</head>
<body>
    <script>
        function lazyload() {
            let images = document.getElementsByTagName('img')
            let len = images.length
            //存储图片加载到的位置,避免每次都从第一张图片开始遍历 
            let n = 0 
            return function() {
                // 浏览器可视区域宽度
                let seeHeight = document.documentElement.clientHeight
                // 滚动条滚动的宽度(document.body.scrollTop是谷歌浏览器中的滚动条滚动的宽度)
                let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
                for (let i = n; i < len; i++) {
                    //images[i].offsetTop代表图片距离上方或者上层控件的位置
                    if (images[i].offsetTop < seeHeight + scrollTop) {
                        if (images[i].getAttribute('src') === 'images/loading.gif') {
                            images[i].src = images[i].getAttribute('data-src')
                        }
                        n++
                    }
                }
            }
        }
        //防抖函数绑定在 scroll 事件上,当页面滚动时,避免函数被高频触发,
        function throttle(fn, wait) {
            let timeout 
            return function() {
                let that = this 
                let args = arguments 
                timeout = setTimeout(function() {
                    fn.apply(that, args)
                }, wait)
            }
        }
        window.addEventListener('scroll', throttle(lazyload, 1000))
    </script>
</body>
</html>

16. 实现一个new

function myNew() {
     // 1. 获取构造函数
     // 构造函数是伪数组的第一个参数,由于是伪数组,所以不能直接调用shift()方法,用call来调用数组上的shift方法
  var constr = Array.prototype.shift.call(arguments);
//   2. 创建一个空对象
  var obj = {}
  // 3. 将新建对象的隐式原型指向构造函数的显示原型
  obj.__proto__ = constr.prototype
//   4. 执行构造函数中的代码
  var result = constr.apply(obj, arguments);
//   5. 如果函数是Object类型的值则返回,否则返回新对象
  return result instanceof Object? result : obj;
}
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
       console.log(this.name)
    }
}
// var person = new Person('Nicholas', 29, 'Front-end developer'); 
var person = myNew(Person, 'Nicholas', 29, 'Front-end developer');
console.log(person.name) // Nicholas
person.sayName();        // Nicholas
console.log(person.__proto__ === Person.prototype);

17. 手写call、apply、bind

apply

Function.prototype.myCall = function(context) {
            // 1.判断有没有传入要绑定的对象,没有默认是window,如果是基本类型的话通过Object()方法进行转换(解决问题3)
            var context = Object(context) || window 
            /*
                在指向的对象obj上新建一个fn属性,值为this,也就是fn()
                相当于obj变成了
                {
                    value: 'hdove',
                    fn: function fn() {
                    console.log(this.value);
                    }
                }
            */
            context.fn = this 
            // 2.保存返回值
            let result = ''
            // 3.取出传递的参数(从下标为0开始截取到最后) 第一个参数是this对象(在本例中是{value:li}), 下面是三种截取除第一个参数之外剩余参数的方法(解决问题1)
            // 首先将类数组的对象arguments转换为数组,再采用数组的方法进行截取(返回值是一个数组)
            const args = [...arguments].slice(1)
            // const args = Array.prototype.slice.call(arguments, 1)
            // const args = Array.from(arguments).slice(1)
            // 4.执行这个方法,并传入参数 ...是es6的语法用来展开数组
            result = context.fn(...args)
            //5.删除该属性(解决问题4)
            delete context.fn 
            //6.返回 (解决问题2)
            return result
        }
        //测试用例
        const obj = {
            value: 'li'
        }
        function fn(name, age) {
            return {
                value: this.value,
                age,
                name,
            }
        }
        
        console.log(fn.myCall(obj,'lz',26))

call

Function.prototype.myCall = function(context,args) {
            var context = Object(context) || window 
            context.fn = this 
            let result = ''
            //4. 判断有没有传入args
            if (args) {
                result = context.fn(...args)
            } else {
                result = context.fn()
            }
            delete context.fn 
            return result
        }
        //测试用例
        const obj = {
            value: 'li'
        }
        function fn(name, age) {
            return {
                value: this.value,
                age,
                name,
            }
        }
        
        console.log(fn.myCall(obj,['lz',26]))

bind

Function.prototype.myBind = function(context) {
            if (typeof this !== 'function') {
                throw Error('Not function')
            }
            let that = this 
            let args1 = [...arguments].slice(1)
            const bindFn =  function() {
                let args2 = [...arguments]
                that.apply(this instanceof bindFn ? this : context, [...args1, ...args2])
            }
            function Func() {}
            Func.prototype = this.prototype
            bindFn.prototype = new Func()
            return bindFn
        }
        const obj = {
            value: 'll'
        }
        function fn(name, age) {
            this.test = 'test'
            console.log(this.value)
            console.log(name)
            console.log(age)
        }
        fn.prototype.pro = 'pro'
        let bindFn = fn.myBind(obj, 'lz')
        // bindFn(26)
        let newBind = new bindFn()
        newBind.__proto__.pro = 'p'
        console.log(newBind.pro)
        console.log(fn.prototype.pro)

18. 手写函数柯里化

Function.prototype.myBind = function(context) {
            if (typeof this !== 'function') {
                throw Error('Not function')
            }
            let that = this 
            let args1 = [...arguments].slice(1)
            const bindFn =  function() {
                let args2 = [...arguments]
                that.apply(this instanceof bindFn ? this : context, [...args1, ...args2])
            }
            function Func() {}
            Func.prototype = this.prototype
            bindFn.prototype = new Func()
            return bindFn
        }
        const obj = {
            value: 'll'
        }
        function fn(name, age) {
            this.test = 'test'
            console.log(this.value)
            console.log(name)
            console.log(age)
        }
        fn.prototype.pro = 'pro'
        let bindFn = fn.myBind(obj, 'lz')
        // bindFn(26)
        let newBind = new bindFn()
        newBind.__proto__.pro = 'p'
        console.log(newBind.pro)
        console.log(fn.prototype.pro)

19. 手写无缝轮播

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>手写无缝轮播图</title>
    <style>
        * {
            padding: 0;
            margin: 0;
            list-style: none;
            border: 0;
        }
        .all {
            width: 500px;
            height: 200px;
            padding: 7px;
            border: 1px solid #ccc;
            margin: 100px auto;
            position: relative;
        }
        .screen {
            width: 500px;
            height: 200px;
            overflow: hidden;
            position: relative;
        }
        .screen li {
            width: 500px;
            height: 200px;
            overflow: hidden;
            float: left;
        }
        .screen ul {
            position: absolute;
            left: 0;
            top: 0px;
            width: 3000px;
        }
        .all ol {
            position: absolute;
            right: 10px;
            bottom: 10px;
            line-height: 20px;
            text-align: center;
        }
        .all ol li {
            float: left;
            width: 20px;
            height: 20px;
            background: #fff;
            border: 1px solid #ccc;
            margin-left: 10px;
            cursor: pointer;
        }
        .all ol li.current {
            background: yellow;
        }
        #arr {
            display: none;
        }
        #arr span {
            width: 40px;
            height: 40px;
            position: absolute;
            left: 5px;
            top: 50%;
            margin-top: -20px;
            background: #000;
            cursor: pointer;
            line-height: 40px;
            text-align: center;
            font-weight: bold;
            font-family: '黑体';
            font-size: 30px;
            color: #fff;
            opacity: 0.3;
            border: 1px solid #fff;
        }
        #arr #right {
            right: 5px;
            left: auto;
        }
    </style>
</head>
<body>
    <div  >
        <div >
            <ul>
                <li><img src="https://www.baidu.com/img/flexible/logo/pc/result.png" width="500" height="200" style="background-color: yellowgreen;"/></li>
                <li><img src="https://www.baidu.com/img/flexible/logo/pc/result.png" width="500" height="200" style="background-color: pink;"/></li>
                <li><img src="https://www.baidu.com/img/flexible/logo/pc/result.png" width="500" height="200" style="background-color: skyblue;"/></li>
                <li><img src="https://www.baidu.com/img/flexible/logo/pc/result.png" width="500" height="200" style="background-color: greenyellow;"/></li>
                <li><img src="https://www.baidu.com/img/flexible/logo/pc/result.png" width="500" height="200" style="background-color: plum;"/></li>
                <li><img src="https://www.baidu.com/img/flexible/logo/pc/result.png" width="500" height="200" style="background-color: orange;"/></li>
            </ul>
            <ol>
                <li >1</li>
                <li>2</li>
                <li>3</li>
                <li>4</li>
                <li>5</li>
            </ol>
    
        </div>
        <div ><span ><</span><span >></span></div>
    </div>
    <script>
        // 1.获取页面对应的元素
        var box=document.getElementById("box"); //最外部大盒子
        var arr=document.getElementById("arr");
        var screen=document.getElementsByClassName("screen")[0]; //轮播图显示区域div
        var ul=document.getElementsByTagName("ul")[0]; //显示图片的ul
        var ol=document.getElementsByTagName("ol")[0]; //显示页码的ol
        var left=document.getElementById("left"); //上一张箭头
        var right=document.getElementById("right"); //下一张箭头
        var index=0; 声明一个变量记录图片的索引,默认第0张图片
    
        //2.给box添加鼠标移入和移出事件
        //2.1 鼠标移入
        box.onmouseover= function () {
            arr.style.display="block"; //显示上一页下一页箭头
            clearInterval(timeId); //清除定时器(即鼠标移入时,图片要停止自动轮播)
        };
        //2.2 鼠标移出
        box.onmouseout= function () {
            arr.style.display="none"; //隐藏箭头
            timeId=setInterval(scroll,2000);  //重启定时器(鼠标移出,图片要恢复自动轮播)
        };
    
        //3.给上一页下一页箭头添加点击事件
        //3.1 下一页,图片向左轮播
        right.onclick= function () {
            scroll();
        };
        //3.2 上一页,图片向右轮播
        left.onclick= function () {
            //(1)边界检测,如果当前已经是第一张,则不做任何处理
            if(index==0){
                //无限轮播原理:如果当前是第一张,则偷偷修改ul的位置是最后一张(第一张与最后一张是同一张图片)
                index=ul.children.length-1; //index恢复到最后一张
                ul.style.left=-index*screen.offsetWidth+"px"; ul回到最后一张位置
            }
            //(2)索引自减
            index--;
             // (3)向左移动ul:目标距离 = -screen的宽度 * 索引
            animationMove(ul,-index*screen.offsetWidth,10);
            indexShow(); //同步页码样式
        };
    
        //4.给页码添加点击事件
        for(var i=0;i<ol.children.length;i++){
             //4.1 循环遍历数组时给每一个页码添加一个liIndex属性记录下标
            ol.children[i].liIndex=i;
            ol.children[i].onclick= function () {
                index=this.liIndex-1;
                scroll();
            };
        }
    
        var timeId=setInterval(scroll,2000);
        // 封装一个向右轮播的函数
        function scroll(){
            //(1)边界检测:如果当前已经是最后一张(第n+1张,n代表需要轮播的图片数量)
            if(index==ul.children.length-1){
                //无限轮播的原理就是滚动到最后一张的时候,偷偷快速的改变ul的位置到第一张(不要任何动画,一瞬间改变)
                index=0; //index恢复到0
                ul.style.left=0+"px"; //ul回到初始位置
            }
            // (2)索引自增
            index++;
            // (3)向右移动ul:目标距离 = -screen的宽度 * 索引
            animationMove(ul,-index*screen.offsetWidth,10);
            indexShow(); //同步页码样式
        }
        //5.页码样式保持同步:排他思想(当前页码添加样式,其他页码移除该样式)
        function indexShow(){
            for(var i=0;i<ol.children.length;i++){
                if(i==index){
                    ol.children[i].classList.add("current");
                }else{
                    ol.children[i].classList.remove("current");
                }
                //特殊情况:当index为最后一张的时候,页码应该显示第一张
                if(index==ul.children.length-1){
                    ol.children[0].classList.add("current");
                }
            }
        }
        // 封装一个滚动动画函数
        function animationMove(obj,target,speed){
            clearInterval(obj.timeId);  //每次执行动画先清除原有的定时器
            obj.timeId=setInterval(function () {
                var currentLeft=obj.offsetLeft; //获取当前位置
               var isLeft=currentLeft>target?true:false;   //是否往左走
               if(isLeft){
                   currentLeft-=10;    //往左走
               }else{
                   currentLeft+=10;    //往右走
               }
               if(isLeft?currentLeft>target:currentLeft<target){
                  obj.style.left=currentLeft+"px";  //如果当前位置不是在目标位置则进行位置处理
               }else{
                   clearInterval(obj.timeId);
                   obj.style.left=target+"px";
               }
                // if(currentLeft>target){
                //     currentLeft-=10;
                //     obj.style.left=currentLeft+"px";
                // }else if(currentLeft<target){
                //     currentLeft+=10;
                //     obj.style.left=currentLeft+"px";
                // }else{
                //     clearInterval(obj.timeId);
                //     obj.style.left=target+"px";
                // }
            },speed);
        }
    </script>
</body>
</html>

20. 手写实现一个事件委托

function delegate(element, eventType, selector, fn) {
            element.addEventListener(eventType, e => {
                let el = e.target
                while (!el.matches(selector)) {
                    if (element === el) {
                        el = null
                        break
                    }
                    el = el.parentNode
                }
                el && fn.call(el, e, el)
            },true)
            return element
        }

21. 手写一个可以拖拽的div

var dragging = false
var position = null

xxx.addEventListener('mousedown',function(e){
  dragging = true
  position = [e.clientX, e.clientY]
})


document.addEventListener('mousemove', function(e){
  if(dragging === false) return null
  const x = e.clientX
  const y = e.clientY
  const deltaX = x - position[0]
  const deltaY = y - position[1]
  const left = parseInt(xxx.style.left || 0)
  const top = parseInt(xxx.style.top || 0)
  xxx.style.left = left + deltaX + 'px'
  xxx.style.top = top + deltaY + 'px'
  position = [x, y]
})
document.addEventListener('mouseup', function(e){
  dragging = false
})

22.数组去重

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

23. 实现flat

let flatDeep = (arr) => {
    return arr.reduce((res, cur) => {
        if(Array.isArray(cur)){
            return [...res, ...flatDep(cur)]
        }else{
            return [...res, cur]
        }
    },[])
}

24. 实现一个对象类型的函数

let isType = (type) => (obj) => Object.prototype.toString.call(obj) === `[object ${type}]`

// let isArray = isType('Array')
// let isFunction = isType('Function')
// console.log(isArray([1,2,3]),isFunction(Map))

25. 实现instanceof

function my_instance_of(leftVaule, rightVaule) {
    if(typeof leftVaule !== 'object' || leftVaule === null) return false;
    let rightProto = rightVaule.prototype,
        leftProto = leftVaule.__proto__;
    while (true) {
        if (leftProto === null) {
            return false;
        }
        if (leftProto === rightProto) {
            return true;
        }
        leftProto = leftProto.__proto__
    }
}

26. reduce相关

1. 手写reduce

Array.prototype.myreduce = function(fn, initVal) {
    let result = initVal,
        i = 0;
    if(typeof initVal  === 'undefined'){
        result = this[i]
        i++;
    }
    while( i < this.length ){
        result = fn(result, this[i])
    }
    return result
}

2. 求和求体积

在这里插入图片描述

3. 计算数组中每个元素出现的次数

在这里插入图片描述

4. 数组去重

在这里插入图片描述

5. 将多维数组转为一位数组

在这里插入图片描述

27. 实现object.create()

//实现Object.create方法
function create(proto) {
    function Fn() {};
    Fn.prototype = proto;
    Fn.prototype.constructor = Fn;
    return new Fn();
}
let demo = {
    c : '123'
}
let cc = Object.create(demo)

28. 十进制转换

function Conver(number, base = 2) {
  let rem, res = '', digits = '0123456789ABCDEF', stack = [];

  while (number) {
    rem = number % base;
    stack.push(rem);

    number = Math.floor(number / base);
  }

  while (stack.length) {
    res += digits[stack.pop()].toString();
  }
  
  return res;
}

29. 十进制转换

给定10进制数,转换成[2~16]进制区间数

function Conver(number, base = 2) {
  let rem, res = '', digits = '0123456789ABCDEF', stack = [];

  while (number) {
    rem = number % base;
    stack.push(rem);

    number = Math.floor(number / base);
  }

  while (stack.length) {
    res += digits[stack.pop()].toString();
  }
  
  return res;
}

30.rgb与16进制之间的转换

1. rgb转16进制

String.prototype.colorHex = function () {
  // RGB颜色值的正则
  var reg = /^(rgb|RGB)/;
  var color = this;
  if (reg.test(color)) {
    var strHex = "#";
    // 把RGB的3个数值变成数组
    var colorArr = color.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(",");
    // 转成16进制
    for (var i = 0; i < colorArr.length; i++) {
      var hex = Number(colorArr[i]).toString(16);
      if (hex === "0") {
        hex += hex;
      }
      strHex += hex;
    }
    return strHex;
  } else {
    return String(color);
  }
};

2. 16进制转rgb

String.prototype.colorRgb = function () {
  // 16进制颜色值的正则
  var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
  // 把颜色值变成小写
  var color = this.toLowerCase();
  if (reg.test(color)) {
    // 如果只有三位的值,需变成六位,如:#fff => #ffffff
    if (color.length === 4) {
      var colorNew = "#";
      for (var i = 1; i < 4; i += 1) {
        colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1));
      }
      color = colorNew;
    }
    // 处理六位的颜色值,转为RGB
    var colorChange = [];
    for (var i = 1; i < 7; i += 2) {
      colorChange.push(parseInt("0x" + color.slice(i, i + 2)));
    }
    return "RGB(" + colorChange.join(",") + ")";
  } else {
    return color;
  }
};

31. 原生js手写table表格

<!DOCTYPE HTML>
<html>
<head>
<meta charset=UTF-8>
<title>table</title>
<style>
table {
    margin:auto;
    width: 60%;
    border: 1px solid black;
    border-collapse: collapse;
}
 
table caption {
    color: blue;
    font: 34px consolas bold;
}
 
table th, table td {
    border: 1px solid black;
    text-align:center;
}
 
table th {
    font: 20px consolas bold;
    background-color: gray;
}
 
table tr:nth-child(n+2){
    background-color: cyan;
}
 
table tr:nth-child(2n+2)  {
    background-color: red;
}
</style>
<script>
var data = [
    {id: "001", fullname: "张三", sex: "男", score: [98,33,48]},
    {id: "002", fullname: "李四", sex: "w", score: [11,22,33]},
    {id: "003", fullname: "kyo", sex: "m", score: [22,33,55]},
    {id: "004", fullname: "yamazaki", sex: "w", score: [99, 100, 80]}
];
 
var Student = function (id, fullname, sex, score)
{
    this.id = id;
    this.fullname = fullname;
    this.sex = sex;
    this.score = score;
}
 
var Score = function (chinese, math, english)
{
    this.chinese = chinese;
    this.math = math;
    this.english = english;
}
 
var students = [];
for (var i = 0; i < data.length; i++)
{
    var d = data[i];
    var s = d["score"];
    students.push(new Student (d["id"], d["fullname"], d["sex"], new Score(s[0], s[1], s[2])));
}
 
onload = function ()
{
    var table = document.createElement("table");
    var tbody = document.createElement("tbody");
    table.appendChild(tbody);
    var caption = table.createCaption();
    caption.innerHTML = "学生成绩表";
    var tr = tbody.insertRow (0);
    var str = "学号,姓名,性别,语文,数学,英语,嘤嘤嘤".split(",");
    for (var i = 0; i < str.length; i++)
    {
        var th = document.createElement("th");
        th.innerHTML = str[i];
        tr.appendChild (th);
    }
    for (var i = 0; i < students.length; i++)
    {
        var tr = tbody.insertRow (tbody.rows.length);
        var obj = students[i];
        for (var p in obj)
        {
            var op = obj[p];
            if (p != "score")
            {
                var td = tr.insertCell (tr.cells.length);
                td.innerHTML = op;
            }
            else
            {
                for (var p in op)
                {
                    var td = tr.insertCell (tr.cells.length);
                    td.innerHTML = op[p];
                }
            }
        }
    }
    document.body.appendChild(table);
}
</script>
<body>
 
</body>
</html>

32. JSONP 动态创建script进行跨域请求

在这里插入图片描述

印象笔记算法汇总

1. 手写一个自定义事件

var myEvent = new Event('clickTest');  
element.addEventListener('clickTest', function () {    
console.log('smyhvae');  
}); 
//元素注册事件   
element.dispatchEvent(myEvent);
//注意,参数是写事件对象 myEvent,不是写 事件名 clickTest

2. 手写一个new

当new Foo()时发生了什么:
(1)创建一个新的空对象实例,它继承自Foo.prototype。
(2)将此空对象的隐式原型指向其构造函数的显示原型。
(3)执行构造函数Foo(传入相应的参数,如果没有参数就不用传),同时 this 指向这个新实例。newFoo 等同于 new Foo(),只能用在不传递任何参数的情况。
(4)如果返回值是一个新对象,那么直接返回该对象,这个对象会取代整个new出来的结果;如果无返回值或者返回一个非对象值,那么就将步骤(1)创建的对象返回。

//Fun为构造函数, args表示传参
function myNew(Fun, ...args) {
    // 1.在内存中创建一个新对象
    let obj = {};
    
    // 2.把新对象的原型指针指向构造函数的原型属性
    obj.__proto__ = Fun.prototype;
    
    // 3.改变this指向,并且执行构造函数内部的代码(传参)
    let res = Fun.apply(obj, args);
    
    // 4.判断函数执行结果的类型
    if (res instanceof Object) {
        return res;
    } else {
        return obj;
    }
}

let obj = myNew(One, "XiaoMing", "18");
console.log("newObj:", obj);


3. 对象继承的集中方式

3.1 借助构造函数

function Parent1() {   
this.name = 'parent1 的属性'; 
}   
function Child1() {    
Parent1.call(this);         //【重要】此处用 call 或 apply 都行:改变 this 的指向   
this.type = 'child1 的属性'; 
}  
console.log(new Child1);

3.2 通过原型链实现继承

/*    通过原型链实现继承     */   
function Parent() {     
this.name = 'Parent 的属性';  
}   
function Child() {    
this.type = 'Child 的属性';
}   
Child.prototype = new Parent(); //【重要】   
console.log(new Child());

3.3 组合的方式:构造函数 + 原型链

/*    组合方式实现继承:构造函数、原型链     */   
function Parent3() {     
this.name = 'Parent 的属性';    
this.arr = [1, 2, 3]; 
}  
function Child3() {   
Parent3.call(this); //【重要1】执行 parent方法       
this.type = 'Child 的属性';  
}  
Child3.prototype = new Parent3(); //【重要2】第二次执行parent方法    var child = new Child3();

3.4 组合的方式:构造函数 + 原型链 优化1

function Parent4() {  
this.name = 'Parent4' 
this.play = [1, 2, 3]} 
function Child4() { //【重要1】  
Parent4.call(this)  
this.type = 'child4'
}
Child4.prototype = Parent4.prototype  //【重要2】 
let s5 = new Child4() 
let s6 = new Child4()
console.log(s5, s6)

3.5 组合的方式:构造函数 + 原型链 优化2

function Parent5() {  
this.name = 'Parent4' 
this.play = [1, 2, 3]
} 
function Child5() {  
Parent4.call(this)  
this.type = 'child4'
}
Child5.prototype =Object.create(Parent5.prototype)
Child5.prototype.constructor = Child5
let s5 = new Child4() 
let s6 = new Child4()
console.log(s5, s6)

4. 请把俩个数组 [A1, A2, B1, B2, C1, C2, D1, D2] 和 [A, B, C, D],合并为 [A1, A2, A, B1, B2, B, C1, C2, C, D1, D2, D]

在这里插入图片描述

5. [2, 10, 3, 4, 5, 11, 10, 11, 20],将其排列成一个新数组,例如 [[2, 3, 4, 5], [10, 11], [20]]。

// 得到一个两数之间的随机整数,包括两个数在内
function getRandomIntInclusive(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值 
}
// 随机生成10个整数数组, 排序, 去重
let initArr = Array.from({ length: 10 }, (v) => { return getRandomIntInclusive(0, 99) });
initArr.sort((a,b) => { return a - b });
initArr = [...(new Set(initArr))];

// 放入hash表
let obj = {};
initArr.map((i) => {
	// 此处是为了使得大数组中有多个小数组的
    const intNum = Math.floor(i/10);
    if (!obj[intNum]) obj[intNum] = [];
    obj[intNum].push(i);
})

// 输出结果
const resArr = [];
for(let i in obj) {
    resArr.push(obj[i]);
}
console.log(resArr);

6. 如何把一个字符串的大小写取反(大写变小写小写变大写),例如 ’AbC’ 变成 ‘aBc’ 。

在这里插入图片描述

7. 实现一个字符串匹配算法,从长度为 n 的字符串 S 中,查找是否存在字符串 T,T 的长度是 m,若存在返回所在位置。

在这里插入图片描述

8. 将一个排序好的数组打乱

在这里插入图片描述
在这里插入图片描述

9. 手写记忆函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页
评论

打赏作者

A丰休

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值