学习笔记:数组篇

学习笔记:数组篇

1.基本性质

数组是一个比较简单的数据结构,数组的具体形式是一连串连续地址的存储,并且其下标是连续的。

 

0

1

2

3

4

5

6

7

8

9

a

d

g

G

k

l

h

d

e

b

10002

10003

10004

10005

10006

10007

10008

10009

10010

10011

 

数组特点:

下标从零开始

地址连续。

 

地址连续意味着其实对于数组来说插入和删除从一般意义来说会比较麻烦

比如删除k,其后所有元素的下标和地址会相应移动

所以实际上数组元素只能覆盖,不方便删除。

0

1

2

3

4

5

6

7

8

 

a

d

g

G

l

h

d

e

b

 

10002

10003

10004

10005

10006

10007

10008

10009

10010

 

 

2.数组常用思路

0)暴力遍历法

1)二分法

2)双指针

3)思路模拟

其中感觉二分法和双指针在后面章节都有更为复杂的应用,但是在数组中的使用却显得尤为简单,体现了其基本的作用方式。

 

1) 二分法

class Solution {

public:

    int search(vector<int>& nums, int target) {

        int left = 0;

        int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]

        while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=

            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2

            if (nums[middle] > target) {

                right = middle - 1; // target 在左区间,所以[left, middle - 1]

            } else if (nums[middle] < target) {

                left = middle + 1; // target 在右区间,所以[middle + 1, right]

            } else { // nums[middle] == target

                return middle; // 数组中找到目标值,直接返回下标

            }

        }

        // 未找到目标值

        return -1;

    }

};

 

二分法及其容易出错,关键就在于随着循环的进行所伴随的一系列包括溢出,死循环等问题。

要解决主要就是要保证每一次循环都和第一次一样,以上代码的关键处都是保证了每次循环的初始条件不会出问题。

2) 双指针法

从这里我觉得很有意思,这是不是意味着所有人想象到的不论什么方法都可以通过编程实现去提高效率呢?

这个方法主要是通过两个指针的变化来达到扫描的目的。两个指针可以开始在同一侧(滑动窗口法),也可以在两侧。

class Solution {

public:

    int removeElement(vector<int>& nums, int val) {

        int size = nums.size();

        for (int i = 0; i < size; i++) {

            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位

                for (int j = i + 1; j < size; j++) {

                    nums[j - 1] = nums[j];

                }

                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位

                size--; // 此时数组的大小-1

            }

        }

        return size;

    }

};

 

3) 模拟行为

基于数组结构的相对好掌控,所以数组也被用来实现一些行为思路的实现。例子就是螺旋矩阵的填写。我开始一直想用矩阵变换去求每个公式,但是还是太不现实了。可以模拟人去填写数组的过程。

其中比较大的启发和教训就是对于循环,可能需要设定很多条件去保证循环的某种不变。

class Solution {

public:

    vector<vector<int>> generateMatrix(int n) {

        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组

        int startx = 0, starty = 0; // 定义每循环一个圈的起始位置

        int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理

        int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)

        int count = 1; // 用来给矩阵中每一个空格赋值

        int offset = 1; // 每一圈循环,需要控制每一条边遍历的长度

        int i,j;

        while (loop --) {

            i = startx;

            j = starty;

 

            // 下面开始的四个for就是模拟转了一圈

            // 模拟填充上行从左到右(左闭右开)

            for (j = starty; j < starty + n - offset; j++) {

                res[startx][j] = count++;

            }

            // 模拟填充右列从上到下(左闭右开)

            for (i = startx; i < startx + n - offset; i++) {

                res[i][j] = count++;

            }

            // 模拟填充下行从右到左(左闭右开)

            for (; j > starty; j--) {

                res[i][j] = count++;

            }

            // 模拟填充左列从下到上(左闭右开)

            for (; i > startx; i--) {

                res[i][j] = count++;

            }

 

            // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)

            startx++;

            starty++;

 

            // offset 控制每一圈里每一条边遍历的长度

            offset += 2;

        }

 

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值

        if (n % 2) {

            res[mid][mid] = count;

        }

        return res;

    }

};

 

写在最后:回想之前小学期其实也不是全无收获,但是奈何学校的小学期真的很难,自己当时也无心学习导致当时信心全无,很长一段时间都在逃避现实,厌恶编程。现在来补功课,害,还得慢慢来吧。通过这一系列学习希望能找到自己习的节奏和学习本身的快乐。加油!

(代码和思想方法皆转载自“代码随想录“,侵删)”

posted @ 2022-05-09 22:26  张喻轩  阅读(93)  评论(0)    收藏  举报