代码随想录
目录
思考:什么情况必须新建数组,什么情况可以在自身上修改?(待补充)
数组:双指针&滑动窗口
滑动窗口是双指针的一种应用,可以按需要选择一段连续的数组。相比之下双指针本身更灵活,可以按需要选择两根指针的位置和移动方向。
有序数组的平方 977
题目:
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
- 输入:nums = [-4,-1,0,3,10]
- 输出:[0,1,9,16,100]
- 解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100]
示例 2:
- 输入:nums = [-7,-3,2,3,11]
- 输出:[4,9,9,49,121]
解法:
暴力解法:先平方后排序 O(n + nlogn)
双指针:因为原本是排序过的数组(左负右正),即最大的平方数一定出现在两侧,可以用双指针夹逼,从两侧向中间逼近。比较左右指针所指的数的平方大小,将更大的优先储存进新的数组。随后移动较大数的指针。O(n)
思考:什么情况必须新建数组,什么情况可以在自身上修改?(待补充)
写完补充其他问题
class Solution {
public int[] sortedSquares(int[] nums) {
int left=0;
int right=nums.length-1;
int [] result=new int [nums.length];
int index= result.length-1;//从大到小记录
while (left<=right){
if(nums[left]*nums[left]>nums[right]*nums[right]){
result[index--]=nums[left]*nums[left];//index--
left++;
}
else{
result[index--]=nums[right]*nums[right];
right--;
}
}
return result;
}
}
注意:
1. while内判断条件是要等于的,因为前面比较完之后还会剩下最后一个数(minimum),需要左右指针同时指向它才能把它存起来。
也就是尽量不要不思考直接用!=做条件,判断条件就写必须成立的条件,而不是自以为的最终判断。
2. 本题的开闭判断?
3. 因为是从左右从大到小记录,所以index是新数组的最后一位(即倒过来记录)
语言基础:
1. 调取数组长度时,不同语言有不同的函数
java:.length
c++:.size()
且因为指针是从0开始,末位必须是 nums.length-1
2. 两个相同的元素相乘时,c++和java都必须用nums[n]*nums[n]
3. java构造新数组
new int[nums.length]
4. 关于left++:
-
left++
是后置递增运算符:它意味着left
的值在表达式求值之后增加 1。换句话说,当前操作使用的是left
递增之前的值。 -
++left
是前置递增运算符:它意味着left
的值在表达式求值之前增加 1。当前操作使用的是递增后的left
的值。
同时,不能使用result[index--] = nums[left++] * nums[left++];或result[index--] = nums[++left] * nums[++left];
,对于left++,
两个 "++" 操作都是在赋值之后发生的。所以它实际上计算的是 nums[left] * nums[left+1],然后 left 的值增加 2。
也就是++推荐使用在调用中只有一次的情况(或是合理设计两次调用)
int left = 5;
int result;
result = left++; // result 的值是 5,然后 left 变成了 6
// left 现在是 6
result = ++left; // left 首先变成了 7,然后 result 的值也是 7
// left 现在是 7
//例2
left = 0;
result[index--] = nums[left++] * nums[left++]; // 假设 nums 是 [1,2,3,4]
// 计算后变为 result[某值] = 1 * 2; left 变为 2
left = 0;
result[index--] = nums[left] * nums[left]; // 假设 index 和 nums 没变
++left;
// 计算后 result[某值] = 1 * 1; 然后 left 变为 1
5. 符号中英文输入问题
长度最小的子数组 209
题目:
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
- 输入:s = 7, nums = [2,3,1,2,4,3]
- 输出:2
- 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
提示:
- 1 <= target <= 10^9
- 1 <= nums.length <= 10^5
- 1 <= nums[i] <= 10^5
解法:
暴力解法:两次循环(起点和终点)
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
滑动窗口:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
-
“不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。”
-
那么两次for循环的问题?: 暴力解法是每次都把窗口左边后面的重新加一遍,但滑动窗口是依靠增减单一元素,使得元素只有出入窗口两次被调用
“所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。”
将两个for循环通过窗口缩减到一个for循环,但势必循环的是窗口结束位置(否则窗口内的数没有被遍历到)
//标答
class Solution {
// 滑动窗口
public int minSubArrayLen(int s, int[] nums) {
int left = 0;
int sum = 0;
int result = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= s) {
result = Math.min(result, right - left + 1);
sum -= nums[left++];
}
}
return result == Integer.MAX_VALUE ? 0 : result;
}
}
//my version
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int left=0;
int right=0;
int length=nums.length+1;
int sum=0;
for (;right<nums.length;right++){
sum+=nums[right];
while (sum>=target){
length= length<(right-left+1)?length:(right-left+1);//先计算
sum-=nums[left++];
}
}
return length<nums.length+1?length:0;
}
}
注意:
1. 第一遍写伪代码的时候,把while (sum >= s)写成了if。还是需要注意结束条件,因为有可能sum超出的时候,左指针需要移动不止一次(减少不止一个数)才能重新小于s。
2. 但其实也可以不需要用最大值来设定长度,可以直接定成nums.length
3. 终止条件:外循环:右指针超出(因为右指针在最后一位的时候,左指针还需要不断移动缩减长度,所以不能是<nums.length-1)
内循环:和大于等于s (注意等于也可以,因为目的是让左指针移动到窗口和小于s的时候再更新右指针)
4. 初始值设定:sum可以先定为0,因为一开始右指针也在第一位,第一轮会只计算第一位的数值。
5. 这一段length变量是在循环里变化的(迭代减小),所以要注意for循环的终止条件,写nums.length而不是length。
int length=nums.length;
int sum=0;
for (;right<nums.length;right++)
6. 异常情况处理的时候,不能最终比较窗口长度是否小于或小于等于原长度,因为
1)全部加起来仍然小于target,设置等于会输出原长度
2)全部加起来等于target,设置小于则会输出零
总之就是因为总长度可能是窗口长度的一个解,为保证判断寻找是否成功,必须使得用来比较的预设窗口长度大于原数组长度(+1或max value)
语言基础:
java
1. 最大数Integer.MAX_VALUE
2. result ==Integer.MAX_VALUE ? 0 : result 可以用?:,“?”前返回值是bool类型,返回1取“:”前的结果,返回0取“:”后的结果。
3. 取二者中小的用Math.min(,)
螺旋矩阵II 59
题目
给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:
输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]
解法
“这道题目可以说在面试中出现频率较高的题目,本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。”
循环不变量原则:“这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。”
class Solution {
public int[][] generateMatrix(int n) {
int[][] matrix= new int[n][n];
int start=0;//每一圈循环开始的地方
int offset=1;//每次少的地方(1开始表示左闭右开),用start和offset同时加一,每次减少一圈
int loop=1;//圈数
//其实offset和loop是一致的,是否可以放一起?
int count=1;//当前填入的数字
int i,j;
while (loop<=n/2){//因为一圈会用到正方形的左右两边,所以圈数是/2,并取下限
//顶部行
//左闭右开,所以判断循环结束时, j 不能等于 n - offset,小于是因为位置比指针大1
for(j=start;j<n-offset;j++){
matrix[start][j]=count++;//第一轮i还未赋值,需要手动添加
}
//右侧向下
for(i=start;i<n-offset;i++){//复制代码的时候要修改变量
matrix[i][j]=count++;
}
//底部行
//左闭右开,所以需要比start大,回到start即开始下一条/下一条循环
for(;j>start;j--){
matrix[i][j]=count++;
}
//左侧向上
for(;i>start;i--){
matrix[i][j]=count++;
}
//更新数据
start++;
offset++;
loop++;
}
if(n%2==1){//奇数n,考虑中心数值不在一轮中
matrix[start][start]=count++;//可以不用++//这和初始是一个情况,跳出循环之后i和j无赋值,需要用start赋值
}
return matrix;
}
}
本题中因为是绕圈,i和j会随着一圈的完成回到下一圈的起始位置
语言基础:
java矩阵【from gpt】
matrix[n][m]:n+1
1. 定义与初始化
定义3*3矩阵【构造的时候数字就是长度,访问时要-1】
int[][] matrix = new int[3][3];
使用初始化列表来直接赋值:
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
2. 访问与修改矩阵元素
你可以通过行和列的索引来访问或修改矩阵中的元素。访问元素:
int element = matrix[1][2]; // 访问第二行第三列的元素,值为6
修改元素:
matrix[0][0] = 10; // 将第一行第一列的元素设为10
3. 遍历矩阵
遍历矩阵可以使用嵌套的 for
循环。遍历所有元素并打印:
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
4. 常见的矩阵操作
矩阵相加:
假设有两个矩阵 matrixA
和 matrixB
,其维数相同,下面是将它们对应元素相加的示例代码:
int[][] matrixA = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
int[][] matrixB = {
{9, 8, 7},
{6, 5, 4},
{3, 2, 1}
};
int[][] result = new int[3][3];
for (int i = 0; i < matrixA.length; i++) {
for (int j = 0; j < matrixA[i].length; j++) {
result[i][j] = matrixA[i][j] + matrixB[i][j];
}
}
矩阵乘法:
矩阵乘法是一个较复杂的操作,需要满足矩阵乘法的条件:第一个矩阵的列数等于第二个矩阵的行数。
int[][] matrixA = {
{1, 2, 3},
{4, 5, 6}
};
int[][] matrixB = {
{7, 8},
{9, 10},
{11, 12}
};
// matrixA is 2x3, and matrixB is 3x2. Result will be 2x2.
int[][] result = new int[matrixA.length][matrixB[0].length];
for (int i = 0; i < matrixA.length; i++) {
for (int j = 0; j < matrixB[0].length; j++) {
for (int k = 0; k < matrixA[0].length; k++) {
result[i][j] += matrixA[i][k] * matrixB[k][j];
}
}
}
5. 矩阵转置
矩阵转置是将矩阵的行变为列,列变为行。
int[][] matrix = {
{1, 2, 3},
{4, 5, 6}
};
int[][] transposed = new int[matrix[0].length][matrix.length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
transposed[j][i] = matrix[i][j];
}
}
6. 示例
完整的示例程序:
public class MatrixExample {
public static void main(String[] args) {
int[][] matrixA = {
{1, 2, 3},
{4, 5, 6}
};
int[][] matrixB = {
{7, 8},
{9, 10},
{11, 12}
};
// Matrix multiplication
int[][] result = new int[matrixA.length][matrixB[0].length];
for (int i = 0; i < matrixA.length; i++) {
for (int j = 0; j < matrixB[0].length; j++) {
for (int k = 0; k < matrixA[0].length; k++) {
result[i][j] += matrixA[i][k] * matrixB[k][j];
}
}
}
// Print result matrix
for (int i = 0; i < result.length; i++) {
for (int j = 0; j < result[i].length; j++) {
System.out.print(result[i][j] + " ");
}
System.out.println();
}
}
}
结论
在 Java 中,矩阵的表示和操作主要依赖于二维数组。通过对数组的遍历和操作,能够实现各种矩阵操作,如加法、乘法和转置等。理解并掌握这些基本操作,对于许多算法和数据处理任务都是非常重要的。
数组总结
“这个图是 代码随想录知识星球 (opens new window)成员:海螺人 (opens new window),所画,总结的非常好”
注:本系列所有引用号内的语句均为代码随想录原文