【笔记】数组

本文是《代码随想录》和《labuladong的算法笔记》实践系列的第一篇,重点讲解数组相关题目,涵盖二分查找、双指针等多种算法技巧。内容包括有序数组的二分查找、原地修改数组、从后到前遍历、滑动窗口以及接雨水问题等经典数组操作,并结合LeetCode题目进行实例解析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本系列总计六篇文章,是 基于STL实现的笔试题常考七大基本数据结构 该文章在《代码随想录》和《labuladong的算法笔记》题目中的具体实践,每篇的布局是这样的:开头是该数据结构的总结,然后是在不同场景的应用,以及不同的算法技巧。本文是系列第一篇,介绍了数组的相关题目,重点是要掌握双指针算法、二分查找算法。

下面文章是在《代码随想录》和《labuladong的算法笔记》题目中的具体实践:
【笔记】数组
【笔记】链表
【笔记】哈希表
【笔记】字符串
【笔记】栈与队列
【笔记】二叉树

0、总结

  • 双指针,快慢双指针(用于数组覆盖),左右双指针(二分查找),滑动窗口(长度最小子数组)
  • 有序数组+无重复,必然想到 二分查找 及其变体,找target,找左边界,找右边界,需要坚持闭区间
  • 用循环不变量的模拟(螺旋数组)

1、二分查找-左右双指针

算法框架

注意,…号出现的四处位置,根据是否闭区间/左闭右开,是否返回一个索引/索引边界,而不同,下面我都坚持闭区间的写法

int binarySearch(int[] nums, int target) {
    int left = 0, right = 1...;

    while(2...) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            3...
        } else if (nums[mid] < target) {
            left = 3...
        } else if (nums[mid] > target) {
            right = 3...
        }
    }
    return 4...;
}

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

思路:二分查找最基础版本,采用左闭右闭区间,因此注意这四处

1、high初始化指向最末尾元素

2、while中的判断是 <= ,因为当 left == right 时区间依然有意义

3、mid每次必须加减1,相等即返回

4、未找到返回-1

另外,写成 if - else if 形式,将逻辑分支判断展开,更有助于理解

【不足】若target有多个值,该解法只能返回其中一个值

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int low = 0;
        int high = nums.size() - 1; // 1
        // 当left == right,区间[left, right]依然有效,所以用 <=
        while (low <= high) { // 2
            int mid = low + (high - low) / 2;
            if (nums[mid] > target) high = mid - 1; // 3
            if (nums[mid] < target) low = mid + 1; // 3
            if (nums[mid] == target) return mid; // 3
        }
        return -1; // 4
    }
};

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

思路:二分法的变体,能处理如下四种情况:

  • 目标值在数组所有元素之前
  • 目标值等于数组中某一个元素
  • 目标值插入数组中的位置
  • 目标值在数组所有元素之后
class Solution {
public:
    int searchInsert(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) high = mid - 1;
            if (nums[mid] < target) low = mid + 1;
            if (nums[mid] == target) return mid;
        }
        return high + 1;
    }
};

!!!以上两种情况,只适合无重复元素+数组升序的版本

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

思路:二分查找变体,能找到target的左右边界

寻找target在数组里的左右边界,有如下三种情况:

  • 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
  • 情况二:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
  • 情况三:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}

注意:寻找右边界时,nums[mid] > target时,直接减小right = mid - 1,**当 nums[mid] == target 时,不要立即返回,而是增大「搜索区间」的左边界 left,通过left = mid + 1,rightBorder = left,使得区间不断向右靠拢,达到锁定右侧边界的目的,nums[mid] < target时,left = mid + 1,因此小于和等于target的情况合并为一种。**左边界同理。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        cout << leftBorder << endl;
        cout << rightBorder << endl;
        // 情况一,边界值从未被更新,说明 target 在数组范围的右边或者左边
        if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
        // 情况二,边界值相差大于1,说明中间至少夹着一个target
        if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
        // 情况三,边界值相邻,target 在数组范围中,但是数组中不存在target
        return {-1, -1};
    }
    // 寻找右边界,不包括target
    int getRightBorder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        // 记录初始值
        int rightBorder = -2;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > target) {
                right = mid - 1;
            } else if (nums[mid] <= target) { // 寻找右边界,找到target时先别返回,继续更新left
                left = mid + 1;
                rightBorder = left;
            }
        }
        return rightBorder;
    }
    int getLeftBorder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        // 记录初始值
        int leftBorder = -2;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
                left = mid + 1;
            } else if (nums[mid] >= target) { // 寻找左边界,用 right 来逼近
                right = mid - 1;
                leftBorder = right;
            }
        }
        return leftBorder;
    }
};

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

