【Leetocde数组题目合集】

【Leetocde数组题目合集】

基本知识点:数组在内存上是连续的;数组内的元素无法删除,只能进行覆盖;

一、双指针求解数组

1. 知识点

  • 可以使用双指针求解数组的前提是该数组为有序数组

2. 模版总结

(1) 快慢指针

适用场景: 原地修改数组,如去重、删除元素
求解模板

slow = 0;
while (fast < nums.size()) {
	if (满足保留条件) {
		nums[slow] = nums[fast];
		slow++;
	}
	fast++;
}
return slow;

典型题目

  • leetcode27 删除元素

数组元素不能删除只能被覆盖,如果没有=val的元素,快慢指针始终指向同一位置,如果有则val位于快慢指针之间;本题的原理就是用非val元素依次覆盖val元素
保留条件:快指针指向元素不等于val
快指针始终指向未处理的元素,慢指针之前为已经处理好的数组,两个指针中间夹着的就是已经被处理过的数组(这个区间内的数字要不值为val,要不就是已经被拿来覆盖之前出现过的val的位置),最后返回慢指针即可

int removeElement(vector<int>& nums, int val) {
       int s = 0, f = 0;
       while (f < nums.size()) {
           if (nums[f] != val) {
               nums[s] = nums[f];
               s++;
           }
           f++;
       }
       return s;
   }
类似题目:
题目特点思路是否掌握
leetcode26 删除重复元素删除重复元素保留条件:快指针和慢指针前一位相比,不一样才保留已掌握
leetcode80 删除元素II相比20,这里是要删除多余的重复元素,使得每个重复元素只会出现2次如果没有重复2次以上的元素,快慢指针始终指向同一位置,快指针指向还未处理的元素;保留条件:快指针的值不等于慢指针前2位元素还未掌握
JZ21 调整数组的顺序使奇数位于偶数前涉及到数组内插入元素,插入前,需要所有元素后移挪出位置保留条件: 如果快指针所指的该元素为奇数,就需要插入到慢指针之前;快指针指向未处理元素,慢指针指向偶数区间第一位还未掌握
leetcode 75 颜色分类三指针,左右指针作为三种颜色的隔开边界;左右指针之间为1,左指针左边都为0,右指针右边都为2保留条件:如果当前元素为0,就和l交换位置;如果为2则和r交换位置并且i位置不变(这是为了防止换过来的还是2)未掌握,需要先判断是不是为2,且要用while一直判断,因为i只会在0-r之间移动,所以对于和r交换过来的元素还要再判断,直到不为2;然后还要再判断一下是不是为0
(2) 左右指针

适用场景:有序数组的搜索(如两数之和)、反转数组、回文判断
求解模板

int l = 0, r = nums.size();
while (l < r) {
	if (满足条件) {
		return 结果;
	} else if (nums[l]+nums[r] < target) {
		l++;
	} else {
		r--;
	}
}

典型题目

  • leetcode 167 两数之和II-输入有序数组

满足条件:左右指针所指向元素之和为target,因为数组有序,和小于target就右移左指针;大于target就左移右指针

vector<int> twoSum(vector<int>& nums, int target) {
    int l = 0, r = nums.size()-1;
    while (l < r) {
        if (nums[l] + nums[r] == target) return vector<int> {l+1, r+1};
        else if (nums[l] + nums[r] < target) l++;
        else r--;
    }
    return vector<int> {};
}
类似题目:
题目特点思路是否掌握
leetcode344 反转字符串交换左右指针元素,防止覆盖满足条件:左右指针依次交换即可,注意引入临时变量防止覆盖已掌握
leetcode977 有序数组的平方最大的平方一定出现在绝对值最大的数字,也就是首尾元素上满足条件:如果左指针所指元素平方更大,则平方数组当前位置放置左指针元素的平方(平方数组为倒序遍历)已掌握
leetcode88 合并两个有序数组直接在数组1上操作,所以只能从末尾开始先确定大的元素,以防覆盖数组1满足条件:如果右指针(数组2)元素更大,则放在tail所指位置,否则放左指针所指向数组1的元素已掌握
leetcode15 三数之和除了左右指针还需要一个指针遍历整个数组,左右指针则寻找指针i右边和为target-nums[i]的数字**满足条件:nums[l]+nums[r] = target - nums[i]**注意移动左右指针的时候要进行重复元素的剪枝已掌握
(3) 二分查找

