笔记
- 数组leetcode专栏
- leetcode 704 二分查找
- leetcode 27 移除元素
- leetcode 26 删除有序数组中的重复项(slow fast 双指针)
- leetcode 80 删除有序数组中的重复项 II
- leetcode 283 移动零
- leetcode 75 颜色分类
- leetcode 215 数组中的第K个最大元素
- leetcode 167 两数之和 II
- leetcode 11 盛最多水的容器
- leetcode 125 验证回文串
- leetcode 345 反转字符串中的元音字母
- leetcode 844 比较含退格的字符串
- leetcode 977 有序数组的平方
- leetcode 189 轮转数组(双指针)
- leetcode 557 反转字符串中的单词 III(双指针)
- leetcode 209 长度最小的子数组
- leetcode 904 水果成篮
- leetcode 76 最小覆盖子串
- leetcode 59 螺旋矩阵 II
- leetcode 54 螺旋矩阵
- leetcode 303 区域和检索 - 数组不可变(前缀和技巧)
- leetcode 304 二维区域和检索 - 矩阵不可变(前缀和技巧)
- leetcode 560 和为 K 的子数组(前缀和技巧)
数组leetcode专栏
leetcode 704 二分查找
class Solution {
public:
int search(vector<int>& nums, int target) {
int low = 0;
int high = nums.size()-1;
while(low <= high){
int mid = low + (high-low) / 2;
if(nums[mid] == target) {
return mid;
}
else if (nums[mid] > target) {
high = mid - 1;
}
else {
low = mid + 1;
}
}
return -1;
}
};
leetcode 27 移除元素
用erase函数
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
sort(nums.begin(), nums.end());
for(auto iter = nums.begin(); iter != nums.end();) {
if(*(iter) == val) {
iter = nums.erase(iter);
}
else {
iter++;
}
}
return nums.size();
}
};
暴力解法
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--;
size--;
}
}
return size;
}
};
双指针解法
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
for(int fast = 0; fast < nums.size(); fast++) {
if(nums[fast] != val) {
nums[slow++] = nums[fast];
}
}
return slow;
}
};
leetcode 26 删除有序数组中的重复项(slow fast 双指针)
我们让慢指针 slow 走在后面,快指针 fast 走在前面探路,找到一个不重复的元素就赋值给 slow 并让 slow 前进一步。
这样,就保证了 nums[0…slow] 都是无重复的元素,当 fast 指针遍历完整个数组 nums 后,nums[0…slow] 就是整个数组去重之后的结果。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size() <= 1) {
return nums.size();
}
int slow = 0;
for(int fast = 1; fast < nums.size(); fast++) {
if(nums[slow] != nums[fast]) {
slow++;
}
nums[slow] = nums[fast];
}
return slow+1;
}
};
leetcode 80 删除有序数组中的重复项 II
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.size() <= 2) {
return nums.size();
}
int slow = 2;
for (int fast = 2; fast < nums.size(); fast++) {
if (nums[fast] != nums[slow-2]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
};
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
return work(nums, 2);
}
int work(vector<int>& nums, int k) {
if (nums.size() <= k) return nums.size();
int slow = k;
for (int fast = k; fast < nums.size(); fast++) {
if (nums[slow-k] != nums[fast]) {
nums[slow++] = nums[fast];
}
}
return slow;
}
};
leetcode 283 移动零
类似 leetcode 27 移除元素,在不改变数组中其他元素的相对位置的情况下,将0移除掉
所以快慢指针得出的思路是这样的,一开始本就需要将所有的非零元素按顺序一次放入数组,那么不如直接放置一个慢指针,在快指针检测是否非零的同时,为非零元素放置的位置做规划。
作者:北枝
链接:https://leetcode-cn.com/leetbook/read/all-about-array/x9rbug/?discussion=xMi3wS
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int slow = 0;
for(int fast = 0; fast < nums.size(); fast++) {
if(nums[fast] != 0) {
nums[slow++] = nums[fast];
}
}
while(slow < nums.size()) {
nums[slow] = 0;
slow++;
}
return;
}
};
leetcode 75 颜色分类
采用三指针的方法
class Solution {
public:
void sortColors(vector<int>& nums) {
int pZero = 0;
int pTwo = nums.size();
int p = 0;
// [0, pZero] store 0
// [pZero, p] store 1
// [pTwo, nums.size()-1] store 2
while (p < pTwo) {
if (nums[p] == 0) {
swap(nums[p], nums[pZero]);
p++;
pZero++;
}
else if (nums[p] == 1) {
p++;
}
else {
pTwo--;
swap(nums[p], nums[pTwo]);
}
}
}
};
采用排序算法
class Solution {
public:
void sortColors(vector<int>& nums) {
quickSort(nums, 0, nums.size()-1);
}
void quickSort(vector<int>& nums, int low, int high) {
if (low < high) {
int pivotpos = partition(nums, low, high);
quickSort(nums, low, pivotpos-1);
quickSort(nums, pivotpos+1, high);
}
}
int partition(vector<int>& nums, int low, int high) {
int pivot = nums[low];
while (low < high) {
while (low < high && nums[high] >= pivot) high--;
nums[low] = nums[high];
while (low < high && nums[low] <= pivot) low++;
nums[high] = nums[low];
}
nums[low] = pivot;
return low;
}
};
class Solution {
public:
void sortColors(vector<int>& nums) {
MergeSort(nums, 0, nums.size()-1);
}
void MergeSort(vector<int>& nums, int low, int high) {
if (low < high) {
int mid = low + (high-low) / 2;
MergeSort(nums, low, mid);
MergeSort(nums, mid+1, high);
Merge(nums, low, mid, high);
}
}
void Merge(vector<int>& nums, int low, int mid, int high) {
vector<int> tmpVec(nums.begin(), nums.end());
int i = low;
int j = mid+1;
int index = low;
while (i <= mid && j <= high) {
if (tmpVec[i] <= tmpVec[j]) {
nums[index++] = tmpVec[i++];
}
else {
nums[index++] = tmpVec[j++];
}
}
while (i <= mid) {
nums[index++] = tmpVec[i++];
}
while (j <= high) {
nums[index++] = tmpVec[j++];
}
}
};
class Solution {
public:
void sortColors(vector<int>& nums) {
heapSort(nums, nums.size());
}
void heapSort(vector<int>& nums, int len) {
buildHeap(nums, len);
for (int i = len-1; i > 0; i--) {
swap(nums[i], nums[0]);
adjustHeap(nums, 0, i);
}
}
void buildHeap(vector<int>& nums, int len) {
for (int i = len/2-1; i >= 0; i--) {
adjustHeap(nums, i, len);
}
}
void adjustHeap(vector<int>& nums, int k, int len) {
int val = nums[k];
int i = 2 * k + 1;
while (i < len) {
if (i < len-1 && nums[i] < nums[i+1]) {
i++;
}
if (val >= nums[i]) {
break;
}
else {
nums[k] = nums[i];
k = i;
}
i = i * 2 + 1;
}
nums[k] = val;
}
};
leetcode 215 数组中的第K个最大元素
//小顶堆 priority_queue <int,vector<int>,greater<int>> q;
//大顶堆 priority_queue <int,vector<int>,less<int>> q;
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> minHeap;
for (int i = 0; i < nums.size(); i++) {
if (i < k) {
minHeap.push(nums[i]);
}
else {
int minEle = minHeap.top();
if (nums[i] > minEle) {
minHeap.pop();
minHeap.push(nums[i]);
}
}
}
return minHeap.top();
}
};
有一些 LeetCode 题目,我们可以采用对撞指针进行求解:指针 i 和 j 分别指向数组的第一个元素和最后一个元素,然后指针 i 不断向前, 指针 j 不断递减,直到 i = j(当然具体的逻辑操作根据题目的变化而变化)。
作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/all-about-array/x99ak2/
leetcode 167 两数之和 II
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int left = 0;
int right = numbers.size()-1;
while (left < right) {
if (numbers[left] + numbers[right] == target) {
return vector<int>{left+1, right+1};
}
else if (numbers[left] + numbers[right] < target) {
left++;
}
else {
right--;
}
}
return vector<int>();
}
};
leetcode 11 盛最多水的容器
// area = min(height[j], height[i])*(j-i)
class Solution {
public:
int maxArea(vector<int>& height) {
int i = 0;
int j = height.size()-1;
int res = 0;
while (i < j) {
res = height[i] > height[j] ? max(res, (j-i)*height[j--]) : max(res, (j-i)*height[i++]);
}
return res;
}
};
leetcode 125 验证回文串
// tolower() toupper()
// isalpha() isalnum()
class Solution {
public:
bool isPalindrome(string s) {
int left = 0;
int right = s.size()-1;
while (left < right) {
char l = s[left];
char r = s[right];
if (isalnum(l) && isalnum(r)) {
l = tolower(l);
r = tolower(r);
if (l != r) {
return false;
}
left++;
right--;
}
else if (!isalnum(l)) {
left++;
}
else {
right--;
}
}
return true;
}
};
leetcode 345 反转字符串中的元音字母
//string::npos == -1
class Solution {
public:
string reverseVowels(string s) {
int left = 0;
int right = s.size()-1;
string str = "aeiouAEIOU";
while (left < right) {
if (str.find(s[left]) == -1) {
left++;
continue;
}
if (str.find(s[right]) == -1) {
right--;
continue;
}
swap(s[left], s[right]);
left++;
right--;
}
return s;
}
};
leetcode 844 比较含退格的字符串
解法1:使用两个栈来存储每个字符串的元素
步骤1:当我们遍历到一个字符时,我们直接把它压入栈中
步骤2:当我们遍历到‘#’字符时,我们需要判断栈是否为空,如果为空则直接忽略掉该退格字符,否则弹出栈顶字符元素
返回结果:我们遍历完两个字符串后,只需要对比两个栈剩下的元素是否相等,即可决定返回true还是false
class Solution {
public:
bool backspaceCompare(string s, string t) {
stack<char> sck1;
stack<char> sck2;
for (int i = 0; i < s.size(); i++) {
if (s[i] >= 'a' && s[i] <= 'z') {
sck1.push(s[i]);
}
else {
if (!sck1.empty()) {
sck1.pop();
}
}
}
for (int j = 0; j < t.size(); j++) {
if (t[j] >= 'a' && t[j] <= 'z') {
sck2.push(t[j]);
}
else {
if (!sck2.empty()) {
sck2.pop();
}
}
}
while (!sck1.empty() && !sck2.empty()) {
char c1 = sck1.top();
sck1.pop();
char c2 = sck2.top();
sck2.pop();
if (c1 != c2) {
return false;
}
}
if (!sck1.empty() || !sck2.empty()) {
return false;
}
return true;
}
};
解法二:采用双指针,我们可以维护两个计数器,记录下退格的个数,然后倒序遍历字符串来决定当前字符是否需要跳过。
步骤一:我们维护两个计数器skips和skipt,然后倒序遍历字符串。
步骤二:当我们遇到‘#’时,将对应的计数器 + 1;当我们遇到字符时,会有如下的判断:
1、如果退格计数器为0,那么该字符无法跳过,此时应该比对当前的字符是否相同。
2、如果退格计数器 > 0,那么该字符需要跳过,需要让遍历指针往前移一位,同时让计数器 - 1。
步骤三:如果遍历过程中,发现两个位置上的字符并不相同,或者有其中一个字符串已经遍历完,那么直接返回 false,否则继续往前遍历剩下的字符。
最后,如果两个字符串都已经遍历完,那么证明它们经过退格的操作后是相等的字符串,返回 true;时间复杂度是 O(N),空间复杂度是O(1)
class Solution {
public:
bool backspaceCompare(string s, string t) {
int skips = 0;
int skipt = 0;
for (int i = s.size()-1, j = t.size()-1; i >= 0 || j >= 0; i--, j--) {
while (i >= 0) {
if (s[i] == '#') { //遇到‘#’时,将对应的计数器 + 1,接着处理下一个字符
skips++;
i--;
}
else if (skips > 0) { //遇到字符时,退格计数器 > 0,那么该字符需要跳过,遍历指针往前移一位,计数器 - 1
skips--;
i--;
}
else { //遇到字符时,退格计数器 == 0,该字符不能跳过,所以需要和另一个字符串对应位置字符比较
break;
}
}
while (j >= 0) {
if (t[j] == '#') {
skipt++;
j--;
}
else if (skipt > 0) {
skipt--;
j--;
}
else {
break;
}
}
if (i >= 0 && j >= 0) { //两个对应位置上的字符并不相等,或者有其中一个字符串已经遍历完,返回false
if (s[i] != t[j]) return false;
}
else if (i >= 0 || j >= 0) {
return false;
}
}
return true; //最后如果两个字符串都已经遍历完,那么证明它们经过退格的操作后是相等的字符串,返回 true
}
};
leetcode 977 有序数组的平方
采用双指针法
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
vector<int> result;
while (left <= right) {
int leftValue = abs(nums[left]);
int rightValue = abs(nums[right]);
if (leftValue < rightValue) {
right--;
result.push_back(pow(rightValue, 2));
}
else {
left++;
result.push_back(pow(leftValue, 2));
}
}
reverse(result.begin(), result.end());
return result;
}
};
leetcode 189 轮转数组(双指针)
//考虑空间复杂度为O(1)的原地算法
class Solution {
public:
void reverse(vector<int>& nums, int left, int right) {
while (left < right) {
swap(nums[left], nums[right]);
left++;
right--;
}
}
void rotate(vector<int>& nums, int k) {
if (nums.size() <= 1) return;
k = k % nums.size();
reverse(nums, 0, nums.size()-1); //区间按照左闭右闭的原则
reverse(nums, 0, k-1);
reverse(nums, k, nums.size()-1);
}
};
leetcode 557 反转字符串中的单词 III(双指针)
class Solution {
public:
string reverse(string s) {
int i = 0;
int j = s.size()-1;
while (i < j) {
swap(s[i++], s[j--]);
}
return s;
}
string reverseWords(string s) {
istringstream iss(s);
string st;
string result;
while (iss >> st) {
string tmp = reverse(st);
result += (tmp + " ");
}
result.pop_back();
return result;
}
};
leetcode 209 长度最小的子数组
暴力搜索解法,O(n^2)的时间复杂度
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = INT_MAX;
int subLen = 0;
for (int i = 0; i < nums.size(); i++) {
int sum = 0;
for (int j = i; j < nums.size(); j++) {
sum += nums[j];
if (sum >= target) {
subLen = j - i + 1;
result = subLen < result ? subLen : result;
break;
}
}
}
return result == INT_MAX ? 0 : result;
}
};
采用双指针,题目给定一个含有n个元素的数组和一个正整数target,让我们找出该数组中其和≥target的长度最小的连续子数组。
我们定义两个 指针i和j指针,将区间[i,j]看成滑动窗口,那么两个指针就分别表示滑动窗口的开始位置和结束位置,同时我们再维护一个sum变量用来存贮区间[i,j]连续数组的和。如果当前滑动窗口维护的区间和sum大于等于target,就说明当前的窗口是可行的,可行中的长度最短的滑动窗口就是答案。
作者:lin-shen-shi-jian-lu-k
链接:https://leetcode-cn.com/problems/minimum-size-subarray-sum/solution/chang-du-zui-xiao-de-zi-shu-zu-tu-jie-sh-ae80/
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int result = INT_MAX;
int subLen = 0;
int i = 0;
int sum = 0;
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
while (sum >= target) {
subLen = j - i + 1;
result = subLen < result ? subLen : result;
sum -= nums[i++];
}
}
return result == INT_MAX ? 0 : result;
}
};
leetcode 904 水果成篮
暴力搜索解法,O(n^2)的时间复杂度
class Solution {
public:
int totalFruit(vector<int>& fruits) {
set<int> st(fruits.begin(), fruits.end());
if(st.size() <= 2) {
return fruits.size();
}
int fru1 = 0;
int fru2 = 0;
int result = INT_MIN;
int subLen = 0;
for (int i = 0; i < fruits.size(); i++) {
fru1 = fruits[i];
for (int k = i; k < fruits.size(); k++) {
if (fruits[k] != fru1) {
fru2 = fruits[k];
break;
}
}
for (int j = i; j < fruits.size(); j++) {
if (fruits[j] == fru1 || fruits[j] == fru2) {
subLen = j - i + 1;
result = subLen > result ? subLen : result;
}
else {
break;
}
}
}
return result;
}
};
这道题目是典型的滑动窗口解法
窗口扩展时寻找可行解,窗口收缩时优化可行解。
右移right指针,然后发现不满足情况开始收缩左指针
左指针收缩什么情况下停止是本题的关键。根据用例第三个实例。可以看出来,需要将数字出现的次数记录下来,然后不断减少。
直到有数字出现次数被减为0 ,此时就停止收缩了。
本算法思想概述如下:
1,使用map保存窗口中数字出现的次数,因为数字不是很大。可以使用数组模拟hash。
2,右指针移动时,如果发现是一个没有出现过的数字,就把cnt++
3,如果cnt个数超过了2. 则开始收缩窗口
4,直到窗口中数字被减少到0. 此时将cnt–,退出收缩流程
// 模板
for () {
// 将新进来的右边的数据,计算进来
// 更新数据
// 判断窗口数据是否不满足要求了
while (窗口数据不满要求 && left < arrSize) {
// 移除left数据,更新窗口数据
left++;
}
right++;
}
作者:navy-5
链接:https://leetcode-cn.com/problems/fruit-into-baskets/solution/pang-hu-xue-suan-fa-hua-dong-chuang-kou-1zypm/
class Solution {
public:
int totalFruit(vector<int>& fruits) {
int result = INT_MIN;
int subLen = 0;
int vecHash[100001] = {0};
int i = 0;
int cnt = 0;
for (int j = 0; j < fruits.size(); j++) {
if (vecHash[fruits[j]] == 0) {
cnt++;
}
vecHash[fruits[j]]++;
if (cnt <= 2) {
subLen = j - i + 1;
result = subLen > result ? subLen : result;
}
while (cnt > 2 && i < fruits.size()) {
vecHash[fruits[i]]--;
if (vecHash[fruits[i]] == 0) {
i++;
cnt--;
break;
}
else {
i++;
}
}
}
return result;
}
};
leetcode 76 最小覆盖子串
暴力搜索解法,超时
class Solution {
public:
string minWindow(string s, string t) {
vector<int> hashMap(128, 0);
int result = INT_MAX;
int left = 0;
int right = 0;
for (int i = 0; i < t.size(); i++) {
hashMap[t[i]]++;
}
for (int i = 0; i < s.size(); i++) {
int subLen = 0;
vector<int> tmpHashMap(hashMap.begin(), hashMap.end());
for(int j = i; j < s.size(); j++) {
tmpHashMap[s[j]]--;
bool flag = true;
for (int k = 0; k < 128; k++) {
if (tmpHashMap[k] > 0) {
flag = false;
}
}
if (flag) {
subLen = j - i + 1;
if (subLen < result) {
result = subLen;
left = i;
right = j;
}
break;
}
}
}
if(result == INT_MAX) return string();
string str = s.substr(left, right-left+1);
return str;
}
};
滑动窗口思想:
用i,j表示滑动窗口的左边界和右边界,通过改变i,j来扩展和收缩滑动窗口,可以想象成一个窗口在字符串上游走,当这个窗口包含的元素满足条件,即包含字符串T的所有元素,记录下这个滑动窗口的长度j-i+1,这些长度中的最小值就是要求的结果。
步骤一
不断增加j使滑动窗口增大,直到窗口包含了T的所有元素
步骤二
不断增加i使滑动窗口缩小,因为是要求最小字串,所以将不必要的元素排除在外,使长度减小,直到碰到一个必须包含的元素,这个时候不能再扔了,再扔就不满足条件了,记录此时滑动窗口的长度,并保存最小值
步骤三
让i再增加一个位置,这个时候滑动窗口肯定不满足条件了,那么继续从步骤一开始执行,寻找新的满足条件的滑动窗口,如此反复,直到j超出了字符串S范围。
面临的问题:
如何判断滑动窗口包含了T的所有元素?
我们用一个哈希表hashMap来表示当前滑动窗口中需要的各元素的数量,一开始滑动窗口为空,用T中各元素来初始化这个hashMap,当滑动窗口扩展或者收缩的时候,去维护这个hashMap,例如当滑动窗口包含某个元素,我们就让hashMap中这个元素的数量减1,代表所需元素减少了1个;当滑动窗口移除某个元素,就让hashMap中这个元素的数量加1。
记住一点:hashMap始终记录着当前滑动窗口下,我们还需要的元素数量,我们在改变i,j时,需同步维护hashMap。
值得注意的是,只要某个元素包含在滑动窗口中,我们就会在hashMap中存储这个元素的数量,如果某个元素存储的是负数代表这个元素是多余的。比如当hashMap等于{‘A’:-2,‘C’:1}时,表示当前滑动窗口中,我们有2个A是多余的,同时还需要1个C。这么做的目的就是为了步骤二中,排除不必要的元素,数量为负的就是不必要的元素,而数量为0表示刚刚好。
回到问题中来,那么如何判断滑动窗口包含了T的所有元素?结论就是当hashMap中所有元素的数量都小于等于0时,表示当前滑动窗口不再需要任何元素。
作者:Mcdull0921
链接:https://leetcode-cn.com/problems/minimum-window-substring/solution/tong-su-qie-xiang-xi-de-miao-shu-hua-dong-chuang-k/
class Solution {
public:
string minWindow(string s, string t) {
vector<int> hashMap(128, 0);
int result = INT_MAX;
int left = 0;
int right = 0;
int subLen = 0;
for (int i = 0; i < t.size(); i++) { //初始化在滑动窗口范围内可行解中我们需要的元素
hashMap[t[i]]++;
}
int i = 0;
for (int j = 0; j < s.size(); j++) { // j为滑动窗口右边界,i为左边界
hashMap[s[j]]--; //将右边界的值加入进来,更新数据
while (i < j && hashMap[s[i]] < 0) {
hashMap[s[i]]++; //尝试在满足要求的条件下收缩左窗口,寻找最优解
i++;
}
int flag = true; //当最左侧元素hashMap值为0时,说明不能再收缩左侧窗口了
for (int k = 0; k < 128; k++) { //判断现在窗口中的元素是否满足要求
if (hashMap[k] > 0) {
flag = false;
break;
}
}
if (flag) { //满足要求的情况下,记录我们需要的最小子串
subLen = j - i + 1;
if (subLen < result) {
result = subLen;
left = i;
right = j;
}
hashMap[s[i]]++; //左边界收缩一格,滑动窗口不满足条件了
i++; //寻找新的满足条件的滑动窗口
}
}
if(result == INT_MAX) return string();
string str = s.substr(left, right-left+1);
return str;
}
};
leetcode 59 螺旋矩阵 II
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> result(n, vector<int>(n, 0));
int loop = n / 2; //需要画的圈的循环次数
int mid = n / 2; //当n为奇数时,矩阵中间的位置
int startx = 0, starty = 0; //定义每循环一个圈的起始位置
int offset = 1; // 每一圈循环,需要控制每一条边遍历的长度
int count = 1; // 用来给矩阵中每一个空格赋值
int i, j;
while (loop--) {
int i = startx;
int j = starty;
for (j = starty; j < starty + n - offset; j++) { //四个for就是模拟转一圈
result[startx][j] = count++;
}
for (i = startx; i < startx + n - offset; i++) {
result[i][j] = count++;
}
for (; j > starty; j--) {
result[i][j] = count++;
}
for (; i > startx; i--) {
result[i][j] = count++;
}
startx++; //第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
starty++;
offset = offset + 2; //offset 控制每一圈里每一条边遍历的长度
}
if (n % 2) {
result[mid][mid] = count;
}
return result;
}
};
leetcode 54 螺旋矩阵
解法一:
这里的方法不需要记录已经走过的路径,所以执行用时和内存消耗都相对较小
首先设定上下左右边界
其次向右移动到最右,此时第一行因为已经使用过了,可以将其从图中删去,体现在代码中就是重新定义上边界
判断若重新定义后,上下边界交错,表明螺旋矩阵遍历结束,跳出循环,返回答案
若上下边界不交错,则遍历还未结束,接着向右向左向上移动,操作过程与第一,二步同理
不断循环以上步骤,直到某两条边界交错,跳出循环,返回答案
作者:youlookdeliciousc
链接:https://leetcode-cn.com/problems/spiral-matrix/solution/cxiang-xi-ti-jie-by-youlookdeliciousc-3/
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int up = 0;
int down = matrix.size() - 1;
int left = 0;
int right = matrix[0].size() - 1;
vector<int> result;
while (true) {
for (int i = left; i <= right; i++) {
result.push_back(matrix[up][i]);
}
if (++up > down) break; //重新设定上边界,若上边界大于下边界,则遍历遍历完成,下同
for (int i = up; i <= down; i++) {
result.push_back(matrix[i][right]);
}
if (--right < left) break;
for (int i = right; i >= left; i--) {
result.push_back(matrix[down][i]);
}
if (--down < up) break;
for (int i = down; i >= up; i--) {
result.push_back(matrix[i][left]);
}
if (++left > right) break;
}
return result;
}
};
解法二:
while循环只遍历环,不成环就不遍历了
while loop 一loop一层
如果一条边从头遍历到底,则下一条边遍历的起点随之变化
选择不遍历到底,可以减小横向、竖向遍历之间的影响
一轮迭代结束时,4条边的两端同时收窄 1
一轮迭代所做的事情变得很清晰:遍历一个“圈”,遍历的范围收缩为内圈
一层层向里处理,按顺时针依次遍历:上、右、下、左。
不再形成“环”了,就会剩下:一行或一列,然后单独判断
四个边界
上边界 top : 0
下边界 bottom : matrix.length - 1
左边界 left : 0
右边界 right : matrix[0].length - 1
矩阵不一定是方阵
top < bottom && left < right 是循环的条件
无法构成“环”了,就退出循环,退出时可能是这 3 种情况之一:
top == bottom && left < right —— 剩一行
top < bottom && left == right —— 剩一列
top == bottom && left == right —— 剩一项(也算 一行/列)
处理剩下的单行或单列
因为是按顺时针推入结果数组的,所以
剩下的一行,从左至右 依次推入结果数组
剩下的一列,从上至下 依次推入结果数组
代码
在遍历过程中坚持左闭右开的原则
每个元素访问一次,时间复杂度 O(mn),m、n 分别是矩阵的行数和列数
空间复杂度 O(mn)
作者:xiao_ben_zhu
链接:https://leetcode-cn.com/problems/spiral-matrix/solution/shou-hui-tu-jie-liang-chong-bian-li-de-ce-lue-kan-/
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
int up = 0;
int down = matrix.size() - 1;
int left = 0;
int right = matrix[0].size() - 1;
vector<int> result;
while (up < down && left < right) {
for (int i = left; i < right; i++) {
result.push_back(matrix[up][i]);
}
for (int i = up; i < down; i++) {
result.push_back(matrix[i][right]);
}
for (int i = right; i > left; i--) {
result.push_back(matrix[down][i]);
}
for (int i = down; i > up; i--) {
result.push_back(matrix[i][left]);
}
up++;
down--;
left++;
right--;
}
if (up == down) {
for (int i = left; i <= right; i++) {
result.push_back(matrix[up][i]);
}
}
else if (left == right) {
for (int i = up; i <= down; i++) {
result.push_back(matrix[i][left]);
}
}
return result;
}
};
前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。
leetcode 303 区域和检索 - 数组不可变(前缀和技巧)
class NumArray {
public:
NumArray(vector<int>& nums) {
this->data = vector<int>(nums.size()+1, 0); // data[i]记录nums[0-i-1]的记录之和,data[0] == 0
for (int i = 1; i < data.size(); i++) { // 计算data[1]直到data[n]的值,代表nums[0-n-1]的前缀和
data[i] = data[i-1] + nums[i-1];
}
}
int sumRange(int left, int right) {
return data[right+1] - data[left];
}
public:
vector<int> data;
};
leetcode 304 二维区域和检索 - 矩阵不可变(前缀和技巧)
class NumMatrix {
public:
// 定义:preSum[i][j] 记录 matrix 中子矩阵 [0, 0, i-1, j-1] 的元素和
NumMatrix(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
// 构造前缀和矩阵
preSum = vector<vector<int>>(m+1, vector<int>(n+1, 0));
for(int i = 1; i <= m; i++) { // 计算每个子矩阵 [0, 0, i-1, j-1] 的元素和
for (int j = 1; j <= n; j++) {
preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] - preSum[i-1][j-1] + matrix[i-1][j-1];
}
}
}
// 目标矩阵之和由四个相邻矩阵运算获得
int sumRegion(int row1, int col1, int row2, int col2) {
return preSum[row2+1][col2+1] - preSum[row2+1][col1] - preSum[row1][col2+1] + preSum[row1][col1];
}
public:
vector<vector<int>> preSum;
};
leetcode 560 和为 K 的子数组(前缀和技巧)
暴力解法,超时
class Solution {
public:
//前缀和数组preSum, preSum[i]表示nums[0-i-1]的和
int subarraySum(vector<int>& nums, int k) {
vector<int> preSum(nums.size()+1, 0);
for (int i = 1; i < preSum.size(); i++) {
preSum[i] = preSum[i-1] + nums[i-1];
}
int cnt = 0;
for (int i = 0; i < preSum.size(); i++) { //i从0开始,因为preSum[0]表示目前没有累加任何元素,前缀和为0
for (int j = i+1; j < preSum.size(); j++) {
if (preSum[j] - preSum[i] == k) {
cnt++;
}
}
}
return cnt;
}
};
算法优化
class Solution {
public:
//前缀和数组preSum, preSum[i]表示nums[0-i-1]的和
// int subarraySum(vector<int>& nums, int k) {
// vector<int> preSum(nums.size()+1, 0);
// for (int i = 1; i < preSum.size(); i++) {
// preSum[i] = preSum[i-1] + nums[i-1];
// }
// int cnt = 0;
// for (int i = 0; i < preSum.size(); i++) { //i从0开始,因为preSum[0]表示目前没有累加任何元素,前缀和为0
// for (int j = i+1; j < preSum.size(); j++) {
// if (preSum[j] - k == preSum[i]) {
// cnt++;
// }
// }
// }
// return cnt;
// }
int subarraySum(vector<int>& nums, int k) {
unordered_map<int, int> preSum; // 存储前缀和,和该前缀和出现的次数
preSum.insert(pair<int,int>(0,1));
int cnt = 0;
int sumj = 0;
for (int i = 0; i < nums.size(); i++) {
sumj += nums[i];
int sumi = sumj - k; // 这是我们想找的前缀和 nums[0..i] preSum[j] - k == preSum[i]
if (preSum.find(sumi) != preSum.end()) {
cnt += preSum[sumi];
}
preSum[sumj]++;// 把前缀和 nums[0..j] 加入并记录出现次数
}
return cnt++;
}
};