算法从入门到放弃 -- leetcode新手村初级刷题(二)

151 阅读2分钟

一:移动零

截屏2022-11-15 下午5.05.50.png

思路:本质上就是快慢指针的问题。

代码一:

var moveZeroes = function(nums) {
    if(nums.length < 1) return nums
    let slow = 0, fast = 0;
    while(fast < nums.length){
        if(nums[fast] !== 0){
            nums[slow] = nums[fast]
            slow++
        }
        fast++
    }
    while(slow<fast){
        nums[slow] = 0
        slow++
    }
    return nums
};

二:移除元素

截屏2022-11-16 上午10.22.25.png

思路:本质上还是快慢指针的问题。

代码一:

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function(nums, val) {
    var n = nums.length
    var slow = 0
    for(let fast = 0; fast < n; fast++){
        if(nums[fast] !== val){
            nums[slow] = nums[fast]
            slow++
        }
    }
    return slow
};

三:删除有序数组中的重复项 II

截屏2022-11-16 上午11.48.30.png

思路:当我们阅读题目的时候,可以看到是一个有序的数组,所以相同的元素一定是相邻的。当看到需要删除重复项的时候,可以想到常用的方法:双指针(快慢指针)法。用快指针(fast)来记录需要查看比较的元素,slow来记录数组长度。可以看到题目是需要保留两个相同的元素,所以可以通过比较nums[fast]和nums[slow-2]的值来确定是否移动慢指针。

代码一:

/**
 * @param {number[]} nums
 * @return {number}
 */
var removeDuplicates = function(nums) {
    var n = nums.length
    if(n <= 2){
        return n
    }
    let fast = 2, slow = 2
    while(fast < n){
        if(nums[slow -2] !== nums[fast]){
            nums[slow] = nums[fast]
            ++slow
        }
        ++fast
    }
    return slow
};

四:颜色分类

截屏2022-11-21 下午4.01.35.png

思路:其实单看题目,第一反应会感觉就是很简单的排序问题。但是题目有做一定的限制:就是不能使用库的sort函数。所以看这个问题就能知道这是很经典的「荷兰国旗问题」

ps:荷兰国旗🇳🇱是由红白蓝三种颜色组成。

什么是荷兰国旗问题?对只含有三种数值的数进行分类或者排序。

代码一:


var sortColors = function(nums) {
  let left = -1;
  let right = nums.length;
  let i = 0;
  // 下标如果遇到 right,说明已经排序完成
  while (i < right) {
    if (nums[i] == 0) {
      swap(nums, i++, ++left);
    } else if (nums[i] == 1) {
      i++;
    } else {
      swap(nums, i, --right);
    }
  }
};
function swap(array, left, right) {
  let rightValue = array[right];
  array[right] = array[left];
  array[left] = rightValue;
}

五:合并两个有序数组

题目:

截屏2022-11-23 下午5.42.08.png 方法一:直接合并之后再排序

知识点:

Array.splice()

定义: 向/从数组中添加/删除/替换项目,然后返回被删除的项目。 语法 ArrayObject.splice(index,howmany,item1,.....,itemX)

  • index——必须,整数,规定添加或者删除的位置,使用负数,从数组尾部规定位置。
  • howmany——必须,要删除的数量,如果为0,则不删除项目。
  • item1,…itemx——可选,向数组添加的新项目。

一:删除: 指定两个参数:要删除的第一项的位置和要删除的项数。

截屏2022-11-25 上午10.00.52.png

二:插入: 三个参数:起始位置,0(要删除的项数),要插入的项。

截屏2022-11-25 上午10.06.28.png

三:替换 三个参数:起始位置,要删除的项数和要插入的任意数量的项。

截屏2022-11-25 上午10.17.49.png

sort()函数 1,无参数的情况:如果不传入比较函数,默认会以字典排序的顺序为结果 2,传入比较函数的情况:具体的结果和比较函数有关。

代码一:

/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function(nums1, m, nums2, n) {
    // 合并数组
    nums1.splice(m, nums1.length - m, ...nums2)
    // 排序
    nums1.sort((a,b)=>a-b)
};

