JS刷剑指offer(待更新)

本文详细探讨了多种与链表和数组相关的算法问题,包括链表的反转、合并、查找特定元素、构建二叉树、滑动窗口最大值等。同时也涉及到了数组的排序、查找逆序对、构建乘积数组和连续子数组最大值等。这些算法问题涵盖了递归、迭代、动态规划、双指针和回溯等多种解题策略,展示了在实际编程中如何高效地处理链表和数组数据结构。
摘要由CSDN通过智能技术生成

JZ2替换空格

/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 * @param s string字符串 
 * @return string字符串
 */
function replaceSpace( s ) {
    // write code here
    return s.replace(/\s/g,'%20');
}
module.exports = {
    replaceSpace : replaceSpace
};

链表

JZ3从尾到头打印链表

描述
输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
示例1
输入:{1,2,3}
返回值:[3,2,1]

使用:unshift从头部添加

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function printListFromTailToHead(head)
{
    // write code here
    const res=[];
    let node=head;
    while(node!==null){
        res.unshift(node.val);
        node=node.next;
    }
    return res;
    
}

JZ14 链表中倒数最后k个结点(双指针)

描述
输入一个链表,输出一个链表,该输出链表包含原链表中从倒数第k个结点至尾节点的全部节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。

分析:第一个指针先移动k步,然后第二个指针再从头开始,这个时候这两个指针同时移动,当第一个指针到链表的末尾的时候,返回第二个指针即可。

/*
 * function ListNode(x){
 *   this.val = x;
 *   this.next = null;
 * }
 */
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param pHead ListNode类 
 * @param k int整型 
 * @return ListNode类
 */
function FindKthToTail( pHead ,  k ) {
    // write code here
    if(pHead===null || k<=0) return null;
    let first=pHead,
        second=pHead;
    while(k--){
        if(first==null){
            // 如果第一个指针还没走k步的时候链表就为空了,直接返回null
            return null;
        }
        first=first.next;
    }
    while(first!=null){
        first=first.next;
        second=second.next;
    }
    return second;
}
module.exports = {
    FindKthToTail : FindKthToTail
};

JZ36 两个链表的第一个公共结点

描述
输入两个无环的单链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

示例1
输入:{1,2,3},{4,5},{6,7}
返回值:{6,7}
说明:第一个参数{1,2,3}代表是第一个链表非公共部分,第二个参数{4,5}代表是第二个链表非公共部分,最后的{6,7}表示的是2个链表的公共部分,这3个参数最后在后台会组装成为2个两个无环的单链表,且是有公共节点的 ,若2个链表没有公共节点 ,返回null

分析:
1.先找到两个链表的长度length1、length2
2.让长一点的链表先走length2-length1步,让长链表和短链表起点相同
3.两个链表一起前进,比较获得第一个相等的节点
时间复杂度O(length1+length2) 空间复杂度O(0)

/*function ListNode(x){
    this.val = x;
    this.next = null;
}*/
function FindFirstCommonNode(pHead1, pHead2)
{
    // write code here
    if(!pHead1||!pHead2) return null;
    let length1=getLength(pHead1);
    let length2=getLength(pHead2);
    let lang,short,cha;
    if(length1>length2){
        lang=pHead1;
        short=pHead2;
        cha=length1-length2;
    }else{
        lang=pHead2;
        short=pHead1;
        cha=length2-length1;
    }
    while(cha--){
        lang=lang.next;
    }
    while(lang){
        if(lang==short){
            return lang;
        }
        lang=lang.next;
        short=short.next;
    }
    return null;
    
}

function getLength(head){
    let cur=head;
    let res=0;
    while(cur){
        res++;
        cur=cur.next;
    }
    return res;
}

JZ15 反转链表(三指针)

描述
输入一个链表,反转链表后,输出新链表的表头。
分析:
至少需要三个指针pPre(指向前一个结点)、pCurrent(指向当前的结点,在代码中就是pHead)、pNext(指向后一个结点)

function ReverseList(pHead)
{
    // write code here
    let pre=null,
        next=null;
    while(pHead!==null){
        next=pHead.next;
        pHead.next=pre;
        pre=pHead;
        pHead=next;
    }
    return pre;
}

JZ16 合并两个排序的链表(普通迭代或递归)

描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
示例1
输入:
{1,3,5},{2,4,6}

返回值:
{1,2,3,4,5,6}

分析:链表头部节点比较,取较小节点
1.递归版本

function Merge(pHead1, pHead2) {
  // 递归的终止条件
  if (!pHead1) {
    return pHead2;
  }
  if (!pHead2) {
    return pHead1;
  }
  if (pHead1.val < pHead2.val) {
    // 如果PHead1的所指节点值小于等于pHead2所指的结点值,那么phead1后续节点和pHead节点继续递归
    pHead1.next = Merge(pHead1.next, pHead2);
    return pHead1;
  } else {
    pHead2.next = Merge(pHead1, pHead2.next);
    return pHead2;
  }
}
  1. 迭代版本
