算法题-数组

1.数组理论基础

数组是存放在连续内存空间上的相同类型数据的集合。

  • 数组下标都是从0开始的
  • 数组内存空间的地址是连续的
  • 数组元素是不能删的,只能覆盖

2.二分查找

2.1二分查找

题目

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;
    }

};

2.2 搜索插入位置

题目

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) 
    {
        int left = 0;
        int right = nums.size() - 1;
        while (left <= right)
        {
            int middle = (right + left) / 2;
            if (nums[middle] < target)
            {
                left = middle + 1;
            }
            else if (nums[middle] > target)
            {
                right = middle -1;
            }
            else
            {
                return middle;
            }
        }
        return right + 1;

    }
};

2.3 在排序数组中查找元素的第一个和最后一个位置(*)

题目

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) 
    {
        int leftBoder = getleftBoder(nums, target);
        int rightBoder = getrightBoder(nums, target);

        return {leftBoder, rightBoder};
    }
private:
    int getrightBoder(vector<int>& nums, int target)
    {
        int rightBoder = -1;
        int left = 0;
        int right = nums.size() - 1;
        while(left <= right)
        {
            int mid = (right + left) / 2;
            if (nums[mid] < target)
            {
                left = mid + 1;
            }
            else if (nums[mid] > target)
            {
                right = mid - 1;
            }
            else if (nums[mid] == target)
            {
                rightBoder = mid;
                left = mid + 1;
            }
        }
        return rightBoder;
    }
    int getleftBoder(vector<int>& nums, int target)
    {
        int leftBoder = -1;
        int left = 0;
        int right = nums.size() - 1;
        while(left <= right)
        {
            int mid = (right + left) / 2;
            if (nums[mid] < target)
            {
                left = mid + 1;
            }
            else if (nums[mid] > target)
            {
                right = mid - 1;
            }
            else if (nums[mid] == target)
            {
                leftBoder = mid;
                right = mid - 1;  
            }
        }
        return leftBoder;
    }
};

这一题需要注意的是找左边界的时候,即使遇到nums[mid] == target也需要进行向左找,right = mid - 1;
找右边界的时候,同样地,继续向右找,left = mid + 1;

2.4 x的平方根

题目

class Solution {
public:
    int mySqrt(int x)
    {
        int rightBoder = 0;
        if (x == 0 || x == 1)
        {
            return x;
        }
        int left = 0; int right = x/2;
        while (left <= right)
        {
            uint64_t mid = (left + right) / 2;
            if (mid *mid <= x)
            {
                rightBoder = mid;
                left = mid + 1;
            }
            else
            {
                right = mid - 1;
            }
        }
        return rightBoder;
    }
};

2.5 有效的完全平方数

题目

class Solution {
public:
    bool isPerfectSquare(int num)
    {
        if (num == 0 || num == 1)
        {
            return true;
        }
        int left = 0; int right = num/2;
        while (left <= right)
        {
            uint64_t mid = (left + right)/2;
            if (mid * mid == num)
            {
                return true;
            }
            else if (mid * mid < num)
            {
                left = mid + 1;
            }
            else if (mid * mid > num)
            {
                right = mid - 1;
            }
        }
        return false;
    }
};

3 移除元素(双指针)

双指针法
题目

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

    }
};

3.1删除有序数组中的重复项

题目

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

3.2 移动零

题目

class Solution {
public:
    void moveZeroes(vector<int>& nums) 
    {
        int slow = 0, fast = 0;
        for (; fast < nums.size(); fast++)
        {
            if(nums[fast] != 0)
            {
                nums[slow++] = nums[fast];
            }
        }
        for (int i = slow; i < nums.size(); i++)
        {
            nums[i] = 0;
        }
    }
};

3.3 比较含退格的字符串(*)

题目

class Solution {
public:
    string dealBackspace(string s)
    {
        int slow = 0, fast = 0;
        for (;fast < s.size(); fast++)
        {
            if (s[fast] != '#')
            s[slow++] = s[fast];
            else if (slow > 0)
            {
                slow--;
            }
        }
        return s.substr(0, slow);
    }
    bool backspaceCompare(string s, string t) 
    {
        return dealBackspace(s) == dealBackspace(t);

    }
};
class Solution {
public:
    string  traversal(string s)
    {
        string res;
        int num = 0;
        for (int i = s.size() - 1; i >= 0; --i)
        {
            if (s[i] == '#')
            {
                num++;
            }
            else if (num > 0)
            {
                num--;
            }
            else
            {
                res += s[i];
            }
        }
        return res;
    }
    bool backspaceCompare(string s, string t) 
    {
        return traversal(s) == traversal(t);
    }
};

这题一开始在traversal函数中没有返回新的string,因为这个函数无法修改原始的string,因此要新定义string进行返回
第二点是traversal中使用for循环比while循环好