使用场景1:有序数组中的元素查找
求解模版

边界条件:
1)左闭右闭:l = 0, r = nums.size()-1, 下次搜索不包含mid,也就是while (l <= r), 下一次更新指针的时候要不是l = mid+1, 要不是r = mid-1;
2)左闭右开:l = 0, r = nums.size(), 下次搜索还要包含mid,也就是while(l < r), 下一次更新指针的时候要不是l = mid+1, 要不是r = mid;

int l = 0, r = nums.size();
while (l < r) {
	int mid = (l + r) / 2;
	if (nums[mid] == target) return mid;
	else if (nums[mid] < target) r = mid;
	else l = mid + 1;
}
return -1;
  • leetcode 704 二分查找
int search(vector<int>& nums, int target) {
    int l = 0, r = nums.size();
    while (l < r) {
        int mid = l + (r - l) / 2;
        if (nums[mid] == target) return mid;
        else if (nums[mid] < target) l = mid+1;
        else r = mid;
    }
    return -1;
}

使用场景2:旋转数组二分
求解模版

int l = 0, r = nums.size()-1;
while (l < r) {
	int mid = l + (r - l) / 2;
	if (nums[mid] > nums[r]) l = mid+1;
	else r = mid;
}
return nums[l]; // 返回最小值
  • leetcode 153 寻找旋转数组中的最小值

不是全程有序,而是变成两段有序数组
target:右边界,确定出当前较小数字组成的有序段是在mid左边还是右边;如果mid比r小,说明较小元素肯定在mid左边,右边界左移;反之,左边界右移

int findMin(vector<int>& nums) {
    int l = 0;
    int r = nums.size()-1;
    while (l < r) {
        int mid = (l + r) / 2;
        if (nums[mid] > nums[r]) l = mid+1;
        else r = mid;
    }
    return nums[l];
}
类似题目
题目特点思路是否掌握
leetcode33 搜索旋转数组相比153不是寻找最小值,而是寻找目标值是否存在,所以条件就会变得更加复杂判断条件:target比mid大还是小、target比右边界大还是小、mid比右边界大还是小未掌握
leetcode34 在排序数组中查找第一个和最后一个位置要查到第一个和最后一个位置,就可以转变为寻找第一个为target的位置,和第一个为target+1的位置;要找到左边界,就需要如果遇到大于等于target的都去移动右边界target值:target && target+1未掌握,在计算end边界前先需要判断一下是否存在end
leetcode 4两个数组直之间比较大小判断条件:数组1和数组2第k/2个数谁更小,则说明谁的前k/2个元素肯定填充在0-k-1之间,不会出现第k个,所以直接可以排除未掌握
(4) 滑动窗口
  • 可以用滑动窗口求解的题目中都包含着==“连续”、“最xxx”==这种字眼
  • 求解时要注意先考虑清楚这仨个问题:窗口内是什么?左边界如何移动?右边界如何移动?

适用场景1:固定窗口
求解模版

分为未达到固定长度前和达到固定长度后分别如何处理

 for (int i = 0; i < nums.size(); i++) {
 	if (i < k) 如何处理;
 	else {
 		去掉越界的第i-k个元素
 		再处理第i个元素
 		维护全局最大
 	}
 }
  • leetcode 643 子数组的平均数I
double findMaxAverage(vector<int>& nums, int k) {
 int max_s = INT_MIN; // 窗口元素和的最大值
 int s = 0; // 维护窗口元素和
 for (int i = 0; i < nums.size(); i++) {
     // 1. 进入窗口
     s += nums[i];
     if (i < k - 1) { // 窗口大小不足 k
         continue;
     }
     // 2. 更新答案
     max_s = max(max_s, s);
     // 3. 离开窗口
     s -= nums[i - k + 1];
 }
 return (double) max_s / k;
}

适用场景2:可变窗口;求最小/最长子数组长度(如无重复字符、覆盖子串)
求解模版