代码二: 双指针,分别指向两个数据,遍历两个数组然后进行比较,将数值比较小的值塞入排序数组中

var merge = function(nums1, m, nums2, n) {
    let p1 = 0, p2 = 0;
    const sorted = new Array(m + n).fill(0);
    var cur;
    while (p1 < m || p2 < n) {
        if (p1 === m) {
            cur = nums2[p2++];
        } else if (p2 === n) {
            cur = nums1[p1++];
        } else if (nums1[p1] < nums2[p2]) {
            cur = nums1[p1++];
        } else {
            cur = nums2[p2++];
        }
        sorted[p1 + p2 - 1] = cur;
    }
    for (let i = 0; i != m + n; ++i) {
        nums1[i] = sorted[i];
    }
};

代码三:代码二是需要开辟额外的数组空间来先储存排序后的数组。先我们可以在原数组上直接进行操作,从前往后操作容易出现值被覆盖的情况,那么我们就从后往前进行操作,那这样就不会存在值被覆盖的情况了。

/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function(nums1, m, nums2, n) {
    // 先定一好指针
    let p0 = m -1,p1 = n -1,all = m + n - 1
    let cur
    // 比较逻辑处理
    while(p0 >= 0 || p1 >= 0){
        if(p0 === -1){
            cur = nums2[p1--]
        }else if(p1 === -1){
            cur = nums1[p0--]
        }else if(nums1[p0] > nums2[p1]){
            cur = nums1[p0--]
        }else{
            cur = nums2[p1--]
        }
        nums1[all--] = cur
    }
};

六:两数之和 II - 输入有序数组

截屏2022-12-01 上午10.54.30.png

/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(numbers, target) {
    // 定义两个指针,rightleft
    // 如果两数之和等于target,返回下标。如果两数之和大于target,right--,反之left++
    let left = 0,right = numbers.length - 1
    while(left < right) {
        if(numbers[left] + numbers[right] == target){
            return [++left, ++right]
        }else if(numbers[left] + numbers[right] < target){
            left++
        }else{
            right--
        }
    }
};

七:验证回文串

截屏2022-12-01 上午11.30.23.png

方法一:双撞指针

/**
 * @param {string} s
 * @return {boolean}
 */
var isPalindrome = function(s) {
    // 判断是否是空字符串,如果是空,直接返回true
    if(s == ''){
        return true
    }
    // 利用正则去掉非字母和空字符串,然后将大写转换为小写
    s=s.replace(/[^a-zA-Z0-9]/g,"").replace(/\s/g,"").toLowerCase()
    // 双指针进行对比
    let left = 0, right = s.length -1
    while(left < right){
        if(s[left] !== s[right]){
            return false
        }else{
            left++
            right--
        }
    }
    return true
};

方法二:利用js的API,将字符串转化成数组,用reverse()颠倒元素顺序,再用join()将其转换成字符串于原字符串进行对比。

 * @param {string} s
 * @return {boolean}
 */
var isPalindrome = function(s) {
    // 判断是否是空字符串,如果是空,直接返回true
    if(s == ''){
        return true
    }
    // 利用正则去掉非字母和空字符串,然后将大写转换为小写
    s=s.replace(/[^a-zA-Z0-9]/g,"").replace(/\s/g,"").toLowerCase()
    return s === [...s].reverse().join('')
};

八:反转字符串中的元音字母

截屏2022-12-05 上午9.43.29.png

知识点:Array.from()

Array.from()方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

代码一:

/**
 * @param {string} s
 * @return {string}
 */
var reverseVowels = function(s) {
    const n = s.length;
    const arr = Array.from(s);
    let i = 0, j = n - 1;
    while (i < j) {
        while (i < n && !isVowel(arr[i])) {
            ++i;
        }
        while (j > 0 && !isVowel(s[j])) {
            --j;
        }
        if (i < j) {
            swap(arr, i, j);
            ++i;
            --j;
        }
    }
    return arr.join('');
};
    // 判断是否是元音
    const isVowel = (ch) => {
        return 'aeiouAEIOU'.indexOf(ch) >= 0
    }
    // 交换函数
    const swap = (arr, a, b)=>{
        let temp = arr[a]
        arr[a] = arr[b]
        arr[b] = temp
    }

