# 组合总和

一个不重复的数组,其内部数字求和为target,内部数字可以重复多次,输出其所有组合例如: [2,4,6] ===> [ [2,2,2], [2,4], [6], ] 使用回溯加减枝可以解决,我们优先深度遍历(dfs),可以先画出二叉树的图方便理解

const algorithm = function(array, target) {
  let sum = 0;
  const res = [];
  function dfs(start, temp) {
    if(sum>= target) {
      if(sum === target) {
        res.push(temp.slice());
      }
      return;
    }
    for(var i = start; i<array.length; i++) {
      temp.push(array[i]);
      sum+=array[i];
      dfs(i,temp);
      sum-=array[i];
      temp.pop();
    }
  }
  dfs(0, []);
  return res;
}

# 排序

// 二分法
var quicksort = function(arr){
  if(arr.length <= 1){
    return arr;
  }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex,1)[0];
  var left = [];
  var right = [];
  for(var i = 0;i < arr.length;i++){
    if(arr[i] < pivot){
      left.push(arr[i]);
    }else{
      right.push(arr[i]);
    }
  }
  return quicksort(left).concat([pivot],quicksort(right));
};

var array = [8,7,0,7,5,2,5,3,1];
quicksort(array); //[0,1,2,3,5,5,7,7,8]

// 冒泡
//两层for循环,假如每一次if条件都满足,就是需要执行 n * (n-1) * 3 次, 取最高次项后常数系数变为1之后可以得到时间复杂度为O(n^2)
/**
* @param {Array} arr 待排序数组
* @returns {Array}
*/
function bubbleSort(arr) {
  for (var i = 0, len = arr.length; i < len;  i++) {
    for (var j = i + 1;  j < len;  j++) {
      if (arr[i] > arr[j]) {
        var temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
      }
    }
  }
  return arr;
}

# 数字千分位处理

function formatNum(str,digit){
  // 要保留的小数位数默认两位
  var digit_number=digit||2;
  //对字符串或者数字进行保留两位四舍五入处理
  str=parseFloat(str).toFixed(digit_number).toString();  
  var iffloat=str.indexOf(".")+1;
  var right=str.substr(iffloat);
  var left=str.substring(0,iffloat);
  left=left.replace(/(\d{3}(?!\.))/g,'$1,');
  return left+right;
}

# 整数逆序后的字符串

用 JavaScript 写一个函数,输入 int 型,返回整数逆序后的字符串。如:输入整型 1234,返回字符串“4321”。要求必须使用递 归函数调用,不能用全局变量,输入函数必须只有一个参数传入,必须返回字符串

function fun(num){
  let num1 = num / 10;
  let num2 = num % 10;
  if(num1<1){
    return num;
  }else{
    num1 = Math.floor(num1)
    return `${num2}${fun(num1)}`
  }
}
var a = fun(12345)
console.log(a)
console.log(typeof a)

const numToReverseStr_0 = num => {
    return num.toString().split('').reverse().join('');
}

# 获取随机数

考虑到性能问题,如何快速从一个巨大的数组中随机获取部分元素

var arr =[1,2,3,4...,1000];
var result = [];
var randomNum = 100;
for(var i = 0,i < randomNum,i++) {
   var luckyDog = Math.floor(Math.random() * (arr.length - i));
   result.push(arr[luckyDog]);
   // 当前元素替换成最后一个元素
   arr[luckyDog] = arr[arr.length - i - 1];
}

# z字形变换

将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列,比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下

L   C   I   R
E T O E S I I G
E   D   H   N

之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"

  • 以 V 字型为一个循环, 循环周期为 n = (2 * numRows - 2) (2倍行数 - 头尾2个)
  • 对于字符串索引值i,计算 x = i % n 确定在循环周期中的位置
  • 则行号 y = min(x, n - x)
/**
 * @param {string} s
 * @param {number} numRows
 * @return {string}
 */
