力扣刷题算法笔记(javascript版)上

本文为js版本算法视频笔记 找了很多算法这个up主讲的真的非常好 适合小白去听 算法都是力扣难度中等和简单的
b站视频链接

1、岛屿最大面积(求两数最大和)

在这里插入图片描述
解析:提供一个数组,给了一个target 要求是在数组中找到两个数的和为target
返回的是数组下标

思路:
1.创建一个map
2、for循环遍历nums数组
3、用target减nums[i]以计算哪个数跟当前数字相加等于target
4、检查map里有没有这个数,有的话就放回结果,没有就把num[I]当做key。 i单做value放入map中

代码实现:

var towSum = function(nums, target){
    const map = new Map();
    for (let i = 0; i <nums.length;i++){
        const complement = target-nums[i];
        if(map.has(complement)){
            return [map.get(complement),i];
        }else {
            map.set(num[i],i);
        }
        
    }
    return [];
};

2、两链表相加

视频链接
在这里插入图片描述
关键的地方处理好进位的问题。两个链表相加会生成一个新的链表
这个已经是逆序的了,不用再反转
代码实现:

// 两数相加
 var addTwoNumbers = function (l1,l2){
    //  定义一个新的节点
     let dummy = new ListNode();
    //  curr是用来遍历列表用的
     let curr = dummy;
      let carry = 0; //处理进位用
      while(l1 !== null || l2 !== null){
          let sum = 0;
          if(l1 !==null){
              sum += l1.val;
              l1 = l1.next;
          }
          if(l2 !== null){
              sum +=l2.val;
              l2= l2.next;
          }
        
          sum += carry; //检carry有没有进位
          curr.next = new ListNode( sum %10); 
          //把得到的sum值各位作为新的list的节点
          carry = Math.floor(sum/10);
           //如果有carry值 就暂存carry的值 carry的值是sum/10之后取整 只要整数 
           curr = curr.next  // curr往后挪一位
      }
      if(carry>0){
        curr.next = new ListNode(carry);
        // 如果最后的carry值大于0,需要单独做一个新节点(最前面的节点)
      }
      return dummy.next;//最后return出去

 }

3、找到无重复字符的最长子串

思路:
1、创建一个set 用来存放新的字符串 还需要知道这个字符串的长度
2、两个指针第一个指针指向字符串的开头-j,第二个随着for循环遍历字符串-i
3、如果set里面有s【i】,则从set里开始删除s【j】,并且递增j,在检查set里是否有s【j】,如此反复直到set里没有s【i】为止
5、重复步骤3和4直到完成遍历整个字符串

·代码实现:

var lengthOfLongSubstring = function(s){
    const set = new Set();
    let i = 0;
    let j = 0;
    maxLength = 0;
    if(s.length === 0){
        return 0;
    }
    for (i; i<s.length;i++){
        if (!set.has(s[i])){
            set.add(s.[i]);
            maxLength = Math.max(maxLength,set.size);
        }else{
            while(set.has(s[i])){
                set.delete(s[j]);
                j++;
            }
            set.add(s[i]);
        }
    }
    return maxLength;
}

4 求三数之和

在这里插入图片描述

求数组里面找三个数相加等于0 答案里面必须是唯一的
求解思路:
1、给数组排序{ 方便指针移动}
2、遍历数组,从0遍历到length-2 (防止下标越界)
3、如果当前的数字等于前一个数字,则跳过这个数字(去重,不能有重复的)
4、如果数字不同,则设置start = i +1, end =length -1,查看i start和end三个数的和比零大还是小 如果比0小 start++ 如果比0大 end–;如果等于0 则把这三个数加到结果中 继续下一次遍历(重点)
5、返回结果 不能重复

代码实现:

var  threeSum = function (nums){
    const result = [];
    //对数组进行排序
    nums.sort(function(a,b){
        return a -b;
    })
    for(let i =0;i<nums.length-2;i++){
        //去重 b重复的就不需要在判断
        if(i === 0 || nums[i] !== nums[i-1]){
            let start = i+1;
            let end = nums.length -1;
            while(start<end){
                //三种结果 大于等于小于0
                if(nums[i] + nums[start] + nums[end]   === 0){
                  result.push ([nums[i],nums[start],nums[end]]);
                  start ++;
                  end--;  
                  //判断是否重复
                  	while(start <end && nums[start] === nums[start -1]){
                     	 start ++;
                  	}
                  	while(start <end && nums[end] === nums[end +1]){
                    	end --;
               		 }

                }else if(nums[i] + nums[start] + nums[end]   < 0){
                    start ++;
                }else{
                    end --;
                }

            }
        }
    } 
    return result;
}

5、最长回文子字符串

思想是中心扩散的思想
遍历每一个字符 把每一个字符当做中心点像两边扩散看两边是否相等,知道不相等为止
回文字符串有两种情况:中间一个不同 两边相同如abcacba a为中心点
另外一个是abccba 两边都相等 没有中间点直接像两边扩散

这题放回的是最长回文字符串 ,还有最长回文字符串的长度