int l = 0, res = 0;
unordered_set<int> set; // set充当可变滑动窗口
for (int i = 0; i < nums.size(); i++) {
	加入nums[r]while (l <= r && 不满足条件) {
		set.erase(nums[l]);//窗口内移除l所指向元素
		l++;
	}
	set.insert(s[r]);// 窗口内塞入r对应的元素
	res = max(res, r-l+1);
	r++;
}
  • leetcode3 无重复字符的最长子串

需要满足的窗口条件:无重复字符

int lengthOfLongestSubstring(string s) {
unordered_set<char> set;//去重
int res= 0;
for (int l = 0, r = 0; r < s.length(); r++) {
    char ch = s[r];
    while (l <= r && set.count(ch)) {
        set.erase(s[l]);
        l++;
    }
    set.insert(s[r]);
    res = max(res, r-l+1);
}
return res;
}

类似题目

题目特点思路是否掌握
leetcode209 长度最小的子数组需要维护窗口长度最小窗口内需要满足的条件:窗口内和不超过target; 否则就先更新区间长度,再移动左边界未掌握

二、前缀和求数组

(1)一维前缀和

使用场景:快速求子数组的和、差分数列区间更新

求解模版

前缀和数组长度和原数组长度一致,且每个元素表示从0到当前索引位置的子区间和

vector<int> prefix (0, nums.size()+1);
for (int i = 1; i <= nums.size(); i++) {
	prefix[i+1] = prefix[i] + nums[i-1];
}	
int sum_ij = prefix[j] - prefix[i]; // 计算区间[i,j]的和

Leetcode 560 和为k的子数组

prefix[presum]表示从0累加到该位置和为presum的数字有多少个,prefix[presum-k]表示前缀和为presum-k的有多少个;那么这些数字到当前索引的区间和就为k。注意有一种情况那就是当前位置的前缀和就为k,即区间和为0,所以需要令prefix[0] = 1;

int subarraySum(vector<int>& nums, int k) {
    unordered_map<int,int> prefix;
    prefix[0] = 1;
    int presum = 0, count = 0;
    for (int i = 1; i <= nums.size(); i++) {
        presum += nums[i-1];
        count += prefix[presum-k];
        prefix[presum]++;
    }
    return count;
}

(2)二维前缀和

求解模版

int m = matrix.size(), n = matrix[0].size();
vector<vector<int>> prefix (n+1, vector<int> (m+1, 0));
for (int i = 0; i <= m; i++) {
    for (int j = 0; i <= n; j++) {
        prefix[i+1][j+1] = matrix[i][j] + prefix[i][j+1] + prefix[i+1][j] - prefix[i][j]
    }
}
# 计算子矩阵和: prefix[x2][y2] - prefix[x1-1][y2] - prefix[x2][y1-1] + prefix[x1-1][y1-1]

在这里插入图片描述

三、矩阵相关

使用场景:矩阵遍历、螺旋打印
求解模版

int t = 0, l = 0;
int b = matrix.size(), r = matrix[0].size();
while (l < r && t < b) {
	// 行顶部
	for (int j = l; j < r; j++) {
		matrix[t][j];
	}
	// 最右边界
	for (int i = t; i < b; i++) {
		matrix[i][r];
	}
	// 最下边
	for (int j = r; j > l; j--) {
		matrix[b][j];
	}
	// 最左边
	for (int i = b; i > t; i--) {
		matrix[i][l];
	}
	l++;
	t++;
	r--;
	b--;
}
if (t == b) {
  // 最后一行,左闭右闭
   for (int i = l; i <= r; i++) {
       matrix[b][i];
   }
} else if (l == r) {
   // 剩的是最后一列
   for (int j = t; j <= b; j++) {
       matrix[j][l];
   }
}

典型题目

  • leetcode54 螺旋矩阵
