Leetcode刷题--数组

说明

本文的刷题顺序是按照 代码随想录 来进行的,在这里记录自己做题的思路。

基本内容

数组是一种数据结构,他和链表经常会被用作比较。他们使用的场景不同。数组需要注意的有:
(1)内存地址是连续的。数组一旦初始化完成之后,他的长度就固定了,不能修改。如果需要修改数组的长度,那么肯定就是建了一个新的数组,可以实现对数组里面的元素进行删除或者增加。
(2)数组是有下标的。下标从0开始,可以通过下标迅速的访问到数组的元素,因为是通过内存地址来访问的,非常快。

Leetcode 题目

类型一 二分查找相关

第1题: 704 二分查找
题目描述:给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

二分法使用的前提
(1)数组是有序的。只有数组是有序的,才能对整段数据进行对半分。
(2)元素不重复。如果元素重复,那么返回的结果可能不唯一。

题解

class Solution {
    public int search(int[] nums, int target) {
        //二分查找
        //定义左中右三个下标,用于下面的循环
        int left = 0;
        int right = nums.length - 1;
        int mid = (left + right) / 2;

		//这里的符号是 <= , 我们定义的区间是一个闭区间,[left,right],所以left == right 的时候是有意义的
        while(left <= right){
            //更新mid
            mid = (left + right) / 2;
            if(nums[mid] == target){
                return mid;
            }else if(target < nums[mid]){
                //左边找,更新right
                right = mid - 1;
            }else{
                //右边找,更新left
                left = mid + 1;
            }
        }
        return -1;
    }
}

考虑使用二分查找的场景:数组是有序的!

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

题解

class Solution {
    public int searchInsert(int[] nums, int target) {
        //二分查找

        //定义左中右下标
        int left = 0;
        int right = nums.length - 1;
        int mid;

        while(left <= right){
            //更新mid
            mid = (left + right) / 2;
            //判断大小
            if(target == nums[mid]){
                return mid;
            }else if(target < nums[mid]){
                //目标数小,往左边找,更新right
                right = mid - 1;
            }else{
                //目标数大,往右边找,更新left
                left = mid + 1;
            }
        }

        //找不到,那就要找插入位置 left > right 了
        //这里的 right + 1要好好琢磨一下,直接返回left好像也是可以的
        return right + 1;
    }
}

第3题: 34. 在排序数组中查找元素的第一个和最后一个位置
题目描述:给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。
题解

class Solution {
    public int[] searchRange(int[] nums, int target) {
        //二分查找
        int left = 0;
        int right = nums.length - 1;
        int mid;

        while(left <= right){
            //更新mid
            mid = (left + right) / 2;
            if(nums[mid] == target){
                //找到,现在要找左右边界
                //左边界
                int l = mid;
                while(l-1 >= 0 && nums[l-1] == target){
                    l--;
                }
                //有边界
                int r = mid;
                while(r+1 <= nums.length - 1 && nums[r+1] == target){
                    r++;
                }
                return new int[]{l,r};
            }else if(nums[mid] < target){
                //右边找
                left = mid + 1;
            }else{
                //左边找
                right = mid - 1;
            }
        }
        return new int[]{-1,-1};
    }
}

第4题: 69. Sqrt(x)
题目描述:给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。

题解
关键点:找到 k * k <= x的最大的k。这就需要定义一个辅助变量用来保存这个值。

class Solution {
    public int mySqrt(int x) {
        //二分查找法 --> 找到 k * k <= x 的最大k
        int left = 0;
        int right = x;
        int ans = -1;  //更新答案

        while(left <= right){
            //更新mid
            int mid = (left + right) / 2;
            //转成long,防止越界
            if((long)mid * mid > x){
                //太大,左边
                right = mid - 1;
            }else if(mid * mid <= x){
                //太小,往右边找,也有可能这就是那个数,因为小数被舍弃
                //更新ans
                ans = mid;
                //更新left
                left = mid + 1;
            }
        }
        return ans;
    }
}

第5题: 367. 有效的完全平方数
题目描述:给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
进阶:不要 使用任何内置的库函数,如 sqrt 。

题解
解法和上一题类似。

