【代码随想录】算法练习—01数组

2024.7.17Day1和2024.7.18两天完成数组部分。题目有:二分查找、移除元素、有序数组的平方、长度最小的子数组、螺旋矩阵。

1、数组的基础知识

  • 数组是存放在连续内存空间上的相同类型数据的集合,数组下标都是从0开始的,数组内存空间的地址是连续的。
  • 数组的元素是不能删的,只能覆盖

代码随想录:数组理论基础

2、 题目

2.1 二分查找

思路:

使用二分查找的前提:数组有序、无重复元素
定义区间left、right、mid,通过不断比较目标值和mid值大小来缩小范围区间,每次比较后更新区间值。在写法的过程中需要注意保持区间的不变性,如果采用写法1,即[left,right],整个过程需要保持不变。区间影响left和right的取值,看代码应该清楚。
写法1(左闭右闭):因为定义target在**[left, right]区间,(1)while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=(2)if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1。
写法1(左闭右开):为定义target在
[left, right)**区间,(1)while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的;(2)if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]

题目:

704.二分查找

答案:

使用JS版本
(版本一)左闭右闭区间 [left, right]

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    // right是数组最后一个数的下标,num[right]在查找范围内,是左闭右闭区间
    let mid, left = 0, right = nums.length - 1;
    // 当left=right时,由于nums[right]在查找范围内,所以要包括此情况
    while (left <= right) {
        // 位运算 + 防止大数溢出
        mid = left + ((right - left) >> 1);
        // 如果中间数大于目标值,要把中间数排除查找范围,所以右边界更新为mid-1;如果右边界更新为mid,那中间数还在下次查找范围内
        if (nums[mid] > target) {
            right = mid - 1;  // 去左面闭区间寻找
        } else if (nums[mid] < target) {
            left = mid + 1;   // 去右面闭区间寻找
        } else {
            return mid;
        }
    }
    return -1;
};

(版本二)左闭右开区间 [left, right)

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    // right是数组最后一个数的下标+1,nums[right]不在查找范围内,是左闭右开区间
    let mid, left = 0, right = nums.length;    
    // 当left=right时,由于nums[right]不在查找范围,所以不必包括此情况
    while (left < right) {
        // 位运算 + 防止大数溢出
        mid = left + ((right - left) >> 1);
        // 如果中间值大于目标值,中间值不应在下次查找的范围内,但中间值的前一个值应在;
        // 由于right本来就不在查找范围内,所以将右边界更新为中间值,如果更新右边界为mid-1则将中间值的前一个值也踢出了下次寻找范围
        if (nums[mid] > target) {
            right = mid;  // 去左区间寻找
        } else if (nums[mid] < target) {
            left = mid + 1;   // 去右区间寻找
        } else {
            return mid;
        }
    }
    return -1;
};

2.2 移除元素

思路:

题目:

答案:

2.2 移除元素

思路:

本篇主题是数组,记住数组只能覆盖,不能删除。注意到这点就有思路了,首先是暴力求解,两层for循环,一个for循环遍历数组元素 ,第二个for循环覆盖更新数组。
第二种方法重点掌握:快慢指针。本质是通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
慢指针:指向更新 新数组下标的位置
看下面这张动图应能理解,刚开始快慢指针的索引相同,当快指针所在的元素为删除元素的值时,慢指针索引保持不变,快指针索引位置加一,并赋值给慢指针所在位置。总的来说,fastIndex所在的值与目标值不等,fastIndex++ ,nums[slowIndex] = nums[fastIndex]; slowIndex++ ;fastIndex所在的值与目标值相等,fastIndex++。因此双指针又称“快慢指针”。
在这里插入图片描述

题目:

27.移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

答案:

//时间复杂度:O(n)
//空间复杂度:O(1)
var removeElement = (nums, val) => {
    let k = 0;
    for(let i = 0;i < nums.length;i++){
        if(nums[i] != val){
            nums[k++] = nums[i]
        }
    }
    return k;
};