vector<int> spiralOrder(vector<vector<int>>& matrix) {
    vector <int> res;
    int l = 0, r = matrix[0].size()-1, t = 0, b = matrix.size()-1;
    while (l < r && t < b) {
        // 左闭右开,以防重复
        // 首行
        for (int j = l; j < r; j++) {
            res.push_back(matrix[t][j]);
        }
        // 最右边界
        for (int i = t; i < b; i++) {
            res.push_back(matrix[i][r]);
        }
        // 最下边
        for (int j = r; j > l; j--) {
            res.push_back(matrix[b][j]);
        }
        // 最左边
        for (int i = b; i > t; i--) {
            res.push_back(matrix[i][l]);
        }
        l++;
        r--;
        t++;
        b--;
    }
    if (t == b) {
        // 最后一行,左闭右闭
        for (int i = l; i <= r; i++) {
            res.push_back(matrix[b][i]);
        }
    } else if (l == r) {
        // 剩的是最后一列
        for (int j = t; j <= b; j++) {
            res.push_back(matrix[j][l]);
        }
    }
    return res;
}
  • leetcode 48 旋转图像

先上下调换,再沿着主对角线翻折

void rotate(vector<vector<int>>& matrix) {
    int n = matrix.size();
    for (int i = 0; i < n /2; i++) {
        for (int j = 0; j < n; j++) {
            int temp = matrix[i][j];
            matrix[i][j] = matrix[n-i-1][j];
            matrix[n-i-1][j] = temp;
        }
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < i; j++) {
            int temp = matrix[i][j];
            matrix[i][j] = matrix[j][i];
            matrix[j][i] = temp;
        }
    }
}

四、哈希表优化

适用场景:快速查找元素是否存在、统计频率
求解模版

unordered_map<int, int> map;
for (auto num : nums) {
	if (map.count(target-num)) return map[target-num];
	else map[target-num] = 索引或者频率;
}

典型题目

  • leetcode1 两数之和

哈希表里记录的是nums[i]的索引,满足条件为target-当前元素差刚好在map里

vector<int> twoSum(vector<int>& nums, int target) {
   unordered_map<int, int> mp;
    for(int i = 0; i < nums.size(); i++) {
        if (mp.count(target - nums[i])) {
            return vector<int> {i, mp[target - nums[i]]};
        } else {
            mp[nums[i]] = i;
        }
    }
    return vector<int> {};
}

-leetcode128 最长连续序列

先确定连续序列最左边界,再确定最右边界;维护一个全局变量记录当前最长连续序列长度

int longestConsecutive(vector<int>& nums) {
    int ans = 0;
    unordered_set<int> map(nums.begin(), nums.end());
    for (int n : map) {
        if (map.contains(n-1)) continue;
        int y = n+1;
        while (map.contains(y)) y++;
        ans = max(ans, y-n);
    }
    return ans;
}

五、单调栈

适用场景:解决”下一个更大/更小元素”、区间极值、维持有序性等
求解模版

维护一个单调递增或单调递减的栈,在遍历数组的时候动态调整栈结构,以便快速找到目标值
单减栈是比栈顶元素大则弹出栈顶元素,以快速找到下一个更大的元素;单增栈是比栈顶元素小的时候就弹出栈顶元素,以快速找到下一个更小的元素

  • leetcode 496 下一个更大的元素I

递减栈+哈希表映射; 维护一个针对nums2的单减栈,然后再判断nums1的数字是否在nums2里,如果在就填入map里记下来的数否则填-1;

vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
    stack<int> st;
    unordered_map<int, int> map;
    for (int num : nums2) {
        while (!st.empty() && num > st.top()) {
            map[st.top()] = num;
            st.pop();
        }
        st.push(num);
    }
    vector<int> res;
    for (int num : nums1) {
        res.push_back(map.count(num) ? map[num] : -1);
    }
    return res;
}

-Leetcode 503 下一个更大的元素II

把数组拼接2次,模拟循环
注意的是这个索引需要还原为idx = i%n; 同时为了保证每个索引只入栈一次,只记录i在第一个数组的边界索引,第二个数组纯属是为了判断尾部元素的边界、

vector<int> nextGreaterElements(vector<int>& nums) {
    stack<int> st;
    int n = nums.size();
    vector<int> res (n, -1);
    for (int i = 0; i < 2 * n; i++) {
        int idx = i % n;
        while (!st.empty() && nums[idx] > nums[st.top()]) {
            int top = st.top();
            st.pop();
            res[top] = nums[idx];
        }
        if (i < n) st.push(idx);
    }
    return res;
}
  • leetcode 739 每日温度

