刷题05数组mid

 11.盛水多的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。

        此题使用双指针对撞的方式行遍历数组 ,然后更新最大值。需要注意的是计算面积时长的计算是i-j,不用加1。

int maxArea(int* height, int heightSize) {
    int mmax=0;
    int i=0,j=heightSize-1;
    while(i<j){
        int cur=fmin(height[i],height[j])*(j-i);
        if(cur>mmax) mmax=cur;
        if(height[i]<height[j]){
            i++;
        }else{
            j--;
        }
    }
    return mmax;
}

416.分割等和子集 

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。

        此题暴力法需要枚举所有子数组之和,判断是否为sum/2,复杂度很高。

        动态规划: dp[i][j] 表示从数组的 [0,i] 下标范围内选取若干个正整数(可以是 0个),是否存在一种选取方案使得被选取的正整数的和等于 j。初始时,dp 中的全部元素都是false。

        如果 j≥nums[i],可以选取也可以不选取,两种情况只要有一个为 true,就有dp[i][j]=true。
        如果不选取 nums[i],则 dp[i][j]=dp[i−1][j];
        如果选取 nums[i],则 dp[i][j]=dp[i−1][j−nums[i]]


bool canPartition(int* nums, int numsSize){
    if(numsSize<2) return false;
    int sum=0;
    int mmax=0;
    for(int i=0;i<numsSize;++i){
        sum+=nums[i];
        mmax=fmax(mmax,nums[i]);
    }
    if(sum%2==1) return false;
    sum=sum/2;
    if(mmax>sum) return false;
    int dp[numsSize][sum+1];
    memset(dp,0,sizeof(dp));
    dp[0][nums[0]]=true;
    for(int i=1;i<numsSize;++i){
        int cur=nums[i];
        for(int j=1;j<=sum;++j){
            if(j>=cur){
                dp[i][j]=dp[i-1][j]|dp[i-1][j-cur];
            }else{
                dp[i][j]=dp[i-1][j];
            }
        }
    }
    return dp[numsSize-1][sum];
}

        另一种优化写法:用滚动数组进行表示,dp[i]表示子序列能够到达的大于等于target的和,当dp[target]==target时,说明存在子序列之和正好为taget。 

bool canPartition(int* nums, int numsSize){
   //dp[i]中的i表示背包内总和
    int sum=0;
    for(int i=0;i<numsSize;++i){
        sum+=nums[i];
    }
    if(sum%2==1) return false;
    int target=sum/2;
    int dp[10002]={0};
    for(int i=0;i<numsSize;++i){
        for(int j=target;j>=nums[i];j--){
            dp[j]=fmax(dp[j],dp[j-nums[i]]+nums[i]);
        }
    }
    if(dp[target]==target) return true;
    return false;
}


316. 去除重复字母

给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。

输入:s = "bcabc" 输出:"abc"

        贪心+单调栈 

  1. 去除字符串中的重复字母;
  2. 不打乱相对位置;
  3. 输出字典序最小。
char* removeDuplicateLetters(char* s) {
    int flag[26]={0};
    int visited[26]={0};
    int n=strlen(s);
    for(int i=0;i<n;++i){
        flag[s[i]-'a']++;
    }
    char* stk=malloc(sizeof(char)*27);
    int top=0;
    for(int i=0;i<n;++i){
        if(!visited[s[i]-'a']){
            //该字母在栈中没有出现过
            while(top>0&&stk[top-1]>s[i]){
                //栈不为空&&栈顶元素字典序靠后
                if(flag[stk[top-1]-'a']>0){
                    //栈顶元素有剩余出现次数
                    visited[stk[--top]-'a']=0;//弹出栈
                }else{
                    break;
                }
            }
            //修改出现状态
            visited[s[i]-'a']=1;
            //字母压栈
            stk[top++]=s[i];
        }
        flag[s[i]-'a']-=1;
        //遍历过的字母出现次数减1
    }
    stk[top]='\0';
    return stk;
}


402. 移掉 K 位数字

 给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。

输入:num = "1432219", k = 3
输出:"1219"
解释:移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219 。
char * removeKdigits(char * num, int k){
    int n=strlen(num);
    int top=0;
    char* stk=malloc(sizeof(char)*(n+1));
    stk[0]=num[0];
    for(int i=1;i<n;++i){
        while(top>0&&stk[top]>num[i]&&k>0){
            top--;
            k--;
        }
        stk[++top]=num[i];
    }
    top-=k;
    char* ans=malloc(sizeof(char)*(n+1));
    int anssize=0;
    bool isZore=true;
    for(int i=1;i<=top;++i){
        if(isZore&&stk[i]=='0'){
            continue;
        }
        isZore=false;
        ans[anssize++]=stk[i];
    }
    if(anssize==0){
        ans[0]='0';ans[1] ='\0';
    }else{
        ans[anssize]='\0';
    }
    return ans;
}