class Solution {
    public boolean isPerfectSquare(int num) {
        //二分查找法 就是看 k * k = x有没有解
        int left = 0;
        int right = num;
        int mid;

        while(left <= right){
            mid = (left + right) / 2;
            if((long)mid * mid == num){
                return true;
            }else if((long)mid * mid < num){
                //往右
                left = mid + 1;
            }else{
                //往左
                right = mid - 1;
            }
        }
        return false;
    }
}

类型二 移除元素

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

思路1:暴力破解。那就是使用双层for循环进行遍历,外层的for循环用来找到是val的值的下标,找到的时候,里面再套一层for循环,把所有的数都往前移动一个位置。

思路2:双指针法。只需一层遍历。快慢指针,快指针用来遍历整个数组,慢指针用来表示这个数组有多少个需要保留的数。 当快指针遍历到这个数是要保留的数时,那么需要将慢指针的值用快指针的值进行覆盖,然后下标都往后移动。 如果快指针遍历到val值时,那么快指针自己移动就行。

题解

class Solution {
    public int removeElement(int[] nums, int val) {
        //双指针法 --> 慢指针代表数组的大小,快指针遍历数据
        int slow = 0;
        for(int fast = 0; fast < nums.length; ++fast){
            //如果当前的指针指向不是val,那这个数就是要保留的,slow要移动,fast也移动
            //同时,slow下标的值被fast的值覆盖
            if(nums[fast] != val){
                nums[slow] = nums[fast];
                slow++;
            }else{
                //要删除的,那就只有fast进行移动,slow不移动,那就不操作
            }
        }
        return slow;
    }
}

第2题: 26. 删除有序数组中的重复项
题目描述:给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

思路:双指针法。也是快慢指针,这里判断是否重复的逻辑有所改变,因为数组是有序的,看连续的两个数是否有序直接使用 fast 和 fast - 1就可以,不能同时使用 slow 和 fast,因为他们不一定相邻。
题解

class Solution {
    public int removeDuplicates(int[] nums) {
        //双指针法 --> 快指针遍历数组,慢指针用来确定这个数组中需要保存的个数
        if(nums == null || nums.length == 0) return 0;
        
        int slow = 1;
        for(int fast = 1; fast < nums.length; ++fast){
            //什么时候是重复的,就是 fast 和 fast - 1 处值相同的时候
            if(nums[fast] != nums[fast - 1]){
                //不相同,此时fast的值是需要保留的
                nums[slow] = nums[fast];
                slow++;
            }else{
                //重复的值,那么让fast自己移动即可。
            }
        }
        return slow;
    }
}

第3题: 283. 移动零
题目描述:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

思路:双指针法。和第一题的思路一样,最后再多一个步骤,将最后的数赋值成0即可。

题解

class Solution {
    public void moveZeroes(int[] nums) {
        //双指针法。 和删除元素这题类似,把0删除,然后将最后几个数赋值成0.
        int slow = 0;
        for(int fast = 0; fast < nums.length; ++fast){
            if(nums[fast] != 0){
                //是要保留的
                nums[slow] = nums[fast];
                slow++;
            }else{
                //fast自己移动
            }
        }

        //将最后的数赋值成0。
        for(int i = slow; i < nums.length; ++i){
            nums[i] = 0;
        }
    }
}

第4题: 844. 比较含退格的字符串
题目描述:给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,请你判断二者是否相等。# 代表退格字符。

如果相等,返回 true ;否则,返回 false 。

思路:双指针法。一个指针处理一个字符串,每次处理完之后就进行比较,这样可以避免过多的处理。

题解

class Solution {
    public boolean backspaceCompare(String s, String t) {
        //双指针法。 一个指针遍历一个字符串,并且从后向前进行遍历,因为 # 只会影响他前面的字符
        int sIndex = s.length() - 1;
        int tIndex = t.length() - 1;
        //定义两个数,用来记录两个字符串分别需要跳过的字符数
        int sSkip = 0; 
        int tSkip = 0;

        while(sIndex >= 0 || tIndex >= 0){
            //处理第一个字符串
            while(sIndex >= 0){
                if('#' == s.charAt(sIndex)){
                    sSkip++;
                    //当前字符处理完毕
                    sIndex--;
                }else{
                    //不是#,判断还有没有跳过的字符串
                    if(sSkip > 0){
                        sSkip--;
                        sIndex--;
                    }else{
                        break;
                    }
                }
            }
            //while跳出之后, sIndex指向的位置就是不为 # 的位置

            //处理第二个字符串
            while(tIndex >= 0){
                if('#' == t.charAt(tIndex)){
                    tSkip++;
                    tIndex--;
                }else{
                    //不是#,判断还有没有跳过的字符串
                    if(tSkip > 0){
                        tSkip--;
                        tIndex--;
                    }else{
                        break;
                    }
                }
            }

            //判断 t 和 s当前的字符是不是相同的
            if(sIndex >= 0 && tIndex >= 0){
                if(s.charAt(sIndex) != t.charAt(tIndex)){
                    return false;
                }
            }else{
                //可能一个遍历完,一个没有遍历完,这也是false
                if(sIndex >= 0 || tIndex >= 0){
                    return false;
                }
            }

            //两个都大于0,并且数都是相等的,那就处理下一个字符
            sIndex--;
            tIndex--;
        }

        return true;
    }
}

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