思路:二分查找变体,在1到x中,用二分查找,寻找x的平方根。避免平方运算溢出,应用除法代替。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

class Solution {
public:
    int mySqrt(int x) {
        if (x == 0 || x == 1) return x;
        int left = 1;
        int right = x;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            // 用除法判断,避免溢出
            int temp = x / mid;
            if (temp > mid) { // mid平方小于x,增大左边界
                left = mid + 1;
            } else if (temp < mid) { // mid平方大于x,减小右边界
                right = mid - 1;
            } else if (temp == mid) { // 找到x = mid平方就返回
                return mid;
            }
        }
        return right;    
    }
};

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

思路:二分搜索!注意在temp = mid时要做判断,如果num可以整除mid则返回true,否则说明出现了 mid * mid < num的情况,需要增大左边界,然后继续循环判断

class Solution {
public:
    bool isPerfectSquare(int num) {
        if (num == 1) return true;
        int left = 0;
        int right = num;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            // 用除法判断,避免溢出
            int temp = num / mid;
            if (temp > mid) { // mid平方小于x,增大左边界
                left = mid + 1;
            } else if (temp < mid) { // mid平方大于x,减小右边界
                right = mid - 1;
            } else { 
                if (num % mid == 0) return true; // num是完全平方数就返回true
                else left = mid + 1; // 说明存在余数,即mid^2 < num的情况
            }
        }
        return false;
    }
};

2、从前到后原地修改数组-快慢双指针

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

思路:双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。 要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。

因此定义快慢指针,当快指针遇见不需要删除的元素时,才从慢指针开始重新覆盖数组

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

注意,本题不考虑resize的问题

class Solution {
public:
    // 返回新长度,同时修改数组
    int removeElement(vector<int>& nums, int val) {
        int j = 0;
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] != val) {
                nums[j++] = nums[i];
            }
        }
        // 本题不需要考虑数组中超出新长度后面的元素
        // nums.resize(j);
        return j;
    }
};

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

思路:定义快慢指针,分别指向第一个和第二个元素,当快、慢指针所指向元素不相等时,才从慢指针指向的下一个元素开始覆盖数组

注意:先slow++再修改,最后返回slow + 1

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        if (nums.size() == 1) return 1;
        int slow = 0;
        for (int fast = 1; fast < nums.size(); ++fast) {
            if (nums[slow] != nums[fast]) nums[++slow] = nums[fast];
        }
        return slow + 1;
    }
};

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

思路:双指针,使得出现次数超过两次的元素只出现两次fast指向的元素和slow - 2不相等时,才从slow开始覆盖

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        // 只含1或2个元素,直接返回
        if (nums.size() <= 2) return nums.size();
        // slow从指向第三个元素开始
        int slow = 2;
        for (int fast = 2; fast < nums.size(); ++fast) {
            // 保证元素至多出现两次
            if (nums[fast] != nums[slow - 2]) {
                nums[slow++] = nums[fast];
            }
        }
        return slow;
    }
};

283. 移动零 - 力扣(LeetCode)

思路:双指针,每次fast遇到非0元素时,和slow交换,不会改变原来非0元素的相对位置

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        if (nums.size() == 1) return;
        int slow = 0;
        // 每次把fast的非零值和slow指向的0交换
        for (int fast = 0; fast < nums.size(); ++fast) {
            if (nums[fast] != 0) {
                swap(nums[slow], nums[fast]);
                ++slow;
            }
        }
    }
};

