前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >LeetCode通关:数组十七连,真是不简单

LeetCode通关:数组十七连,真是不简单

作者头像
三分恶
发布于 2021-08-10 02:46:43
发布于 2021-08-10 02:46:43
39400
代码可运行
举报
文章被收录于专栏:三分恶的专栏三分恶的专栏
运行总次数:0
代码可运行

分门别类刷算法,坚持,进步! 刷题路线参考:https://github.com/chefyuan/algorithm-base https://github.com/youngyangyang04/leetcode-master/

大家好,我是老三,一个刷题困难户,接下来我们开始数组类型算法的刷题之旅!

数组基础

数组基本上是我们最熟悉的数据结构了,刚会写“Hello World”不久,接着就是“杨辉三角”之类的练习。

数组基本结构

  • 数组是存放在连续内存空间上的相同类型数据的集合

上图是一个字符数组的例子。

因为内存空间连续,所以可以直接通过下标获取对应的元素。

但是删除就麻烦一点,相当于填坑,一个元素被移走,留下的坑,需要其它元素来填上。

Java中,多维数组的存储本质上也是一个行优先的一维数组。

数组是引用传递

我们都知道,在Java中的 “=” 用在基本数据类型上,是值传递,用在引用数据类型上,是引用传递。

这一块展开可以写一篇文章,我们只简单看个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ArrayTest {

    public static void main(String[] args) {
        int[] array = {1, 2, 3};
        int[] newArray = array;
        newArray[1] = 0;
        //结果: 1 0 3
        printArray(array);
        //结果: 1 0 3
        printArray(newArray);
    }


    static void printArray(int[] data) {
        for (int d : data) {
            System.out.print(d + " ");
        }
        System.out.println();
    }
}

大家可以看到,newArray改变了,array也跟着变了。

为什么呢?

在Java中,数组是引用数组类型。array、newArray都是存储在栈中的引用,它们指向堆中真正存储的数组对象。

所以改变了newArray,实际是改变了newArray指向的数组。

这一点是我们刷题需要注意的,复制数组需要在循环中一个个复制。

好了,接下来,让我们愉快地开始刷题吧!

二分查找

LeetCode704. 二分查找

☕ 题目:704. 二分查找 (https://leetcode-cn.com/problems/binary-search/)

❓ 难度:简单

? 描述:

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

? 思路:

二分查找可以说我们都很熟了。

因为数组是有序的,所以定义三个指针,low、high、mid,每次与中间指针指向的元素nums[mid]比较,

  • 相等,命中
  • 比nums[mid]大,目标元素就在(mid,high]区间;
  • 比 nums[mid]小,目标元素就在 [low,mid)区间
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
      /**
     * 704. 二分查找
     *
     * @param nums
     * @param target
     * @return
     */
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (target == nums[mid]) {
                return mid;
            } else if (target > nums[mid]) {
                //target在(mid,high]区间
                //右移
                left = mid + 1;
            } else if (target < nums[mid]) {
                //target 在[low,mid)区间
                //左移
                right = mid - 1;
            }
        }
        return -1;
    }

但是这个代码还有一处问题,在哪呢?

int mid = (left + right) / 2;

这个地方可能会因为left和right数值太大导致内存溢出,所以应该写为 int mid = left + ((right - left) >> 1);

修改之后代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
             int mid=left+((right-left)>>1);
            if (target == nums[mid]) {
                return mid;
            } else if (target > nums[mid]) {
                //target在(mid,high]区间
                //右移
                left = mid + 1;
            } else if (target < nums[mid]) {
                //target 在[low,mid)区间
                //左移
                right = mid - 1;
            }
        }
        return -1;
    }

⏰ 时间复杂度:O(logn)

LeetCode35. 搜索插入位置

