31. 下一个排列
实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。
如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。
必须原地修改,只允许使用额外常数空间。
以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1
算法描述
目标是找到第比当前序列大的序列的最小值
那么 显然为了使得找到的序列最小 我们从往前找 找到一个逆序 因为若是升序 在交换完了之后 无法使得序列变大 我们找到第一个逆序 把他和升序中第一个大于他的元素交换 并且将升序反转成降序 即可满足要求
算法流程:
1 从后往前找 找到第一个逆序的元素 k-1
2 不存在k-1 那么 表明初始序列就是升序的(从后往前看) 直接翻转就行了
3 存在k-1 那么我们从k开始找 找到第一个大于k-1的元素 然后交换他们 并把k到末尾翻转即可
找规律
class Solution {
public:
void nextPermutation(vector<int>& nums) {
int k=nums.size()-1;
while(k>0 && nums[k-1]>=nums[k]) k--;
if(k<=0){
reverse(nums.begin(),nums.end());
}else{
int t=k;
while(t<nums.size() && nums[t]>nums[k-1]) t++;
swap(nums[t-1],nums[k-1]);
reverse(nums.begin()+k,nums.end());
}
}
};
32. 最长有效括号
给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。
输入: “)()())”
输出: 4
解释: 最长有效括号子串为 “()()”
算法描述
我们按照第一个非法右括号的位置将字符串分割为若干部分 可以证明 有效子括号串不会跨越我们的分割线 那么在每个部份内计算有效子括号 然后 取一个max就好了
证明:假设存在一段有效子括号 跨越了分割线 那么 分割线左边的部分一定满足 左括号的个数大于等于右括号 又 分割线左边的部分按照我们划分的定义来说 肯定有右括号的个数严格大于左括号的个数 那么这部分去掉有效子括号在分割线的左边部分 右括号的个数一定严格大于左括号个数 也就是需要被分割 这与我们的假设矛盾 所以假设不成立
算法流程:
1 用栈维护左括号的位置 start记录当前部分的起始位置 初始值为0
2 遇到左括号 当前位置入栈
3 遇到右括号 如果当前栈 非空 栈顶元素出栈 然后继续看栈是否为空 ,如果为空 就代表以当前这个右括号的结尾的有效子括号的开头是start 长度为i-start+1 更新res;如果栈非空 就代表以当前这个右括号结尾的有效子括号的开头是st.top()+1 那么长度就是i-st.top()-1+1
4 遇到右括号且当前栈为空 表明这是一个非法的右括号 更新start=i+1
栈
class Solution {
public:
int longestValidParentheses(string s) {
int n=s.size();
stack<int> st;
int start=0,res=0;
for(int i=0;i<n;i++)
{
if(s[i]=='(')
st.push(i);
else{
if(!st.empty()){
st.pop();
if(st.empty())
res=max(res,i-start+1);
else
res=max(res,i-st.top());
}else{
start=i+1;
}
}
}
return res;
}
};
算法描述
从前往后统计合法有效子括号串 令 左括号的权值为1 有括号的权值为-1,和上面一样 start是部分的开始 随着i的移动 当[start,i] 的区间和为0的时候 显然是一个有效的子括号串 更新答案;若区间和>0 说明 左括号又多余 继续枚举 ;区间和小于0 则说明这是一个非法的右括号 更新start为i+1
因为对于一些特殊的情况 算法不能得到正确解 我们从后往前再比哪里一次即可
贪心
class Solution {
public:
int longestValidParentheses(string s) {
int n=s.size();
int start=0,val=0,res=0;
for(int i=0;i<n;i++)
{
if(s[i]=='(') val++;
else val--;
if(val<0){
val=0;
start=i+1;
}else if (val==0)
res=max(res,i-start+1);
}
start=n-1,val=0;
for(int i=n-1;i>=0;i--)
{
if(s[i]==')') val++;
else val--;
if(val<0)
{
val=0;
start=i-1;
}else if(val==0)
res=max(res,start-i+1);
}
return res;
}
};
33. 搜索旋转排序数组
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
算法描述
给定升序数组旋转之后的数组 要求查找某个数
先二分 找到两个升序数组的分叉点
再判断target是在哪个升序数组上 然后再次二分找到他
二分
class Solution {
public:
int search(vector<int>& nums, int target) {
if(nums.empty()) return -1;
int n=nums.size();
int l=0,r=n-1;
while(l<r){
int mid=(l+r+1)>>1;
if(nums[mid]>=nums[0]) l=mid;
else r=mid-1;
}
if(target>=nums[0]) l=0;
else l=r+1,r=n-1;
while(l<r){
int mid=l+r>>1;
if(nums[mid]>=target) r=mid;
else l=mid+1;
}
if(nums[r]==target) return r;
return -1;
}
};
34. 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
你的算法时间复杂度必须是 O(log n) 级别。
如果数组中不存在目标值,返回 [-1, -1]。
输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]
算法描述
先二分第一个大于等于目标数的数 再二分第一个小于等于目标的数
二分
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.empty()) return {-1,-1};
int n=nums.size();
int l=0,r=n-1;
while(l<r){
int mid=l+r>>1;
if(nums[mid]>=target) r=mid;
else l=mid+1;
}
if(nums[r]!=target) return {-1,-1};
int L=r;
l=0,r=n-1;
while(l<r){
int mid=l+r+1>>1;
if(nums[mid]<=target) l=mid;
else r=mid-1;
}
return {L,r};
}
};
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
输入: [1,3,5,6], 5
输出: 2
二分
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int l=0,r=nums.size();
while(l<r){
int mid=l+r>>1;
if(nums[mid]>=target) r=mid;
else l=mid+1;
}
return r;
}
};