670. 最大交换

给定一个非负整数,你至多可以交换一次数字中的任意两位。返回你能得到的最大值。

输入: 2736
输出: 7236
解释: 交换数字2和数字7。
void swap(char* s1,char* s2){
    char temp=*s1;
    *s1=*s2;
    *s2=temp;
}
int maximumSwap(int num) {
    char str[100];
    sprintf(str,"%d",num);
    int n=strlen(str);
    int maxIdx=n-1;
    int idx1=-1,idx2=-1;
    //从右到左枚举每一位
    for(int i=n-1;i>=0;--i){
        if(str[i]>str[maxIdx]){
            //找到更大的位数
            maxIdx=i;
        }else if(str[i]<str[maxIdx]){
            //基于当前最大位找到左侧更小的位数
            idx1=i;
            idx2=maxIdx;
        }
    }
    if(idx1>=0){
        //如果可以交换,则进行交换
        swap(&str[idx1],&str[idx2]);
        return atoi(str);
    }else{
        return num;
    }
}


1053. 交换一次的先前排列

给你一个正整数数组 arr(可能存在重复的元素),请你返回可在 一次交换(交换两数字 arr[i] 和 arr[j] 的位置)后得到的、按字典序排列小于 arr 的最大排列。如果无法这么操作,就请返回原数组。

输入:arr = [3,2,1]
输出:[3,1,2]
解释:交换 2 和 1
int* prevPermOpt1(int* arr, int arrSize, int* returnSize) {
    //贪心从后往前找小于a[i]的最大的数a[j],如果存在多个数满足条件,选第一个进行交换。
    for (int i=arrSize-2; i>=0; i--) {//从后进行枚举
        if (arr[i]>arr[i+1]) {//第一个非递减位置
            int j=arrSize-1;
            while (arr[j]>=arr[i]||arr[j]==arr[j-1]) {
                j--;//寻找并自己小并且距离最近的元素,元素已经是递增排列。直接枚举即可
            }
            int val=arr[i];//交换
            arr[i]=arr[j];
            arr[j]=val;
            break;
        }
    }
    *returnSize=arrSize;
    return arr;
}

678. 有效的括号字符串

给你一个只包含三种字符的字符串,支持的字符类型分别是 '('')' 和 '*'。请你检验这个字符串是否为有效字符串,如果是有效字符串返回 true 。

任何左括号 '(' 必须有相应的右括号 ')'

任何右括号 ')' 必须有相应的左括号 '(' 。

左括号 '(' 必须在对应的右括号之前 ')'

'*' 可以被视为单个右括号 ')' ,或单个左括号 '(' ,或一个空字符串。

一个空字符串也被视为有效字符串。

输入:s = "(*))"
输出:true

 两次遍历 第一次遍历:从前往后遍历确定')',排除类似'))'的情况; 第二次遍历:从后往前遍历确定'(',排除类似'(('的情况; 其余的都符合

bool checkValidString(char* s) {
    int l=0,m=0;//l记录左括号,m记录*
    int n=strlen(s);
    for(int i=0;i<n;++i){
        char c=s[i];
        if(c=='('){
            l++;
        }else if(c==')'){
            l--;
        }else{
            m++;
        }
        if(l<0){
            m--;
            l++;
        }
        if(m<0) return false;
    }
    int r=0;//r记录右括号
    m=0;
    for(int i=n-1;i>=0;--i){
        char c=s[i];
        if(c==')'){
            r++;
        }else if(c=='('){
            r--;
        }else{
            m++;
        }
        if(r<0){
            m--;
            r++;
        }
        if(m<0) return false;
    }
    return true;
}

 
738. 单调递增的数字

当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。

给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增 。

输入: n = 10
输出: 9
int monotoneIncreasingDigits(int n) {
    char s[12];
    sprintf(s,"%d",n);
    int l=strlen(s);
    // flag用来标记赋值9从哪里开始
    // 设置为这个默认值,为了防止第二个for循环在flag没有被赋值的情况下执行
    int flag=l;
    for(int i=l-1;i>0;--i){
        if(s[i-1]>s[i]){
            flag=i;
            s[i-1]--;
        }
    }
    //因为要返回最大的值,故后面都填上9
    for(int i=flag;i<l;++i){
        s[i]='9';
    }
    return atoi(s);
}

767. 重构字符串

给定一个字符串 s ,检查是否能重新排布其中的字母,使得两相邻的字符不同。

返回 s 的任意可能的重新排列。若不可行,返回空字符串 "" 。

输入: s = "aab"
输出: "aba"

        计数,然后将相同的字母放在相隔的位置,并且奇数下标与偶数下标交错。