维护的还是一个单减栈,只不过结果里记录的是下一个最大值和当前栈顶元素的间隔

vector<int> dailyTemperatures(vector<int>& temperatures) {
   stack<int> st; // 递增栈
   vector<int> result(temperatures.size(), 0); // 长度和温度一样,初始化为0
   // 这样如果有右边没有比该元素大的,就直接为0
   for (int i = 0; i < temperatures.size(); i++) {
       while (!st.empty() && temperatures[i] > temperatures[st.top()]) {
           int idx = st.top();
           st.pop();
           result[idx] = i - idx ;
       }
       st.push(i);
   }
   return result;
}
  • leetcode 84 柱状图中的最大矩形

维护一个单增栈,从而确定当前高度可以扩展的左右边界
注意要在栈内先增加一个哨兵,以免出现整个高度数组是单减数列,这样的话栈内永远只有一个数字,没有左边界,这个时候算宽度会得到-1;为了避免这种情况所以先在栈内增加一个哨兵
同时在数组尾部也增加一个0,这是为了确保栈内所有元素都会被弹出算面积

int largestRectangleArea(vector<int>& heights) {
   stack<int> st;
   int max_s = 0;
   st.push(-1);
   heights.push_back(0);
   for (int i = 0; i < heights.size(); i++) {
       while (st.top() != -1 && heights[i] < heights[st.top()]) {
           int h = heights[st.top()];
           st.pop();
           int w = i - st.top() -1;
           max_s = max(max_s, h * w);
       }
       st.push(i);
   }
   return max_s;
}
  • leetcode 316 去除重复字母

用String模拟一个单增栈,使得下一个加入栈的字母只会让整个字符更小
已经加入栈的要进行标记,同时统计每个字母出现的次数;在弹出的时候相比之前的题还需要判断一下该字母后面是不是还有,如果没有了则不能弹出

string removeDuplicateLetters(string s) {
   vector<int> count(256, 0); // 统计每个字母出现的次数
   vector<bool> visited(256, false); // 统计每个字母是否在栈内
   string st;
   for (char ch : s) count[ch]++;
   for (char ch : s) {
       count[ch]--;
       if (visited[ch]) continue;
       while (!st.empty() && ch < st.back() && count[st.back()]) {
           visited[st.back()] = false;
           st.pop_back();
       }
       st.push_back(ch);
       visited[ch] = true;
   }
   return st;
}
  • leetcode 155 最小栈

维护一个栈,栈内存的是当前数和当前最小值的差值
如果栈顶元素小于0,说明当前差值对应的元素为最小值,所以弹出它的时候最小值也要随着更新。否则说明最小值还在它前面

void push(int val) {
   if (st.size() == 0) {
       st.emplace(0LL); //栈内无元素时,存放差值0
       minVal = val; //当前入站元素为最小值
   } else {
       st.emplace((long long)val - minVal); //记录当前元素与最小值的差值
       minVal =min(minVal, (long long)val); //更新最小值 
   }
}

void pop() {
  //栈顶元素为正值,直接弹出,若为负值,表明在这个为最小值发生改变,需要还原
  minVal -= min(st.top(), 0LL);
  st.pop();
}

int top() {
  //用最小值还原栈顶元素
  return (int)(minVal + max(st.top(), 0LL));
}

六、模拟

1. 位运算

确定只出现一次的数字:异或

  • Leetcode 136 只出现一次的数字
int singleNumber(vector<int>& nums) {
  int x = 0;
   for (int num : nums) x ^= num;
   return x;
}

2. 多数投票法

  • leetcode 169

维护一个候选数和计数器,如果遇到的数字和候选数一样则count++, 否则count–;如果count变为负则换候选数

int majorityElement(vector<int>& nums) {
   int count = 1;
   int result = nums[0];
   for (int i = 1; i < nums.size(); i++) {
       
       if (result == nums[i]) count++;
       else {
           count--;
           if (count < 0) {
               count = 1;
               result = nums[i];
           }
       }
   }
   return result;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值