文章目录
- leetcode 198 打家劫舍
- leetcode 414 第三大的数
- leecode 134 加油站
- leetcode 300 最长递增子序列
- leetcode 128 最长连续子序列 & 剑指offer II
- leetcode 34 找到目标元素在数组中第一次出现和最后一次出现的位置
- leetcode 77 组合--回溯 `M`
- leetcode 39 组合总和 `M`----回溯
- leetcode 40 组合总和 II
- leetcode 62 不同路径
- leetcode 63 不同路径 II
- leetcode 287 寻找重复数字 M
- leetcode 135 分发糖果 H
- leetcode 26 删除有序数组中的重复项 I
- leetcode 80 【删除有序数组中的重复项 II】
- leetcode 316 【去除重复字母M】
- 删除字符串中的所有相邻重复项
- leetcode 1209 删除字符串中的相邻重复元素 II(删除k个相邻重复的元素)
leetcode 198 打家劫舍
//打家劫舍
class Solution
{
public:
int rob(vector<int>& nums)
{
if (nums.size() == 0)return 0;
if (nums.size() == 1)return nums[0];//只有一间房子
vector<int>dp(nums.size());
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);//有两间房子
for (int i = 2; i < nums.size(); i++) //大于两间房子的情况
{
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[nums.size() - 1];
}
};
leetcode 414 第三大的数
利用set这个数据结构
实现:
//小顶堆实现 堆顶元素是最小的元素
class Solution
{
public:
int findK(vector<int>& nums)
{
priority_queue<int, vector<int>, greater<int>>q;//小顶堆实现
for (int i = 0; i < nums.size(); i++)
{
if (i < 3)
{
q.push(nums[i]);
}
else if (nums[i] > q.top()) //在队列里面维护三个较大的元素 这个三个元素自动排序 最大的元素在队首
{
q.pop();
q.push(nums[i]);
}
}
return q.top();
}
};
//返回数组中第三大的数字
class Solution
{
public:
int thirdMax(vector<int>& nums)
{
set<int>s;//set中的数字自动排序 从大到小排序
for (int num : nums)
{
s.insert(num);
if (s.size() > 3)
{
s.erase(s.begin());
}
}
return s.size() == 3 ? *s.begin() : *s.rbegin();//返回集合头元素或尾元素
}
};
leecode 134 加油站
当可以绕环路一周的时候 返回起始加油站的编号;
在一条环路上有n个
加油站,其中第i个
加油站有汽油gas[i]
升;
你有一辆油箱容量无限的汽车,从第i个
加油站开往第i+1个
加油站需要消耗其汽油cost[i]
升,你从其中一个加油站出发,开始时油箱为空;
给定两个整数数组gas和cost
,如果你可以绕环路一周(返回最初出发点的时候,油箱里的油量>=0),则返回出发时加油站的编号
,否则返回-1,如果存在解,则保证它是唯一的。
(每到达一个车站,你都可以获取这个车站的油,并且你取了之后就不会再有了,每个车站的油只存在一次)
例如:
输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。
小注
:
1)如果总油量gas之和减去总消耗cost之和大于等于0
,那么一定可以跑完一圈,说明各个站点的加油站剩油量rest[i]相加一定是大于等于0的;
2)每个加油站的剩余量rest[i]=gas[i]-cost[i]
3) i 从0开始累加rest[i],和记为cursum
,一旦cursum小于0,说明[0,i]区间都不能作为起始位置,起始位置从i+1开始算起,再从0计算cursum
;
如下图所示:
C++实现:
//贪心算法
class Solution
{
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost)
{
int cursum = 0;//记录当前遍历到的所有的加油站的剩余油量之和
int totalsum = 0;//记录一圈下来的剩余油量
int start = 0;//记录出发位置的下标
for (int i = 0; i < gas.size(); i++) //这个循环逻辑是在假设可以走完一圈的前提下进行的
{
cursum += gas[i] - cost[i];//记 rest[i]=gas[i]-cost[i]
totalsum += gas[i] - cost[i];
if (cursum < 0)
{
start = i + 1;//不断的更新起始下标start;i之前的任意一个位置都不能作为起点
cursum = 0;//重置为0
}
}
if (totalsum < 0)return -1;//所有站点的剩余油量之和为负 说明肯定是不能回到原点的
return start;//返回出发位置的索引下标
}
};
leetcode 300 最长递增子序列
序列必须是严格递增的,可以不连续
;
//动态数组的含义
//dp[i]为考虑前i个元素 以第i个数字结尾的最长递增子序列的长度,nums[i]必须被选取
//dp[j]是(0,i-1)中递增子序列的最大值,dp[i]可由dp[j]的状态递推而来;
//dp[j]是以第i个数字结尾的最长递增子序列的长度;
class Solution
{
public:
int lengthOfLIS(vector<int>& nums)
{
if (nums.size() <= 1)return nums.size();
vector<int>dp(nums.size(), 1);//初始化为1 因为最长连续的子序列至少是1
int res = 0;//用于返回一个最长的子序列
for (int i = 1; i < nums.size(); i++)
{
for (int j = 0; j < i; j++)
{
if (nums[i] > nums[j])//遇到可以递增的序列元素
{
dp[i] = max(dp[i], dp[j] + 1); //取二者中的最大值;dp[i] dp[j]初始都是1
}
if (dp[i] > res)res = dp[i];//更新最长的子序列;左值是返回值
}
}
return res;
}
};
int main()
{
vector<int>nums = { 1,2,3,3,2,1,3,2,4,5,3,2,1 };
Solution so;
int ans = 0;
ans = so.lengthOfLIS(nums);
cout << "最长递增子序列的长度为:" << ans << endl;
system("pause");
return 0;
}
leetcode 128 最长连续子序列 & 剑指offer II
题目要求:
给定一个未排序的数组nums
,找到数组中最长的连续子序列
例如
输入:nums=[100,4,200,1,3,2]
输出:4 这个最长的连续子序列是 [1,2,3,4]
它的长度为4
输入:[0,3,7,2,5,8,4,6,0,1]
输出:9 这个连续子序列是[0,1,2,3,4,5,6,7,8]
C++实现:
//会用到哈希集合去重 因为对于所找的最长的子序列 重复元素和非重复的效果是一样的。
//最长连续序列
class Solution
{
public:
int longestConsecutive(vector<int>& nums)
{
unordered_set<int>set;//创建一个无序集合 unordered_set 去重 不含重复元素;
for (const int& num : nums) //num是对象的引用,避免了拷贝 效率更高一些
{
set.insert(num);//将数组中所有的元素都加入到集合中
}
int longestStreak = 0;//初始化一个表示最长子序列的变量
for (const int& num : set)//用范围for循环的方式来遍历集合
{
if (!set.count(num - 1))//比num小1的元素在集合中是不存在的 保证最长序列的长度是从最小的值开始相加的,而不会从一个较大值开始叠加
{
int currentNum = num;
int currentStreak = 1;//当前连续序列的长度 是会一直更新的
while (set.count(currentNum + 1))//比当前值大1的元素在集合中存在 说明可以找得到连续的序列了
//比如在这里找到1之后 就会一直连续的找到1 2 3 4
{
currentNum += 1;//数值加一
currentStreak += 1;//表示当前最长序列的长度加一
}
longestStreak = max(longestStreak, currentStreak);//维护一个最长的连续子序列
}
}
return longestStreak;
}
};
leetcode 34 找到目标元素在数组中第一次出现和最后一次出现的位置
给定一个按照升序排列的整数数组nums和一个目标值target
;请找出给定目标值在数组中的开始位置和结束位置;如果数组中不存在目标值,返回[-1,-1].
两次二分查找
的实现:
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target)
{
vector<int>res(2,-1);//初始化一个容器 元素初始化为-1
if(nums.empty())return res;
int n=nums.size(),l=0,r=n-1;//左右边界
while(l<r)//二分查找 第一次查找时左右指针的范围是整个数组
{
int m=l+(r-l)/2;
if(nums[m]>=target)r=m;//更新右边界
else l=m+1;//更新左边界
}
if(nums[l]!=target)return res;//说明不存在目标元素
res[0]=l;//左边界元素就是我们要找的元素第一次出现的位置
r=n;//右边界r的位置可能发生变动 因此要重新设置一下右边界 防止溢出
while(l<r) //二分查找 从左向右查找 第二次查找时左右指针的范围从目标元素第一次出现的位置向右到数组尾部
{
int m=l+(r-l)/2;
if(nums[m]<=target)l=m+1;//更新左边界
else r=m;//更新右边界
}
res[1]=l-1;
return res;
}
};
leetcode 77 组合–回溯 M
给定两个整数n和k
,返回1.....n
中所有可能的k个数的组合;返回的组合中是不可以包含重复的元素的;
例如:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
回溯算法的思路:
这里是需要设置startIndex这个变量的,这个参数用来记录本层递归中,集合从哪里开始遍历
;(类似于排列组合中的 先找以1 开头的 再找以2开头的 依次往下找…)
因为每次从集合中选取元素,可选择的范围随着选择的进行而收缩
,调整可选择的范围,就是靠startIndex;
可以;理解为以1开头的数字 以2开头的数字 以3开头的数字 以4开头的数字
;这四种情况的数字可选范围是不一样的;
回溯实现:
//77 组合
class Solution
{
private:
vector<vector<int>>res;
vector<int>path;
void backtracking(int n, int k, int startIndex)
{
if (path.size() == k)
{
res.push_back(path);
return;
}
for (int i = startIndex; i <= n; i++) //以1 开头的数字 以2开头的数字 ...这几种情况的遍历起始位置是不一样的,startIndex就是来控制这个起始位置的;
{
path.push_back(i);
backtracking(n, k, i + 1);
path.pop_back();//回溯 撤销对节点的处理
}
}
public:
vector<vector<int>>combine(int n, int k)
{
res.clear();
path.clear();
backtracking(n, k, 1);//集合下标是从1开始计算的 因为题目指定的是从1到n的元素中选取;
return res;
}
};
leetcode 39 组合总和 M
----回溯
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target
,找出 candidates 中可以使数字和为目标数 target 的 所有不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。返回的每个组合中是可以包含重复数字的;
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
例如:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
本题中是用到startIndex来控制for循环的起始位置
的,如果是一个集合来求组合的话就需要用到startindex
;
为什么要有这个startIndex呢?
每次从集合中选取元素,可选择的范围随着选择的进行而收缩
,调整可选择的范围,就是靠startIndex;
//candidades中的数字是可以多次重复选取的,每个组合中可以包含重复的数字
class Solution
{
private:
vector<vector<int>>res;//存放最终的结果
vector<int>path;//存放单条路径
void backtracking(vector<int>& candidates, int target, int sum, int startIndex)//startIndex用来控制for循环的起始位置;
{
if (sum > target)return;//递归终止
if (sum == target)
{
res.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size(); i++)//startindex用来控制一次只能选取一个数字,但是下次还是可以选取和上次一样的数字的;
{
sum += candidates[i];//处理当前节点
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i);//因为是可重复的 下一次的递归还是可以从头开始遍历 所以是i 不是i+1
sum -= candidates[i];//开始向上回溯
path.pop_back();
}
}
public:
vector<vector<int>>combinationSum(vector<int>& candidates, int target)
{
res.clear();
res.clear();
backtracking(candidates, target, 0, 0);
return res;
}
};
leetcode 40 组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target
,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次
例如:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
小注:
题目要求:
组合中有重复元素,但是不能有重复的组合
;
因此在搜索的过程中就要去掉重复的组合
理解去重:所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重
。
同时,树层去重的话,需要对数组进行排序;
所谓的去重是不能以相同的元素开头
;
class Solution3
{
private:
vector<vector<int>>res;
vector<int>path;
void backtracking(vector<int>&candidates,int target,int sum,int startIndex,vector<bool>&used)
{
if(sum>target)return;//剪枝操作
if (sum == target)
{
res.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size(); i++)
{
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false)continue;//同一层上去重操作 如果是同一树枝上 是used[i-1]=true;
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = true;
backtracking(candidates, target, sum, i + 1, used);//数组中的数字是不能重复选取的
path.pop_back();
sum -= candidates[i];
used[i] = false;
}
}
public:
vector<vector<int>>combine(vector<int>& candidates, int target)
{
vector<bool>used(candidates.size(), false);
sort(candidates.begin(), candidates.end());//因为涉及去重的问题 所以去重之前先排序
backtracking(candidates, target, 0, 0, used);
return res;
}
};
leetcode 62 不同路径
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步
。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
输入:m = 3, n = 7
输出:28
用dp(i,j)表示从左上角走到(i,j)位置的路径数量
,其中i和j的范围分别是[0,m) [0,n);
由于我们每一步只能从向下或者向右移动一步(每一格的路径是由其左一格和上一格决定的
),因此要想走到(i,j) 如果向下走一步,那么会从(i-1,j)走过来;如果向右走一步,那么会从(i,j-1)走过来;可以写出动态规划方程:
dp(i,j)=dp(i−1,j)+dp(i,j−1)
;
需要注意的是,如果 i=0,那么 dp(i−1,j)并不是一个满足要求的状态,我们需要忽略这一项;同理,如果 j=0,那么 dp(i,j−1)并不是一个满足要求的状态,我们需要忽略这一项。
初始条件为 dp(0,0),即从左上角走到左上角有一种方法
。
最终的答案即为 dp(m−1,n−1)
。
对于第一行dp[0][j] 或者第一列dp[i][0] 由于都是在边界上的点, 所以只能为1,即从左上角到dp[0][j] 或到dp[i][0]都是只有一种路径可以走
;
class Solution
{
public:
int uniquePaths(int m, int n) //m行n列
{
vector<vector<int>>dp(m, vector<int>(n));//初始化一个m行n列初始值都为0的二维数组,用于存放求得的路径值
for (int i = 0; i < m; i++) //遍历行 忽略 j=0 的情况
{
dp[i][0] = 1;//
}
for (int j = 0; j < n; j++) //遍历列 忽略 i=0 的情况
{
dp[0][j] = 1;
}
for (int i = 1; i < m; i++)//i=0 j=0的情况在上面已经考虑到了 因此这里i j 都从1开始遍历
{
for (int j = 1; j < n; j++)
{
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
};
leetcode 63 不同路径 II
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步
。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
现在考虑网格中有障碍物
。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1 和 0 来表示。
例如:
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
- 向右 -> 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右 -> 向右
代码实现:
//动态数组dp[i][j]的含义是到达当前位置(i,j)的路径数目
class Solution
{
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid)//输入是一个二维数组
{
int m = obstacleGrid.size();//行
int n = obstacleGrid[0].size();//列
vector<vector<int>>dp(m, vector<int>(n, 0));//初始化一个m行n列的二维数组
for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++)//一直向下走第一列并且第一列中没有障碍物
{ //对于只走第一列且第一列中有障碍物的额情况是不用考虑的,此时是没有路径的;
dp[i][0] == 1;//只有一条路径情况
}
for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++)//一直向右走第一行并且第一行没有障碍物
{
dp[0][j] == 1;
}
for (int i = 1; i < m; i++)
{
for (int j = 1; j < n; j++)
{
if (obstacleGrid[i][j] == 1)continue;
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
};
leetcode 287 寻找重复数字 M
给定一个数组nums,假设这个数组中只存在一个重复的数字
,请返回这个重复数字;
用一个哈希表
实现数组出现次数的统计;
代码实现:
class Solution {
public:
int findDuplicate(vector<int>& nums)
{
unordered_map<int,int>table;
for(auto num:nums)
{
table[num]++;
}
int res=0;
for(auto p:table)
{
if((p.second)>1)
{
return res=p.first;
}
}
return res;
}
};
leetcode 135 分发糖果 H
n
个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分
。
你需要按照以下要求,给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目
。
例如:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果
贪心算法实现
-------整体思路分为从左向右遍历
和从右向左遍历
两个大的步骤 ;
从左向右遍历:
局部最优:只要右边评分比左边大,右边的孩子就多一个糖果;
全局最优:相邻的孩子中,评分高的右孩子比左边孩子得到更多的糖果;
如果ratings[i] > ratings[i - 1]
那么[i]的糖 一定要比[i - 1]的糖多一个,所以贪心:candyVec[i] = candyVec[i - 1] + 1
从右向左的遍历
如果 ratings[i] > ratings[i + 1]
,此时candyVec[i](第i个小孩的糖果数量)就有两个选择
了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。
那么又要贪心了,
局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量即大于左边的也大于右边的
。
全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
局部最优可以推出全局最优。
所以就取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,candyVec[i]只有取最大的才能既保持对左边candyVec[i - 1]的糖果多,也比右边candyVec[i + 1]的糖果多
。
如下图所示:
代码实现:
class Solution
{
public:
int candy(vector<int>& ratings)
{
vector<int>candyVec(ratings.size(), 1);//初始化一个容器存放最终的结果
//从前向后遍历
for (int i = 1; i < ratings.size(); i++)
{
if (ratings[i] > ratings[i - 1])candyVec[i] = candyVec[i - 1] + 1;
}
//从后向前遍历
for (int i = ratings.size() - 2; i >= 0; i--)//i从数组的倒数第二个元素开始计数 配合下面的i+1;
{
if (ratings[i] > ratings[i + 1])
{
candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);
}
}
//统计最后的结果
int res = 0;
for (int i = 0; i < candyVec.size(); i++)
{
res += candyVec[i];
}
return res;
}
};
leetcode 26 删除有序数组中的重复项 I
给你一个有序数组nums
,请你原地删除重复出现的元素,使每个元素最多只出现一次
,返回删除后数组的长度;
例如:
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
双指针解法
核心思想:每遇到一个不同的元素都将其复制到前面的一个位置,也就是slow指针之前的元素是不存在重复元素的
;
由于给定的数组是有序的,因此对于任意i<j
如果nums[i]=nums[j]
则对于任意i<=k<=j
一定有nums[i]=nums[k]=nums[j]
即相等的元素在数组中的下标一定是连续的,利用数组有序的特点,可以用双指针的解法进行求解;
假设数组nums的长度为n,将快指针fast依次遍历从1到n-1的每个位置
,对于每个位置,如果nums[fast]不等于nums[fast-1] 说明nums[fast]和之前的元素都不相同,因此将nums[fast]的值复制到nums[slow]
然后将slow的值加一 即指向下一个位置;
遍历结束后,从nums[0]到nums[slow-1]的每个元素都不相同且包含原数组中的每个不同的元素
,因此新的长度即为slow 返回slow即可;
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;
int fast = 1, slow = 1;//初始化快慢指针
while (fast < n)
{
if (nums[fast] != nums[fast - 1]) //当快指针指向的当前元素和前一个元素不等时,开始用快指针指向的元素对慢指针指向的元素进行覆盖
{
nums[slow] = nums[fast];//用元素覆盖的方式实现去重
++slow;
}
++fast;//当快慢指针指向相同的元素时,快指针持续前进
}
return slow;//最终慢指针指向的元素的 位置下标就是去重后的数组的长度;
}
};
//删除有序数组中的重复元素 使每个元素最多只出现一次
class Solution
{
public:
int removeDuplicates(vector<int>& nums)
{
int n = nums.size();
for (int i = 1; i < n; i++)//从数组的第二个元素开始遍历
{
if (nums[i] == nums[i - 1])
{
nums.erase(nums.begin() + i);//删除重复元素的第二个
i--;//下一次的遍历从重复元素的第一个开始遍历
n--;//删除了一个重复数字之后,数组总的长度减一;
}
}
return nums.size();
}
};
//如果是返回去重之后的数组
class Solution
{
public:
vector<int>removeDuplicates(vector<int>& nums)
{
vector<int>ans;
int n = nums.size();
for (int i = 1; i < n; i++)
{
if (nums[i] == nums[i - 1])
{
nums.erase(nums.begin() + i);
i--;
n--;
}
}
for (int i = 0; i < nums.size(); i++)
{
ans.push_back(nums[i]);
}
return ans;
}
};
int main()
{
vector<int>nums = { 1,2,3,3,3,5,6,5 };
Solution so;
vector<int>res;
res = so.removeDuplicates(nums);
for (auto it = res.begin(); it != res.end(); it++)
{
cout << "去重之后的数组是:" << *it << endl;
}
system("pause");
return 0;
}
leetcode 80 【删除有序数组中的重复项 II】
给你一个有序数组nums
,请你原地删除重复出现的元素,使每个元素最多只出现两次
,返回删除后数组的长度;
例如:
输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 不需要考虑数组中超出新长度后面的元素。
代码实现:
//这种类型题的通解做法
//原地删除有序数组中的重复项 使每个数字只出现k次
//由于是保留 k 个相同数字,对于前 k 个数字,我们可以直接保留
//对于后面的任意数字,能够保留的前提是:与当前写入的位置前面的第 k 个元素进行比较,不相同则保留
//我们令 k = 2,假设有如下样例
//当len<k时直接保留即可;当len>k后 看nums[len-k]是否等于num 不等才保留;
//
//[1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3]
//
//首先我们先让前 2 位直接保留,得到 1, 1
//对后面的每一位进行继续遍历,能够保留的前提是与当前位置的前面 k 个元素不同(答案中的第一个 1)
//因此我们会跳过剩余的 1,将第一个 2 追加,得到 1, 1, 2
//继续这个过程,这时候是和答案中的第 2 个 1 进行对比,因此可以得到 1, 1, 2, 2
class Solution
{
public:
int removeDuplicate(vector<int>& nums)
{
return work(nums, 2);
}
int work(vector<int>& nums, int k)
{
int len = 0;
for (auto num : nums)
{ //len<k时 直接保留;len>k时 比较nums[len-k]和当前num是否相等 不等才进行保留;
if (len < k || nums[len - k] != num)//对于前k个数字不论是否重复 直接保留即可;
{
nums[len++] = num;//当前值符合保留的条件 对当前遍历到的数值进行保留
}
}
return len;
}
};
leetcode 316 【去除重复字母M】
给你一个字符串 s
,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小
(要求不能打乱其他字符的相对位置)。
比如:
输入:s = “bcabc”
输出:“abc”
输入:s = “cbacdcbc”
输出:“acdb”
代码实现:
// 之所以用这种方法求解是因为要保持字典序最小;
// 从a到z对应ascii码值是递增的;
class Solution {
public:
string removeDuplicateLetters(string s)
{
vector<int> Hashtable(128, 0);//数组作哈希表 比单纯的哈希表要快
vector<bool> flag(128, false);//判断这个值是否已经“定下来了”,假如定下来了,那扫到的时候直接跳,当没看见就完事了
for (char x : s) Hashtable[x]++;//记录下次数
string ans = "0";//为什么要预先放个零呢?充当guard,省的判空那么麻烦了,后面的肯定比零大,因为都是字母,返回的时候从1开始返回
for (char x : s)
{
Hashtable[x]--;//每遍历到一个元素 就递减其出现次数
if (flag[x]) continue;//元素已经被确定了 跳过重复的元素; //当前元素比尾部元素大,就一直在尾部添加元素;当前元素比尾部元素小 就一直删除尾部元素 维持尾部的元素是一个最小元素
while (x < ans.back() && Hashtable[ans.back()]) //以bcabc为例 对于b 我们要的b是a之后的b 我们要的c是a之后的c 这样才可以保证字典序的顺序;
{ //删除元素就是没使用这个元素 将其标记为false
flag[ans.back()] = false;//对于bcabc当遍历到a后,因为a<b a<c就会依次删除尾部的元素c b
ans.pop_back();//删除这个元素的同时要判断这个元素在后面还是会出现的,保证这个元素在新的字符串中只出现一次;
}
ans += x;
flag[x] = true;//在新的字符串中确定了一个元素
}
return ans.substr(1);//获取字符串ans中从第一位开始到最后一位的字符串;第0位是"0"不获取
}
};
删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母
,并删除它们。
在 S 上反复执行重复项删除操作,直到无法继续删除。
输入:"abbaca"
输出:"ca"
解释:
例如,在 “abbaca” 中,我们可以删除 “bb” 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 “aaca”,其中又只有 “aa” 可以执行重复项删除操作,所以最后的字符串为 “ca”。
代码实现:
class Solution {
public:
string removeDuplicates(string S)
{
stack<char>st;
for(char s:S)
{
if(st.empty()||s!=st.top())//有两种情况会入栈 一种是一开始栈为空时,另一种是当前元素和栈顶元素不等的时候;
{
st.push(s);
}
else
{
st.pop();
}
}
string res="";
while(!st.empty())
{
res+=st.top();
st.pop();
}
reverse(res.begin(),res.end());
return res;
}
};
//用1209的方法求解即可 k==2
class Solution {
public:
string removeDuplicates(string s) {
vector<pair<char,int>>res;
for(auto&i:s)
{
if(!res.empty()&&res.back().first==i)
{
if(++res.back().second==2)
{
res.pop_back();
}
}
else
{
res.push_back(make_pair(i,1));
}
}
string ans;
for(auto&s:res)
{
int n=s.second;
while(n--)
{
ans+=s.first;
}
}
return ans;
}
};
leetcode 1209 删除字符串中的相邻重复元素 II(删除k个相邻重复的元素)
题目就是删除字符串中相邻的k个字符串
,返回删除之后的字符串;
对字符串重复进行操作,直到无法继续进行为止;
比如:
输入:s = "deeedbbcccbdaa", k = 3
输出:“aa”
解释:
先删除 “eee” 和 “ccc”,得到 “ddbbbdaa”
再删除 “bbb”,得到 “dddaa”
最后删除 “ddd”,得到 “aa”
对于上述例子:
会先向容器中存入 d-1 e-2 (d 出现次数为1, e出现次数为2)
当遍历到第三个e时,因为此时2+1=3=k正好满足题目的k ,然后进行删除,删除e-2即可,即达到删除重复3次的元素的目的;
小注
:
容器中保存的是元素和其出现次数组成的pair数据结构,不是多个相同的元素,对于重复的元素,只存储一个元素和一个表示其出现次数的数值
;
题目给定的k是3,以删除e为例,当遍历到第三个e时,发现前面已经遍历到了两个e,则2+1=3 此时就要删除e这个元素,那么容器中就不会存在这个e元素了,每遍历到相同的元素,pair就会递增其出现次数并存储下来,当计数值达到k-1时,就在容器中删除这个元素;
因为我们的数据存储类型是pair, 那么存储的e 的数据模式是一个e对应一个数字3,只删除这个
代码实现:
//删除字符串中连续重复k个的元素
class Solution
{
public:
string removeDuplicates(string s, int k)
{
vector<pair<char, int>>res;//vector中存放的元素类型是pair
for (auto& i : s)
{
if (!res.empty() && res.back().first == i)//遇到重复元素 并且此时这个元素的出现次数加一正好等于k
{
if (++res.back().second == k)//只有当达到指定的重复次数时 才开始删除元素;没到之前 每遇到重复的元素 就会递增他们的出现次数
{
res.pop_back();
}
}
else
{
res.push_back(make_pair(i, 1));//存入一个pair类型的数据进res中 i是元素 初始化元素的出现次数为1 ;如果遇到的是相同的元素 递增第二个参数,增加他们的出现次数
}
}
string ans;//拼接返回最后的结果
for (auto& i : res)
{
int n = i.second;//n是几个单词 表示其出现次数
while (n--)
{
ans += i.first;//拼接几个重复的单词即可
}
}
return ans;
}
};