解题思想:
1、如果字符串长度小于2 直接返回原字符串
2、定义两个变量 一个start存储当前找到的最大会问字符串的起始位置,另一个maxLength记录字符串的长度 终止位置就是start+maxLength
3、创建一个helper function 判断左边和右边是否越界 同时左边的字符串是否等于右边的字符串,如果以上三个条件都满足,则判断是否需要更新回文字符串最大长度以及字符串的起始位置,然后将left-- right++,继续判断 直到不满足三个条件之一。
4遍历字符串 每个位置调用helper function两遍
第一遍检查i-1,i+1,第二遍检查i i+1(两种回文)

代码实现:

var longestPalindrome = function(s){
    //如果之后一个字符直接返回
    if(s.length<2){
        return s;
    }
    let start = 0;
    let maxLength = 1; //当有两个字符的时候,返回的回文字符串至少是其中一共字符 所以length应该初始化为1
    //helper函数因为要被反复用到 所以这点提出来
    function expandAroundCenter(left,right){
        //上述提到的三个条件同时满足
        while(left > =0 && right <s.length && s[left] === s[right]){
            //比较字符串长度 如果新的字符串比之前的大 则进行更新
            if(right - left + 1 >maxLength){
                        maxLength = right -left +1;
                        start = left
            }
            left --;
            right ++;
        }
    }
    // 遍历的时候把每一个当做中心 这里需要考虑两种回文字符串 因此需要在循环中执行两次函数
    for(let i = 0; i < s.length; i++){
        expandAroundCenter(i-1,i+1);
        expandAroundCenter(i ,i+1);
    }
    //最后放回的是截取的字符串
    return s.substring(start.start+maxLength);
}

6、删除链表的倒数第N个节点

题目描述:给定一个链表 删除链表的倒数第n个节点 并返回链表的头节点
在这里插入图片描述

思路 使用双指针的思路定位到我们要删除的元素
在这里插入图片描述

在这里插入图片描述
单项链表是 走到3的时候直接指向5 不通过4了
在这里插入图片描述
在这里插入图片描述
边界问题 如果之后1个节点
代码实现:

var  removeNthFromEnd = function (
//先创建一个dummy节点一会需要返回
    let dummy = new ListNode();
    dummy.next = head;
    //需要双指针 定义
    let n1 = dummy;
    let  n2 = dummy;
    //先把n2移动到后面的n个位置 (画图就能看清楚了)因为是要删除倒数第n个
    for (let i=0;i<n;i++){
        n2 = n2.next;
    }
    //同时循环 n2没有到最后的空的时候 n1也开始移动 依次向后面移动
    while(n2.next !== null){
        n1 = n1.next;
        n2 = n2.next;
    }
    //当n2到空的时候做关键的操作 去除我们要删除的节点
    n1.next = n1.next.next;
    return dummy.next;
}

7、有效的括号

括号应该是正括号和反括号相对应的 怎么找出错误的括号
实现思路:
利用到栈的概念
1、创建一个HsapMap,把括号配对放进去,
2、创建一个stack (array)for循环遍历字符串
对于每一个字符,如果map里有这个key 那说明他是一个左括号 从map里取得对应的右括号,把他push进stack里面 否则的话他就是右括号,需要pop出stack李的第一个字符然后看他是否等于当前字符 如果不相等 则返回false
3、循环结束 如果stack不为空 则说明还剩余一些左括号,没有被闭合 返回false 否则返回true
代码实现:

var  isValid = function(s){
    //创建函数把配对情况放进去

    const mappings = new Map();
    mappings.set ("(",")");
    mappings.set("[","]");
    mappings.set("{","}");
    //创建一个栈
    const stack = [];
    for(let i = 0; i<s.lenght;i++){
        //如果mapping里有key的话就要从mappings里面取得这个i push到stack里面
        if(mappings.has(s[i])){
            stack.push(mappings.get(s[i]));
        }else{
            //先判断他是否等于i 如果不等于的话返回false
            if(stack.pop() !== s[i]){
                return false;
            }
        } 
    }
    //循环结束之后需要检查stack是否为空 不为空返回false
    if(stack.length !== 0){
        return false;
    }
    return true;
}

8、合并两个有序链表

题目描述:将两个有序链表合并为一个新的有序链表并返回 新链表是通过拼接给定的两个链表的所有节点组成的
在这里插入图片描述
思路总结:
两个数组 开一个新的数组 在两个数组的开头放一个i和j 找到比较小的push到新数组里面 i++或者J++ 在比较两个数 小的push到新数组直到结束;
链表中只有next操作,所以有局限性 要注意这个点所以需要使用dummy来进行空的占位

代码实现:


var mergeTwoLists = function(l1,l2){
    //定义一个头部空节点 用他来生成结果 通过next串成一个链表
    let curr = new listNode();
    let dummy = curry;// 
    //对两个链表进行循环比较 需要判断两个链表不为空
    while (l1 !== null && l2 !== null ){
        if(li.val <l2.val){
            curr.next = l1;
            l1 = l1.next;//这一步相当于i++的操作 链表这样表示
        }else{
            curr.next = l2;
            l2 = l2.next;
        }
        //上面的操作新链表德 指针没有动 我们需要手动移动指针.下次添加的时候就继续从后面添加
        curr = curr.next
    }
    // 其中一共链表为空的时候 把剩余链表加到后面
    if(l1 !==null){
        curr.next = l1;
    }
    if(l2 !==null){
        curr.next = l2;
    }
    //第二个指针 dummy占住了头部的位置 curr可以随便走(因为单项链表不能返回)后面不用他了
    return dummy.next;
    //curr在最开始我们使用的是空节点,所以最后返回的应该是dummy.next 也就是空节点后面的第一个节点
}