char* reorganizeString(char* s) {
    int n=strlen(s);
    if(n<2) return s;
    int flag[26]={0};
    int mmax=0;
    for(int i=0;i<n;++i){
        char c=s[i];
        flag[c-'a']++;
        mmax=fmax(mmax,flag[c-'a']);
    }
    if(mmax>(n+1)/2){
        return "";
    }
    char* res=malloc(sizeof(char)*(n+1));
    for(int i=0;i<n;++i){
        res[i]=' ';
    }
    res[n]='\0';
    int evenidx=0,oddidx=1;
    int half=n/2;
    for(int i=0;i<26;++i){
        char c='a'+i;
        while(flag[i]>0&&flag[i]<=half&&oddidx<n){
            res[oddidx]=c;
            flag[i]--;
            oddidx+=2;
        }
        while(flag[i]>0){
            res[evenidx]=c;
            flag[i]--;
            evenidx+=2;
        }
    }
    return res;
}

 
945. 使数组唯一的最小增量

给你一个整数数组 nums 。每次 move 操作将会选择任意一个满足 0 <= i < nums.length 的下标 i,并将 nums[i] 递增 1。返回使 nums 中的每个值都变成唯一的所需要的最少操作次数。

输入:nums = [1,2,2]
输出:1
解释:经过一次 move 操作,数组将变为 [1, 2, 3]。

        排序后,记录需要改变的次数。 

int cmp(int* a,int* b){
    return *a-*b;
}
int minIncrementForUnique(int* nums, int numsSize) {
    qsort(nums,numsSize,sizeof(int),cmp);
    int ans=0;
    for(int i=0;i<numsSize-1;++i){
        if(nums[i]>=nums[i+1]){
            ans+=nums[i]+1-nums[i+1];
            nums[i+1]=nums[i]+1;
        }
    }
    return ans;
}


1567. 乘积为正数的最长子数组长度

 给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。请你返回乘积为正数的最长子数组长度。

输入:nums = [1,-2,-3,4]
输出:4
解释:数组本身乘积就是正数,值为 24 。
int getMaxLen(int* nums, int numsSize){
    int n=numsSize;
    int pos[n],neg[n];//表示下标i结尾的最长的正负数组长度
    if(nums[0]>0){//初始化
        pos[0]=1;
    }else if(nums[0]<0){
        neg[0]=1;
    }
    int mmax=pos[0];
    for(int i=1;i<n;++i){
        if(nums[i]>0){
            //如果当前为正
            pos[i]=pos[i-1]+1;
            neg[i]=(neg[i-1]>0?neg[i-1]+1:0);
        }else if(nums[i]<0){
            //如果当前为负
            pos[i]=(neg[i-1]>0?neg[i-1]+1:0);
            neg[i]=pos[i-1]+1;
        }else{
            pos[i]=0;
            neg[i]=0;
        }
        mmax=fmax(mmax,pos[i]);
    }
    return mmax;
}


2358. 分组的最大数量

 给你一个正整数数组 grades ,表示大学中一些学生的成绩。你打算将 所有 学生分为一些 有序 的非空分组,其中分组间的顺序满足以下全部条件:

  • 第 i 个分组中的学生总成绩 小于 第 (i + 1) 个分组中的学生总成绩,对所有组均成立(除了最后一组)。
  • 第 i 个分组中的学生总数 小于 第 (i + 1) 个分组中的学生总数,对所有组均成立(除了最后一组)。返回可以形成的 最大 组数。
输入:grades = [10,6,12,7,3,5]
输出:3
解释:下面是形成 3 个分组的一种可行方法:
- 第 1 个分组的学生成绩为 grades = [12] ,总成绩:12 ,学生数:1
- 第 2 个分组的学生成绩为 grades = [6,7] ,总成绩:6 + 7 = 13 ,学生数:2
- 第 3 个分组的学生成绩为 grades = [10,3,5] ,总成绩:10 + 3 + 5 = 18 ,学生数:3 
可以证明无法形成超过 3 个分组。
int cmp(int* a,int* b){
    return *a-*b;
}
int maximumGroups(int* grades, int gradesSize){
    if(gradesSize<=1) return 1;
    //按成绩从小到大排序,后面只需要考虑人数即可
    qsort(grades,gradesSize,sizeof(int),cmp);
    int ans=0;
    int count=0;
    for(int i=1;i<gradesSize;++i){
        ans++;
        if(count+i>gradesSize){
            //若加上一个分组的人数超过总人数,就将剩余的人分配到前一组
            return ans-1;
        }
        else{
            count+=i;
        }
    }
    return ans;
}


2811. 判断是否能拆分数组

 给你一个长度为 n 的数组 nums 和一个整数 m 。请你判断能否执行一系列操作,将数组拆分成 n 个 非空 数组。

在每一步操作中,你可以选择一个 长度至少为 2 的现有数组(之前步骤的结果) 并将其拆分成 2 个子数组,而得到的 每个 子数组,至少 需要满足以下条件之一:

  • 子数组的长度为 1 ,或者
  • 子数组元素之和 大于或等于  m 。