function Merge(pHead1, pHead2)
{
    // write code here
    let head=new ListNode(0);
    let cur=head;
    while(pHead1!==null&&pHead2!==null){
        if(pHead1.val<=pHead2.val){
            cur.next=pHead1; 
            pHead1=pHead1.next;
        }else {
            cur.next=pHead2;
            pHead2=pHead2.next;
        }
        cur=cur.next;
    }
    // 循环直到l1或者l2为nullptr. 将l1或者l2剩下的部分链接到cur的后面
    if(pHead1==null){cur.next=pHead2}
    if(pHead2==null){cur.next=pHead1}
    return head.next;

}

JZ25 复杂链表的复制(难)

描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。

示例:
输入:{1,2,3,4,5,3,5,#,2,#}
输出:{1,2,3,4,5,3,5,#,2,#}
解析:我们将链表分为两段,前半部分{1,2,3,4,5}为ListNode,后半部分{3,5,#,2,#}是随机指针域表示。
以上示例前半部分可以表示链表为的ListNode:1->2->3->4->5
后半部分,3,5,#,2,#分别的表示为
1的位置指向3,2的位置指向5,3的位置指向null,4的位置指向2,5的位置指向null

// function RandomListNode(x){
//     this.label = x;
//     this.next = null;
//     this.random = null;
// }
function Clone(pHead) {
    // 第一次遍历,复制每个节点和 next 指针,并且保存“原节点-复制节点”的映射关系
    // 第二次遍历,通过哈希表获得节点对应的复制节点,更新 random 指针
    if (!pHead || !pHead.next) {
        return pHead;
    }
    const map = new Map();
    let node = pHead;
    const newHead = new RandomListNode(node.label);
    let newNode = newHead;
    map.set(node, newNode);

    while (node.next) {
        newNode.next = new RandomListNode(node.next.label);
        node = node.next;
        newNode = newNode.next;
        map.set(node, newNode);
    }

    newNode = newHead;
    node = pHead;
    while (newNode) {
        newNode.random = map.get(node.random);
        newNode = newNode.next;
        node = node.next;
    }
    return newHead;
}

JZ56 删除链表中的重复节点(迭代:添加虚拟头节点)

描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

function deleteDuplication(pHead) {
  // write code here
  if (pHead === null || pHead.next === null) {
    return pHead;
  }
  // 添加虚拟头节点
  const Head = new ListNode(-1);
  Head.next = pHead;
  let pre = Head;
  let cur = Head.next;
  while (cur !== null) {
    if (cur.next !== null && cur.val === cur.next.val) {
      // 找到最后的一个相同节点,因为相同节点可能重复多个
      while (cur.next !== null && cur.val === cur.next.val) {
        cur = cur.next;
      }
      // 改变指针方向
      pre.next = cur.next;
      cur = cur.next;
    } else {
      pre = pre.next;
      cur = cur.next;
    }
  }
  return Head.next;
}

JZ55 链表中环的入口结点

描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

输入描述:
输入分为2段,第一段是入环前的链表部分,第二段是链表环的部分,后台将这2个会组装成一个有环或者无环单链表
返回值描述:
返回链表的环的入口结点即可。而我们后台程序会打印这个节点,没有环,返回null

输入:
{1,2},{3,4,5}
返回值:3

1.一快一慢指针,先找到碰撞点。
2.碰撞点到环入口节点的距离就是头结点到入口节点的距离。
https://www.cnblogs.com/wuguanglin/p/LoopOfLinkList.html

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

function EntryNodeOfLoop(pHead) {
  let fast = pHead;
  let slow = pHead;
  while (fast !== null && fast.next !== null) {
    slow = slow.next;
    fast = fast.next.next;
    if (fast === slow) {
      // 两者相遇
      let p = pHead;
      while (p !== slow) {
        p = p.next;
        slow = slow.next;
      }
      return p;
    }
  }
  return null;
}

JZ4 重建二叉树(递归,二叉树)

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
示例1 输入:
[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]

返回值:{1,2,5,3,4,6,7}
思路:前序遍历的第一个节点就是根节点,根据值获取到在中序遍历中的位置index,index前的就是中序左子树,后就是中序右子树,递归重建二叉树

function reConstructBinaryTree(pre, vin)
{
    // write code here
    if(pre.length===0){
        return null;
    }
    if(pre.length===1){
        return new TreeNode(pre[0])
    }
    const value=pre[0];
    const index=vin.indexOf(value);
    const vinleft=vin.slice(0,index);
    const vinright=vin.slice(index+1);
    const preleft=pre.slice(1,index+1);
    const preright=pre.slice(index+1);
    const node=new TreeNode(value);
    node.left=reConstructBinaryTree(preleft,vinleft);
    node.right=reConstructBinaryTree(preright,vinright);
    return node;
}

JZ5两个栈实现队列

描述
用两个栈来实现一个队列,分别完成在队列尾部插入整数(push)和在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。

示例:
输入:
[“PSH1”,“PSH2”,“POP”,“POP”]
返回:
1,2

思路:一个用于push,一个用于pop,考虑两个栈不为空的时候

const outstack=[],
      instack=[];
function push(node){
    instack.push(node);
}
function pop(){
    if(outstack.length===0){
        while(instack.length){
            outstack.push(instack.pop())
        }
    }
    return outstack.pop()
}

JZ20 包含min函数的栈(辅助栈)

描述
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数,并且调用 min函数、push函数 及 pop函数 的时间复杂度都是 O(1)
push(value):将value压入栈中
pop():弹出栈顶元素
top():获取栈顶元素
min():获取栈中最小元素

示例:
输入: [“PSH-1”,“PSH2”,“MIN”,“TOP”,“POP”,“PSH1”,“TOP”,“MIN”]
输出: -1,2,1,-1

题目抽象:要求实现一个O(1)时间复杂度的返回最小值的栈。正常情况下,栈的push,pop操作都为O(1),
但是返回最小值,需要遍历整个栈,时间复杂度为O(n),所以这里需要空间换时间的思想,方法:使用一个辅助栈

var dataStack=[];
var minStack=[];

function push(node)
{
    // write code here
    dataStack.push(node);
    if(minStack.length===0 || node<min()){
        minStack.push(node);
    }else{
        minStack.push(min())
    }
}
function pop()
{
    // write code here
    minStack.pop();
    return dataStack.pop();
}
function top()
{
    // write code here
    var length=dataStack.length;
    return length>0&&dataStack[length-1];
    
}
function min()
{
    // write code here
    var length=minStack.length;
    return length>0&&minStack[length-1];
}

JZ21 栈的压入、弹出序列(辅助栈)

描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
示例1
输入:[1,2,3,4,5],[4,3,5,1,2]
返回值:false

function IsPopOrder(pushV, popV)
{
    // write code here
    if(!pushV||!popV||pushV.length==0 || popV.length==0){
        return false;
    }
    var temp=[];
    var index=0;
    for(var i=0;i<pushV.length;i++){
        temp.push(pushV[i]);
        while(temp.length && temp[temp.length-1]==popV[index]){
            temp.pop();
            index++;
        }
    }
    // 判断辅助栈是否为空,若为空,则返回true
    return temp.length==0;
}

JZ11二进制中1的个数(数学)

描述
输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。
示例1
输入:10

返回值:2
思路:举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

function NumberOf1(n)
{
    // write code here
    let count=0;
    while(n){
        n=n&(n-1);
        count++;
    }
    return count;
}

数组

JZ1二维数组中的查找(二分查找)

描述:
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1, 2, 8, 9],
[2, 4, 9, 12],
[4, 7, 10, 13],
[6, 8, 11, 15]
]
给定 target = 7,返回 true。

给定 target = 3,返回 false。
思路:二分查找,选择左下角作为起始点,array[array.length-1][0],比他大就往右走,比他小就往上面走

function Find(target, array) {
  // write code here
  const n = array.length,
    m = array[0].length
  let row = n - 1,
    col = 0;
  if (m === 0 && n === 0) {
    return false;
  }
  while (row >= 0 && col <= m - 1) {
    if (array[row][col] > target) {
      row--;
    } else if (array[row][col] < target) {
      col++;
    } else return true;
  }
  return false;
}

JZ6旋转数组中的最小数字(二分)

function minNumberInRotateArray(rotateArray)
{
    // write code here
   第一种方法:我们发现旋转数组在旋转后,有个分界点,而这个分界点就是最小的那个数。       
    <!-- if(rotateArray.length===0) return 0; 
    for(let i=0;i<rotateArray.length;i++){
        if(rotateArray[i+1]<rotateArray[i]) return rotateArray[i+1]
    }
    return rotateArray[0] -->
    
    let left=0,
        right=rotateArray.length-1;
    while(right>left){
        if(rotateArray[left]<rotateArray[right]) return rotateArray[left]
        let mid=left+(right-left >> 1);
        if(rotateArray[mid]>rotateArray[right]){
            left=mid+1;
        }else if(rotateArray[mid]<rotateArray[right]){
            right=mid;
        }else {
            --right;
        }   
    }
    return rotateArray[left]
}

JZ13调整数组顺序让奇数在前,偶数在后

描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
示例1
输入:[1,2,3,4]
返回值:[1,3,2,4]

function reOrderArray( array ) {
// write code here
var left=[];
var right=[];

for(var i=0;i<array.length;i++){
    if(array[i]%2===0){
        // array[i]&1 
        right.push(array[i]);
    }else{
        left.push(array[i]);
    }
}
return left.concat(right);

}
module.exports = {
reOrderArray : reOrderArray
};

JZ42和为s的两个数字(双指针)

描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,返回两个数的乘积最小的,如果无法找出这样的数字,返回一个空数组即可。
返回值描述:
对应每个测试案例,输出两个数,小的先输出。
示例1
输入:[1,2,4,7,11,15],15

返回值:[4,11]

function FindNumbersWithSum(array, sum)
{
// write code here
// 双指针
if(array&&array.length>0){
let left=0;
let right=array.length-1;
while(left<right){
const s=array[left]+array[right];
if(s>sum){
right–;
}else if(s<sum){
left++;
}else{
return [array[left],array[right]];
}
}
}
return [];
}

JZ41和为S 的连续正数序列(双指针)

描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列?

返回值描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
示例1
输入:9
返回值:
[[2,3,4],[4,5]]

分析:
创建一个容器child,用于表示当前的子序列,初始元素为1,2
记录子序列的开头元素small和末尾元素big
big向右移动子序列末尾增加一个数 small向右移动子序列开头减少一个数
当子序列的和大于目标值,small向右移动,子序列的和小于目标值,big向右移动

function FindContinuousSequence(sum) {
// write code here
// big向右移动,子序列末尾增加一个数;small向右面移动子序列开头减少一个数
// 当子序列的和大于目标值,small向右移动;小于时,big向右移动;
const res = [];
const temp = [1, 2];
let big = 2;
let small = 1;
let curSum = 3;
while (big < sum) {
while (curSum < sum && big < sum) {
temp.push(++big);
curSum += big;
}
while (curSum > sum && small < big) {
temp.shift();
curSum -= small++;
}
if (curSum === sum && temp.length > 1) {
res.push(temp.slice());
temp.push(++big);
curSum += big;
}

}
return res;

}

leetcode1两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

分析:在map中查找是否有两数之差的值,有就返回,没有就将当前值作为key,下标作为值存入到map
/**

  • @param {number[]} nums

  • @param {number} target

  • @return {number[]}
    */
    var twoSum = function(nums, target) {
    // write code here
    const map={};
    if(Array.isArray(nums)){
    for(let i=0;i<nums.length;i++){
    if(map[target-nums[i]]!=undefined){
    return [map[target-nums[i]],i]
    }else{
    map[nums[i]]=i;
    }
    }

    }
    return [];
    }

JZ19顺时针打印矩阵(二维数组, 难)

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵:
[[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,16]]
则依次打印出数字
[1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10]
分析:
设起点坐标为(start,start),矩阵的行数为rows,矩阵的列数为columns
循环结束条件为 rows>start2 并且 columns>start2

// 顺时针打印
function printMatrix(matrix) {
var start = 0;
var rows = matrix.length;
var coloums = matrix[0].length;
var result = [];
if (!rows || !coloums) {
return false;
}
while (coloums > start * 2 && rows > start * 2) {
printCircle(matrix, start, coloums, rows, result);
start++;
}
return result;
}

// 打印一圈
function printCircle(matrix, start, coloums, rows, result) {
var entX = coloums - start - 1;
var endY = rows - start - 1;
// 从左到右
for (var i = start; i <= entX; i++) {
result.push(matrix[start][i]);
}
// 结束行号大于开始行号,从上到下
if (endY > start) {
for (var i = start + 1; i <= endY; i++) {
result.push(matrix[i][entX]);
}
// 结束列号大于开始列好,从右到左
if (entX > start) {
for (var i = entX - 1; i >= start; i–) {
result.push(matrix[endY][i]);
}
// 结束行号大于开始行号,从下到上
if (endY > start + 1) {
for (var i = endY - 1; i > start; i–) {
result.push(matrix[i][start]);
}
}
}
}
}

JZ51构建乘积数组(难,看不懂)

描述
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。

思路:B[i]的值是A数组所有元素的乘积再除以A[i],但是题目中给定不能用除法,将所有的Ai设置为1
分成两部分,Ci Di
function multiply(array) {
const result = [];
if (Array.isArray(array) && array.length > 0) {
// 计算下三角
result[0] = 1;
for (let i = 1; i < array.length; i++) {
result[i] = result[i - 1] * array[i - 1];
}
// 乘上三角
let temp = 1;
for (let i = array.length - 2; i >= 0; i–) {
temp = temp * array[i + 1];
result[i] = result[i] * temp;
}
}
return result;
}

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

描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。你可以假设数组是非空的,并且给定的数组总是存在多数元素。1<=数组长度<=50000

function MoreThanHalfNum_Solution(numbers)
{
// write code here
// 额外开辟一个内存空间存储每个值出现的次数
if(numbers && numbers.length>0){
var len=numbers.length;
var temp={};
for(var i=0;i<len;i++){
if(!temp[numbers[i]]){
temp[numbers[i]]=1;
}else{
temp[numbers[i]]++;
}
if(temp[numbers[i]]>len/2){
return numbers[i]
}
}
return 0;
}
}

描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,返回两个数的乘积最小的,如果无法找出这样的数字,返回一个空数组即可。
返回值描述:
对应每个测试案例,输出两个数,小的先输出。
示例1
输入:
[1,2,4,7,11,15],15
复制
返回值:
[4,11]
描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
返回值描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
示例1
输入:
9
复制
返回值:
[[2,3,4],[4,5]]

JZ37 数字在升序数组中的出现的次数

描述
统计一个数字在升序数组中出现的次数。
示例1
输入:[1,2,3,3,3,3,4,5],3

返回值:4
// indexOf直接查找索引值
function GetNumberOfK(data, k)
{
// write code here
if (!Array.isArray(data) || !k) return 0
const firstIndex = data.indexOf(k)
const lastIndex = data.lastIndexOf(k)
if (firstIndex === -1) return 0
return lastIndex - firstIndex + 1
}
// 二分法
第一次位置:找到目标值,并且前一位的数字和当前值不相等
最后一次位置:找到目标值,并且后一位的数字和当前值不相等
function GetNumberOfK(data, k) {
if (data && data.length > 0 && k != null) {
const firstIndex = getFirstK(data, 0, data.length - 1, k);
const lastIndex = getLastK(data, 0, data.length - 1, k);
if (firstIndex != -1 && lastIndex != -1) {
return lastIndex - firstIndex + 1;
}
}
return 0;
}

function getFirstK(data, first, last, k) {
if (first > last) {
return -1;
}
const mid = parseInt((first + last) / 2);
if (data[mid] === k) {
if (data[mid - 1] != k) {
return mid;
} else {
return getFirstK(data, first, mid - 1, k);
}
} else if (data[mid] > k) {
return getFirstK(data, first, mid - 1, k);
} else if (data[mid] < k) {
return getFirstK(data, mid + 1, last, k);
}
}

function getLastK(data, first, last, k) {
if (first > last) {
return -1;
}
const mid = parseInt((first + last) / 2);
if (data[mid] === k) {
if (data[mid + 1] != k) {
return mid;
} else {
return getLastK(data, mid + 1, last, k);
}
} else if (data[mid] > k) {
return getLastK(data, first, mid - 1, k);
} else if (data[mid] < k) {
return getLastK(data, mid + 1, last, k);
}
}

JZ50 数组中的重复数字

描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任一一个重复的数字。 例如,如果输入长度为7的数组[2,3,1,0,2,5,3],那么对应的输出是2或者3。存在不合法的输入的话输出-1

/**

  • 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
  • @param numbers int整型一维数组
  • @return int整型
    */
    function duplicate( numbers ) {
    // write code here
    const map={};
    for(let i=0;i<numbers.length;i++){
    if(!map[numbers[i]]){
    map[numbers[i]]=1;
    }else{
    return numbers[i];
    }
    }
    return -1;
    }
    module.exports = {
    duplicate : duplicate
    };

JZ32把数组排成最小的数

描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
分析:
主要就是定义新的排序规则
也就是把前一个数和后一个数拼接起来的数,然后再与后一个数和前一个数拼接起来的数比较字典序
使用数组的sort方法,底层是快排,也可以手写一个快排。
sort方法接收一个比较函数,compareFunction:如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;

function PrintMinNumber(numbers)
{
// write code here
if(!numbers || numbers.length===0){
return “”;
}
return numbers.sort(compare).join(’’)
}

function compare(a,b){
const front="" +a+b;
const behind=""+b+a;
return front-behind;

}

JZ35 数组中的逆序对(难,归并排序,递归,未做)

JZ64 滑动窗口的最大值(难,双端队列,存下标)

描述
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。

例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

窗口大于数组长度的时候,返回空
示例1
输入:[2,3,4,2,6,2,5,1],3
返回值:[4,4,6,6,6,5]

遍历数组的每一个元素,
如果容器为空,则直接将当前元素加入到容器中。
如果容器不为空,则让当前元素和容器的最后一个元素比较,如果大于,则将容器的最后一个元素删除,然后继续讲当前元素和容器的最后一个元素比较
如果当前元素小于容器的最后一个元素,则直接将当前元素加入到容器的末尾
如果容器头部的元素已经不属于当前窗口的边界,则应该将头部元素删除

function maxInWindows(nums, k)
{
//双向队列;
if(nums.length === 0 || k === 0) return []
let queue = [], res = [];
for(let i = 0; i < nums.length; i++) {
if(i - queue[0] >= k) queue.shift();
while(nums[queue[queue.length - 1]] <= nums[i]) {
queue.pop();
}
queue.push(i);
if(i >= k - 1) res.push(nums[queue[0]])
}
return res;
}

JZ30连续子数组的最大值

描述
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为 O(n).
示例:
输入的数组为{1,-2,3,10,—4,7,2,一5},和最大的子数组为{3,10,一4,7,2},因此输出为该子数组的和 18
function FindGreatestSumOfSubArray(array)
{
// write code here
// 动态规划
var sum=array[0];
var res=array[0];
for(let i=1;i<array.length;i++){
// 当前数值array[i]和通过状态转移得到array[i]+sum哪一个大
sum=Math.max(sum+array[i],array[i]);
// 比较sum res ,res=两者最大值
res=Math.max(sum,res);
}
return res;
}

function FindGreatestSumOfSubArray(array) {
if (Array.isArray(array) && array.length > 0) {
let sum = array[0];
let max = array[0];
for (let i = 1; i < array.length; i++) {
if (sum < 0) {
sum = array[i];
} else {
sum = sum + array[i];
}
if (sum > max) {
max = sum;
}
}
return max;
}
return 0;
}

动态规划

JZ7 斐波那契数列(动态规划)

function Fibonacci(n)
{
// write code here
if(n0||n1){
return n;
}
// return Fibonacci(n-1)+Fibonacci(n-2);
let pre = 0;
let current = 1;
let result = 0;
for(let i=2;i<=n;i++){
result = pre + current;
pre = current;
current = result;
}
return result;
}

JZ8 跳台阶

function jumpFloor(n)
{
// write code here
if(n<=2){
return n
}
let i=3,
pre=1,
cur=2,
res=0;
for(let i=3;i<=n;i++){
res=pre+cur;
pre=cur;
cur=res;
}
return res;
}
function jumpFloor(n)
{
let dp = [];
dp[1] = 1;
dp[2] = 2;
for(let i = 3;i <= n;i++){
dp[i] = dp[i-2] + dp[i - 1];
}
return dp[n];
}

JZ9 跳台阶拓展-变态跳台阶(动态规划)

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶(n为正整数)总共有多少种跳法。
分析:每个台阶可以选择跳或者不跳,最后一个必跳,所以有2的n-1次

function jumpFloorII(n)
{
// write code here
// let i=1;
// while(–n){
// i*=2;
// }
// return i;

return 1<<(--n)// 左移几位就是原数*2的几次幂

}

JZ10 矩形覆盖(动态规划)

描述
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2n的大矩形,从同一个方向看总共有多少种不同的方法?比如n=3时,23的矩形块有3种不同的覆盖方法(从同一个方向看)

输入描述:21的小矩形的总个数n
返回值描述:覆盖一个2
n的大矩形总共有多少种不同的方法(从同一个方向看)

分析:假设有8个块
第1块竖着放,后面还剩7块,共f(7)种方法。
第1块横着放,后面还剩6块,共f(6)种方法。
即f(8)=f(6)+f(7)

function rectCover(n)
{
// write code here
if(n<=2){
return n;
}
let i=3,
pre=1,
cur=2,
res=0;
while(i++<=n){
res=pre+cur;
pre=cur;
cur=res;
}
return res;
}

LeetCode-63机器人不同路径

题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

最优子结构:F(m,n-1) F(m-1,n) 边界:第一行 第一列 状态转移方程
/**

  • @param {number[][]} arr
  • @return {number}
    */
    var uniquePathsWithObstacles = function(arr) {
    // 行数
    let n = arr.length;
    // 列数
    let m = arr[0].length;
    // 起点或终点为障碍物
    if(arr[0][0] === 1 || arr[n - 1][m - 1] === 1){
    return 0;
    }
    // 记录到达每个位置的路径可能数
    var dp= [];
    // 遍历每一行
    for(let i = 0; i < n; i++){
    dp[i] = []; // 遍历每一行的每个元素
    for(let j = 0; j < m; j++){
    // 若某节点是障碍物,则通向该节点的路径数量为0
    if(arr[i][j] === 1){
    dp[i][j] = 0;
    } else if(i === 0){
    // 若是第一行 每个节点是否能通过都依赖它左方节点
    dp[i][j] = dp[i][j - 1] === 0 ? 0 : 1;
    } else if(j === 0){
    // 若是第一列 每个节点是否能通过都依赖它上方节点
    dp[i][j] = dp[i - 1][j] === 0 ? 0 : 1;
    } else {
    // 否则递归
    dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
    }
    }
    }
    return dp[n - 1][m - 1];

};

LeetCode-198打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:
输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
  偷窃到的最高金额 = 1 + 3 = 4 。

分析:f(k) = 从前 k 个房屋中能抢劫到的最大数额,首先看n = 1 的情况,显然 f(1) = A1。再看 n = 2,f(2) = max(A1, A2)。
对于 n = 3,有两个选项: 抢第三个房子,将数额与第一个房子相加;不抢第三个房子,保持现有最大数额。

显然,你想选择数额更大的选项。于是,可以总结出公式:f(k) = max(f(k – 2) + Ak, f(k – 1))

/**

  • @param {number[]} nums
  • @return {number}
    */
    var rob = function (nums) {
    var len = nums.length;
    if (len < 2) {
    return nums[len - 1] ? nums[len - 1] : 0;
    }
    var current = [nums[0], Math.max(nums[0], nums[1])];
    for (var k = 2; k < len; k++) {
    current[k] = Math.max(current[k - 2] + nums[k], current[k - 1]);
    }
    return current[len - 1];
    };

leetcode-70爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
时间空间复杂度:O(n)
/**

  • @param {number} n
  • @return {number}
    */
    var climbStairs = function(n) {
    const dp=[];
    dp[0]=1;
    dp[1]=1;
    for(i=2;i<=n;i++){
    dp[i]=dp[i-1]+dp[i-2];
    }
    return dp[n]
    };
    优化空间复杂度为O(1)
    在此基础上,我们还可以通过压缩空间来对算法进行优化。因为 dp[i]只与 dp[i-1] 和 dp[i-2] 有关,没有必要存储所有出现过的dp 项,只用两个临时变量去存储这两个状态即可。
    const climbStairs = function(n) {
    let a1 = 1;
    let a2 = 1;
    for (let i = 2; i <= n; i++) {
    [a1, a2] = [a2, a1 + a2];
    }
    return a2;
    }

贪心

LeetCode455分发饼干

思路:贪心+双指针
给一个孩子的饼干应当尽量小并且能满足孩子,大的留来满足胃口大的孩子
因为胃口小的孩子最容易得到满足,所以优先满足胃口小的孩子需求
按照从小到大的顺序使用饼干尝试是否可满足某个孩子
当饼干 j >= 胃口 i 时,饼干满足胃口,更新满足的孩子数并移动指针 i++ j++ res++
当饼干 j < 胃口 i 时,饼干不能满足胃口,需要换大的 j++
时间复杂度:O(nlogn)
空间复杂度:O(1)

/**

  • @param {number[]} g
  • @param {number[]} s
  • @return {number}
    */
    var findContentChildren = function(g, s) {
    g=g.sort((a,b)=>a-b)
    s=s.sort((a,b)=>a-b);
    let i=0;
    let j=0;
    let res=0;
    while(i<g.length && j<s.length){
    if(s[j]>=g[i]){
    i++;
    j++;
    res++;
    }else{
    j++;
    }
    }
    return res;
    };

回溯

leetcode-17电话号码的字母组

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

const letterCombinations = function (digits) {
if (!digits) {
return [];
}
const len = digits.length;
const map = new Map();
map.set(‘2’, ‘abc’);
map.set(‘3’, ‘def’);
map.set(‘4’, ‘ghi’);
map.set(‘5’, ‘jkl’);
map.set(‘6’, ‘mno’);
map.set(‘7’, ‘pqrs’);
map.set(‘8’, ‘tuv’);
map.set(‘9’, ‘wxyz’);
const result = [];

function generate(i, str) {// i是扫描的指针
  // 指针越界,递归的终止条件
    if (i == len) {
        result.push(str);
        return;
    }
    const tmp = map.get(digits[i]);// 当前数字对应的字母
    for (let r = 0; r < tmp.length; r++) {
        generate(i + 1, str + tmp[r]);// 生成新的字符串,指针右移继续翻译
    }
}
generate(0, '');// 初始字符串为‘’,从下标0开始翻译
return result;

};

JZ27 字符串的排列(递归全排列或回溯)

描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。

分析:
current当前排列好的字符串
temp即将进入排列的字符
quene存储还未被排列的字符
// 回溯
function Permutation(str) {
const result = [];
if (str) {
// 将字符串分割为字符数组
queue = str.split(’’)
PermutationCore(queue, result);
}
result.sort();
// set去重
return [… new Set(result)];
}

function PermutationCore(queue, result, temp = “”, current = “”) {
current += temp;
if (queue.length === 0) {
result.push(current);
return;
}
for (let i = 0; i < queue.length; i++) {
temp = queue.shift();
PermutationCore(queue, result, temp, current);
// 回溯:每次递归完成都要将当前排列的字符返回quene
queue.push(temp);
}
}

// 递归全排列法
function Permutation(str) {
let res = [];
let arr=[];
if (str.length <= 0) return res;
arr = str.split(’’); // 将字符串转化为字符数组
res = permutate2(arr, 0, res);
res = […new Set(res)]; // 去重
res.sort();
return res;
}
function permutate2(arr, index, res) {
if (arr.length === index) {  
return res.push(arr.join(’’));
}
for (let i = index; i < arr.length; i++) {
[arr[index], arr[i]] = [arr[i], arr[index]]; // 交换
permutate2(arr, index + 1, res);
[arr[index], arr[i]] = [arr[i], arr[index]]; // 交换
}
return res;
}

JZ65矩阵中的路径(难,回溯,看不懂,上下左右)

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。

路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。 如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。

例如a b c e s f c s a d e e这样的3 X 4矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

分析:回溯算法 问题由多个步骤组成,并且每个步骤都有多个选项。

依次验证path中的每个字符(多个步骤),每个字符可能出现在多个方向(多个选项)

1.根据给定的行列,遍历字符,根据行列数计算出字符位置
2.判断当前字符是否满足递归终止条件
3.递归终止条件:(1).行列越界 (2).与路径不匹配 (3).已经走过(需设定一个数组标识当前字符是否走过)
4.若路径中的字符最后一位匹配成功,则到达边界且满足约束条件,找到合适的解
5.递归不断寻找四个方向是否满足条件,满足条件再忘更深层递归,不满足向上回溯
6.如果回溯到最外层,则当前字符匹配失败,将当前字符标记为未走
/**

  • 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
  • @param matrix char字符型二维数组
  • @param word string字符串
  • @return bool布尔型
    */
    function hasPath( matrix , word ) {
    // write code here
    for(let i=0;i<matrix.length;i++){
    for(let j=0;j<matrix[0].length;j++){
    if(dfs(matrix,word,i,j,0)) return true;
    }
    }
    return false;
    }
    function dfs(matrix,word,i,j,k){
    if(i<0 || j<0 || i>=matrix.length||j>=matrix[0].length|| matrix[i][j]!=word[k]) return false;
    if(k==word.length-1) return true;
    var tmp = matrix[i][j];
    // 实际上每次回溯都会产生一个变量tmp,不会覆盖前面的值,变成’/'是为了不走重复路
    matrix[i][j] = ‘/’;
    var res = dfs(matrix,word,i+1,j,k+1) || dfs(matrix,word,i-1,j,k+1)||
    dfs(matrix,word,i,j+1,k+1) || dfs(matrix,word,i,j-1,k+1);
    // 四周找不到的时候,将该值重置回原来的值
    matrix[i][j] = tmp;
    // 然后回溯上一个值,继续遍历它的四周,如果还是不符合,继续重置回溯的那个值
    return res;
    }

module.exports = {
hasPath : hasPath
};

JZ66机器人的运动范围(同上类似)

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。

例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

分析:
从第一个格开始走,进入递归
判断当前字符是否满足递归终止条件
递归终止条件:(1).行列越界 (2).行列值超出范围 (3).已经走过(需设定一个数组标识当前字符是否走过)
条件不满足,返回0,向上回溯。
若上面三个条件都满足,继续向下递归,返回四个方向的递归之和+1(当前节点)
function movingCount(threshold, rows, cols) {
const flag = createArray(rows, cols);
let count = 0;
if (rows > 0 && cols > 0) {
count = movingCountCore(0, 0, threshold, rows, cols, flag);
}
return count;
}

function movingCountCore(i, j, threshold, rows, cols, flag) {
if (i < 0 || j < 0 || i >= rows || j >= cols) {
return 0;
}
if (flag[i][j] || condition(i, j, threshold)) {
flag[i][j] = true;
return 0;
}
flag[i][j] = true;
return 1 + movingCountCore(i - 1, j, threshold, rows, cols, flag) +
movingCountCore(i + 1, j, threshold, rows, cols, flag) +
movingCountCore(i, j - 1, threshold, rows, cols, flag) +
movingCountCore(i, j + 1, threshold, rows, cols, flag);
}

/**

  • 判断是否符合条件
    */
    function condition(i, j, threshold) {
    let temp = i + ‘’ + j;
    let sum = 0;
    for (var i = 0; i < temp.length; i++) {
    sum += temp.charAt(i) / 1;
    }
    return sum > threshold;
    }

/**

  • 创建一个二维空数组
    */
    function createArray(rows, cols) {
    const result = new Array(rows) || [];
    for (let i = 0; i < rows; i++) {
    const arr = new Array(cols);
    for (let j = 0; j < cols; j++) {
    arr[j] = false;
    }
    result[i] = arr;
    }
    return result;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wanglu的博客

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值