算法

336 阅读15分钟

双指针

// 灵感来源于 LeetCode移动零
let j = 0; 
for (let i = 0; i < res.data.Body.length; i++) {
    const t = res.data.Body[i];
    if (t.StaffId === StaffInfo.StaffId) {
        const s = res.data.Body[j]
        res.data.Body[j] = t
        res.data.Body[i] = s
        j++
    }
}

我原来是这么实现的

//需求是把数组里的某个复合特定要求的对象,移动到数组最前列
const needShift = [];
for (let i = 0; i < res.data.Body.length; i++) {
    const t = res.data.Body[i];
    if (t.StaffId === StaffInfo.StaffId) {
        const dt = res.data.Body.splice(i, 1);
        i--
        needShift.unshift(...dt)
    }
}
setTags(needShift.concat(res.data.Body))

判断数组是否有重复元素

    function check(arr){
       return [... new Set(arr)].length !== arr.length
    }

求数组内连续元素的最大和

    eg:[-2,1,-3,4,-1,2,1,-5,4] => 6
    var maxSubArray = function(nums) {
    let result = nums[0];
    let sum = result;
    for(let i = 1;i<nums.length;i++){
        const item = nums[i];
        if(sum>0){
           sum += item
        }else{
          sum = nums[i]
        }
        if(sum>result){
                result = sum
        }
      
    }
    console.log( result );
};

思路就是既然是求最大和,那么如果之前累加的和为负数,就不需要向下累加了,因为无论下一个元素是什么,都肯定会被当前的负数sum所拖累。然后每次循环中都拿计算好的sum和缓存的result做比较,给result重新赋值。一定要注意就是循环中 一定要先判断sum是大于0的情况下再进行累加当前数组索引(sum+=item),不要直接累加索引,因为如果sum是负数就完全可以舍弃了,你可能会考虑这种case情况:

[-1,-10000,-200000,-300000]

,这种的显然第一项-1会被舍掉,不会进行后面的进一步累加(但其实本case的最后结果就是-1),但请注意,我们后面有一步非常关键的对比sum和result的操作,我们之前已经将-1缓存到result的了,所以后面的result还是-1,不会变。

找出数组中不重复的两个数(重复的数字均成对出现);

eg:[1,1,2,3,3,5,5,6,4,6] => [2,4]

判断一堆括号中括号花括号是否合法

// 这种解法没什么好说的,算是利用堆栈这种数据结构的一种把。
eg:[[]]() => true ;  {{{{}}}}[) => false
var isValid = function(s) {
    const flag = {
        ")":"(",
        "}":"{",
        "]":"["
    }
    const stack  = [];
    for(let i= 0; i< s.length; i++){
        const sn = s[i]
        const ls = stack[stack.length-1]
        if(ls && flag[sn] === ls){ //不要忘记判断stack里面有没有值,如果没有或最后一位的值无法匹配flag表,就往里推
            stack.pop()
        }else{
            stack.push(sn)
        }
    }
    return stack.length === 0
};

// 下面这种解法还是挺好玩的,记录一下
var isValid = function(s){
    while(s.includes('()') || s.includes('{}') || s.includes('[]')){
        s = s.replace('()','')
        s = s.replace('{}','')
        s = s.replace('[]','')
    }
    return s.length === 0
}

数组和对象flat

数组flat

// 递归(感觉有点像深度的意思,递归向下找)
const flags = (arr) => {
      let result = [];
      for(let i of arr){
        if(Array.isArray(i)){
          result.push(...flags(i))
        }else{
          result.push(i)
        }
      }
      return result 
    }
 // 或者直接调用api
 
 arr.flat(Infinity)
 
 // 使用广度优先遍历(先全摊开再说)
 function flat(arr){
  const queue = arr;
  const result = [];
  while(queue.length>0){
    const _x = queue.shift();
    if(Array.isArray(_x)){
      queue.push(..._x)
    }else{
      result.push(_x)
    }
  }
  return result
}

对象flag

// 使用深度优先遍历
const obj = {
    a: {
      b:1,
      c:2,
      d:{
         e:5
      }
    },
    b:[1,3,{a:2,b:3}],
    c:3
}
输出:
{
    'a.b':1,
    'a.c':2,
    'a.d.e':5,
    'b[0]':1,
    'b[1]':3,
    'b[2].a':2,
    'b[2].b':3,
     c:3
}

