数组

本文为学习代码随想录时所做的笔记,仅供学习参考,不做任何商业用途,若有侵权,请联系删除。

数组

二分查找

704. 二分查找 - 力扣(LeetCode)

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

1
2
3
输入: nums = [-1,0,3,5,9,12], target = 9     
输出: 4
解释: 9 出现在 nums 中并且下标为 4

思路

二分法应当注意对区间的定义。区间的定义就是不变量,要在二分查找的过程中,保持不变量,就是在while寻找每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则。

二分法区间的定义一般为两种,左闭右闭 [left, right],或者左闭右开 [left, right)。

  • [left, right]

    • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
    • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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;
    }
    };
  • [left, right)

    • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
    • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Solution {
    public:
    int search(vector<int>& nums, int target) {
    int left = 0;
    int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
    while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
    int middle = left + ((right - left) >> 1);
    if (nums[middle] > target) {
    right = middle; // target 在左区间,在[left, middle)中
    } else if (nums[middle] < target) {
    left = middle + 1; // target 在右区间,在[middle + 1, right)中
    } else { // nums[middle] == target
    return middle; // 数组中找到目标值,直接返回下标
    }
    }
    // 未找到目标值
    return -1;
    }
    };

相关题目

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

35. 搜索插入位置 - 力扣(LeetCode)

69. x 的平方根 - 力扣(LeetCode)

367. 有效的完全平方数 - 力扣(LeetCode)

移除元素

27. 移除元素 - 力扣(LeetCode)

思路

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

  • 快指针:寻找新数组的元素,新数组就是不含有目标元素的数组
  • 慢指针:指向更新新数组下标的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};

相关题目推荐

26. 删除有序数组中的重复项 - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.size() == 0)
return 0;

int slow = 0;
int fast = 0;
while (fast < nums.size()) {
if (nums[slow] != nums[fast]) {
slow ++;
nums[slow] = nums[fast];
}
fast ++;
}

return slow + 1;
}
};

283. 移动零 - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.size() == 0)
return 0;

int slow = 0;
int fast = 0;
while (fast < nums.size()) {
if (nums[slow] != nums[fast]) {
slow ++;
nums[slow] = nums[fast];
}
fast ++;
}

return slow + 1;
}
};

844. 比较含退格的字符串 - 力扣(LeetCode)

一个字符是否会被删掉,只取决于该字符后面的退格符,而与该字符前面的退格符无关。因此当我们逆序地遍历字符串,就可以立即确定当前字符是否会被删掉。

一个字符是否会被删掉,只取决于该字符后面的退格符,而与该字符前面的退格符无关。因此当我们逆序地遍历字符串,就可以立即确定当前字符是否会被删掉。

具体地,我们定义skip 表示当前待删除的字符的数量。每次我们遍历到一个字符:

若该字符为退格符,则我们需要多删除一个普通字符,我们让skip加 11;

若该字符为普通字符:

若 skip 为 0,则说明当前字符不需要删去;

若 skip 不为 0,则说明当前字符需要删去,我们让 skip 减 11。

这样,我们定义两个指针,分别指向两字符串的末尾。每次我们让两指针逆序地遍历两字符串,直到两字符串能够各自确定一个字符,然后将这两个字符进行比较。重复这一过程直到找到的两个字符不相等,或遍历完字符串为止。

作者:LeetCode-Solution 链接:https://leetcode.cn/problems/backspace-string-compare/solution/bi-jiao-han-tui-ge-de-zi-fu-chuan-by-leetcode-solu/ 来源:力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
public:
bool backspaceCompare(string S, string T) {
int i = S.length() - 1, j = T.length() - 1;
int skipS = 0, skipT = 0;

while (i >= 0 || j >= 0) {
while (i >= 0) {
if (S[i] == '#') {
skipS++, i--;
} else if (skipS > 0) {
skipS--, i--;
} else {
break;
}
}
while (j >= 0) {
if (T[j] == '#') {
skipT++, j--;
} else if (skipT > 0) {
skipT--, j--;
} else {
break;
}
}
if (i >= 0 && j >= 0) {
if (S[i] != T[j]) {
return false;
}
} else {
if (i >= 0 || j >= 0) {
return false;
}
}
i--, j--;
}
return true;
}
};

977. 有序数组的平方 - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n);
for (int i = 0, j = n - 1, pos = n - 1; i <= j;) {
if (nums[i] * nums[i] > nums[j] * nums[j]) {
ans[pos] = nums[i] * nums[i];
++i;
}
else {
ans[pos] = nums[j] * nums[j];
--j;
}
--pos;
}
return ans;
}
};

滑动窗口

209. 长度最小的子数组 - 力扣(LeetCode)

思路

所谓滑动窗口,就是不断地调节子序列的起始位置和终止位置,从而得出我们想要的结果。

在本题内实现滑动窗口,主要确定如下三点:

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。

滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int result = INT32_MAX;
int sum = 0; // 滑动窗口数值之和
int i = 0; // 滑动窗口起始位置
int subLength = 0; // 滑动窗口的长度
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while (sum >= s) {
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength;
sum -= nums[i ++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};

相关题目推荐

904. 水果成篮 - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Solution {
public:
int totalFruit(vector<int>& fruits) {
if (fruits.size() < 3)
return fruits.size();

int fr1 = -1, fr2 = -1; //分别标记当前篮子里的水果种类
int maxNums = 0;
int index = 0;
int j = 0; //滑动窗口起始位置
for (int i = j; i < fruits.size(); i ++) {
if (fr1 == -1 || fr1 == fruits[i]) {
fr1 = fruits[i];
int lens = i - j + 1;
maxNums = maxNums > lens ? maxNums : lens;
} else if (fr2 == -1 || fr2 == fruits[i]) {
if (fr2 == -1)
index = i;
fr2 = fruits[i];
int lens = i - j + 1;
maxNums = maxNums > lens ? maxNums : lens;
} else {
j = index;
i = j - 1;
fr1 = -1;
fr2 = -1;
}
}

return maxNums;
}
};

76. 最小覆盖子串 - 力扣(LeetCode)

螺旋矩阵

59. 螺旋矩阵 II - 力扣(LeetCode)

思路

循环不变量原则

边界条件很多,应当按照固定规则来遍历

这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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 < n - offset; j++) {
res[startx][j] = count++;
}
// 模拟填充右列从上到下(左闭右开)
for (i = startx; i < 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 += 1;
}

// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2) {
res[mid][mid] = count;
}
return res;
}
};

数组
https://www.spacezxy.top/2022/06/26/Algorithm/algorithm-1/
作者
Xavier ZXY
发布于
2022年6月26日
许可协议