var convert = function(s, numRows) {
    if (numRows === 1) return s;
    const rows = new Array(numRows).fill("");
    const n = 2 * numRows - 2;
    for(let i = 0; i < s.length; i++) {
        const x = i % n;
        rows[Math.min(x, n - x)] += s[i];
    }
    return rows.join("");
};

# 整数和罗马数互转

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M;通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例 如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表 示为 IX。这个特殊的规则只适用于以下六种情况:

  • I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9
  • X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90
  • C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900

给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内?例如:

输入: 1994 输出: "MCMXCIV" 解释: M = 1000, CM = 900, XC = 90, IV = 4

输入: 58 输出: "LVIII" 解释: L = 50, V = 5, III = 3.

var intToRoman = function(num) {
  let nums=[1000,900,500,400,100,90,50,40,10,9,5,4,1],
      chars=['M','CM','D','CD','C','XC','L','XL','X','IX','V','IV','I'];
  let result='';
  while(num){
      if(num>=nums[0]){
          result+=chars[0];
          num-=nums[0];
      }else{
          nums.shift();
          chars.shift();
      }
  }
  return result;
};
var romanToInt = function(s) {
  let o = {
    'I':1,
    'V':5,
    'X':10,
    'L':50,
    'C':100,
    'D':500,
    'M':1000
  },
  n = 0;
  for(let i=0,len=s.length; i<len; i++){
      const temp = o[s[i]]
      if( i < len - 1 &&  temp < o[s[i+1]] ){
          n -= temp
      }else{
          n += temp
      }
  }
  return n
};

# 两个日期中的有效日期

function rangeDate(startDate, endDate) {
  let start_ = new Date(startDate).getTime();
  let end_ = new Date(endDate).getTime();
  let day = 24 * 60 * 60 * 1000;
  let arr = [];
  for (let i = start_; i <= end_; i += day) {
    arr.push(i);
  }
  return arr.map(item => {
    let date = new Date(item);
    let year = date.getFullYear();
    let month = (date.getMonth() + 1);
    let day = date.getDate();
    return `${year}-${month}-${day}`;
  });
}
console.log(rangeDate("2015-2-8", "2015-3-3"));

# 最长公共前缀

function long(params) {
  let start = params.shift();
  start = start.split('');
  let str = '';
  let res = '';
  for (let i = 0 ; i< start.length; i++) {
    str += start[i];
    let flag = true;
    for (let j = 0 ; j< params.length; j++) {
      if(params[j].indexOf(str) === -1) {
        flag = false;
        break;
      }
    }
    if (!flag) {
      str = str.substring(1);
    } else {
      if (str.length > res.length) {
        res = str;
      }
    }
  }
  return res;
}

# 电话号码的字母数字组合

如同老式的电话一样,一个数字对应几个字母,找出一串数字对应的所有字母排列可能

// 回溯法dfs
const letterCombinations = (digits) => {
  if (digits.length == 0) return [];
  const res = [];
  const map = { '2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl', '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz' };

  const dfs = (curStr, i) => {      // curStr是当前字符串,i是扫描的指针
    if (i > digits.length - 1) {    // 指针越界,递归的出口
      res.push(curStr);             // 将解推入res
      return;                       // 结束当前递归分支,进入另一个递归分支
    }
    const letters = map[digits[i]]; // 当前数字对应有哪些字母
    for (const l of letters) {      // 不同的字母选择代表不同的递归分支
      dfs(curStr + l, i + 1);       // 生成新字符串,i指针右移,递归
    }
  };
  dfs('', 0); // 递归的入口,初始字符串为'',指针为0
  return res;
};

# 四数之和

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组

const algorithm = function(array, target) {
  let sum = 0;
  const res = [];
  array.sort((a, b) => a - b);
  function dfs(start, temp) {
    if(sum === target && temp.length === 4) {
      res.push(temp.slice());
      return;
    }
    let r;
    for(var i = start; i<array.length; i++) {
      if(r === array[i]) continue;
      r = array[i];
      temp.push(array[i]);
      sum+=array[i];
      dfs(i+1,temp);
      sum-=array[i];
      temp.pop();
    }
  }
  dfs(0, []);
  return res;
}