思路:双指针法。左右两边向中间进行遍历。左右的平方肯定是最大的数,因为数组是有序的。

题解

class Solution {
    public int[] sortedSquares(int[] nums) {
        //双指针法,两边从中间进行遍历
        int left = 0;
        int right = nums.length - 1;
        int index = nums.length - 1;
        int[] ans = new int[index + 1];

        while(left <= right){
            if(nums[left] * nums[left] <= nums[right] * nums[right]){
                //右边大
                ans[index--] = nums[right] * nums[right];
                right--;
            }else{
                //左边大
                ans[index--] = nums[left] * nums[left];
                left++;
            }
        }

        return ans;
    }
}

类型三 滑动窗口

第1题: 209. 长度最小的子数组
题目描述:给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0

思路1:暴力破解。那就是使用双层for循环进行遍历。

思路2:滑动窗口,也是双指针法。就是维护一个窗口,让这个窗口不断的向前移动,当这个窗口的大小 >= target 的时候,看是否更新我们的最终结果。

题解

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        //子序列长度
        int sub = 0;
        //结果
        int ans = Integer.MAX_VALUE;
        //窗口数值之和
        int sum = 0;
        //窗口左指针
        int left = 0;

        //遍历nums,并且定义右指针
        for(int right = 0; right < nums.length; ++right){
            //将当前数加入串口
            sum += nums[right];

            //窗口是否达到要求
            while(sum >= target){
                //当前窗口长度
                sub = right - left + 1;
                //更新结果
                ans = (sub <= ans) ? sub : ans;
                //收缩窗口,左指针向前移动
                sum -= nums[left++];
            }
        }

        return ans == Integer.MAX_VALUE ? 0 : ans;
    }
}

第2题: 904. 水果成篮
题目描述
在一排树中,第 i 棵树产生 tree[i] 型的水果。
你可以从你选择的任何树开始,然后重复执行以下步骤:

把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。
移动到当前树右侧的下一棵树。如果右边没有树,就停下来。
请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。

你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。

用这个程序你能收集的水果树的最大总量是多少?

思路:滑动窗口法。这题翻译成人话就是问最长的连续子串是多少,这个子串中最多只能包含两种不同的数。和上一题非常类似,但是窗口的维护不同。这题的关键是在怎么确定这个窗口是否满足条件,可以使用HashMap来做这样的事。
题解

class Solution {
    public int totalFruit(int[] fruits) {
        //滑动窗口,双指针法。现在的窗口里面维护的是 最多两个不同的数字。
        //关键在怎么确定当前的窗口里面只有两种数 --> 使用HashMap

        //创建HashMap维护窗口
        Map<Integer,Integer> map = new HashMap<>();
        //最终结果
        int ans = 0;
        //窗口左指针
        int left = 0;

        //窗口右指针,并遍历水果
        for(int right = 0; right < fruits.length; ++right){
            //将当前数加入
            map.put(fruits[right], map.getOrDefault(fruits[right], 0) + 1);

            //如果水果超过3种,需要处理,直至变成两种为止
            while(map.size() > 2){
                //最左边的数去除
                map.put(fruits[left], map.get(fruits[left]) - 1);
                //如果变成0,将把这个水果去除
                if(map.get(fruits[left]) == 0){
                    map.remove(fruits[left]);
                }
                left++;
            }

            //此时窗口是满足要求的
            ans = Math.max(ans, right - left + 1);
        }

        return ans;
    }
}

第3题: 76. 最小覆盖子串

题目描述
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:

对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。