3、从后到前遍历-双指针、左右双指针

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

  • 思路一:双指针,一次操作两个字符串,从后向前逐位比较,先删除两个字符串目前遇到的#,直到指向非#字符或遍历完串。消除完#后,进行比较,若不相等直接返回false,否则进行下一轮比较
class Solution {
public:
    bool backspaceCompare(string s, string t) {
        int sSkipNum = 0;
        int tSkipNum = 0;
        int i = s.size() - 1;
        int j = t.size() - 1;
        // 从后向前,逐位消除,逐位比较
        while (1) {
            // 如果遇到#,进行消除,直到当前指向不为#
            while (i >= 0) {
                if (s[i] == '#') ++sSkipNum;
                else if (s[i] != '#') {
                    if (sSkipNum > 0) --sSkipNum;
                    else break;
                }
                --i;
            }
            while (j >= 0) {
                if (t[j] == '#') ++tSkipNum;
                else if (t[j] != '#') {
                    if (tSkipNum > 0) --tSkipNum;
                    else break;
                }
                --j;
            }
            // 消除完#后,进行比较,准备下一轮消除和比较
            if (i < 0 || j < 0) break; // s或t遍历到头了
            if (s[i] != t[j]) return false;
            --i;
            --j;
        }
        // 同时遍历完毕
        if (i == -1 && j == -1) return true;
        return false;
    }
};
  • 思路二:将string视为栈,利用栈实现匹配,将非#字符读入栈,若遇到#,弹出栈顶元素
class Solution {
public:
    bool backspaceCompare(string s, string t) {
        string ss;
        string tt;
        for (char c : s) {
            if (c != '#') ss.push_back(c);
            else if (!ss.empty()) ss.pop_back();
        }
        for (char c : t) {
            if (c != '#') tt.push_back(c);
            else if (!tt.empty()) tt.pop_back();
        }
        return ss == tt;
    }
};

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

思路:左右双指针一次遍历,时间复杂度O(N),但是本题无法原地修改,只能引入空间复杂度O(N)的新数组用来存放平方后的数组。从两边向中间遍历,每次把较大的数从后向前写入到新数组中。

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        vector<int> result(nums.size(), 0);
        int index = result.size() - 1;
        while (left <= right && index >= 0) {
            if (abs(nums[right]) >= abs(nums[left])) {
                result[index--] = pow(nums[right], 2);
                --right;
            } else {
                result[index--] = pow(nums[left], 2);
                ++left;
            }
        }
        return result;
    }
};

4、滑动窗口-左右双指针+哈希表

算法框架

其中两处 ... 表示的更新窗口数据的地方,到时候直接往里面填就行了,坚持左闭右开版本,减少边界处理时候不必要的麻烦

/* 滑动窗口算法框架 */
void slidingWindow(string s) {
    unordered_map<char, int> window;
    
    int left = 0, right = 0;
    while (right < s.size()) {
        // c 是将移入窗口的字符
        char c = s[right];
        // 增大窗口
        right++;
        // 进行窗口内数据的一系列更新
        1...

        /*** debug 输出的位置 ***/
        // 注意在最终的解法代码中不要 print
        // 因为 IO 操作很耗时,可能导致超时
        printf("window: [%d, %d)\n", left, right);
        /********************/
        
        // 判断左侧窗口是否要收缩
        while (window needs shrink) {
            // d 是将移出窗口的字符
            char d = s[left];
            // 缩小窗口
            left++;
            // 进行窗口内数据的一系列更新
            2...
        }
    }
    return 需要的值;
}