☕ 题目:35. 搜索插入位置 (https://leetcode-cn.com/problems/search-insert-position/)

❓ 难度:简单

? 描述:

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

? 思路:

二分查找比较简单,但写对还要费点功夫,再做一道基本一样的题巩固一下。

这道题基本一样,插入的位置可能有四种情况:

  • target<nums[0] : 在最左侧插入
  • target>nums[length-1] :在最右侧插入
  • target=nums[i] : 和数组中元素相同,插入位置i
  • nums[i]<target<nums[i+1] :在i位置之后插入

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /**
     * 35. 搜索插入位置
     *
     * @param nums
     * @param target
     * @return
     */
    public int searchInsert(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        //target小于最左侧,或者大于最右元素
        if (target < nums[left]) {
            return 0;
        }
        if (target > nums[right]) {
            return right + 1;
        }
        while (left <= right) {
            int mid=left+((right-left)>>1);
            if (target == nums[mid]) {
                //和数组元素相等
                return mid;
            } else if (target > nums[mid]) {
                //右侧
                left = mid + 1;
            } else if ((target < nums[mid])) {
                //左侧
                right = mid - 1;
            }
        }
        // 在某个元素之后插入
        //因为退出条件是left==right,所以返回left或者right都可以
        return left;
    }

⏰ 时间复杂度:O(logn)

LeetCode34. 在排序数组中查找元素的第一个和最后一个位置

☕ 题目:34. 在排序数组中查找元素的第一个和最后一个位置 (https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/)

❓ 难度:中等

? 描述:

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

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

进阶:

  • 你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?

? 思路:

看到时间复杂度 O(log n) ,数组有序,我们知道,二分查找该上场了。

但是这道题有点不一样,它需要寻找边界。

那我们怎么办呢?

这就引入了寻找边界的二分查找。

这道题的思路是什么呢?

我们分别用二分查找来寻找左边界和右边界。

一般的二分查找:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  if (nums[mid] == target) {
       return mid;
  }else if (nums[mid] < target) {
      left  = mid + 1;
  }else if (nums[mid] > target) {
      right = mid - 1;
  }

注意,我们这里的返回条件是 nums[mid] == target,但是寻找边界的时候就不能这样了,因为我们不能确定mid是不是我们的边界。

以寻找左边界为例,条件是 target <= nums[mid]的时候,我们接着往左移动。

寻找右边界也类似。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int[] searchRange(int[] nums, int target) {
        //左边界
        int leftBound = leftBound(nums, target);
        //右边界
        int rightBound = rightBound(nums, target);
        //不存在情况
        if (rightBound < leftBound) {
            return new int[]{-1, -1};
        }
        return new int[]{leftBound, rightBound};
    }

    /**
     * @return int
     * @Description: 求左边界
     */
    int leftBound(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            //往左移动
            if (target <= nums[mid]) {
                right = mid - 1;
            } else if (target > nums[mid]) {
                //向右移动
                left = mid + 1;
            }
        }
        return left;
    }

    /**
     * @return int
     * @Description: 求右边界
     */
    int rightBound(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + ((right - left) >> 1);
            //往右移动
            if (target >= nums[mid]) {
                left = mid + 1;
            } else if (target < nums[mid]) {
                //往左移动
                right = mid - 1;
            }
        }
        return right;
    }

⏰ 时间复杂度:O(logn)

双指针

LeetCode27. 移除元素

