# 一、理论基础几乎无。只要记住数组是相同类型、连续存储、支持随机访问的。
# 二、题目实践# 1、704. 二分查找 #双指针
没啥难度的,基本一次过,以下是个人版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : int search (vector<int >& nums, int target) { int left = 0 , right = nums.size () - 1 ; while (left <= right) { int mid = left + (right - left) / 2 ; if (target == nums[mid]) { return mid; } else if (target < nums[mid]) { right = mid-1 ; } else if (target > nums[mid]) { left = mid+1 ; } } return -1 ; } };
# 2、27. 移除元素 #双指针 #快慢指针
作为 “老狐狸”,重刷这道题的时候一上来的思路就是双指针,左指针从左到右找到第一个值为 val 的位置,右指针从右到左找到第一个不为 val 的位置,然后交换左右指针的值。右指针移动过的次数即为 val 的个数,用数组长度减去该值即结果。
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 class Solution {public : int removeElement (vector<int >& nums, int val) { if (nums.size () == 1 ) { return nums[0 ] == val ? 0 : 1 ; } int ret = 0 ; int left = 0 ; int right = nums.size () - 1 ; while (left <= right) { while (nums[right] == val && left < right) { ret++; right--; } while (nums[left] != val && left < right) left++; if (left == right) { return nums[left] == val ? (nums.size () - ret - 1 ) : (nums.size () - ret); } if (nums[left] == val && nums[right] != val) { nums[left] = nums[right]; nums[right] = val; ret++; left++; right--; } } return (nums.size () - ret); } };
上上次做这道题,则是把上面这个步骤拆分成两次循环,第二次循环用于得到 val 的数量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : int removeElement (vector<int >& nums, int val) { int length = nums.size (); for (int head = 0 , tail = length - 1 ; head < tail;) { if (nums[head] != val) ++head; if (nums[tail] == val) --tail; if (nums[head] == val && nums[tail] != val && head < tail) { nums[head] = nums[tail]; nums[tail] = val; } } int valCount = 0 ; for (int tail = length - 1 ; tail >= 0 && nums[tail] == val; --tail) { ++valCount; } return (length - valCount); } };
然而,针对这种思路,由于其实我们不需要考虑 k 以外的数据,所以不需要把左边的值交换 / 备份到右边,只需要不断更新左边的值即可。这样我们可以简化上面的代码,得到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution {public : int removeElement (vector<int >& nums, int val) { int left = 0 , right = nums.size (); while (left < right) { if (nums[left] == val) { nums[left] = nums[right - 1 ]; right--; } else { left++; } } return left; } };
除了双指针,其实这题用快慢指针也可以。 慢指针起初与快指针同频,当快指针遇到 val 时,慢指针停留不动,快指针继续向后寻找非 val 值。
1 2 3 4 5 6 7 8 9 10 11 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; } };
# 3、977. 有序数组的平方 #双指针
由于负数的出现,导致乍一看时没有什么多快好省的思路 pop 出来,所以先写个暴力解法压压惊: 两次循环,第一次利用 for_each
函数得到平方后的数组,第二次将平方数组排序:
1 2 3 4 5 6 7 8 9 class Solution {public : vector<int > sortedSquares (vector<int >& nums) { for_each(nums.begin (), nums.end (), [](int & num) { return num = num*num; }); sort (nums.begin (), nums.end (), std::less <int >()); return nums; } };
稍作思考,想出来的仍然是双指针: 由于是升序数组,且可能存在负数,故先找到 “中心点”,即绝对值最小的位置。 然后双指针从中心向左右两边扩散,逐个比较左指针和右指针所指向的数组元素的平方大小,然后压入 vec 中。 (注意,一次比较不能把两个参与比较的值都依次压入,因为可能存在连续相同的值,如: [-10,-8,-7,-5,1,1,10]
)
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 class Solution {public : vector<int > sortedSquares (vector<int >& nums) { int left = 0 ; while (left < nums.size () && nums[left] < 0 ) left++; int right = left--; vector<int > ret; for (; left >= 0 && right < nums.size (); ) { int left_s = nums[left]*nums[left]; int right_s = nums[right]*nums[right]; if (left_s < right_s) { ret.push_back (left_s); left--; } else { ret.push_back (right_s); right++; } } for (; left >= 0 ; left--) { ret.push_back (nums[left]*nums[left]); } for (; right < nums.size (); right++) { ret.push_back (nums[right]*nums[right]); } return ret; } };
其实,进一步地,还能优化。我们不必要执着于找到绝对值最小的位置,我们可以直接从左右两边开始,向中心收缩 —— 因为左右两边的绝对值是最大的。于是有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution {public : vector<int > sortedSquares (vector<int >& nums) { int fill = nums.size () - 1 ; vector<int > result (nums.size(), 0 ) ; for (int i = 0 , j = nums.size () - 1 ; i <= j; ) { if (nums[i] * nums[i] < nums[j] * nums[j]) { result[fill--] = nums[j] * nums[j]; j--; } else { result[fill--] = nums[i] * nums[i]; i++; } } return result; } };
# 4、209. 长度最小的子数组 #双指针 #滑动窗口
思路还是蛮容易想到的,还是双指针,又或者说是滑动窗口。 左右指针均从左向右滑动,当二者形成的窗口序列和大于 target 时,左指针开始向左移动以缩小窗口(直到序列和再一次小于 target 为止)。而当序列和小于 target 时,右指针向右移动一步。右指针的右滑通过 for 循环的迭代步进即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : int minSubArrayLen (int target, vector<int >& nums) { int ret = INT32_MAX; int sum = 0 ; int current_len = 0 ; int i = 0 ; for (int j = 0 ; j < nums.size (); j++) { sum += nums[j]; while (sum >= target) { current_len = (j - i + 1 ); ret = min (ret, current_len); sum -= nums[i++]; } } return (ret == INT32_MAX ? 0 : ret); } };
# 5、59. 螺旋矩阵 II 这题只想得出模拟(好像也确实只有模拟的解法),但是光想想就觉得这个逻辑很复杂,想不出来。 这里学习了个评论区大佬的解法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : vector<vector<int >> generateMatrix (int n) { int t = 0 ; int b = n-1 ; int l = 0 ; int r = n-1 ; vector<vector<int >> ret (n, vector <int >(n)); int k = 1 ; while (k <= n*n) { for (int i = l; i <= r; ++i, ++k) ret[t][i] = k; ++t; for (int i = t; i <= b; ++i, ++k) ret[i][r] = k; --r; for (int i = r; i >= l; --i, ++k) ret[b][i] = k; --b; for (int i = b; i >= t; --i, ++k) ret[i][l] = k; ++l; } return ret; } };
# 6、15. 三数之和 #双指针
不要因为 “两数之和” 系列的题做多了,就什么都想往哈希身上套!
思路 1: 先对 nums 进行排序。 然后我们使用索引 i 遍历 nums,同时,我们用两个指针 left 和 right 分别指向:i 的下一位、nums 的最后一位。 然后就是在固定 i 的情况下,left 和 right 不断向中间收缩,内层循环控制收缩。 在内层循环中,我们每次迭代时计算三个数之和,如果结果大于零,那么 right 指针向左,如果结果小于零,那么 left 指针向右。(这个需要建立在数组递增的情况下,所以需要先对数组进行排序!)这里需要解决重复的问题,有两种情况: 第一种情况是,在 i 固定的情况下,left 和 right 所指向的内容重复了,导致整体重复。需要在找到 left 和 right 时顺便把重复元素都剔除;(剔除之后记得同时收缩 left 和 right,以退出内层循环) 第二种情况是,虽然在第一种情况剔除了 left 和 right 遇到的重复,但是 i 在下一轮循环中重复了。故需要在进行内层循环前把重复的 i 剔除;
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 class Solution {public : vector<vector<int >> threeSum (vector<int >& nums) { vector<vector<int >> ret; sort (nums.begin (), nums.end ()); for (int i = 0 ; i < nums.size (); i++) { if (i > 0 && nums[i] == nums[i - 1 ]) continue ; for (int left = i + 1 , right = nums.size () - 1 ; left < right; ) { int sum = nums[i] + nums[left] + nums[right]; if (sum == 0 ) { ret.push_back ({nums[i], nums[left], nums[right]}); while (left < right && nums[left] == nums[left+1 ]) ++left; while (left < right && nums[right] == nums[right-1 ]) --right; --right; ++left; } else if (sum < 0 ) { ++left; } else if (sum > 0 ) { --right; } } } return ret; } };
# 7、18. 四数之和 #双指针
基本上就是在 “三数之和” 的基础上再套一层循环(虽然听起来很逆天,但是它确实是比直接暴力解要快):
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 class Solution {public : vector<vector<int >> fourSum (vector<int >& nums, int target) { vector<vector<int >> ret; sort (nums.begin (), nums.end ()); for (int i = 0 ; i < nums.size (); i++) { if (i > 0 && nums[i] == nums[i - 1 ]) continue ; for (int j = i + 1 ; j < nums.size (); j++) { if (j > i + 1 && nums[j] == nums[j-1 ]) continue ; for (int left = j + 1 , right = nums.size () - 1 ; left < right; ) { if ((long )nums[i] + nums[j] + nums[left] + nums[right] < target) { ++left; } else if ((long )nums[i] + nums[j] + nums[left] + nums[right] > target) { --right; } else { ret.push_back ({nums[i], nums[j], nums[left], nums[right]}); while (left < right && nums[left] == nums[left+1 ]) ++left; while (left < right && nums[right] == nums[right-1 ]) --right; --right; ++left; } } } } return ret; } };
这里有几个细节要注意:
首先,在第二层循环 j 中,对于重复元素的判断,条件仍然需要从
j > i + 1
开始,不然会用力过度,把
2 + 2 + 2 + 2 = 8
的这种测试用例给剔除掉。
其次,四个数字相加,测试用例给的数字较大,会导致加法溢出。这里导致我出错好几次。要解决这个问题,首先我们不要用一个 int 的 sum 来存储结果,而是现场计算之后直接拿来比较,而且需要先把第一个元素强转成 long 型然后进行计算。(不要把四个数的求和括起来然后对整体的结果再强转成 long,因为在计算的过程中就会溢出了。所以必须先对其中一个数字强转,其他数字才会被触发隐式转换)。
基础题基本就这些了,可以看到数组相关的题,基本就是利用双指针来解题。