双指针强化练习
以下是一些经典的双指针题目,这些题目不仅可以帮助你强化双指针技巧,还能加深你对常见算法的理解。
回顾:26. 删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums
,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums
中唯一元素的个数。考虑 nums
的唯一元素的数量为 k
,你需要做以下事情确保你的题解可以被通过:
- 更改数组
nums
,使nums
的前k
个元素包含唯一元素,并按照它们最初在nums
中出现的顺序排列。nums
的其余元素与nums
的大小不重要。 - 返回
k
。
示例 1:
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
**解法:**使用双指针法,left
指针跟踪新的唯一元素位置,right
指针遍历数组,找到与left
指针指向的元素不同的元素时,将right
指针指向的元素放到left + 1
的位置。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int left=0, right=0;
while(right<nums.size()){
if(nums[right]!=nums[left]){
left++;
nums[left]=nums[right];
}else{
right++;
}
}
return left+1;
}
};
80. 删除有序数组中的重复项 II
给你一个有序数组 nums
,请你 原地 删除重复出现的元素,使得出现次数超过两次的元素只出现两次 ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3。 不需要考虑数组中超出新长度后面的元素。
示例 2:
输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:函数应返回新长度 length = 7, 并且原数组的前七个元素被修改为 0, 0, 1, 1, 2, 3, 3。不需要考虑数组中超出新长度后面的元素。
**解法:**使用双指针left
指针维护当前可用的位置,right
指针遍历数组。通过count
变量来记录当前元素的出现次数,若次数小于等于2,则保留该元素。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size()<=2) return nums.size();
int left=0, right=1;
int count=1;
while(right<nums.size()){
if(nums[right]==nums[right-1]){
count=count+1;
}else{
count=1;
}
if(count<=2){
left++;
nums[left]=nums[right];
}
right++;
}
return left+1;
}
};
125. 验证回文串
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。
字母和数字都属于字母数字字符。
给你一个字符串 s
,如果它是 回文串 ,返回 true
;否则,返回 false
。
示例 1:
输入: s = "A man, a plan, a canal: Panama"
输出:true
解释:"amanaplanacanalpanama" 是回文串。
示例 2:
输入:s = "race a car"
输出:false
解释:"raceacar" 不是回文串。
示例 3:
输入:s = " "
输出:true
解释:在移除非字母数字字符之后,s 是一个空字符串 "" 。
由于空字符串正着反着读都一样,所以是回文串。
解法:使用两个指针left
和right
,分别从字符串的两端开始,跳过非字母数字字符并比较字符是否相同。
class Solution {
public:
bool isPalindrome(string s) {
string news;
for(int i=0; i<s.size();i++){
char temp=s[i];
if(temp>='A'&&temp<='Z') temp=temp-'A'+'a';
if((temp>='a' && temp <= 'z' ) || (temp>='0' && temp<='9')){
news+=temp;
}
}
int left=0, right=news.size()-1;
while(left<=right){
if(news[left]==news[right]){
left++;
right--;
}else{
return false;
}
}
return true;
}
};
75. 颜色分类
给定一个包含红色、白色和蓝色、共 n
个元素的数组 nums
,原地 对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
解法:使用三指针法(p0
、p2
和 p
)。p0
指向数组的开始,负责放置0
,p2
指向数组的末尾,负责放置2
,p
用于遍历数组。
class Solution {
public:
void sortColors(vector<int>& nums) {
//整3个指针,p0指针负责存放开头0,p2指针负责存放开头2,p指针负责便利寻找,p0和p2分完了,自然都是1
int p0=0, p2=nums.size()-1, p=0;
while(p<=p2){
if(nums[p]==0){
swap(nums,p0,p);
p0++;p++;//因为p是从前往后遍历的,所以p前面的都遍历过(0或1),所以可跳过
}else if(nums[p]==2){
swap(nums,p,p2);
p2--;
}else{p++;}
}
}
void swap(vector<int>& nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
};
88. 合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1
和 nums2
,另有两个整数 m
和 n
,分别表示 nums1
和 nums2
中的元素数目。请你 合并 nums2
到 nums1
中,使合并后的数组同样按 非递减顺序 排列。
**注意:**最终,合并后数组不应由函数返回,而是存储在数组 nums1
中。为了应对这种情况,nums1
的初始长度为 m + n
,其中前 m
个元素表示应合并的元素,后 n
个元素为 0
,应忽略。nums2
的长度为 n
。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
解法:使用三个指针p1
、p2
和p
,分别从nums1
和nums2
的末尾开始,比较两个数组的元素,将较大的元素放到nums1
的末尾。
class Solution {
public:
//直接从nums1后面往前面放最大的元素(nums1前段和nums2对比)即可
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p=nums1.size()-1;
int p1=m-1;
int p2=n-1;
while(p1>=0&&p2>=0){
if(nums1[p1]>=nums2[p2]){
nums1[p]=nums1[p1];
p1--;
}else{
nums1[p]=nums2[p2];
p2--;
}
p--;
}
while(p2>=0){
nums1[p]=nums2[p2];
p2--;
p--;
}
}
};
977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums
,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
解法:使用双指针法,left
指针从数组的左侧开始,right
指针从数组的右侧开始。比较两边的绝对值,平方后较大的数放到结果数组的末尾。
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> res(nums.size(),0);
int left=0, right=nums.size()-1;
int p=res.size()-1;
while(left<=right){
if(abs(nums[left])>=abs(nums[right])){
res[p]=nums[left]*nums[left];
left++;
}else{
res[p]=nums[right]*nums[right];
right--;
}
p--;
}
return res;
}
};
1329. 将矩阵按对角线排序
矩阵对角线 是一条从矩阵最上面行或者最左侧列中的某个元素开始的对角线,沿右下方向一直到矩阵末尾的元素。例如,矩阵 mat
有 6
行 3
列,从 mat[2][0]
开始的 矩阵对角线 将会经过 mat[2][0]
、mat[3][1]
和 mat[4][2]
。
给你一个 m * n
的整数矩阵 mat
,请你将同一条 矩阵对角线 上的元素按升序排序后,返回排好序的矩阵。
示例 1:
输入:mat = [[3,3,1,1],[2,2,1,2],[1,1,1,2]]
输出:[[1,1,1,1],[1,2,2,2],[1,2,3,3]]
示例 2:
输入:mat = [[11,25,66,1,69,7],[23,55,17,45,15,52],[75,31,36,44,58,8],[22,27,33,25,68,4],[84,28,14,11,5,50]]
输出:[[5,17,4,1,52,7],[11,11,25,45,8,69],[14,23,25,44,58,15],[22,27,31,36,50,66],[84,28,75,33,55,68]]
class Solution {
public:
vector<vector<int>> diagonalSort(vector<vector<int>>& mat) {
int m = mat.size();
int n = mat[0].size();
unordered_map<int,vector<int>> map;
//元素放入哈希表中,(i-j)用于区分每一个对角线
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
map[i-j].push_back(mat[i][j]);
}
}
//分别对每个对角线上的元素进行排序
for(auto& [_,num]:map){
sort(num.begin(),num.end());
}
//排序后的元素放进去
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
mat[i][j]=map[i-j].front();
map[i-j].erase(map[i-j].begin());
}
}
return mat;
}
};
1260. 二维网格迁移
给你一个 m
行 n
列的二维网格 grid
和一个整数 k
。你需要将 grid
迁移 k
次。
每次「迁移」操作将会引发下述活动:
- 位于
grid[i][j]
(j < n - 1
)的元素将会移动到grid[i][j + 1]
。 - 位于
grid[i][n - 1]
的元素将会移动到grid[i + 1][0]
。 - 位于
grid[m - 1][n - 1]
的元素将会移动到grid[0][0]
。
请你返回 k
次迁移操作后最终得到的 二维网格。
示例 1:
输入:grid = [[1,2,3],[4,5,6],[7,8,9]], k = 1
输出:[[9,1,2],[3,4,5],[6,7,8]]
示例 2:
输入:grid = [[3,8,1,9],[19,7,2,5],[4,6,11,10],[12,0,21,13]], k = 4
输出:[[12,0,21,13],[3,8,1,9],[19,7,2,5],[4,6,11,10]]
示例 3:
输入:grid = [[1,2,3],[4,5,6],[7,8,9]], k = 9
输出:[[1,2,3],[4,5,6],[7,8,9]]
二维转一维数组进行处理,把后面k个调到前面,就是先反转后面k个,再反转前部分,最后整体反转
class Solution {
public:
vector<vector<int>> shiftGrid(vector<vector<int>>& grid, int k) {
int m=grid.size();
int n=grid[0].size();
k=k%(m*n);
vector<int> temp{};
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
temp.push_back(grid[i][j]);
}
}
reverse(temp.end()-k,temp.end());
reverse(temp.begin(),temp.end()-k);
reverse(temp.begin(),temp.end());
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
grid[i][j]=temp[i*n+j];
}
}
return grid;
}
};
867. 转置矩阵
给你一个二维整数数组 matrix
, 返回 matrix
的 转置矩阵 。矩阵的 转置 是指将矩阵的主对角线翻转,交换矩阵的行索引与列索引。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[1,4,7],[2,5,8],[3,6,9]]
示例 2:
输入:matrix = [[1,2,3],[4,5,6]]
输出:[[1,4],[2,5],[3,6]]
class Solution {
public:
vector<vector<int>> transpose(vector<vector<int>>& matrix) {
//因为这个数组如果是2*3的,转置后就是3*2的,所以无法在原数组上修改,只能新建一个矩阵
int m = matrix.size();
int n = matrix[0].size();
vector<vector<int>> result(n,vector<int>(m));
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
result[j][i]=matrix[i][j];
}
}
return result;
}
};
14. 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
示例 1:
输入:strs = ["flower","flow","flight"]
输出:"fl"
示例 2:
输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
int m = strs.size(); //行数
int n = strs[0].size(); //列数
string res;
for(int j=0; j<n; j++){
bool issame=true;
for(int i=1; i<m; i++){
char now = strs[i][j];
char prev = strs[i-1][j];
if(now != prev){
issame=false;
}
}
if(issame==false){
return res;
}else{
res.push_back(strs[0][j]);
}
}
return res;
}
};