题目列表
解题过程
1、977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
思路:
这道题有两种解法:
第一种是直接计算数字的平方,然后对整个数组进行快速排序,时间复杂度为O(nlogn)。
代码实现
class Solution {
public int[] sortedSquares(int[] nums) {
for(int i = 0; i < nums.length; i++) {
nums[i] = nums[i] * nums[i];
}
Arrays.sort(nums);
return nums;
}
}
第二种解法就是使用双指针,观察发现,数字平方后,最大值一定由原数组两边的数字平方后产生,因此可以使用双指针,从两头到中间进行数字平方再比较,得到从大到小的有序数组。
考虑到题目要求输出从小到大的顺序,因此可以从数组最后一个位置开始赋值,从后向前插入从大到小的数字(已平方),时间复杂度为O(n)。
代码实现
class Solution {
public int[] sortedSquares(int[] nums) {
int left = 0, right = nums.length - 1;
//定义一个数组,存储平方排序后的结果
int[] result = new int[nums.length];
//从后往前赋值
int index = nums.length - 1;
//进入循环
while (left <= right) {
int lefts = nums[left] * nums[left];
int rights = nums[right] * nums[right];
if (lefts >= rights) {
result[index--] = lefts;
left++;
} else {
result[index--] = rights;
right--;
}
}
return result;
}
}
注意
- 从后向前进行赋值可以得到原题目要求的数组顺序。
2、209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
思路:
利用滑动窗口的思路,和双指针类似,但这里考察的是两个指针中间的范围数组。
所谓滑动窗口,就是不断地调节子序列的起始位置和终止位置,从而得出我们要想的结果。
用一个for循环代替暴力所需要的两个for循环,for循环中的参数j代表的是子数组的终止位置,起始位置初始化为i=0位置,当j遍历到该子数组的和≥target时,移动起始位置,收缩子数组的长度。
代码实现
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left = 0, sum = 0, ans = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) {
//更新ans的值
ans = Math.min(ans, right - left + 1);
//起始位置向后移动,sum值更新,left++
sum -= nums[left++];
}
}
//是否存在这样的子数组
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}
注意
for循环中的j代表子数组的终止位置;- 终止位置向后移动时,
sum的值加上终止位置指向的值; - 起始位置将要向后移动时,
sum的值减去起始位置指向的值; - 在这个过程中,滑动窗口一直在滑动和变化。
3、59.螺旋矩阵II
给你一个正整数 n ,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
思路:
要遵循循环不变量的原则,防止陷入死循环。这里遵循 `左闭右开` 的规则进行遍历,把最后一个结点留给下一条边进行处理。
代码实现
class Solution {
public int[][] generateMatrix(int n) {
//while循环的次数
int loop = 0;
int[][] result = new int[n][n];
//起始位置
int start = 0;
//当前处理位置
int i, j;
//定义填充数字值
int count = 1;
while (loop++ < n/2) {
//模拟上侧从左往右,j能到达的最右的地方和循环次数loop有关
for (j = start; j < n - loop; j++) {
result[start][j] = count++;
}
//模拟右侧从上到下
for (i = start; i < n - loop; i++) {
result[i][j] = count++;
}
//模拟下侧从右往左,j能到达的最左的地方就是循环次数loop
for (; j >= loop; j--) {
result[i][j] = count++;
}
//模拟左侧从下到上
for (; i >= loop; i--) {
result[i][j] = count++;
}
start++;
}
//边界处理,如果n是奇数,最后对未处理到的格子进行赋值
if (n % 2 == 1) {
result[start][start] = count;
}
return result;
}
}
注意
- 循环不变量的原则;
while循环中是转的总圈数。
4、904.水果成篮
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
- 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
- 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
- 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。
思路:
首先要理解题目的意思是“找至多包含两种元素的最长子串,返回其长度”。
看到最长子串,考虑滑动窗口,for循环中的终止位置向后移动,当出现的类型大于2种时,起始位置移动,然后终止位置再继续向后移动,过程中比较和更新子串的长度。
代码实现
class Solution {
public int totalFruit(int[] fruits) {
//声明起始位置,种类数,和最后返回的水果数
int left = 0, total = 0, ans = 0;
//cnts数组存储被选中的水果的种类,>=1代表被选中了
int[] cnts = new int[fruits.length + 10];
for (int right = 0; right < fruits.length; right++) {
cnts[fruits[right]]++;
if (cnts[fruits[right]] == 1) {
total++;
}
//到了要移动起始位置的时候
while (total > 2) {
//将最先放入的水果种类对应的水果全部移除
if(--cnts[fruits[left++]] == 0) {
total--;
}
}
//更新ans的值
ans = Math.max(ans, right - left + 1);
}
return ans;
}
}
注意
- 声明一个数组
cnts代表被选中水果的个数; - 利用滑动窗口。
5、76.最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
思路: 引用他人题解
1.在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引左闭右开区间 [left, right) 称为一个「窗口」;
2.不断地增加 right 的值扩大窗口 [left, right),直到窗口中的字符串符合要求(包含了 T 中的所有字符);
3.此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right),直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果;
4.重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。
代码实现
class Solution {
public String minWindow(String source, String target) {
char[] s = source.toCharArray();
char[] t = target.toCharArray();
// needs是需要的字符和数量,window记录窗口中有效的字符和数量
HashMap<Character, Integer> needs = new HashMap<>();
HashMap<Character, Integer> window = new HashMap<>();
// valid 变量表示窗口中满足 need 条件的字符个数
int valid = 0;
int left = 0, right = 0;
// 记录最小覆盖子串的起始索引及长度
int start = 0, len = Integer.MAX_VALUE;
for(char c : t){
needs.put(c, needs.getOrDefault(c, 0) + 1);
}
while(right < s.length){
// c 是将移入窗口的字符
char c = s[right];
// 扩大窗口
right++;
// 进行窗口内数据的一系列更新
if(needs.containsKey(c)){
window.put(c, window.getOrDefault(c, 0) + 1);
// ⭐注意:两个Integer类型的数据不能直接用< == >判断
if(window.get(c).equals(needs.get(c))){
valid++;
}
}
// 判断左侧窗口是否要收缩
while(valid == needs.size()){
// 在这里更新最小覆盖子串(更新最终结果)
if(right - left < len){
start = left;
len = right - left;
}
// d 是将移要出窗口的字符
char d = s[left];
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
if(needs.containsKey(d)){
window.put(d, window.get(d) - 1);
// window窗口内的数据无法满足,不再有效
// ⭐注意:两个Integer类型的数据不能直接用< == >判断
if(window.get(d) < Integer.valueOf(needs.get(d))){
valid--;
}
}
}
}
// 返回最小覆盖子串
return len == Integer.MAX_VALUE ? "" : source.substring(start, start + len);
}
}
总结
今天的题目(2023.3.16)主要涉及双指针、滑动窗口和循环不变量的原则,难度比昨天大一点,拓展题目 76.最小覆盖子串 还需要花时间好好复习一下。