9、两两交换链表中的节点

题目描述:给定一个链表 两两交换其中相邻的节点 并返回交换后的链表
解题思路:
一共需要六步 需要三个指针 交换n1和n2需要在n1n2前面设置一个p指针

1、n1 = p.next
2、 n2 = p.next.next
3、p.next = n2 从第三步开始操作
4、n1.next = n2.next
5、 n2.next = n1
6、 p = n1
原图:
在这里插入图片描述
在这里插入图片描述
在走第二遍:因为上一步p和n1交换了 可以直接一直持续 直到结束
在这里插入图片描述
开头处理:第一种方法使用dummy去处理(空头) 第二种用if单独去处理
return的时候使用dummy.next
代码实现:

var swaPairs = function(head){
    //创建一个dummy空节点在最开始
    let dummy = new ListNode();
    dummy.next = head ;
    //遍历链表定义的指针 这里的current其实就是上面说的p
    let current = dummy;
    //保证n1和n2存在 单个节点不需要交换
    while(current.next !== null && current.next.next !== null){
        //开始六个步骤
        let n1 = current.next
        let n2 = current.next.next;
        current.next = n2;
        n1.next = n2.next;
        n2.next = n1;
        current = n1;
    }
    return dummy.next;
    //最后为啥使用dummy.next前面说过
}

10、字符异位词分组

解释 两个单词 打乱顺序之后还能组成新的单词
在这里插入图片描述
思路:
两种方法:第一种是把数组进行排序 然后比较两个数组单词是否相同 最优解第二种是 空间换时间,使用26位数组
1、检查是否为空数组
2、简历一个长度为26的数组 起始值为0 26个0
3、遍历所欲字符串 将字母出现的频率放到数组中对应的位置上 利用ascii码
4遍历数组 按照相同字母出现频率进行分组归类(使用hashMap)
5、遍历map 将结果返回

代码实现:


var groupAnagrams = function (strs) {
    //检查数组是否为空
    if (strs.length === 0) {
        return [];
    }
    const map = new Map();
    //遍历字符串数组 遍历两次 遍历元素 或者元素出现多少次
    for (const str of strs) {
        const characters = Array(26).fill(0); //在长度为26的数组里面填充fill 值为0的数组
        for (let i = 0; i < str.length; i++) {
            const ascii = str.charCodeAt(i) - 97; //取到ascii码
            characters[ascii]++;
        }
        const key = characters.jion(","); //生成key并转化成字符串
        // 分类操作
        if (map.has(key)) {
            //把key放到新的map里面
            // map.set(key,map.get(key).push(str)) //这是es5的方法
            map.set(key, [...map.get(key), str]) //es6扩展方法
        } else {
            // 没有key的话就把key放到里面
            map.set(key, [str])
        }
    }
    //遍历map并返回结果
    const result = [];
    for(const arr of map){
        result.push(arr[1])
    }
    return result;

}

11、最大子序和(动态规划)

题目描述:给定一个整数数组nums 找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和
在这里插入图片描述
思路: 有两种 动态规划和分治法,这里采用动态规划
这题还思路还有点不是很明确后期补上

代码实现:

//最大子序和
 var maxSubArray = function(nums){
     const memo = [];
     memo[0] = nums[0];
     for(let i=1; i<nums.length;i++){
         memo[i]= Math.max(nums[i]+memo[i-1],nums[i]);
     }
     for(let i= 1; i<memo.length;i++){
         max = Math.max(max, memo[i]);
     }
     return max;

 }

12、螺旋矩阵

在这里插入图片描述
思路:把二维数组走完 需要几个变量 记录走的方向
在这里插入图片描述
1、如果数组为空 返回空数组
2.定义4个边界以及当前方向
3、当左边界小于等于右边界,且上边界小于等于下边界的时候,执行while循环 按照右下左上的顺序 依次将路径上的字符添加到结果里
4、while循环结束后 返回结果

代码实现:


//螺旋矩阵
 var spiralOrder = function(matrix){
     if(matrix.length === 0){
         return [];
     }
     //定义四边界以及当前的方向 这四个都比较好理解
     let top = 0;
     let bottom = matrix.length -1;
     let left = 0;
     let right = matrix[0].length -1;

     let direction = "right";//一开始是right
     let result = [];//最终返回的数组放在里面
    //  满足这个条件开始循环 顺序应该是右下左上
     while(left <= right && top <= bottom){
         if(direction === "right"){
             for(let i = left; i<= right;i++){
                 result.push(matrix[top][i]);//把路线当中的每一个数字都push到result里面
                 //[top][i] 往左的时候top是不变的 变的是i [top][i]这个是他的二维坐标放在这个位置
             }
             top++; //第一行执行完成 top++ 变成第二行
             direction = "down"//改变方向
         }
         //后面的就是剩下三个方向写法
         else if (direction === "down"){
            for(let i = top; i<=bottom; i++){
                result.push(matrix[i][right]);
            }
            right --;
            direction = "left";
         }
         else if (direction === "left"){
            for(let i = right; i<=left; i--){
                result.push(matrix[bottom][i]);
            }
            bottom --;
            direction = "top";
         }
         else if (direction === "top"){
            for(let i = bottom; i<=top; i--){
                result.push(matrix[i][left]);
            }
            left ++;
            direction = "right";
         }
     }
     return result;
 }