☕ 题目:27. 移除元素 (https://leetcode-cn.com/problems/remove-element/)

❓ 难度:简单

? 描述:

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

? 思路

暴力解法

暴力解法没什么好说的,和上道题类似,找到要删除的元素,把它后面的元素全部向前移动一位。

这里有两点需要注意:

  • 需要先定义变量 length 获取数组长度,因为后面我们的返回的数组长度是改变的
  • 每找到一个需要删除的值的时候,需要 i--,防止出现多个需要删除的值在一起的情况,然后漏删

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 public int removeElement(int[] nums, int val) {
        int length = nums.length;
        int i = 0;
        for (; i < length; i++) {
            if (nums[i] == val) {
                for (int j = i; j < length - 1; j++) {
                    nums[j] = nums[j + 1];
                }
                //防止漏删
                i--;
                //数组长度减一
                length--;
            }
        }
        return length;
    }

⏰ 时间复杂度:O(n²)。

双指针法

双指针法,是数组和链表题中非常常用的一种方法。

这道题用双指针法怎么解决呢?

定义两个指针,一个前,一个后。没有找到目标的时候front和after一起移动,找到目标的时候,after停下来,front接着移动,把front指向的值赋给after指向的值。

这样一来,双指针就通过一个循环完成了双循环完成的事情。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int removeElement(int[] nums, int val) {
        //定义前后指针
        int front = 0;
        int after = 0;
        for (; front < nums.length; front++) {
            if (val != nums[front]) {
                nums[after] = nums[front];
                after++;
            }
        }
        return after;
    }

⏰ 时间复杂度:O(n)。

LeetCode26. 删除有序数组中的重复项

☕ 题目:27. 移除元素 (https://leetcode-cn.com/problems/remove-element/)

❓ 难度:简单

? 描述:

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

? 思路

趁着上一道题劲儿还没缓过来,赶紧做一道基本一样的巩固一下。

直接上代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int removeDuplicates(int[] nums) {
        int front = 1;
        int after = 1;
        for (; front < nums.length; front++) {
            if (nums[front] != nums[front - 1]) {
                nums[after] = nums[front];
                after++;
            }
        }
        return after;
    }

⏰ 时间复杂度:O(n)。

LeetCode283. 移动零

☕ 题目:283. 移动零 (https://leetcode-cn.com/problems/move-zeroes/)

❓ 难度:简单

? 描述:

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:

  1. 必须在原数组上操作,不能拷贝额外的数组。
  2. 尽量减少操作次数

? 思路

继续沿着上一道题的思路。

  • 第一步:我们可以把为零的元素先给它删掉,怎么删呢?就是LeetCode26的两个指针的删除方式
  • 第二步:但是我们这是将零移动到末尾,怎么办呢?我们把通过移动方式删除,导致数组末尾的坑用零填上就行了。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /**
     * @return void
     * @Description: 283. 移动零
     * @author 三分恶
     * @date 2021/7/30 7:44
     */
    public void moveZeroes(int[] nums) {
        int after = 0;
        int front = 0;
        //移动元素
        for (; front < nums.length; front++) {
            if (nums[front] != 0) {
                nums[after] = nums[front];
                after++;
            }
        }
        //将末尾元素置为0
        for (; after < nums.length; after++) {
            nums[after] = 0;
        }
    }

⏰ 时间复杂度:O(n)。

LeetCode977. 有序数组的平方

☕ 题目:977. 有序数组的平方 (https://leetcode-cn.com/problems/squares-of-a-sorted-array/)

❓ 难度:简单

? 描述:

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

? 思路

暴力排序法

这道题一看,最直观的做法是什么呢?

先求数字平方的数组,然后再把新数组排序。

代码也好写:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /**
     * @return int[]
     * @Description: 977. 有序数组的平方-暴力法
     * @author 三分恶
     * @date 2021/7/30 8:03
     */
    public int[] sortedSquares(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            nums[i] *= nums[i];
        }
        //快排,时间复杂度O(nlogn)
        Arrays.sort(nums);
        return nums;
    }

⏰ 时间复杂度:遍历时间复杂度O(n),快排时间复杂度O(nlogn),所以时间复杂度O(n+nlogn)。

? 思路

双指针法

我们连写几道双指针了,这道题能不能用双指针实现呢?

我们分析一下,这个数组在取平方之前,是有序的,那么它绝对值最大的数一定是在两端的。

所以我们可以定义两个指针,一个指向最左端,一个指向最右端,比较两者平方的大小,大的平方放入结果数组,并移动指针。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   /**
     * @return int[]
     * @Description: 977. 有序数组的平方-双指针法
     * @author 三分恶
     * @date 2021/7/30 8:29
     */
    public int[] sortedSquares(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        int[] result = new int[nums.length];
        int r = nums.length - 1;
        while (left <= right) {
            int leftRes = nums[left] * nums[left];
            int rightRes = nums[right] * nums[right];
            //右边大
            if (leftRes <= rightRes) {
                result[r] = rightRes;
                right--;
            } else {
                //左边大
                result[r] = leftRes;
                left++;
            }
            r--;
        }
        return result;
    }

⏰ 时间复杂度:O(n)。

两数之和

LeetCode1. 两数之和

☕ 题目:1. 两数之和 (https://leetcode-cn.com/problems/two-sum/)

❓ 难度:简单

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

? 思路:

暴力解法

上来我们先来个最简单的暴力解法,大家应该都知道冒泡排序吧,类似的两层循环。

代码写起来也很简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int[] twoSum(int[] nums, int target) {
        int[] result = new int[2];
        for (int i = 0; i < nums.length; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[i] + nums[j] == target) {
                    result[0] = i;
                    result[1] = j;
                    return result;
                }
            }
        }
        return result;
    }

⏰ 时间复杂度:看到这个双循环,就知道时间复杂度O(n²)。

哈希辅助法

时间复杂度O(n²)多少有点过了,这道题的重点是两个元素相加之和的判断。

我们可以用一个Hash集合把元素存起来,这样一来遍历一遍就够了,例如目标和9,当前元素2,只需要判断集合里是否有元素7就行了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer, Integer> map = new HashMap<>(16);
        int[] result = new int[2];
        for (int i = 0; i < nums.length; i++) {
            //目标元素
            int goal = target - nums[i];
            if (map.containsKey(goal)) {
                result[0] = map.get(goal);
                result[1] = i;
                return result;
            }
            //将数组值作为key,下标作为value
            map.put(nums[i], i);
        }
        return result;
    }

⏰ 时间复杂度:从Hash查询和取值时间复杂度都是O(1),所以整体时间复杂度是O(1)。

LeetCode15. 三数之和

☕ 题目:15. 三数之和 (https://leetcode-cn.com/problems/3sum/)

❓ 难度:简单

? 描述:

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

? 思路:

哈希法

做完两数之和以后,我们首先想到的就是哈希法。

两层循环,取到a,b,再通过 0-(a+b) 来确定c。

但是这里还有一个问题,答案中不可以包含重复的三元组。

所以,我们还要想办法去掉Hash里的重复元素。

可以加入一个约束,第三个数的索引大于第二个数才存入。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>(16);
        if (nums.length < 3) {
            return result;
        }
        //排序
        Arrays.sort(nums);
        HashMap<Integer, Integer> map = new HashMap<>();
        //将元素存入hash表
        for (int i = 0; i < nums.length; i++) {
            map.put(nums[i], i);
        }
        Integer c;
        int target = 0;
        for (int a = 0; a < nums.length; a++) {
            target = -nums[a];
            //去重
            if (a > 0 && nums[a] == nums[a - 1]) {
                continue;
            }
            for (int b = a + 1; b < nums.length; b++) {
                //去重
                if (b > a + 1 && nums[b] == nums[b - 1]) {
                    continue;
                }
                //从hash表获取c
                if ((c = map.get(target - nums[b])) != null) {
                    //c下彪必须大于b
                    if (c > b) {
                        result.add(new ArrayList<>(Arrays.asList(nums[a], nums[b], nums[c])));
                    } else {
                        break;
                    }
                }
            }
        }
        return result;
    }

⏰ 时间复杂度:双循环,O(n²)。

虽然这么也写出来了,但是,说实话,很难写出没有问题的代码。

我们写了这么多双指针,那么有没有可能用双指针的方式呢?

双指针法

首先对数组进行排序,然后遍历数组。

然后再在当前节点后面取左右指针,判断左右指针的值是否等于0-nums[i],然后分别左右移动。

怎么去重呢?

满足条件时,看左指针的值是否和前一个位置相等,右指针的值是否和和它后一个位置的值相等。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public static List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>(16);
        if (nums.length < 3) {
            return result;
        }
        //排序
        Arrays.sort(nums);
        //遍历
        for (int i = 0; i < nums.length-2; i++) {
            //如果当前元素大于0,三数之和一定大于0
            if (nums[i] > 0) {
                break;
            }
            int left = i + 1;
            int right = nums.length - 1;
            int count = 0 - nums[i];
            //去重
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum == 0) {
                    result.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));
                    //去重,注意去重逻辑要放在找到第一个三元组之后
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    //找到结果,双指针同时移动
                    left++;
                    right--;
                } else if (sum < 0) {
                    //左指针右移
                    left++;
                } else if (sum > 0) {
                    //右指针左移
                    right--;
                }
            }
        }
        return result;
    }

