数据结构---删去k个数字后的最小值

给出一个整数,从该整数中去掉k个数字,要求剩下的数字形成的新整数尽可能小。应该如何选取被去掉的数字?
其中整数的长度大于或等于k,给出的整数的大小可以超过long类型的数字范
围。
在这里插入图片描述

思路

但我们不妨把问题简化一下:如果只删除1个数字,如何让新整数的值最小?
数字的大小固然重要,数字的位置则更加重要。

无论删除哪一个数字,最后的结果都是从9位整数变成8位整数。既然同
样是8位整数,显然应该优先把高位的数字降低,这样对新整数的值影响最大。

在这里插入图片描述

关键点:如何把高位数字减低
把原整数的所有数字从左到右进行比较,如果发现某一位数字大于它右面的数字,那么在删除该数字后,必然会使该数位的值降低,因为右面比它小的数字顶替了它的位置。

贪心算法

依次求得局部最优解,最终得到全局最优解的思想,叫作贪心算法。
在本题中:每一步都要求得到删除一个数字后的最小值,经历3次,相当于求出了删除k(k=3)个数字后的最小值。

第一步
在这里插入图片描述
第二步
在这里插入图片描述
第三步
在这里插入图片描述

JAVA实现1

public class removeNDigits {

    //清除字符串左边的数字0
    private  static String removeZero(String num){
        for (int i=0;i<num.length()-1;i++){
            if(num.charAt(0)!='0'){
                //首元素不为零,跳出,不用删0了
                break;
            }
            //删除第一个位置的0(之后继续循环)
            num = num.substring(1,num.length());
        }

        return num;
    }


    /**
     *
     * @param num   原整数
     * @param k     删除数量
     * @return
     */
    public static String removekDigits(String num,int k){
        String numNew = num;
        for (int i=0;i<k;i++){
            //是否截取字符串的标记
            boolean hasCut=  false;
            //从左向右遍历,找到比自己右侧数字大的数字并删除
            //substring截取不包括右边的下标
            for (int j=0;j<numNew.length()-1;j++){
                if(numNew.charAt(j)>numNew.charAt(j+1)){
                    numNew = numNew.substring(0,j)+numNew.substring(j+1,numNew.length());
                    hasCut = true;
                    //完成一轮了(一共k)
                    break;
                }
            }
            //在一轮中,如果没有找到要删除的数字,则删除最后一个数字
            if (!hasCut){
                numNew = numNew.substring(0,numNew.length()-1);
            }
            //清除整数左侧的数字0
            numNew=removeZero(numNew);
        }
        //如果整数的所有数字都被删除了,直接返回0
        if(numNew.length()==0){
            return "0";
        }
        return numNew;
    }

测试类

public static void main(String[] args) {
        System.out.println(removekDigits("30200",1));
        System.out.println(removekDigits("1593212",1));
        System.out.println(removekDigits("1593212",2));
        System.out.println(removekDigits("1593212",3));
    }

在这里插入图片描述
代码使用了两层循环,外层循环次数就是要删除的数字个数k,内层循环
从左到右遍历所有数字。当遍历到需要删除的数字时,利用字符串的自身方法subString()把对应的数字删除,并重新拼接字符串。

时间复杂度是O(kn)。

存在的问题

  1. 每一次内层循环都需要从头开始遍历所有数字。
    事实上,我们应该停留在上一次删除的位置继续进行比较,而不是再次从头开始遍历。

  2. subString方法本身性能不高

    subString方法的底层实现,涉及新字符串的创建,以及逐个字符的复制。这个方法自身的时间复杂度是O(n)。

JAVA实现2

以遍历数字作为外循环,以k作为内循环
利用栈来实现
栈的特性,在遍历原整数的数字时,让所有数字一个一个入栈,
利用栈来回溯遍历过的数字及删除数字
当某个数字需要删除时,让该数字出栈。最后,程序把栈中的元素转化为字符串类型的结果。

当栈顶数字大于遍历到的当前数字时,栈顶数字出栈(相当于删除数字)
while (top>0&&stack[top-1]>c&&k>0){ top = top-1; //栈顶元素出栈 k = k-1; //需要删除的元素少一个(刚删了一个) }

例子:541 270 936,k=3
在这里插入图片描述
当遍历到数字4时,发现栈顶5>4,栈顶5出栈,数字4入栈。
在这里插入图片描述
当遍历到数字1时,发现栈顶4>1,栈顶4出栈,数字1入栈
在这里插入图片描述
在这里插入图片描述

遍历数字0,发现栈顶7>0,栈顶7出栈,数字0入栈。
在这里插入图片描述
此时k的次数已经用完,无须再比较,让剩下的数字一起入栈即可
在这里插入图片描述

遍历的时间复杂度是O(n),把栈转化为字符串的时间复杂度也是O(n),所以最终的时间复杂度是O(n)。
程序中利用栈来回溯遍历过的数字及删除数字,所以程序的空间复杂度是O(n)。

public static String removeDigits(String num,int k){
        //新整数的最终长度 = 原整数长度-k(删去k个数字后)
        int newLength = num.length()-k;
        //创建一个栈,用于接收元来字符串num中所有的数字(底层实现是数组)
        char[] stack = new char[num.length()];
        int top = 0;//栈顶指针
        for (int i = 0;i<num.length();++i){
            //遍历当前字符串num的数字
            char c = num.charAt(i);
            //当栈顶数字大于遍历到的当前数字时,栈顶数字出栈(相当于删除数字)
            //栈顶元素stack[top-1]
            //k>0表示还有元素需要删除没有
            //top>0表示非空栈
            while (top>0&&stack[top-1]>c&&k>0){
                top = top-1; //栈顶元素出栈
                k = k-1;     //需要删除的元素少一个(刚删了一个)
            }
            //到这一步,表示没有需要出栈的,就把遍历的元素入栈
            stack[top++] = c;
        }
        //现在已经删除好了,就差删除第一个非零元素前面的0了
        // 找到栈中第1个非零数字的位置,以此构建新的整数字符串
        int offset = 0;//就是找的第一个非零元素的位置
        while (offset<newLength&&stack[offset]=='0'){
            offset++;
        }
        /**
         * 如果offset==0,说明这个栈组成的数字就是0了
         * 如果offset!=0,就截取栈元素,我们需要非零元素之后的元素组成的数字
         * 也就是【offset,newLength-offset】
         */
        return offset==newLength?"0":new String(stack,offset,newLength-offset);

    }

测试方法:

public static void main(String[] args) {
        System.out.println(removeDigits("541270936",3));
    }

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值