13、跳跃游戏

描述:给定一个非负整数 你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度,判断你是否能够到达最后一个位置
给定的数组中 跳到这个位置上的数表示你最多能跳多少步 比如在2的位置上可以跳一步或者两部 只要最后到达最后一个位置即可;
在这里插入图片描述
贪心算法/动态规划 所有的动态规划的方法都比贪心算法慢
动态规划有两种一种是从上而下 从下往上

解题思路:利用树递归的解法
在这里插入图片描述
怎么把动态规划加入到递归里面
如果知道1是死路 这个点标记为死点直接放回 比如0 使用这样一个表
在这里插入图片描述
代码实现1:动态规划从上到下

//  跳跃游戏
var canJump = function (nums){
    const totalLength = nums.length; //获取数组的长度
    const memo = Array (totalLength).fill(0);//初始化数组都为0
    memo[totalLength-1]= 1;//最后一个数是1 1是可以通道终点的
    function jump(position){
        // 判断当前的位置是否被标记1 如果被标记的话就返回true
        if(memo[position] === 1){
            return true;
            //   判断当前的位置是否被标记2 如果被标记的话就返回false
        }else if(memo[position] === -1){
            return false;
        }
        //maxJump记录一个数 最大只能到totalLength-1
        const maxJump = Math.min(position + nums[position],totalLength-1)//防止下标越界
        for(let i = position+1; i<=maxJump;i++){
            // 每一条的分支都会记录结果
            const jumpResult = jump(i);
            // 只要有其中一条返回true就返回true
            if(jumpResult === true){
                memo[position] = 1;
                return true;
            }
        }
        memo[position] = -1;
        return false; 
    }
    let result = jump(0);
    return result;
}

代码实现2 从后到前:
思路是一样的只是顺序是反的
在这里插入图片描述
我们最终要到4这个点 2能到4 ,就可以简化为只要能到2就能到4这样的思路,依次往前面推 0不行 1不行 3OK 那么只要能到3就能到4

// 跳跃游戏
var canJump = function (nums){
    const totalLength = nums.lenght;
    const memo = Array (totalLength).fill(0);
    memo[totalLength -1] = 1;//最后一位标记为1
    for(let i= totalLength-2; i=0;i--){//从后往前 -2是最后一位不用看了
        const maxJump = Math.min(i+nums[i],totalLength-1);//防止数组越界
        for(let j= i+1;j<=maxJump;j++){
            if(memo[j] === 1){
                memo[i]=1;
                break;//找到1之后其他的就不用看了
            }
        }
    }
    if(memo[0] === 1){
        return true;
    }else{
        return false;
    }

}

代码实现3:贪心算法
不用数组 使用变量 maxJump =4 从后往前遍历 每一个点判断他当前的数值加上他的index值,如果是大于等于maxJump的 就一定能走到maxJump的位置。 继续下一个 maxJump=3 判断方法如上,如果小于maxJump,则放弃这个点,继续下一个。for循环遍历完成之后,判断如果maxJump等于0,return为true 如果不等于0 则返回false

// 跳跃游戏贪心算法实现
var canJump = function (nums){
    let maxJump = nums.lenght -1;//初始化maxJump
    //倒着回来循环 i倒数第二个 (最后一个不考虑)
    for(let i=nums.length-2;i>=0;i--){
        if(i+nums[i] >= maxJump){
            maxJump = i;
        }
    }
    return maxJump === 0;
}

14、合并区间

题目描述:给了很多的区间 要尽可能的去合并这些区间。最后产出合并后的区间
在这里插入图片描述
解题思路:
1.将数组中的区间按照起始位置排序
2、用curr数组记录当前合并的最大区间 遍历数组中的每一个区间 如果当前区间的起始位置小于等于curr的终点位置 则可以继续合并 所以合并 并更新curr的起始位置和终止位置,如果当前区间的起始位置大于curr的终止位置 则无法合并 所以把curr加到result里面 并用当前的区间替换curr的值
3.最后放回result的值
在这里插入图片描述
代码实现:

var merge = function (interVals){
    //如果只有一个区间 返回就OK了
    if(!interVals.length<2){
        return interVals;
    }
    //如果区间不止一个 那就得需要去排序一下数字 按照起始位置进行排序
    interVals.sort(function(a,b){
        return a[0] - b[0]//按照起始位置排序
    })
    // curr数组里面只有起始位置和终止位置两个数
    let curr = interVals[0]; //curr记录当前合并的最大区间 先初始化为第一个元素
    let result = [];//最终返回的数组
    for (let interval of interVals){//这里使用的for of 循环
        if(curr[1] >= interVals[0]){//终止位置大于起始位置
            curr[1] = Math.max(curr[1],interval[1]);//扩展curr的终止位置 起始位置不变
        }else{
            result.push(curr);
            curr= interVals//存放curr之后把curr更新为下一个区间
        }
    }
    // 最后需要判断一下 如果curr还存在 就需要吧curr放到result里面 
    // 这里的curr其实就是最后一个数组(没有比较)
    if(curr.lenght !== 0){
        result.push(curr);
    }
    return result;
}