⏰ 时间复杂度:O(n²)

LeetCode18. 四数之和

☕ 题目:18. 四数之和 (https://leetcode-cn.com/problems/4sum/)

❓ 难度:简单

? 描述:

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:答案中不可以包含重复的四元组。

? 思路:

我们延续三数之和的思路,在三数之和外面再套一层循环。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>(16);
        if (nums.length < 4) {
            return result;
        }
        //排序
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 3; i++) {
            //去重
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            for (int j = i + 1; j < nums.length - 2; j++) {
                //去重
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                int left = j + 1;
                int right = nums.length - 1;
                while (left < right) {
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum == target) {
                        result.add(new ArrayList<>(Arrays.asList(nums[i], nums[j], nums[left], nums[right])));
                        //去重
                        while (left < right && nums[left] == nums[left + 1]) {
                            left++;
                        }
                        while (left < right && nums[right] == nums[right - 1]) {
                            right--;
                        }
                        left++;
                        right--;
                    } else if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    }
                }
            }
        }
        return result;
    }

⏰ 时间复杂度:O(n³)

滑动窗口

LeetCode209. 长度最小的子数组

☕ 题目:209. 长度最小的子数组(https://leetcode-cn.com/problems/minimum-size-subarray-sum/)

❓ 难度:中等

? 描述:

给定一个含有 n 个正整数的数组和一个正整数 target 。

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

? 思路

这道题是一道经典的滑动窗口问题[4]。

  • 使用start、end指针,分别表示滑动窗口的起始、终止位置
  • 移动end指针,扩大窗口,直到子数组达到目标值target
  • 移动start指针,缩小窗口,直到子数组不再满足>=target

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int minSubArrayLen(int target, int[] nums) {
        int result = Integer.MAX_VALUE;
        //起、止指针
        int start = 0, end = 0;
        //总和
        int sum = 0;
        while (end < nums.length) {
            //sum添加,end右移
            sum += nums[end++];
            while (sum >= target && start < end) {
                //因为end++,所以序列长度end - start
                result = Math.min(result, end - start);
                //移动start
                sum -= nums[start++];
            }
        }
        return result == Integer.MAX_VALUE ? 0 : result;
    }

⏰ 时间复杂度:O(n),虽然循环里套循环了,但是starrt和end各自被移动了n次,所以时间复杂度是O(n)。

LeetCode219. 存在重复元素 II

☕ 题目:219. 存在重复元素 II (https://leetcode-cn.com/problems/contains-duplicate-ii/)

❓ 难度:简单

? 描述:

给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。

?思路:

上面我们做了一道滑动窗口的题,我们接着再做一道也可以用滑动窗口解决的问题。

这道题的滑动窗口略有区别,上一道题的窗口是活动的,这个是固定的滑动窗口,维护一个长度为k的固定窗口,如果窗口内含有目标值,返回。如果窗口进入新的元素,就需要把头部的元素移除掉,保持窗口的长度。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public boolean containsNearbyDuplicate(int[] nums, int k) {
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            if (set.contains(nums[i])) {
                return true;
            }
            set.add(nums[i]);
            if (set.size() > k) {
                set.remove(nums[i - k]);
            }
        }
        return false;
    }

⏰ 时间复杂度:O(n)。

LeetCode1052. 爱生气的书店老板

☕ 题目:1052. 爱生气的书店老板(https://leetcode-cn.com/problems/grumpy-bookstore-owner/)

❓ 难度:中等

? 描述:

今天,书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。

在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。

书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。

请你返回这一天营业下来,最多有多少客户能够感到满意。

示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], X = 3
输出:16
解释:
书店老板在最后 3 分钟保持冷静。
感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.

?思路:

这道题是一道固定窗口的问题。

整体思路就是把不生气的部分作为固定窗口,固定窗口把customers分成了三部分,最后求三部分的最大和。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int maxSatisfied(int[] customers, int[] grumpy, int minutes) {
        // 窗口值总和
        int winSum = 0;
        //左区间总和
        int leftSum = 0;
        //右区间总和
        int rightSum = 0;
        int len = customers.length;
        //窗口位于起点
        for (int i = 0; i < minutes; i++) {
            winSum += customers[i];
        }
        //窗口位于起点时右区间的值
        for (int i = minutes; i < len; i++) {
            //不生气
            if (grumpy[i] == 0) {
                rightSum += customers[i];
            }
        }
        //窗口左右-开始移动窗口
        int left = 1;
        int right = minutes;
        int maxSum = winSum + leftSum + rightSum;
        //移动
        while (right < len) {
            //重新计算左区间的值
            if (grumpy[left - 1] == 0) {
                leftSum += customers[left - 1];
            }
            //重新计算右区间的值
            if (grumpy[right] == 0) {
                rightSum -= customers[right];
            }
            //窗口值
            winSum = winSum - customers[left - 1] + customers[right];
            //最大总和
            maxSum = Math.max(maxSum, leftSum + winSum + rightSum);
            //移动固定窗口
            left++;
            right++;
        }
        return maxSum;
    }

⏰ 时间复杂度:O(n)。

? 空间复杂度: O(1)。

原地置换

面试题3. 数组中重复的数字

☕ 题目:面试题3. 数组中重复的数字 (https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/)

❓ 难度:复杂

? 描述:

找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

示例 1:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:23 

?思路:

哈希法

这种找重复的数字问题,我们脑子里第一下就想起来,用Hash存储元素,然后进行比对。

代码实现也很简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int findRepeatNumber(int[] nums) {
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            if (set.contains(nums[i])) {
                return nums[i];
            }
            set.add(nums[i]);
        }
        return 0;
    }