九:盛最多水的容器

截屏2022-12-05 上午10.19.39.png

代码一:

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function(height) {
// 定义变量,左右指针,面积变量,最大面积存放变量
var n = height.length
let right = n - 1, left = 0, area = 0, max = 0
// 逻辑处理
while(left < right){
    area = Math.min(height[left], height[right])*(right - left)
    max = Math.max(max, area)
    if(height[left] > height[right]){
        right--
    }else{
        left++
    }
}
return max
};

十:长度最小的子数组

截屏2022-12-07 上午10.32.51.png 方法一:

所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果

/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function(target, nums) {
    // 定义变量,起始位置,结束位置,数组和变量,存储最小长度变量
    let n = nums.length
    var start = 0, end = 0, sum = 0, ans = n + 1
    // 不断移动end指针,将sum[end]值相加,当sum>=target时移动start指针
    while(end < n){
        sum += nums[end]
        if(sum >= target){
            while(sum - nums[start] >= target){
                sum -= nums[start++]
            }
            ans = Math.min(ans, end-start+1)
        }
        end++
    }
    return ans === n+1 ? 0 : ans
};

十一:寻找数组的中心下标

截屏2022-12-08 上午11.11.59.png 方法一:

/**
 * @param {number[]} nums
 * @return {number}
 */
var pivotIndex = function(nums) {
    for(let i = 0; i < nums.length; i++){
        if(sum(sliceArr(nums, i)[0]) === sum(sliceArr(nums, i)[1])){
            return i
        }
    }
    return -1
};
//数组分割
var sliceArr = (arr, index)=>{
    return [arr.slice(0, index), arr.slice(index+1, arr.length)]
}
// 求和
var sum = (arr) => {
    if(arr.length === 0){
        return 0
    }
    return arr.reduce((prev,curr)=>prev+curr)
}

方法二(官方答案):前缀和

/**
 * @param {number[]} nums
 * @return {number}
 */
var pivotIndex = function(nums) {
    var total = nums.reduce((prev,curr)=>prev+curr)
    var sum = 0
    for(let i = 0; i < nums.length; i++){
        if(2*sum + nums[i] === total){
            return i
        }
        sum += nums[i]
    }
    return -1
};

十二:搜索插入位置

截屏2022-12-08 上午11.44.48.png 方法一:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var searchInsert = function(nums, target) {
    for(let i = 0; i < nums.length; i++){
        if(target <= nums[i]){
            return i
        }
    }
    return nums.length
};

方法二:(官方答案:二分查找算法)

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var searchInsert = function(nums, target) {
    // 使用二分查找
    var n = nums.length
    // ans即最终答案,开始的时候设置为要查询的数组最大下标
    var left = 0, right = n -1, ans = n
    while(left <= right){
        // 此操作等同于求平均值
        let mid = ((right - left) >> 1) + left
        if(target <= nums[mid]){
            ans = mid
            right = mid - 1
        }else{
            left = mid + 1
        }
    }
    return ans
};

十三:合并区间

截屏2022-12-09 上午10.15.48.png

代码一:

/**
 * @param {number[][]} intervals
 * @return {number[][]}
 */
var merge = function(intervals) {
    // 如果数组长度只有1个,直接返回
    if(intervals.length === 1) return intervals
    // 根据数组的第一个元素进行排序(从小到大)
    const sortedIntervals = intervals.sort((a,b) => a[0] - b[0])
    // 逐个比较
    const result = []
    let current = sortedIntervals[0]
    for(let i = 1; i < sortedIntervals.length; i++) {
        // 循环比较后面的区间
        const interval = sortedIntervals[i]
        // 如果下一个区间的最小值,存在于当前区间之中,就合并
        if(interval[0] <= current[1]) {
            // 合并取两个区间的最大值
            current[1] = Math.max(current[1], interval[1])
        }else{
            // 如果不在前一个区间内,说明当前区间于下一个区间不连续,则把当前区间添加到结果集中
            result.push(current)
            current = interval
        }
    }
    result.push(current);
    return result;
};