# 有效的括号

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

  • 左括号必须用相同类型的右括号闭合
  • 左括号必须以正确的顺序闭合
  • 注意空字符串可被认为是有效字符串
var isValid = function (s) {
  if (!s) return true
  const stack = []
  const JoinMap = {
    '{': '}',
    '(': ')',
    '[': ']',
  }
  for (let i = 0; i < s.length; i++) {
    let val = s[i]
    let tmp
    if (tmp = JoinMap[val]) {
      stack.push(tmp)
    } else {
      if (val !== stack.pop()) {
          return false
      }
    }
  }
  return !stack.length
};

# 括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合

// 例如输入:n = 3
// 输出:[
//   "((()))",
//   "(()())",
//   "(())()",
//   "()(())",
//   "()()()"
// ]
// 使用回溯加剪枝
var generateParenthesis = function (n) {
  const res = [];

  const dfs = (lRemain, rRemain, str) => { // 左右括号所剩的数量,str是当前构建的字符串
    if (str.length == 2 * n) { // 字符串构建完成
      res.push(str);           // 加入解集
      return;                  // 结束当前递归分支
    }
    if (lRemain > 0) {         // 只要左括号有剩,就可以选它,然后继续做选择(递归)
      dfs(lRemain - 1, rRemain, str + "(");
    }
    if (lRemain < rRemain) {   // 右括号比左括号剩的多,才能选右括号
      dfs(lRemain, rRemain - 1, str + ")"); // 然后继续做选择(递归)
    }
  };

  dfs(n, n, ""); // 递归的入口,剩余数量都是n,初始字符串是空串
  return res;
};

回溯,死抓三个要点

  • 回溯,死抓三个要点
    • 每次最多两个选择,要么左括号,要么右括号,“选择” 会展开出一棵解的空间树
    • 用 DFS 的方式遍历这棵树,找出所有的解,这个过程叫回溯
  • 约束条件
    • 什么情况可以选左括号,什么情况下可以选右括号,是约束条件
    • 利用约束做“剪枝”,即,去掉不会产生解的选项,即,剪去不会通往合法解的分支
// 比如(),现在左右括号各剩一个,再选)就成了()),这是错的选择,通过判断不让它成为选项(不让它落入递归):
if (right > left) { // 右括号剩的比较多,才能选右括号
  dfs(str + ')', left, right - 1);
}
  • 目标
    • 构建出一个用尽 n 对括号的合法括号串
    • 意味着,当构建的长度达到 2*n,就可以结束递归(不用继续选了)

经过了充分的剪枝,所有不会通往合法解的选项,都被干掉,只要往下递归,都通向合法解。即,只要递归到:当构建的字符串的长度 达 2*n 时,一个合法解就生成了,直接加入解集即可

# 两两交换链表中节点

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换, 链表对于js就是对象中有个next指向下一个节点

var swapPairs = function(head) {
    if (head === null|| head.next === null) {
        return head;
    }
    const newHead = head.next;
    head.next = swapPairs(newHead.next);
    newHead.next = head;
    return newHead;
};

# 串联所有单词的子串

给定一个字符串 s 和一些长度相同的单词 words。找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置。注意子串 要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序,

// 输入:
//   s = "barfoothefoobarman",
//   words = ["foo","bar"]
// 输出:[0,9]
// 从索引 0 和 9 开始的子串分别是 "barfoo" 和 "foobar",输出的顺序不重要, [9,0] 也是有效答案
/**
 * @param {string} s
 * @param {string[]} words
 * @return {number[]}
 */