// 卡了很久,感觉应该是用深广度遍历的知识,就去学了一下 (总结在这篇文章https://juejin.cn/post/7065876830380621860)。
// 然后写了一个超级丑陋的方法出来
    function flatten(obje){
     let keys = Object.keys(obje);
     const result = {}
     for(let key of keys){
       const stack = []//每开始一个顶层节点都新声明一个数组(模拟一下栈),
       stack.push(key)// 入栈
       fla(key,obje,stack,result)
     }
     console.log(result)
   }
   function fla (k,ks,stack,res){
     if(typeof ks[k] === 'object'){
       let keys = Object.keys(ks[k])
       for(let key of keys){
         Array.isArray(ks[k])?stack.push('['+key+']') : stack.push(key)// 入栈
         fla(key,ks[k],stack,res) // 通过递归调用fla不断向下(深)寻找子节点
       }
     }else{
       let _k = stack.join('.').replace('.[','[');
       res[_k] = ks[k];
       stack.pop() // 当拿到非引用类型的值得时候做出栈操作,然后去寻找同级别的下一个节点
     }
   }
 flatten(obj);

数组快速排序

  // 磨磨蹭蹭的搞了一下午,非常简单没有什么好说的,就是递归的应用,注意循环要从1开始,还有就是结束递归的条件考虑好,别忘记
    const flags = (arr) => {
     if(arr.length === 0) return []
     if(arr.length === 1) return arr
     let ins = arr[0];
     let left = [];
     let right = [];
     for(let i=1;i<arr.length;i++){
       const inx = arr[i]
       if(inx>ins){
         left.push(inx)
       }else{
         right.push(inx)
       }
     }
     return [...flags(left),ins,...flags(right)]
   }
 

将数组转为树结构,将树结构摊平

数组转tree

 let arr = [    {id: 1, name: '部门1', pid: 0},    {id: 2, name: '部门2', pid: 1},    {id: 3, name: '部门3', pid: 1},    {id: 4, name: '部门4', pid: 3},    {id: 5, name: '部门5', pid: 4},]