2.3 搜索插入位置

题目:

34.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。

思路:

这个题目分为两步,第一步通过二分查找判断是否存在数组中,第二步不存在则按顺序插入位置。解决这道题的关键思路是理清楚二分查找最后两步left和right的位置关系,通过left和right的位置关系就能确定新元素按顺序插入的位置。
举一个简单的数组,去模拟算法执行的过程,不难发现新元素的顺序位置就是二分查找后的left或者right+1的位置(while循环停止的条件是left>right)

答案:

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var searchInsert = function(nums, target) {
let mid , left = 0, right = nums.length -1;
let p;
while (left <= right){
    mid = left + ((right -left)>>1)
    if(nums[mid] > target){
    right = mid - 1
    }else if(nums[mid] < target){
    left = mid + 1
    }else{
        return mid
    }
}
return left
};

2.5 有序数组的平方

题目:

977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。

思路:

暴力求解大家应该很容易想到,可以array.map.sort(funtion(a, b){return a-b})。双指针的做法可以通过一个for循环解决,平方后的大小与原数组绝对值大小相对应,因此分别比较平方后两端的大小即可。使用

答案:

版本一:map+sort

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortedSquares = function(nums) {
 var newArr = nums.map(function(item){
    return item*item
 })
 return newArr.sort(function(a, b){
    return a-b
 })

};

版本二:快慢指针

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortedSquares = function(nums) {
let result = [];
let k = nums.length -1
for(i=0, j=nums.length-1;i<=j;){
//取大的值 result[k]=nums[i]*nums[i]  ;k--
    if(nums[i]*nums[i] > nums[j]*nums[j]){
        result[k--] = nums[i]*nums[i];
        i++;
    }else{
        result[k--] = nums[j]*nums[j];
        j--;
    }
}
return result
};

2.6 长度最小的子数组

题目:

209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

思路:

需要使用到滑动窗口的思想。所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在这里插入图片描述
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

答案:

var minSubArrayLen = function(target, nums) {
    let start, end
    start = end = 0
    let sum = 0
    let len = nums.length
    let ans = Infinity
    
    while(end < len){
        sum += nums[end];
        while (sum >= target) {
            ans = Math.min(ans, end - start + 1);
            sum -= nums[start];
            start++;
        }
        end++;
    }
    return ans === Infinity ? 0 : ans
};

2.2 螺旋矩阵II

题目:

59螺旋矩阵
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix
在这里插入图片描述

思路:

这个题目没有特别多的技巧,需要找到每一圈的套路。
在这里插入图片描述
需要注意的是,n为奇数时,最后一圈就是一个数字。掌握每一圈的规律,以及每一圈之间的变化(offset,startX,startY)即可。

答案:


var generateMatrix = function(n) {
    let startX = startY = 0;   // 起始位置
    let loop = Math.floor(n/2);   // 旋转圈数
    let mid = Math.floor(n/2);    // 中间位置
    let offset = 1;    // 控制每一层填充元素个数
    let count = 1;     // 更新填充数字
    let res = new Array(n).fill(0).map(() => new Array(n).fill(0));

    while (loop--) {
        let row = startX, col = startY;
        // 上行从左到右(左闭右开)
        for (; col < n - offset; col++) {
            res[row][col] = count++;
        }
        // 右列从上到下(左闭右开)
        for (; row < n - offset; row++) {
            res[row][col] = count++;
        }
        // 下行从右到左(左闭右开)
        for (; col > startY; col--) {
            res[row][col] = count++;
        }
        // 左列做下到上(左闭右开)
        for (; row > startX; row--) {
            res[row][col] = count++;
        }

        // 更新起始位置
        startX++;
        startY++;

        // 更新offset
        offset += 1;
    }
    // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
    if (n % 2 === 1) {
        res[mid][mid] = count;
    }
    return res;
};

总结

在这里插入图片描述

  • 23
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值