var findSubstring = function(s, words) {
  const wordSize = words[0].length
  const substringLen = wordSize * words.length
  const wordsCount = {}
  words.forEach(w => (wordsCount[w] = (wordsCount[w] || 0) + 1))
  const res = []
  for (let i = 0; i <= s.length - substringLen; i++) {
    const tempCount = {...wordsCount}
    let count = words.length
    for (let j = i; j < i + substringLen; j += wordSize) {
      const word = s.slice(j, j + wordSize)
      if (!(word in tempCount) || tempCount[word] <= 0) break
      tempCount[word]--
      count--
    }
    if (count === 0) res.push(i)
  }
  return res
};

# 排序数组中找元素第一和最后一个位置

直观的思路肯定是从前往后遍历一遍。用两个变量记录第一次和最后一次遇见 \textit{target}target 的下标,但这个方法的时间 复杂度为 O(n),没有利用到数组升序排列的条件。由于数组已经排序,因此整个数组是单调递增的,我们可以利用二分法来加速查找的 过程。考虑target开始和结束位置,其实我们要找的就是数组中「第一个等于target 的位置」(记为leftIdx)和「第一个大于target 的位置减一1」(记为rightIdx)

二分查找中,两者的判断条件不同,为了代码的复用,我们定义binarySearch(nums, target, lower) 表示在nums 数组中二分查 找target 的位置,如果lower为true,则查找第一个大于等于target 的下标,否则查找第一个大于target 的下标

最后,因为target 可能不存在数组中,因此我们需要重新校验我们得到的两个下标leftIdx和rightIdx,看是否符合条件,如果符合 条件就返回[leftIdx,rightIdx],不符合就返回[−1,−1]

const binarySearch = (nums, target, lower) => {
  let left = 0, right = nums.length - 1, ans = nums.length;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (nums[mid] > target || (lower && nums[mid] >= target)) {
      right = mid - 1;
      ans = mid;
    } else {
      left = mid + 1;
    }
  }
  return ans;
}
var searchRange = function(nums, target) {
  let ans = [-1, -1];
  const leftIdx = binarySearch(nums, target, true);
  const rightIdx = binarySearch(nums, target, false) - 1;
  if (leftIdx <= rightIdx && rightIdx < nums.length && nums[leftIdx] === target && nums[rightIdx] === target) {
    ans = [leftIdx, rightIdx];
  } 
  return ans;
};

# 广度优先深度优先遍历

对于算法来说 无非就是时间换空间 空间换时间

  • 深度优先不需要记住所有的节点, 所以占用空间小, 而广度优先需要先记录所有的节点占用空间大
  • 深度优先有回溯的操作(没有路走了需要回头)所以相对而言时间会长一点
  • 深度优先采用的是堆栈的形式, 即先进后出
  • 广度优先则采用的是队列的形式, 即先进先出
const data = [
    {
        name: 'a',
        children: [
            { name: 'b', children: [{ name: 'e' }] },
            { name: 'c', children: [{ name: 'f' }] },
            { name: 'd', children: [{ name: 'g' }] },
        ],
    },
    {
        name: 'a2',
        children: [
            { name: 'b2', children: [{ name: 'e2' }] },
            { name: 'c2', children: [{ name: 'f2' }] },
            { name: 'd2', children: [{ name: 'g2' }] },
        ],
    }
]
// 深度遍历, 使用递归
function getName(data) {
    const result = [];
    data.forEach(item => {
        const map = data => {
            result.push(data.name);
            data.children && data.children.forEach(child => map(child));
        }
        map(item);
    })
    return result.join(',');
}
// 广度遍历, 创建一个执行队列, 当队列为空的时候则结束
function getName2(data) {
    let result = [];
    let queue = data;
    while (queue.length > 0) {
        [...queue].forEach(child => {
            queue.shift();
            result.push(child.name);
            child.children && (queue.push(...child.children));
        });
    }
    return result.join(',');
}
console.log(getName(data))
console.log(getName2(data))
//a,b,e,c,f,d,g,a2,b2,e2,c2,f2,d2,g2
// a,a2,b,c,d,b2,c2,d2,e,f,g,e2,f2,g2 

leetcode;