15、不同路径(动态规划)

描述:
在这里插入图片描述
思路分析:要注意的是路径不是步数
棋盘类似于数组 我们应该如何计算其中一个格子的路径其实就是此格子到上面的格子和左边的格子的路线总和
在这里插入图片描述
这个数组是二维的
在这里插入图片描述
代码实现:

var uniquePaths = function (m,n){
    //在数组里面使用for循环 里面放空数组 就变成二维数组
    const memo = [];
    for(let i = 0;i<n;i++){
        memo.push([]);
    }
    //第一行路径都是1  只有一条路径
    for(let row = 0; row <n; row ++){
        memo[row][0] = 1;
    }
    // 这个是第一列
    for (let col = 0; col<m; col ++){
        memo[0][col] = 1;
    }
    // 这个才是正式开始算后面格子的路径
    // 两层循环就是行列二维数组 
    for (let row = 1; row <n; row ++){
        for (let col = 1; col <m ;col++){
            // 最终路径等于他上面的格子的路径加左面格子的路径
            memo[row][col]= memo[row-1][col]+memo[row][col-1];
        }
    }
    return  memo[n-1][m-1]; //最后终点的值是n-1,m-1  所以返回这

}

16、数组最后位加一

题目描述:
在这里插入图片描述
思路实现:
两种情况 如果最后一位不是9的话直接加一 如果是9的话需要进位 如果是999的话就需要多一位
我们在循环的时候应该逆序循环 判断最后一位是否是9 如果是9 的话重置为零 在继续循环
代码实现:


var plusOne = function (digits){
    //逆序遍历
    for (let i=digits.lenght -1;i>=0;i--){
        //如果不是9 直接最后一位++ 返回结果即可
        if(digits[i] !==9){
            digits[i]++;
            return digits;
        }else{
            digits[i]= 0;//变成0之后后面不用处理 能直接跳转到前一位加一 实现进位
        }
    }
    // 如果全是9的时候 需要多加一位
    //这里不需要单独去判断了 因为上面的if判断中,如果全是9 就会循环之后跳出for循环
    const result = [1,...digits];//创建一个新的数组 第一位是1  后面全是0 
    //[1].concat(digits) 也可以使用ES5的写法
    return result;
}

17.爬楼梯(动态规划)

描述:
在这里插入图片描述
解法分析:思路就是到n个台阶位置需要多少次 然后动态记录这个点 后面在这个基础上继续去写 那就要用到递归
在这里插入图片描述

代码实现:

var  climbStairs = function (n){
    const memo = [];
    memo[1]= 1;
    memo[2]= 2;
    //3 = memo[1]+memo[2] 后面的循环从3开始 
    for(let i =3 ; i <= n; i++){
        memo[i]= memo[i-2] + memo[i-1];
    }
    return memo [n];
}

18、矩阵置零

描述:一个矩阵里面有0 那么把0这行和列都变成0
在这里插入图片描述
解法思路:(不能开新的数组 只能使用原数组)每一行或者一列只要有0 就把行列变成0
1.检查并标记第一行和第一列是否有0 (firstColHasZero)和(firstRowHasZero)
2、使用第一行和第一列 来标记其余行列是否含有0
3、接下来 利用第一行和第一列的标0情况 将matrix中的数字标0
4、最后 处理第一行和第一列
如果firstColHasZero 等于true 第一列全设为0
如果firstRowHasZero 等于true ,将第一行全设为0

代码实现:

// 矩阵置零
var setZeroes = function (matrix){
    // 定义两个变量标记第一行或者第一列是否有0  有就改变为ture最后要根据他来变化第一行列的0
    let firstColHasZero = false;
    let firstRowHasZero = false;
    //检查第一列是否有0
    for(let i = 0; i<matrix.lenght;i++){
        if (matrix[i][0] === 0){
            firstColHasZero  = true;
        }
    }
    //检查第一行是否有0
    for(let i = 0; i<matrix.lenght;i++){
        if (matrix[0][i] === 0){
            firstRowHasZero  = true;
        }
    }
    // 使用第一行和第一列 来标记其余行列是否含有0
    for(let row = 1; row < matrix.length; row ++){
        for (let col = 1;col < matrix[0].length;col++){
            if(matrix[row][col] === 0){
                matrix[row][0] = 0;
                matrix[0][rol] = 0;
            }
        }
    }

    //接下来处理第一行和第一列中的标0情况 将matrix中的数字标0
    for(let row = 1; row < matrix.length; row ++){
        for (let col = 1;col < matrix[0].length;col++){
           if(matrix[row][0] === 0 || matrix [0] [col] === 0){
               matrix[row][col] === 0;
           }
        }
    }

    // 最后 处理第一行和第一列
    // 如果firstColHasZero等于true 将第一列全设为0
    if(firstColHasZero){
        for(let i= 0;i<matrix.length;i++){
            matrix[i][0] = 0;
        }
    }
    // 如果firstRowHasZero等于true 将第一行全设为0
    if(firstRowHasZero){
        for(let i= 0;i<matrix[0].length;i++){
            matrix[0][i] = 0;
        }
    }
    return matrix;
}