4.1 基于数组的滑动窗口

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

  • hash思路:扩大窗口,将数组元素入windows,计算sum,一旦sum >= target满足条件就要更新result,然后缩小窗口。循环该操作
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result = INT_MAX;
        int sum = 0;
        // 双指针最难版本,滑动窗口
        unordered_map<int, int> window;
        int left = 0;
        int right = 0;
        while (right < nums.size()) {
            int i = nums[right];
            right++;
            window[i]++;
            sum += i;
            while (sum >= target) {
                result = min(result, right - left);
                int j = nums[left];
                left++;
                window[j]--;
                sum -= j;
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};
  • 非hash思路:本题只涉及求和,不涉及key的value数目,故可不用哈希
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result = INT_MAX;
        int sum = 0;
        int left = 0;
        for (int right = 0; right < nums.size(); ++right) {
            // sum小于target时,不断扩大窗口
            sum += nums[right];
            while (sum >= target) {
                result = min(result, right - left + 1);
                // 满足条件,不断缩小窗口
                sum -= nums[left++];
            }
        }
        return result == INT32_MAX ? 0 : result;
    }
};

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

  • 思路,本题其实求 至多包含两种元素的最长连续子序列,与上题同理,但应注意,本题用size方法统计key的种类,通过window[j]–收缩窗口后,最后key对应的value为0时,但是key本身还存在,需要手动删除key,否则window.size()的结果不会改变
class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        unordered_map<int, int> window;
        int left = 0, right = 0;
        int result = 0;
        while (right < fruits.size()) {
            // 元素种类不超过2时扩大窗口
            int i = fruits[right++];
            window[i]++;
            // 元素种类大于2时缩小窗口
            while (window.size() > 2) {
                int j = fruits[left++];
                window[j]--;
                // 前面用size计算key的种类,因此key对应的value为0时,key本身还存在,需要手动删除key
                if (window[j] == 0) window.erase(j);
            }
            result = max(result, right - left);
        }
        return result;
    }
};

4.2 基于字符串的滑动窗口

3. 无重复字符的最长子串 - 力扣(LeetCode)

思路:

双指针 的最难版本,滑动窗口,辅助数据结构用unordered_map,map内始终维护着不重复的子串,未遇到重复元素,right++不断扩大窗口,若map中出现重复元素,left++减小窗口直到重复元素被删除

扩大窗口和缩小窗口的操作对称

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if (s.size() == 0) return 0;
        unordered_map<char, int> window;
        // 双指针
        int left = 0, right = 0;
        int result = 0;
        while (right < s.size()) {
            // 未遇到重复字符串就一直加,right右移
            char c = s[right];
            right++;    
            window[c]++;
            // 有重复left左移直到删除该重复的字符串
            while (window[c] > 1) {
                char d = s[left];
                left++;
                window[d]--;
            }
            // 收缩完后肯定剩下不重复的字串,记录长度
            result = max(result, right - left);
        }
        return result;
    }
};

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

思路:

首先,初始化 windowneed 两个哈希表,记录窗口中的字符和需要凑齐的字符

然后,使用 leftright 变量初始化窗口的两端,区间 [left, right) 是左闭右开,所以初始情况下窗口没有包含任何元素

其中 valid 变量表示窗口中满足 need 条件的字符个数,如果 validneed.size 的大小相同,则说明窗口已满足条件,已经完全覆盖了串 T

注意,本题需要返回 s 中满足覆盖 t 的最小子串,需要记录符合要求字串的起、止位置,便于返回,此外,只有length被更新时候,才能start = left,不因此能用min()

class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char, int> window, need;
        for (char ch : t)
            need[ch]++;
        // 左右指针
        int left = 0, right = 0;
        // 符合要求字串的起、止位置
        int start = 0, length = INT_MAX;
        // window中符合need的元素数量
        int valid = 0;
        while (right < s.size()) {
            // 扩大窗口,符合要求的元素才会被记录
            char c = s[right];
            right++;
            // 遇到t中需要的元素,将其放入窗口
            if (need.count(c)) {
                window[c]++;
                // 如果t中的该元素都找到(可能有多个),更新记录
                if (window[c] == need[c])
                    valid++;
            }
            // 当t可以被s的子串覆盖时,更新长度
            while (valid == need.size()) {
                // 只有长度被更新时,才能重新记录子串起始位置,因此不能用min
                if (right - left < length) {
                    start = left;
                    length = right - left;
                }
                // 收缩窗口
                char d = s[left];
                left++;
                // 当要弹出的元素是t中需要的元素时
                if (need.count(d)) {
                    // 只要少了元素就减少valid
                    if (window[d] == need[d])
                        valid--;
                    window[d]--;
                }
            }
        }
        return length == INT_MAX ? "" : s.substr(start, length);
    }
};

