LeetCode 【数据结构与算法专栏】【数组】

数组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(m
n)

作者: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++;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值