##19、删除排序链表的重复元素
描述:(简单)
给定一个排序链表 删除所有元素 使得每个元素只出现一次
在这里插入图片描述
思路分析:
1我自己的想法:设置一个新的数组 把元素组的每一个数遍历进去 如果之前有重复的就不进去新的数组 这样就能筛选出来(不太成功哈)注意到是链表,
老毕想法:因为是排序好的数组 只需要一个指针 起始位置和下一个位置比较 重复的元素就删去设置成后面一个节点;

代码实现:

// 删除排序列表链表中的重复元素
var deleteDuplicates = function(head){
    //定义一个head 指针
    let current = head;
    while(current !== null && current.next !==null){
        // 如果当前节点和下一个节点相同时 ,当前节点指向后面第二个节点 跳过
        if(current.val === current.next.val){
            current.next = current.next.next;
        }else{
            // 不相等就指针移动一下就OK
            current = current.next;
        }
    }
    return head;
}

19、反转链表m到n之间

题目描述:反转从位置m到n的链表 请使用一趟扫描完成
在这里插入图片描述
解题思路:
1、反转m到n之间的链表
2、将反转后的链表与原链表拼接
代码实现:

// 反转链表2 
var reverseBetween =  function(head,m,n){
    let prev = null;
    let curr = head ;
    let next = head; 
    // 快进到m开始的位置
    for (let i = 1; i < m ;i++){
        prev = curr;
        curr= curr.next;
    }
    // 占住prev和curr的位置 后面要用到
    let prev2 = prev;
    let curr2 = curr;
    // 开始反转
    for (let i =m; i<=n;i++){
        next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    // 拼接起来 
    if(prev2 !== null){ // m>1
        prev2.next = prev;
    }else{//当m等于1 的时候  没有前端 head也改变了
        head = prev;
    }
    curr2.next =curr;
    return head;
}

20、买卖股票的最佳时期1

给定一数组,代表每天股票的价位 获取最大利润 低点买进高点卖出(最后返回最大利润)
在这里插入图片描述
解题思路:卖出点应该在买入点的后面 所以我们应该是找到一个点然后找到他左边区域的最低点算出最大利润 区最大值 也就是遍历每一个点 然后找到他前面的最低点 相减之后进行比较
把最大变量存在一个值中每次比较产生的利润和这个变量比较 留下大的
代码实现:


//g股票买卖
// prices就是给定的数组
var maxProfit = function (prices){
    // 数组长度为0直接返回
    if(prices.length === 0){
        return 0;
    }
    //d定义两个变量 一个是左半边的最小值
    
    let minPrice = prices[0],
        maxProfit = 0;
    for (let i = 0; i < prices.length; i ++){
        //判断当前的价格是否小于最小的价格
        if(prices[i] < minPrice){
            minPrice = prices[i];
            // 当前价格减去最小价格 是否大于之前存储的最大利润
        }else if ((prices[i] - minPrice) > maxPrice){
            maxPrice = prices[i] -minPrice;
        }
    }
    return maxProfit;

}

21、买卖股票的最佳时期 2

题目描述:给定一个数组 他的第i个元素是一只给定股票的第i天的价格,设计一个算法 来计算你所能获取的最大利润, 你可以尽可能的完成多多笔交易 多次买卖一支股票
注意:每次只能持有一支股票 必须在再次买入之前出手之前的股票
在这里插入图片描述
思路分析:
在向上的趋势下 从底端买入 上端卖出 下跌的时候 不买入也不卖出
所以只需要找出向上的趋势买入 向下或者平的时候不做操作 根据节点比较左右的大小!!!
在这里插入图片描述
代码编写:

// 股票买卖2
var maxProfit = function (prices){
    if(prices.length === 0){
        return 0;
    }
    // profit最大利润 底端最小值valley 顶端值peak(初始化后面会变)
    let profit = 0,valley = prices [0],peak = prices[0];
    let i = 0;
    //循环 当前的数和数组的后面一个数进行对比
    while (i<prices.length -1){
        // 第一种情况 跌平 i>=i+1  i++不用管
        while(i<prices.length -1 && prices[i] >= prices[i+1]){
            i++;
        }
        valley = prices[i];//不是跌或者平就说明是谷底把valley设置成为当前i的价格
        // 第二种情况 i< i +1的时候是在涨 让他继续涨
        while(i<prices.length -1 && prices[i] <= prices[i+1]){
            i++;
        }
        peak = prices[i];//涨结束之后的i值 就是波峰的值 使用peak记录
        profit += peak - valley; //之前的profit和现在profit都加起来
    }
    return profit;
}

23 、买卖股票的最佳时期3(困难)

这道题比较困难 后面在看
描述:给定一个数组 他的第i的元素是一支给定的股票在第i天的价格
设计一个算法来计算你所能获取的最大利润,你最多可以完成两笔交易
注意:你不能同时参与多笔交易 (你必须在再次购买签出售掉之前的股票)
在这里插入图片描述

24、验证回文串 valid PalindRome

描述:忽略费字符 空格等 还有大小写 只判断字母是否是回文串

在这里插入图片描述
解题思路:
1.用正则表达式去掉非数字和字母
2.如果字符串小于2,直接返回true,
3、定义两个指针 一个在字符串开头 一个在字符串结尾
4.简历一个while循环 当left<right时执行循环,如果在任意地点S[left] !=== s[right] return false.否则执行left++ right --。继续执行循环。
5、当循环完成之后没有return false的时候 则return false

代码实现:

// 字符串判断:
var  isPalindrome = function (s) {
    // 正则字符串转变小写
    s = s.toLowerCase().replace(/[\W_]/g, "");
    //小于2直接返回
    if (s.length <2){
        return true;
    }
    // 定义left和right双指针
    let left = 0;
    let right = s.length-1;
    while(left<right){
        if(s[left] !== s[right]){
            return false;
        }
        left++;
        right --;

    }
    // 如果没有返回false  那么后面也不用再去判断了 直接返回true
    return true;
}

25、加油站

描述:现在一条环路上有N个加油站 其中第i个加油站有汽油gass[i]升。
你有一辆油箱容量无线的汽车 从第i个加油站往第i+1个加油站需要消耗汽油cost[i]升, 你从其中的一个加油站出发 开始时油箱为空 如果你可以绕环路行驶一周 则返回出发时加油站的编号 否则则返回-1.
在这里插入图片描述
在这里插入图片描述
解题思路:
1、每一个点都是起点 模拟去做 这就是暴力解法
2、动态规划 绕一圈就OK了 把所有油加起来 路程消耗的油 如果所有油大于等于路程消耗的油 那么一定是有至少一个解 否则直接返回、
重点:走不通的时候以这个点为起点 继续完后面走这样的话一圈就可以完事了
代码实现:

//加油站
var canCompleteCircuit = function (gas, cost){
    //初始化所有的油和路程
    let totalGas = 0;
    let totalCost = 0;
    for(let i= 0; i < gas.length;i++){
        //总的Gas和cost
        totalGas += gas[i];
        totalCost += cost[i];
    }
    //总油量小于总耗油量
    if(totalGas < totalCost){
        return -1;
    }
    //定义两个变量
    let currentGas = 0;
    let start = 0;
    for (let i = 0; i<gas.length; i++){
        //当前点的油= 之前的油量-路程消耗的油量+这个加油站加的油量
        currentGas = currentGas - cost [i] +gas [i];
        // 如果油量小于0 则把currentGas设置为0 更新start的位置
        if(currentGas < 0){
            currentGas = 0 ;
            start = i+1;
        }
    }
    return start;
}

26、环形链表(判断)

题目描述:给定一个链表 判断链表中知否有环
为了表示给定链表中的环 我们使用整数pos来表好似链表尾连接到链表中的位置(索引从0开始) 如果pos是-1 则该链表中有环
在这里插入图片描述
思路:
需要两个指针 一个快指针一个慢指针 慢指针一次走一步 快指针一次走两步 如果相遇则就是有环 (如果到达终点则结束)
在这里插入图片描述
代码实现:

// 环形链表判断
var hasCycle = function(head){
    //空数组直接返回
    if(head === null){
        return false;
    }
    let slow = head ;
    let fast = head;
    while(fast.next !== null && fast.next.next !==null){
        //slow是一步一步走 fast是一次走两步
        slow = slow.next;
        fast = fast.next.next
        //如果两个相等 就直接返回true
        if(slow === fast){
            return true;
        }
    }
    return false;
}

27、环形链表||

描述:给定一个链表 返回链表开始入环的第一个节点 如果链表无环 则返回null
为了表示给定链表中的环 我们使用整数pos来表示链表尾连接到链表中的位置 (索引从0开始) 如果pos是-1 则该链表中没有环;
在这里插入图片描述
在这里插入图片描述
思路解析:
先判断一个链表是否有环(可以借用1的代码)
然后在判断环开始的位置
快慢两个指针 快指针两步 慢指针一步
两个指针相遇的时候 就说明他是环形链表,接下来需要做的是把快指针放回头部 让快指针也一步一步走,当他们相遇的时候 相遇的点 就是就是链表环的起始位置
算法正确原因:弗洛伊德算法
代码实现:

// 环形链表2
var detectCycle = function (head){
    if(head === null ){
        return null;
    }
    let slow = head ;
    let fast = head;
    let isCycle = false;
    
    while(fast.next !== null & fast.next.next !== null ){
    //慢指针走一步 快指针走两步
        slow = slow.next;
        fast = fast.next.next;
        if(slow === fast){
            isCycle = true;
            break;
        }
    }
    if(!isCycle){
        return null;
    }
    //把快指针放回头部重新开始
    fast = head;
    while(slow !== fast ){
        slow =slow.next;
        //快指针也一步一步的走
        fast = fast.next;
    }
    return fast;
}

27、乘积最大子序列(动态规划)

描述 :给定一个整数数组nums 找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)
在这里插入图片描述
解题思想:需要两个新的数组,遍历原数组中的了每一个数 大的放在新数组中大数组,小的放在新数组中小数组(因为可能是负的 两个负数乘积为正)
不但要保留最大乘积的子数组 还需要保留最小乘积的子数组(负数)
代码实现:


// 最大子序和乘积
var maxProduct = function(nums){
    //定义两个数组 一个大的一个小的 初始化为原数组的第一个
    const maxProductMemo = [];
    const minProductMemo = [];
    maxProductMemo[0]= nums[0];
    minProductMemo[0]=nums[0];
    let max = nums[0];//最大值初始化也是nums的第一个数
    for (let i =1;i<nums.length; i++){
        // 需要比较三个数 最后一个最小的也需要去比较一下 
        maxProductMemo[i] = Math.max(nums[i],nums[i]*maxProductMemo[i-1],nums[i]*minProductMemo[i-1])
        // 保留min乘积 后面可能也要用到
        minProductMemo[i] = Math.min(nums[i],nums[i]*maxProductMemo[i-1],nums[i]*minProductMemo[i-1])
        max = Math.max(max,maxProductMemo[i]);
    }
    return max;

}

28、寻找旋转排序数组中的最小值

描述:有一个被旋转过的排序数组 排序书序的定义就是从某个树进行反转到前面或者后面 在这个数组中寻找一个最小的元素
在这里插入图片描述
解题思路:数组分成两部分 都是上升的 左半边应该完全大于右半边(如果被反转过后)
1.如果数组长度为1 返回唯一值的一个数
2、定义两个指针 第一个left指向数组开头 第二个right指向数组结尾
3、检查数组是否翻转 如果没有 则返回数组里的第一个数(有可能)
4.当left小于right时 取中间作为mid进行二分搜索如果mid的左边一个数大于mid 或者mid的右边一个数小于mid 则返回mid
5、否则的话 如果left所在的数小于mid 则将left右移动至mid+1的位置 砍掉左半边
6、否则的话 将right左移值mid-1的位置(砍掉右半边)
代码实现:

//旋转排序数组中的最小值
var findMin = function (nums){
    if(nums.length === 1){
        return nums[0]
    }
    let left = 0,right = nums.length - 1;
    //这种情况是没有被反转的数组 直接可以返回第一个数 
    if(nums[right] > nums[0]){
        return nums[0];
    }
    while(left < right ){
        let mid = Math.floor(left + (right -left)/2);

        //判断几种情况
        if(nums[mid] > nums[mid+1]){
            return nums[mid +1];

        }
        if (nums[mid - 1] > nums[mid]){
            return nums[mid];
        }
        if(nums[mid] > nums[left]){
            left = mid +1;
        }else{
            right = mid +1;
        }
    }
}

29、相交链表

编写一个程序 找到两个单链表相交的起始节点
在这里插入图片描述

解题思路:指定两个指针 从a链表 n1和b链表 n2同步走到链表最后 对比n1是否等于n2,
走到最后之后在进行交换 n1到b链表开头 n2到a链表开头,最终会在交汇点相聚 相当与每个指标走了两次链表。
代码实现:

 var getIntersectionNode = function (headA,headB){
    //  不直接操作head的原因是head在后面还需要用到 
     let n1 = headA;
     let n2 = headB;
     while(n1 !== n2){
     //n1走到最后在把n1放到headB走一遍
         if(n1 === null){
             n1 = headB;
         }else{
             n1 = n1.next;
         }
			//n2走到最后也放到headA走一遍
         if(n2 === null){
            n2 = headA;
        }else{
            n2 = n2.next;
        }

     }
     return n1;
 }

30、重复的NDA序列

描述:所有的DNA都有一系列的所谓为ACGT的核苷酸组成 列如:ACGAATTCCG. 在DNA 的研究中 识别DNA 中的重复序列有时会对研究非常有帮助
编写一个函数来查找字符串 目标子串的长度为10 在DNA字符串S中出现的次数超过一次。 超过一次的就是重复DNA 可以使用 吧出现次数超过一次的字符串放到map中
在这里插入图片描述
在这里插入图片描述
代码编写:

 //寻找重复DNA序列
  var findRepeatedDnaSequences = function (s){
    //   定义一个map result和指针
      const map = new Map();
      const result = [];
      let i = 0;
      while(i+10<=s.length){
        //   取出子字符串
        const dna = s.substring(i,i+10);
        //如果map中没有dna放入map中 
        if(map.get(dna) === undefined){
            map.set(dna,1);//dna出现的次数是1
        }else if(map.get(dna) === 1){
            map.set(dna,2)//出现一次的话就把dna的次数变为2
            result.push(dna)//超过两次就加入到result中
        }else{
            //出现次数大于2次
            map.set(dna,map.get(dna)+1);

        }
        i++;
       
      }
      return result;
  }

31 打家劫舍

你是一个专业的小偷 计划投钱沿街的房屋,每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通防盗系统 (所以不能偷相邻的两家)
给定一个代表每个房屋能存放金额的非负整数数组 计算你在不触动警报装置下 能偷到的最高金额。

这周工作任务有点重,下半部分周末看完在继续写!!

  • 5
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

十九万里

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

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

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

打赏作者

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

抵扣说明:

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

余额充值