3.4 有序数组的平方

题目

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums)
    {
        vector<int> res(nums.size(), 0);//初始化res的大小
       int left = 0;
       int right = nums.size() - 1;
       int top = nums.size() - 1;
       while (left <= right)
       {
        if (nums[left]*nums[left] >= nums[right]*nums[right])
        { 
            res[top--] = nums[left]*nums[left];
            left++;
        }
        else if (nums[left]*nums[left] < nums[right]*nums[right])
        {
            res[top--] = nums[right]*nums[right];
            right--;
        }
       } 
    return res;

    }
};

4 长度最小的子数组(滑动窗口)(*)

题目

class Solution 
{
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
        int length = INT_MAX;
        int left = 0;
        int sum = 0;
        for (int right = 0; right < nums.size(); right++)
        {
            sum += nums[right];

            while (sum >= target)
            {
                length = min(length, right - left + 1);
                sum -= nums[left++];
            }
        }

        return length == INT_MAX ? 0 : length;
    }

};

class Solution 
{
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
        int min_len = INT_MAX;
        int left = 0, right = 0;
        int sum = 0;
        
        while (right < nums.size())
        {
            sum += nums[right];
            
            while (sum >= target)
            {
                min_len = min(min_len, right - left + 1);
                sum -= nums[left];
                left++;
            }
            
            right++;
        }
        
        return min_len == INT_MAX ? 0 : min_len;
    }
};

4.1 水果成篮(**)

题目

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int i = 0, j = 0; // i为窗口起始位置,j为窗口结束位置
        unordered_map<int, int> a; // 使用unordered_map代替数组作为哈希表
        int max_fruits = 0;
        int sum = 0; // 记录种类

        for (j = 0; j < fruits.size(); ++j) {
            if (a[fruits[j]] == 0) { // 当前种类的果树数量为零
                sum++; // 果树种类+1
            }
            a[fruits[j]]++; // 相同类型树数目+1

            while (sum > 2) { // 当果树种类大于2,就要对窗口进行收缩
                a[fruits[i]]--; // 从窗口起始位置遍历,将当前果树的数量减1
                if (a[fruits[i]] == 0) { // 如果当前果树的数量为零
                    sum--; // 果树种类-1
                }
                ++i; // 继续遍历,直到果树种类只有两种
            }
            max_fruits = std::max(max_fruits, j - i + 1); // 更新最大值
        }
        return max_fruits;
    }
};

示例用unordered_map实现
我自己做的时候用的unordered_set,效率较低

更简单的版本

class Solution {
public:
    int totalFruit(vector<int>& fruits) 
    {
        unordered_map<int, int> mp;
        int max_num = 0; // 采摘的最大水果树数量
        int left = 0; // 滑动窗口的左边界

        for (int right = 0; right < fruits.size(); right++)
        {
            mp[fruits[right]]++;
            
            // 当窗口内的水果种类超过2种时,调整左边界
            while (mp.size() > 2) 
            {
                mp[fruits[left]]--;
                if (mp[fruits[left]] == 0)
                {
                    mp.erase(fruits[left]);
                }
                left++;
            }
            
            // 更新最大数量
            max_num = max(max_num, right - left + 1);
        }
        return max_num;
    }
};
class Solution {
public:
    int totalFruit(vector<int>& fruits)
    {
        unordered_set<int> set;
        int left = 0, right = 0;
        int max_sum = 0;
        for(; right < fruits.size(); right++)
        {
            if(set.size() < 2)
            {
                set.insert(fruits[right]);
                max_sum = max(max_sum, right - left + 1);
                //cout << "第" << right << "次遍历" << "水果种类个数" << set.size() << endl;
            }
            else if (set.find(fruits[right]) != set.end())
            {
                max_sum = max(max_sum, right - left + 1);
                //cout << "最大水果个数:" << max_sum << endl;
            }
            else
            {
                set.clear();
                set.insert(fruits[right]);
                set.insert(fruits[right - 1]);
                //找窗口起始位置
                int start = right - 1;
                while(start > 0)
                {
                    if (fruits[start - 1] != fruits[start])
                    {
                        break;
                    }
                    start--;
                }
                left = start;
                //cout <<"窗口起始位置" << left << endl;
                
            }
        }
        return max_sum;
 
    }
};

4.2 最小覆盖子串(**)

题目

class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char, int> hs, ht;
        for (auto c: t) ht[c] ++ ;
        string res;
        int cnt = 0;
        for (int i = 0, j = 0; i < s.size(); i ++ ) {
            hs[s[i]] ++ ;
            if (hs[s[i]] <= ht[s[i]]) cnt ++ ;

            while (hs[s[j]] > ht[s[j]]) hs[s[j ++ ]] -- ;
            if (cnt == t.size()) {
                if (res.empty() || i - j + 1 < res.size())
                    res = s.substr(j, i - j + 1);
            }
        }
        return res;
    }
};