567. 字符串的排列 - 力扣(LeetCode)

思路:

题目实际的含义是,s2中是否存在一个子串,包含 s1 中所有字符,且不包含其他字符?就是不含冗余元素,s2中找到子串,恰好是s1串的排列,即窗口大小恰好等于s1.size()

对于这道题的解法代码,基本上和最小覆盖子串一模一样,只需要改变两个地方:

1、本题移动 left 缩小窗口的时机是窗口大小大于 t.size() 时,因为排列嘛,显然长度应该是一样的。

2、当发现 valid == need.size() 时,就说明窗口中就是一个合法的排列,所以立即返回 true

至于如何处理窗口的扩大和缩小,和最小覆盖子串完全相同。

PS:由于这道题中 [left, right) 其实维护的是一个定长的窗口,窗口大小为 t.size()。因为定长窗口每次向前滑动时只会移出一个字符,所以可以把内层的 while 改成 if,效果是一样的。

class Solution {
public:
    bool checkInclusion(string s1, string s2) {
        unordered_map<char, int> need, window;
        for (char c : s1) need[c]++;
        int left = 0, right = 0;
        int valid = 0;
        while (right < s2.size()) {
            char c = s2[right];
            right++;
            if (need.count(c)) {
                window[c]++;
                if (window[c] == need[c])
                    valid++;
            }
            // 注意收缩窗口的时机
            while (right - left >= s1.size()) {
                // 只要s1中元素已经全部被找到,立即返回
                if (valid == need.size())
                    return true;
                char d = s2[left];
                left++;
                if (need.count(d)) {
                    if (window[d] == need[d])
                        valid--;
                    window[d]--;
                }
            }
        }
        // 均未找到返回false
        return false;
    }
};

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)

思路:

实际上就是找排列,只不过本题需要返回所有有效的排列的起始下标,上题是找到即返回

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        // 新建数组用于收集结果
        vector<int> result;
        result.clear();
        unordered_map<char, int> need, window;
        for (char c : p) need[c]++;
        int left = 0, right = 0;
        int valid = 0;
        while (right < s.size()) {
            char c = s[right];
            right++;
            if (need.count(c)) {
                window[c]++;
                if (window[c] == need[c])
                    valid++;
            }
            while (right - left >= p.size()) {
                // 满足条件不着急返回,存入result
                if (valid == need.size())
                    result.push_back(left);
                char d = s[left];
                left++;
                if (need.count(d)) {
                    if (window[d] == need[d])
                        valid--;
                    window[d]--;    
                }
            }
        }
        return result;
    }
};

5、螺旋遍历数组

54. 螺旋矩阵 - 力扣(LeetCode)

剑指 Offer 29. 顺时针打印矩阵 - 力扣(LeetCode)

思路:按照右、下、左、上的顺序遍历数组,并使用四个变量圈定未遍历元素的边界

注意:如果考察逆时针,同理

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if (matrix.empty()) return {};
        vector<int> result;
        // 四个边界
        int left = 0;
        int right = matrix[0].size() - 1;
        int top = 0;
        int bottom = matrix.size() - 1;
        // res.size() == m * n 则遍历完整个数组
        while (result.size() < matrix.size() * matrix[0].size()) {
            if (top <= bottom) {
                for (int i = left; i <= right; i++)
                    result.push_back(matrix[top][i]);
                top++;
            }
            if (left <= right) {
                for (int i = top; i <= bottom; i++)
                    result.push_back(matrix[i][right]);
                right--;
            }
            if (top <= bottom) {
                for (int i = right; i >= left; i--)
                    result.push_back(matrix[bottom][i]);
                bottom--;
            }
            if (left <= right) {
                for (int i = bottom; i >= top; i--)
                    result.push_back(matrix[i][left]);
                left++;
            }
        }
        return result;
    }
};

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