思路:滑动窗口法。关键还是在怎么去维护这样一个滑动窗口。能处理好窗口的关系就可以了。

题解

class Solution {
    public String minWindow(String s, String t) {
        //滑动窗口方式 --> 使用HashMap来判断当前的窗是否已经满足条件了
        HashMap<Character,Integer> need = new HashMap<>();  //需要包含的所有的字符及其数量
        HashMap<Character,Integer> window = new HashMap<>();  //滑动窗口中的字符

        //滑动窗口参数
        int left = 0; int right = 0;
        //最终结果
        int len = Integer.MAX_VALUE;  //字符串的长度
        int start = 0;  //满足条件字符串的起始位置
        int valid = 0;  //window中满足字符的个数

        //先处理好 need 这个集合
        for(int i = 0; i < t.length(); ++i){
            Character cur = t.charAt(i);
            need.put(cur, need.getOrDefault(cur, 0) + 1);
        }

        //遍历 s 这个字符串
        while(right < s.length()){
            //获取当前字符
            Character cur = s.charAt(right);
            right++;

            //查看这个字符是不是在need里面,不在的话直接跳过
            if(need.containsKey(cur)){
                //加入窗
                window.put(cur, window.getOrDefault(cur, 0) + 1);
                //是否达到要求
                if(need.get(cur).equals(window.get(cur))){  //这里使用equals,防止越界导致的判断出错
                    valid++;
                }
            }

            //看valid是否已经满足
            while(valid >= need.size()){
                //更新结果
                if(right - left <= len){
                    len = right - left;
                    start = left;
                }

                //更新窗口
                Character lCur = s.charAt(left); //窗口最左边的字符串
                left++;
                if(need.containsKey(lCur)){  //只有这个字符是需要的,才需要被处理,不然直接扔了
                    window.put(lCur, window.get(lCur) - 1);
                    //看是否还满足要求
                    if(need.get(lCur) > window.get(lCur)){
                        valid--;
                    }
                }
            }
        }

        //返回字符串
        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
    }
}

类型四 边界、螺旋相关

第1题: 9. 螺旋矩阵 II
题目描述
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。

思路:就是顺时针遍历一个二维数组,然后把数放进去,遵循好相应的原则就行,比如:左闭右闭,左闭右开等。这里使用左闭右闭的原则。

题解

class Solution {
    public int[][] generateMatrix(int n) {
        //边界法 --> 每次都必须遵循左闭右闭的原则
        int[][] ans = new int[n][n];

        //边界
        int top = 0, bottom = n - 1, left = 0, right = n - 1;
        //不断上升的数
        int count = 1;
        while(count <= n * n){
            //上边界,左到右的循环
            for(int i = left; i <= right; ++i){  // i < right 确定右边是开的
                ans[top][i] = count++;
            }
            top++;

            //右边界,上到下的循环
            for(int i = top; i <= bottom; ++i){
                ans[i][right] = count++;
            }
            right--;

            //下边界,从右到左的循环
            for(int i = right; i >= left; --i){
                ans[bottom][i] = count++;
            }
            bottom--;

            //左边界,从下到上的循环
            for(int i = bottom; i >= top; --i){
                ans[i][left] = count++;
            }
            left++;
        }

        return ans;
    }
}

第2题: 54. 螺旋矩阵
剑指 Offer 29. 顺时针打印矩阵
题目描述
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

思路:和上一题的区别在于,现在是矩阵,行和列不固定。但基本思想一致。遵循好相应的原则就行,比如:左闭右闭,左闭右开等。这里使用左闭右闭的原则。

题解

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> res = new ArrayList<>();
        //边界
        int left = 0, right = matrix[0].length - 1, top = 0, bottom = matrix.length - 1;
        int count = (right + 1) * (bottom + 1);
        while(res.size() < count){
            //上边界,从左到右
            for(int i = left; i <= right && res.size() < count; ++i){
                res.add(matrix[top][i]);
            }
            top++;

            //右边界,从上到下
            for(int i = top; i <= bottom && res.size() < count; ++i){
                res.add(matrix[i][right]);
            }
            right--;

            //下边界,从右到左
            for(int i = right; i >= left && res.size() < count; --i){
                res.add(matrix[bottom][i]);
            }
            bottom--;

            //左边界
            for(int i = bottom; i >= top && res.size() < count; --i){
                res.add(matrix[i][left]);
            }
            left++;
        }

        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值