=>
[ { "id": 1, "name": "部门1", "pid": 0, "children": [ { "id": 2, "name": "部门2", "pid": 1, "children": [] }, { "id": 3, "name": "部门3", "pid": 1, "children": [ // 结果 ,,, ] } ] } ]
// 标准答案
function arrayToTree(items) {
  const result = [];   // 存放结果集
  const itemMap = {};  // 
    debugger
  // 先转成map存储
  for (const item of items) {
    itemMap[item.id] = {...item, children: []}
  }
  
  for (const item of items) {
    const id = item.id;
    const pid = item.pid;
    const treeItem =  itemMap[id];
    if (pid === 0) {
      result.push(treeItem);
    } else {
      if (!itemMap[pid]) {
        itemMap[pid] = {
          children: [],
        }
      }
      itemMap[pid].children.push(treeItem)
    }

  }
  return result;
}
// 我自己瞎写的
const makeObj = (arr) => {
  let result = {}
  for(let v of arr){
    v.children= []
    result[v.id] =v 
  }
  return result
}
let cacheObj = makeObj(arr);
let result = [];
for(let v of arr){
  if(v.pid !== 0){
    cacheObj[v.pid].children.push(v)
  }else{
    result.push(v)
  }
}
console.log(result)

将数组转为树结构这种问题的解决思路非常简单,主要是看你脑袋能不能bie(四声)过来那个弯儿。核心思路就是对象的引用。 说白了就是 你只要把每个一级对象推到结果数组,其他的层级结构的拼装,都依赖你自己提炼出来的Map对象处理,通过map对象的遍历去只安排某个单一层级的依赖关系,也就是相当于你手里有两份数据,一份是输出的result,这个用来保证输出层级结构,另一个是你自己组装的map,这个通过拼装单一级别的数据之后,利用对象的引用原理自动填充到数组的result结构中就可以了(map结构中的children数组变了,因为result中也是同样的引用,所以result里的数据也会跟着变)。

tree转数组

const treeNode = [{
   parentId: 0,
   title: "目录1",
   id:1,
   children: [{
      parentId: 1,
      title: "子目录1-1",
      id:22, 
   },{
      parentId: 1,
      title: "子目录1-2",
      id:33, 
   }],
},{
   parentId: 0,
   title: "目录2",
   id:2,
   children: [{
      parentId: 2,
      title: "子目录2-1",
      id:44, 
   },{
      parentId: 1,
      title: "子目录2-2",
      id:55, 
   }],
}];

// 直接广度优先
function fn(tree){
 const result = [];
 while (tree.length>0) {
   const value = tree.shift();
   if(value.children){
     tree.push(...value.children);
     delete value.children;
   }
   result.push(value)
 }
 return result
}

用最少的固定面额硬币取出固定面额

function solution(coins, amount){
    const amountArr = new Array(amount+1);
    for(let i = 0 ; i < amountArr.length; i++){
        const price = i;
        if(i === 0){
            amountArr[i] = 0;
        }else if(coins.includes(price)){  //{{1}}
            amountArr[i] = 1;
        }else {
            const _priceArr = [];
            for(let j = 0; j < coins.length ; j++){
                const coin = coins[j];
                if(price > coin) {
                    const _price = price - coin;
                    if(_price > 0){
                        _priceArr.push(amountArr[_price] + 1)
                    }else {
                        _priceArr.push(Infinity)
                    }
                }else {
                    _priceArr.push(Infinity)
                }
            }
            amountArr[i] = Math.min(..._priceArr)
        }
    }
    return amountArr[amountArr.length - 1] === Infinity  ? -1 : amountArr[amountArr.length - 1]
}

注意点:

  1. 注意声明amountArr数组要amount+1,因为数组是从0开始的,否则会取不到最后结果;
  2. 因为要取最小值,所以不符合的结果全部使用Infinity代替,后序返回的时候替换为-1,不要一上来就用-1,因为计算期间会取最小值,用-1就会有问题
  3. 要理解解决这种问题的思想

跳台阶

var numWays = function(n) {
     let steps = new Array(n + 1);
    steps[0] = 1;
    steps[1] = 1;
    steps[2] = 2;
    // n  = f(n - 1) + f(n - 2)
    if(n > 2){
        for(let i = 2; i<steps.length; i++){
            steps[i] = steps[i - 2] + steps[i - 1]
        }
    }
    return steps[n]
};

这个不复杂,没什么好说的,n级台阶之前有两种到达n级的办法,跳1步f(n-1)或跳2步f(n-2),这两种不同的跳法把之前的到达n的方式分成了两个不同的集合,把这两个集合加起来就是到达n的所有方法了

斐波那契数列

Z 字形变换

var convert = function(s, numRows) {
    if (numRows === 1) return s;
    if(s.length === 1 || s.length === 2 ) return s;
    let flag = 1;
    let handler = [];
    let inx = 0;
    for(let i = 0; i < numRows; i++){
        handler.push([])
    };
    for(let i = 0; i < s.length; i++){
        const curStr = s[i];
        if(flag > 0){
            handler[inx].push(curStr)
            inx++;
            if(inx === numRows){
                flag = -1;
                inx = inx - 2
            }
        }else {
            handler[inx].push(curStr);
            inx--;
            if(inx === -1){
                flag = 1;
                inx= inx + 2
            }
        }
    }
    return handler.flat(2).join('')
};

核心思路就是利用flag来判断正反方向,然后遍历字符串通过flag来依次将char填入生成的二维数组中即可。

普通接雨水

var maxArea = function(height) {
if (height.length === 2) {
        return Math.min(...height);
      }
      let left = 0,
        right = height.length - 1,
        result = 0;
      while (left < right) {
        if (height[left] < height[right]) {
          const curResult = (right - left) * height[left];
          result = Math.max(result, curResult);
          left++;
        } else {
          const curResult = (right - left) * height[right];
          result = Math.max(result, curResult);
          right--;
        }
      }
      return result;
};

典型双指针使用场景。主要是要想明白为什么要每次移动短的指针,移动一次短的指针,相当于舍弃了一个边界。具体见题解:leetcode.cn/problems/co…

全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 苦思冥想好久没做出来,看答案了。看答案后的第三天自己按照答案的思路实现了一下。主要翻了一个错误就是忘记深拷贝了,就是slice那里。第一次接触回溯算法。

aa061a6233fa2bd640b4e1416c12dceceb0c4b4e6f2610240740a3cb6ea1496c-微信截图_20200514183911.png

var permute = function(nums) {
    const usedFlag = {};  // 记录当前这条路径下哪个数字被用过了,下次跳过
    const result = [];  // 结果数组
    function getResult(curArr){  // 递归调用方法
        if(curArr.length === nums.length) { // 如果满了结束这个路径的查找,这个路径的递归结束
            result.push(curArr.slice(0)); // 此处注意拷贝一下数组,防止下面pop的时候会影响结果数组
            return;
        }
        for(let i = 0;i < nums.length; i++){
            const curNum = nums[i]
            if(usedFlag[curNum])continue  // 如果这个数字用过了就跳过
            curArr.push(curNum); 
            usedFlag[curNum] = true;
            getResult(curArr)
            const popNum = curArr.pop() // 上个路径结束了,推出curArr最后一个,看下一个数字
            usedFlag[popNum] = false  //推出的数字置为false ,未使用状态
        }
    } 
    getResult([])
    return result
};

字符串大小写翻转

eg:HeLL => hEll

const input = 'Hello World!';

const reverseCase = txt => txt.replace(/[a-z]/gi, char => String.fromCharCode(char.charCodeAt(0) ^ 32));

console.log(reverseCase(input)); // "hELLO wORLD!"

在 ASCII 码中,同一字母的大小写只有第 6 位不同,大写为 0 小写为 1,因此大小写转换只需要反转第 6 位,也就是 x^(1<<5);
1<<5也就是32的二进制是100000 ,所以前5位的异或操作都是得到需要转换字符的ASCII码本身,只有第六位是1才会取反,达到了转换大小写的目的。注意要用正则拆分出字母,其他的字符不符合这个规则,例如逗号一类的,如果同样被转换可能得到奇怪的符号。

整数转罗马数字

我的解答:


var intToRoman = function(num) {
    const StrNum = String(num);
    const rapMap = new Map([[1,'I'],[10,'X'],[100,'C'],[1000,'M']]);
    const vMap = new Map([[5,'V'],[50,'L'],[500,'D']]);
    const createUni = (num,rgp) => new Array(num).fill(0).map(()=>rapMap.get(rgp)).join('');
    const result = [];
    for(let i = StrNum.length - 1;i>=0;i--){
          const s = +StrNum[i];
          const pow = StrNum.length - i - 1;
          const powValue = Math.pow(10,pow); // 1000 100 10 1
          const realValue = powValue*s; // 2000 300 60 5
          if(s === 5){
            result.push(vMap.get(realValue))
          }else if(s < 4){
            result.push(createUni(s,powValue))
          }else if(s > 5 && s < 9){
            result.push(vMap.get(5*powValue) + createUni(s-5,powValue))
          }else if(s === 4){
            result.push(rapMap.get(powValue) + vMap.get(5*powValue))
          }else if(s === 9){
            result.push(rapMap.get(powValue)+rapMap.get(powValue*10))
          }
    }
    return result.reverse().reduce((a,b)=>a+b,'')
};

好的解答,贪心算法:

class Solution {
public:
    string intToRoman(int num) {
        int values[]={1000,900,500,400,100,90,50,40,10,9,5,4,1};
        string reps[]={"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
        
        string res;
        for(int i=0; i<13; i++){
            while(num>=values[i]){
                num -= values[i];
                res += reps[i];
            }
        }
        return res;
    }
};

罗马数字转整数

我的解答:

var romanToInt = function(s) {
    const signMap = new Map([
        ['I',1],
        ['IV',4],
        ['V',5],
        ['IX',9],
        ['X',10],
        ['XL',40],    
        ['L',50],
        ['XC',90],
        ['C',100],
        ['CD',400],
        ['D',500],
        ['CM',900],
        ['M',1000]
    ]);
    const _s = s.split('');
    let result = 0;
    for(let i = 0;i<_s.length;i++){
        const value = s[i];
        const nextValue = s[i] +  s[i+1];
        if(signMap.has(nextValue)){
            result+=signMap.get(nextValue)
            i++
        }else{
            result+=signMap.get(value)
        }
    }
    return result
};

好的解答: leetcode.cn/problems/ro… 这个解答和评论区里的解答都非常有意思;可以看看

写出一个函数trans,将数字转换成汉语的输出,输入为不超过10000亿的数字。

trans(123456) => 十二万三千四百五十六
trans(100010001) => 一亿零一万零一

function trans(number) {
        const getLevel1 = (arr) => {
          let result = "";
          const clone = unit.slice(0);
          clone.splice(0, clone.length - arr.length);
          arr.forEach((v, i) => {
            const t = v == 0 ? "零" : cn[v] + clone[i];
            result += t;
          });
          return result.replace(/零+/, "零").replace(/零$/, "");
        };
        const unit = ["万", "千", "百", "十", ""];
        const cn = {
          1: "一",
          2: "二",
          3: "三",
          4: "四",
          5: "五",
          6: "六",
          7: "七",
          8: "八",
          9: "九",
          0: "零",
        };
        let arr = Array.from(String(number));
        if (arr.length < 6) {
          console.log(getLevel1(arr));
        }
        if (arr.length > 5 && arr.length < 9) {
          const l1 = arr.slice(arr.length - 4);
          const l2 = arr.slice(0, arr.length - 4);
          console.log(getLevel1(l2) + "万" + getLevel1(l1));
        }
        if (arr.length > 8) {
          const l1 = arr.slice(0, arr.length - 8);
          const l2 = arr.slice(arr.length - 8, arr.length - 4);
          const l3 = arr.slice(arr.length - 4);
          console.log(
            getLevel1(l1) + "亿" + getLevel1(l2) + "万" + getLevel1(l3)
          );
        }
      }
      trans(1000099990102);

三数之和

var threeSum = function(nums) {
   let result = [];
   nums.sort((a,b)=>a-b);
   for(let i = 0;i<nums.length;i++){
       const cur = nums[i];
       if(cur > 0)break;
       if(i>0 && cur === nums[i-1])continue;
       let left = i+1,right = nums.length - 1;
       while(left < right){
           let L = nums[left],R = nums[right];
           if(cur+L+R === 0){
               result.push([cur,L,R]);
               left++
               while(nums[left]===nums[left-1]){
                   left++
               }
               right--
               while(nums[right]===nums[right+1]){
                   right--
               }
           }else if(cur+L+R > 0){
               right--
           }else if(cur+L+R < 0){
               left++
           }
       }
   }
    return result;

};

自己最开始写的方案测试用例超时;以上是看了答案之后过了一周自己又按照答案的思路重新写了一遍,整体思路是对的, 期间还是有一些要注意的地方;
1.首先要第一个要想到的就是将数组要是升序排列;这样当某项大于0的时候后面的元素就直接可以不用考虑了
2.最大的难点是能想到这种双指针的思路,然后还有个难点就是去重,认真考虑几次去重的原因;尤其

if(cur+L+R === 0){}

这个判断内部的两个 去重判断,要想明白;另外比如要考虑好为什么使用了

if(i>0 && cur === nums[i-1])continue;

而没有使用

if(nums[i] === nums[i+1])continue;

来进行判断,乍一看这两种写法似乎都差不多,但你考虑到其中会漏项的原因了吗?

 最接近的三数之和

三数之和的变体,连调再猜搞了一下午才写出来,期间跑了n趟厕所,先贴一个最初我写的的超烂版本,完全没优化的

var threeSumClosest = function(nums, target) {
    nums.sort((a,b)=>a-b);
    let result = Infinity;
    for (let i = 0; i < nums.length; i++) {
          if (i > 0 && nums[i] === nums[i - 1]) continue;
          if (result === target) break;
          let L = i + 1;
          let R = nums.length - 1;
          if(L===R)break;
          const sums = nums[i] + nums[L] + nums[R];
          const cha = sums - target;
          if (Math.abs(cha) < Math.abs(result - target)) {
            result = sums;
          }
          if (cha === 0) {
            result = sums;
            break;
          } else if (cha > 0) {
            // 已经大于0了,左指针没必要移动,越移动和target的差值越大,距离越远
            R--;
            while (L < R) {
              const sums = nums[i] + nums[L] + nums[R];
              const r = sums - target;
              if (Math.abs(r) < Math.abs(result - target)) {
                result = sums;
              }
              if (r < 0) {
                L++
              }else {
                R--
              }
            }
          } else if (cha < 0) {
            // 小于0的情况, 移动左指针
            L++;
            while (L < R) {
              const sums = nums[i] + nums[L] + nums[R];
              const r = sums - target;
              if (Math.abs(r) < Math.abs(result - target)) {
                result = sums;
              }
              if (r < 0) {
                L++
              }else {
                R--
              }
            }
          }
        }
    return result;
};

实际上两个while循环应该是可以合并的,没时间了,明天补充;
次日补充上面的优化后的版本:

var threeSumClosest = function(nums, target) {
    nums.sort((a,b)=>a-b);
    let result = Infinity;
    for (let i = 0; i < nums.length; i++) {
        if (i > 0 && nums[i] === nums[i - 1]) continue;
        if (result === target) break;
        let L = i + 1;
        let R = nums.length - 1;
        while (L < R) {
            const sums = nums[i] + nums[L] + nums[R];
            const r = sums - target;
            if (Math.abs(r) < Math.abs(result - target)) {
                result = sums;
            }
            if(r === 0){
                result = sums;
                break
            }
            if (r < 0) {
                L++
            }else {
                R--
            }
         }
    }
    return result;
};

删掉多余的while循环,以及while循环外面的某些操作本质上都可以放到while循环里面

合并区间 leetcode.cn/problems/me…

var merge = function(intervals) {
    intervals.sort((a,b)=>Math.min(...a)-Math.min(...b));
    for(let i = 1;i<intervals.length;i++){
        const pre = intervals[i-1];
        const cur = intervals[i];
        const [preS,preE] = pre;
        const [curS,curE] = cur;
        let newArr;
        if(preE > curS){
            if(preE<=curE){
                newArr = [preS,curE]
            }else{
                newArr = [preS,preE]
            }
        }else if(preE === curS){
            newArr = [preS,curE]
        }
        if(newArr){
            intervals.splice(i-1,2,newArr)
            i--;
        }
    }
    return intervals
};

要优先能想到用每个子项的最小值进行排序

电话号码的字母组合 leetcode.cn/problems/le…

憋了一天半,连蒙带猜外加一顿debug才整出来,算是完全凭直觉写出来的,这种回溯算法确实很难用常规思维去思考,递归很恶心;

var letterCombinations = function(digits) {
   if (digits === "") return [];
        const numLetter = {
          2: ["a", "b", "c"],
          3: ["d", "e", "f"],
          4: ["g", "h", "i"],
          5: ["j", "k", "l"],
          6: ["m", "n", "o"],
          7: ["p", "q", "r", "s"],
          8: ["t", "u", "v"],
          9: ["w", "x", "y", "z"],
        };

        const len = digits.length;
        const result = [];
        let curStr = "";
        function getResult(curStr) {
          const curNum = digits[curStr.length];
          const curLetter = numLetter[curNum];
          for (let i = 0; i < curLetter.length; i++) {
            curStr += curLetter[i];
            if (curStr.length === len) {
              result.push(curStr);
              curStr = curStr.slice(0, -1);
            } else {
              getResult(curStr);
              curStr = curStr.slice(0, -1);
            }
          }
        }
        getResult(curStr);
       return result

};

看题解中,这个使用队列处理的方案很好,比较容易理解

var letterCombinations = function(digits) {
   if (digits === "") return [];
        const numLetter = {
          2: ["a", "b", "c"],
          3: ["d", "e", "f"],
          4: ["g", "h", "i"],
          5: ["j", "k", "l"],
          6: ["m", "n", "o"],
          7: ["p", "q", "r", "s"],
          8: ["t", "u", "v"],
          9: ["w", "x", "y", "z"],
        };
        const result = [""];
        for (let i = 0; i < digits.length; i++) {
          const digit = digits[i];
          let size = result.length;
          for (let j = 0; j < size; j++) {
            const cur = result.shift();
            for (let k = 0; k < numLetter[digit].length; k++) {
              result.push(cur + numLetter[digit][k]);
            }
          }
        }
        return result;

};

这个解题方法中要关键想明白为什么要用

let size = result.length;

这个size放到循环中,而不是直接这样用:

for(let j = 0; j < resule.length;j++){}

如果直接把取length的操作放到循环体中,那么就死循环了,实际这里的操作是从队列前面shift出一个值,所以只要保证原始队列长度的循环次数即可,就能保证把该队列的所以元素全部shift出来,不会漏掉,因为你不是arr[i]取值,所以这样写没问题,如果arr[i]取值那就拿不准了,这里永远做shift,就是永远拿第一个,所以没事儿。

组合 leetcode.cn/problems/co… 回溯问题

 var combine = function (n, k) {
        const result = [];
        const stack = [];
        function getResult(index) {
          for (let i = index; i < n; i++) {
            const cur = i + 1;
            stack.push(cur);
            if (stack.length === k) {
              result.push([...stack]);
              stack.pop();
            } else {
              getResult(i + 1);
            }
          }
          stack.pop();
        }
        getResult(0);
        return result;
      }

这种回溯问题的传统标准解法还是要好好研究研究,准备面试的时候突击一下。几乎每次都是一顿试