思路:现在把矩阵换成方阵,然后由输出元素改为向矩阵中填值,稍微修改上题的代码即可

注意:如果考察逆时针,同理

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> matrix(n, vector<int>(n, 0));
        int num = 1;
        // 四个边界
        int left = 0;
        int right = n - 1;
        int top = 0;
        int bottom = n - 1;
        // 打印
        while (num <= n * n) {
            if (top <= bottom) {
                for (int i = left; i <= right; i++)
                    matrix[top][i] = num++;
                top++;
            }
            if (left <= right) {
                for (int i = top; i <= bottom; i++)
                    matrix[i][right] = num++;
                right--;
            }
            if (top <= bottom) {
                for (int i = right; i >= left; i--)
                    matrix[bottom][i] = num++;
                bottom--;
            }
            if (left <= right) {
                for (int i = bottom; i >= top; i--)
                    matrix[i][left] = num++;
                left++;
            }
        }
        return matrix;
    }
};

6、顺时针旋转二维数组90度

48. 旋转图像 - 力扣(LeetCode)

  • 思路一:先以主对角线为对称轴进行对称翻转,然后在按每行分别进行翻转,即可实现旋转90度

注意:

1、若问180度,270度(逆时针90度)可以重复上述操作多次

2、考察逆时针旋转90度的话,可以先以副对角线为对称轴进行对称翻转,然后在按每行分别进行翻转

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        // 先主对角线做对称
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                swap(matrix[i][j], matrix[j][i]);
            }
        }
        // 然后反转二维矩阵的每一行
        for (int i = 0; i < n; i++) 
            reverse(matrix[i]);
    }
    void reverse(vector<int>& row) {
        for (int i = 0, j = row.size() - 1; i < j; i++, j--)
            swap(row[i], row[j]);
    }
};
  • 思路二:先上下翻转,再对角翻转,swap两种用法,翻转一对单个int元素和翻转一对数组
class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        for (int i = 0; i < n / 2; i++) matrix[i].swap(matrix[n - 1 - i]);
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
                swap(matrix[i][j], matrix[j][i]);
            }
        }
    }
};

7、接雨水-左右双指针

42. 接雨水 - 力扣(LeetCode)

思路:某个位置能容纳的雨水量和左、右柱子高度较小者,以及该位置本身的高度有关。双指针记录位置,l_maxheight[0..left] 中最高柱子的高度,r_maxheight[right..end] 的最高柱子的高度,这两个值的较小者减去该位置的高度,即为该位置能容纳的雨水量。

难点:只知道 height[left]height[right] 的高度,能算出 leftright 之间能够盛下多少水吗?不能,因为不知道 leftright 之间每个柱子具体能盛多少水,得通过每个柱子的 l_maxr_max 来计算才行,即本题考虑柱子宽度

class Solution {
public:
    int trap(vector<int>& height) {
        int left = 0, right = height.size() - 1;
        int l_max = 0, r_max = 0;
        int water = 0;
        while (left < right) {
            // 记录高度
            l_max = max(l_max, height[left]);
            r_max = max(r_max, height[right]);
            // 较小者决定雨水量
            if (l_max < r_max) {
                water += l_max - height[left];
                left++;
            } else {
                water += r_max - height[right];
                right--;
            }
        }
        return water;
    }
};

11. 盛最多水的容器 - 力扣(LeetCode)

思路:本题不考虑柱子宽度,两柱子间盛水量只由较低的柱子高度和两柱子之间距离决定

注意:if 语句移动较低的一边原因,是由于盛水量只由较低的柱子高度决定,如果移动较低的那一边,那条边可能会变高,使得矩形的高度变大,进而就「有可能」使得矩形的面积变大;相反,如果移动较高的那一边,矩形的高度是无论如何都不会变大的,所以不可能使矩形的面积变得更大

class Solution {
public:
    int maxArea(vector<int>& height) {
        int left = 0, right = height.size() - 1;
        int area = 0;
        while (left < right) {
            int tempArea = (right - left) * min(height[left], height[right]);
            area = max(area, tempArea);
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return area;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值