如果你可以将给定数组拆分成 n 个满足要求的数组,返回 true ;否则,返回 false 。

注意:子数组是数组中的一个连续非空元素序列。

输入:nums = [2, 2, 1], m = 4
输出:true
解释:
第 1 步,将数组 nums 拆分成 [2, 2] 和 [1] 。
第 2 步,将数组 [2, 2] 拆分成 [2] 和 [2] 。
因此,答案为 true 。
bool canSplitArray(int* nums, int numsSize, int m){
    //如果nums 中任何长为2的子数组的元素和都小于m,那么无法满足要求。
    //只要存在相邻的两个之和大于m即可
    if(numsSize<=2) return true;
    for(int i=0;i<numsSize-1;++i){
        if(nums[i]+nums[i+1]>=m) return true;
    }
    return false;
}


10.11. 峰与谷

 在一个整数数组中,“峰”是大于或等于相邻整数的元素,相应地,“谷”是小于或等于相邻整数的元素。例如,在数组{5, 8, 4, 2, 3, 4, 6}中,{8, 6}是峰, {5, 2}是谷。现在给定一个整数数组,将该数组按峰与谷的交替顺序排序。

输入: [5, 3, 1, 2, 3]
输出: [5, 1, 3, 2, 3]
void swap(int *a,int* b){
    int tmp=*a;
    *a=*b;
    *b=tmp;
}
void wiggleSort(int* nums, int numsSize){
    //如果按照偶数位为峰,奇数位为偶来排列
    for(int i=1;i<numsSize;++i){
        if(i%2==0){
            //应该为峰,如果是谷,则交换数值
            if(nums[i]<nums[i-1]){
                swap(&nums[i],&nums[i-1]);
            }
        }else{
            //应该为谷,如果是峰,则交换数值
            if(nums[i]>nums[i-1]){
                swap(&nums[i],&nums[i-1]);
            }
        }
    }
}

        另一种解法: 

int cmp(int* a,int* b){
    return *a-*b;
}
void wiggleSort(int* nums, int numsSize){
    int* ans=malloc(sizeof(int)*numsSize);
    qsort(nums,numsSize,sizeof(int),cmp);
    int pos=0;
    //偶数位放较小值
    for(int i=0;i<numsSize;i+=2){
        ans[i]=nums[pos++];
    }
    //奇数位放较大值
    for(int i=1;i<numsSize;i+=2){
        ans[i]=nums[pos++];
    }
    for(int i=0;i<numsSize;++i){
        nums[i]=ans[i];
    }
}

 16. 部分排序

 给定一个整数数组,编写一个函数,找出索引mn,只要将索引区间[m,n]的元素排好序,整个数组就是有序的。注意:n-m尽量最小,也就是说,找出符合条件的最短序列。函数返回值为[m,n],若不存在这样的mn(例如整个数组是有序的),请返回[-1,-1]

输入: [1,2,4,7,10,11,7,12,6,7,16,18,19]
输出: [3,9]

        暴力法:用一个辅助数值进行排序,比较两个数组不同的值的下标即可 

int cmp(int* a,int* b){
    return *a-*b;
}
int* subSort(int* array, int arraySize, int* returnSize){
    int* res=malloc(sizeof(int)*2);
    res[0]=-1,res[1]=-1;
    *returnSize=2;
    if(arraySize==0) return res;
    int tmp[arraySize];
    for(int i=0;i<arraySize;++i){
        tmp[i]=array[i];
    }
    qsort(array,arraySize,sizeof(int),cmp);
    int l=0,r=0;
    for(int i=0;i<arraySize;++i){
        if(tmp[i]!=array[i]){
            res[0]=i;break;
        }
    }
    for(int i=arraySize-1;i>=0;--i){
        if(tmp[i]!=array[i]){
            res[1]=i;break;
        }
    }
    return res;
}

        双指针+贪心:找到第一个不满足升序条件的下标

int* subSort(int* array, int arraySize, int* returnSize){
    int* res=malloc(sizeof(int)*2);
    *returnSize=2;
    res[0]=-1;res[1]=-1;
    if(arraySize==0) return res;
    int mmax=INT_MIN,mmin=INT_MAX;
    int l=-1,r=-1;
    //对于r,就是要找到左侧比它大的元素
    for(int i=0;i<arraySize;++i){
        if(array[i]<mmax){
            r=i;
        }else{
            mmax=array[i];
        }
    }
    //对于l, 是需要找到它的右侧有元素比它小
    for(int i=arraySize-1;i>=0;--i){
        if(array[i]>mmin){
            l=i;
        }else{
            mmin=array[i];
        }
    }
    res[0]=l;res[1]=r;
    return res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值