⏰ 时间复杂度:O(n)。

? 空间复杂度:O(n)

但今天的主角不是它,而是?

原地置换法

我们注意到一个条件所有数字都在 0~n-1 的范围内,那就在这方面进行操作,我们可以把元素放到它的值对应的下标的位置。

例如 num[2]=1,那我们就把它放到下标1的位置。

接着遍历,元素发现它应该待的坑已经被它的双胞胎兄弟给占了,它就知道,它是多余的那个。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int findRepeatNumber(int[] nums) {
        if (nums.length == 0) {
            return -1;
        }
        for (int i = 0; i < nums.length; i++) {
            while (nums[i] != i) {
                //判断位置是否被占
                int index = nums[i];
                if (nums[index] == nums[i]) {
                    return nums[i];
                }
                //交换位置
                int temp = nums[i];
                nums[i] = nums[index];
                nums[index] = temp;
            }
        }
        return -1;
    }

⏰ 时间复杂度:O(n)。

? 空间复杂度:O(1)

LeetCode41. 缺失的第一个正数

☕ 题目:41. 缺失的第一个正数 (https://leetcode-cn.com/problems/first-missing-positive/)

❓ 难度:复杂

? 描述:

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

? 思路

辅助数组

这道题有一个非常巧妙地的办法![1]

可以引入一个辅助数组,从1开始,在对应的位置存入原数组对应的元素。如原数组num[0]=1,那么这个元素就应该存入辅助数组 helper[1]。

然后遍历辅助数组,发现的第一个坑就是缺失的第一个正数。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int firstMissingPositive(int[] nums) {
        if (nums.length == 0) {
            return 1;
        }
        //辅助数组
        int[] helper = new int[nums.length + 1];
        //将数组正数元素存入辅助数组中
        for (int n : nums) {
            if (n > 0 && n < helper.length) {
                helper[n] = n;
            }
        }
        //遍历查找,找到不一样元素
        for (int i = 1; i < helper.length; i++) {
            if (helper[i] != i) {
                return i;
            }
        }
        return helper.length;
    }

⏰ 时间复杂度:O(n)。

? 空间复杂度:O(n)。

原地置换法

我们上面用了原地置换法解决了一个问题,降低了空间复杂度,我们这道题是不是也可以呢?

原地置换没法修改数组长度,我们肯定不能nums[i] 存 i 了,我们左移一下,num[i-1]存i。

代码实现如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int firstMissingPositive(int[] nums) {
        if (nums.length == 0) {
            return 1;
        }
        //原地置换
        for (int i = 0; i < nums.length; i++) {
            //将正数填入对应位置
            //需要考虑指针移动情况,大于0,小于len+1,不等与i+1,两个交换的数相等时,防止死循环
            while (nums[i] > 0 && nums[i] < nums.length + 1 && nums[i] != i + 1 && nums[i] != nums[nums[i] - 1]) {
                //下标
                int index = nums[i] - 1;
                //交换
                int temp = nums[index];
                nums[index] = nums[i];
                nums[i] = temp;
            }
        }
        //遍历置换后的数组
        for (int j = 0; j < nums.length; j++) {
            if (nums[j] != j + 1) {
                return j + 1;
            }
        }
        return nums.length + 1;
    }

⏰ 时间复杂度:O(n)。

? 空间复杂度:O(1)。

螺旋矩阵

LeetCode54. 螺旋矩阵

☕ 题目:54. 螺旋矩阵 (https://leetcode-cn.com/problems/spiral-matrix/)

❓ 难度:中等

? 描述:

给你一个 mn 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。

? 思路

这道题,思路比较容易想,就是上右下左四个方向顺时针遍历数组。

但是这道题的细节是魔鬼。

有两种,一种是一圈遍历完成,上下左右的位置移动,遍历是左闭右开[的条件。

我们采用的是第二种,每遍历完一条边,就移动对应的位置,遍历就是左闭右闭的条件。

还有一点细节就是值得注意的是,遍历过程中可能会出现出现 top > bottom || left > right ,其中一对边界彼此交错了。

这意味着此时所有项都遍历完了,如果没有及时 break ,就会重复遍历。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public List<Integer> spiralOrder(int[][] matrix) {
        List<Integer> result = new ArrayList<>(16);
        //边界
        int left = 0, right = matrix[0].length - 1;
        int top = 0, bottom = matrix.length - 1;
        int size = matrix.length * matrix[0].length;
        //遍历
        while (result.size() != size) {
            //上层遍历
            for (int i = left; i <= right; i++) {
                result.add(matrix[top][i]);
            }
            top++;
            if (top > bottom) break;
            //右层遍历
            for (int i = top; i <= bottom; i++) {
                result.add(matrix[i][right]);
            }
            right--;
            if (left > right) break;
            //下层遍历
            for (int i = right; i >= left; i--) {
                result.add(matrix[bottom][i]);
            }
            bottom--;
            if (top > bottom) break;
            //左层遍历
            for (int i = bottom; i >= top; i--) {
                result.add(matrix[i][left]);
            }
            left++;
            if (left > right) break;
        }
        return result;
    }

? 时间复杂度:O(mn),其中 m 和 n 分别是输入矩阵的行数和列数。

LeetCode59. 螺旋矩阵 II

☕ 题目:59. 螺旋矩阵 II (https://leetcode-cn.com/problems/spiral-matrix-ii/)

❓ 难度:中等

? 描述:

给你一个正整数 n ,生成一个包含 1n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix

? 思路

和上面一道题基本一模一样,我们往里面套就行了。

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public int[][] generateMatrix(int n) {
        int[][] res = new int[n][n];
        int left = 0, right = n - 1;
        int top = 0, bottom = n - 1;
        int x = 1;
        while (x <= n * n) {
            //上
            for (int i = left; i <= right; i++) {
                res[top][i] = x;
                x++;
            }
            top++;
            if (top > bottom) break;
            //右
            for (int i = top; i <= bottom; i++) {
                res[i][right] = x;
                x++;
            }
            right--;
            if (left > right) break;
            //下
            for (int i = right; i >= left; i--) {
                res[bottom][i] = x;
                x++;
            }
            bottom--;
            if (left > right) break;
            //左
            for (int i = bottom; i >= top; i--) {
                res[i][left] = x;
                x++;
            }
            left++;
            if (left > right) break;
        }
        return res;
    }

? 时间复杂度:O(n²)

剑指 Offer 29. 顺时针打印矩阵 也是一道类似的题目。

总结

写了个顺口溜总结一下:

简单事情重复做,重复事情认真做,认真事情创造性地做! 我是三分恶,一个能文能武地全栈开发。 点赞关注不迷路,咱们下期见!


博主是个算法练习生,路线和思路参考如下大佬!建议关注!

参考:

[1].https://github.com/chefyuan/algorithm-base

[2].https://github.com/youngyangyang04/leetcode-master/

[3].https://www.zhihu.com/question/31203609

[4].https://leetcode-cn.com/problems/squares-of-a-sorted-array/solution/you-xu-shu-zu-de-ping-fang-shuang-zhi-zh-9qxf/

[5].https://leetcode-cn.com/problems/minimum-size-subarray-sum/solution/hua-tu-fen-xi-209-ti-chang-du-zui-xiao-d-uip8/

[6].https://leetcode-cn.com/problems/single-number/solution/hua-jie-suan-fa-136-zhi-chu-xian-yi-ci-de-shu-zi-b/

[7]. https://leetcode-cn.com/problems/spiral-matrix/solution/shou-hui-tu-jie-liang-chong-bian-li-de-ce-lue-kan-/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-08-07 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
[LeetCode题解]开篇!求和问题总结:2Sum/3Sum/4Sum/KSum
之前在美国做访学,日子比较清闲。当时对数据结构和算法几乎一窍不通,便开始在Leetcode上刷算法题,也算是为找工作做准备,经过了一年多,也积累了很多Leetcode题解文章,并在CSDN上开了题解专栏。
Rude3Knife的公众号
2019/08/06
1.7K0
Leetcode数组题目
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
润森
2019/11/14
6360
leetcode周赛(195)
给你一个整数数组 arr 和一个整数 k ,其中数组长度是偶数,值为 n 。现在需要把数组恰好分成 n / 2 对,以使每对数字的和都能够被 k 整除。
你的益达
2020/08/05
4900
Array相关LeetCode题目笔记
Array相关LeetCode题目笔记 11. 盛最多水的容器 题目描述: 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 说明:你不能倾斜容器。 示例 1: 输入:[1,8,6,2,5,4,8,3,7] 输出:49 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。
晓果冻
2022/09/08
1640
数据结构与算法之双指针
力扣地址:https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/
java小杰要加油
2021/08/13
1.4K0
数据结构与算法之双指针
[Leetcode][Python/Java]两数之和 Two Sum/两数之和 II - 输入有序数组 Two Sum II
https://leetcode-cn.com/problems/two-sum/solution/
蛮三刀酱
2019/03/26
8710
算法笔记(一)
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
chuckQu
2022/08/19
6210
LeetCode通关:通过排序一次秒杀五道题,舒服!
大家好,我是拿输出博客督促自己刷题的老三,前面学习了十大排序:万字长文|十大基本排序,一次搞定!,接下来我们看看力扣上有没有什么能拿排序解决的题目吧!
三分恶
2021/09/10
8860
LeetCode通关:通过排序一次秒杀五道题,舒服!
LeetCode | 数据结构与算法 | 3月 合集
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
yiyun
2023/05/18
1840
LeetCode | 数据结构与算法 | 3月 合集
力扣刷题(数组篇)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 (leetcode-cn.com)  小王的主页:(14条消息) 学好c语言的小王同学的博客_CSDN博客-领域博主 小王的gitee:比特王信哲 (bitewang) - Gitee.com
王同学要努力
2022/12/20
2120
力扣刷题(数组篇)
【leetcode速通java版】02——有序数组、子数组、螺旋矩阵
上面冒泡排序让算法的时间复杂度变成了O(n+n^2),可以换成快速排序。如果您还不知道什么是快速排序,可以参考博客:快速排序
半旧518
2022/10/26
3190
【leetcode速通java版】02——有序数组、子数组、螺旋矩阵
搞定大厂算法面试之leetcode精讲5.二分查找
搞定大厂算法面试之leetcode精讲5.二分查找 视频教程(高效学习):点击学习 目录: 1.开篇介绍 2.时间空间复杂度 3.动态规划 4.贪心 5.二分查找 6.深度优先&广度优先 7.双指针 8.滑动窗口 9.位运算 10.递归&分治 11剪枝&回溯 12.堆 13.单调栈 14.排序算法 15.链表 16.set&map 17.栈 18.队列 19.数组 20.字符串 21.树 22.字典树 23.并查集 24.其他类型题 二分搜索 时间复杂度O(logn) 步骤: 从数组中间的元素开始,如果中
全栈潇晨
2021/11/24
3210
搞定大厂算法面试之leetcode精讲19.数组
搞定大厂算法面试之leetcode精讲19.数组 视频讲解(高效学习):点击学习 数组操作的时间复杂度 Access:O(1) Search:O(n) Insert: 平均O(n),最好的情况下O(1),也就是在数组尾部插入O(1),最坏的情况下O(n) Delete;平均O(n),最好的情况下O(1),也就是在数组尾部删除O(1),最坏的情况下O(n) 283. 移动零 (easy) 动画过大,点击查看 方法1:两次遍历 思路:遍历数组,定义索引j为数组的第一个位置,遇上非0元素,让
全栈潇晨
2021/12/04
4620
LeetCode 01:有人相爱,有人夜里开车看海,有人LeetCode第一题都做不出来
在LeetCode的第一题下面,有这样一句评论“有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。”看到这条评论,你是得意的笑呢,还是苦涩的笑?
程序新视界
2021/01/14
9600
LeetCode 01:有人相爱,有人夜里开车看海,有人LeetCode第一题都做不出来
轻松拿下两数、三数、四数和N数之和 | 必备算法
时间复杂度:O(N^2),其中N是数组中的元素数量。最坏情况下数组中所有数都需要被匹配 空间复杂度:O(1)
程序员荒生
2022/03/04
3630
LeetCode - 两数之和
开启新的一个篇章,LeetCode题解,个人的一些解题思路分享(可能有的题目就是这么菜,之后慢慢更,可以先把自己做完的100多题每天1题的方式先发出来,一遍更一遍写新题目)
晓痴
2019/07/24
2990
LeetCode - 两数之和
LeetCode热题Top100 | 中等 | 上
1. 两数相加(2)# 给你两个非空的链表,表示两个非负的整数。 它们每位数字都是按照逆序的方式存储的,并且每个节点只能存储一位数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0开头。 示例 1: 输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8] 解释:342 + 465 = 807. 示例 2: 输入:l1 = [0], l2 = [0] 输出:[0] 示例 3: 输入:l1 = [9,9,9,9,9,9
素履coder
2022/05/19
1.6K0
​LeetCode刷题实战34:在排序数组中查找元素的第一个和最后一个位置
算法的重要性,我就不多说了吧,想去大厂,就必须要经过基础知识和业务逻辑面试+算法面试。所以,为了提高大家的算法能力,这个公众号后续每天带大家做一道算法题,题目就从LeetCode上面选 !
程序员小猿
2021/01/20
1.2K0
LeetCode链表知识点&题型总结
刚开始准备刷算法题目的时候,感觉真的是好难,十道题目有九道是不会的。心中曾一万只草泥马跑过,自己怎么这么辣鸡。
程序员徐公
2020/08/04
1.6K0
LeetCode链表知识点&题型总结
用javascript分类刷leetcode19.数组(图文视频讲解)5
数组操作的时间复杂度Access:O(1)Search:O(n)Insert: 平均O(n),最好的情况下O(1),也就是在数组尾部插入O(1),最坏的情况下O(n)Delete;平均O(n),最好的情况下O(1),也就是在数组尾部删除O(1),最坏的情况下O(n)图片167. 两数之和 II - 输入有序数组 (easy)给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers
hellocoder2028
2023/01/09
5240
相关推荐
[LeetCode题解]开篇!求和问题总结:2Sum/3Sum/4Sum/KSum
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文