超时的解法

class Solution 
{
private:
    bool traversal(const string& s, unordered_map<char,int>& t_map, int left, int right)
    {
        //需要满足t中重复字符的数量
        unordered_map<char, int> s_map;
        for (int i = left; i <= right; i++)
        {
            if (t_map.find(s[i]) != t_map.end())
            {
                s_map[s[i]]++;
            }
        }
        for (auto &pair : t_map)
        {
            if (s_map[pair.first] < pair.second)
            {
                return false;
            }
        }
        return true;

    }
public:
    string minWindow(string s, string t) 
    {
        unordered_map<char, int> t_map;
        for(int i = 0; i < t.size(); i++)
        {
            t_map[t[i]]++;
        }
        int left = 0, right = 0;
        int min_left = 0;
        int min_right = 0;
        int min_len = INT_MAX;
        string min_s = "";
        while (right < s.size())
        {
            //找到窗口起始位置(必须为t中的字符串)
            while (left < s.size() && t.find(s[left]) == string::npos)
            {
                left++;
            }
            //窗口向右扩展,直到窗口中出现t的所有字符
            right = max(right, left);//???
            while(right < s.size() && !traversal(s, t_map, left, right))
            {
                right++;
            }
            if (right >= s.size()) break;
            //比较最小窗口长度和当前窗口长度
            //若当前窗口长度小于最小窗口长度,则截取子字符串
            if (min_len > right - left + 1)
            {
                min_len = right - left + 1;
                min_s = s.substr(left, right - left + 1);

            }
            //窗口初始位置后移
            left++;

        }
        return min_s;
    }
};

优化
捉住滑动窗口本质,窗口什么时候扩展,什么时候收缩

class Solution
{
public:
    string minWindow(string s, string t) 
    {
        if (s.size() == 0 || t.size() == 0)
        return "";

        unordered_map<char, int> t_map;
        for (char c : t)
        {
            t_map[c]++;
        }
        unordered_map<char, int> win_map;
        int min_len = INT_MAX;
        int min_left = 0;
        int left = 0, right = 0;
        int matched = 0;

        //窗口向右扩展
        while (right < s.size())
        {
            char rightc = s[right];    
            if (t_map.find(rightc) != t_map.end())
            {
                win_map[rightc]++;
                if (win_map[rightc] == t_map[rightc])
                {
                    matched++;
                }        
            }
            //如果窗口满足条件,窗口左边界需要收缩
            while (matched == t_map.size())
            {
                if (min_len > right - left + 1)
                {
                    min_len = right - left + 1;
                    min_left = left;
                }
                char leftc = s[left];
                if (t_map.find(leftc) != t_map.end())
                {
                    win_map[leftc]--;
                    if (win_map[leftc] < t_map[leftc])
                    {
                        matched--;
                    }
                }
                left++;
            }
            right++;          
        }
        return min_len == INT_MAX ? "" : s.substr(min_left, min_len);
    }
};

5 螺旋矩阵II(*)

题目

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) 
    {
        vector<vector<int> > matrix(n, vector<int>(n));
        int num = 1;
        int top = 0;int bottom = n - 1;
        int left = 0; int right = n - 1;
        
        while (num <= n * n)
        {
            //从左到右填充上边界
            for (int i = left; i <= right; i++)
            {
                matrix[top][i] = num++;
            }
            top++;
            //从上到下填充右边界
            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;      
                                                                                                                                                                                                                                                                                                                        
    }
};

以矩阵的层为单位进行填充,由外层向内层填充

class Solution {
private:
    int dir[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右、下、左、上四个方向

    int select_next_dir(int curx, int cury, int layer, int n) {
        if (curx == layer && cury < n - 1 - layer) {
            return 0; // 右
        }
        else if (curx < n - 1 - layer && cury == n - 1 - layer) {
            return 1; // 下
        }
        else if (curx == n - 1 - layer && cury > layer) {
            return 2; // 左
        }
        else {
            return 3; // 上
        }
    }

public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0));
        int num = 1;
        int curx = 0, cury = 0;
        int layer = 0; // 当前层数

        while (num <= n * n) 
        {
            res[curx][cury] = num++;
            int nextdir = select_next_dir(curx, cury, layer, n);
            int nextx = curx + dir[nextdir][0];
            int nexty = cury + dir[nextdir][1];

            // 如果下一个位置越界或已填充,说明需要进入下一层
            if (nextx < layer || nextx >= n - layer || nexty < layer || nexty >= n - layer || res[nextx][nexty] != 0) 
            {
                layer++;
                curx = layer;
                cury = layer;
            } else 
            {
                curx = nextx;
                cury = nexty;
            }
        }

        return res;
    }
};

6.总结

6.1二分法

6.2双指针法

6.3滑动窗口

6.4模拟行为

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值