代码随想录算法day2

代码随想录

目录

代码随想录

数组:双指针&滑动窗口

有序数组的平方 977

题目:

解法:

思考:什么情况必须新建数组,什么情况可以在自身上修改?(待补充)

注意:

语言基础:

长度最小的子数组 209

题目:

解法:

注意:

语言基础:

螺旋矩阵II 59

题目

解法

语言基础:

java矩阵【from gpt】

1. 定义与初始化

2. 访问与修改矩阵元素

3. 遍历矩阵

4. 常见的矩阵操作

矩阵相加:

矩阵乘法:

5. 矩阵转置

6. 示例

结论

数组总结


数组:双指针&滑动窗口

滑动窗口是双指针的一种应用,可以按需要选择一段连续的数组。相比之下双指针本身更灵活,可以按需要选择两根指针的位置和移动方向。

有序数组的平方 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

题目

力扣题目链接(opens new window)

给定一个正整数 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),所画,总结的非常好”

注:本系列所有引用号内的语句均为代码随想录原文

  • 26
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值