其他分享
首页 > 其他分享> > leetcode — 402. 移掉 K 位数字

leetcode — 402. 移掉 K 位数字

作者:互联网

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

示例 1 :

输入:num = "1432219", k = 3
输出:"1219"
解释:移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219 。

示例 2 :

输入:num = "10200", k = 1
输出:"200"
解释:移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。

示例 3 :

输入:num = "10", k = 2
输出:"0"
解释:从原数字移除所有的数字,剩余为空就是 0 。

提示:


        题意:给出一个整数,从该整数中去掉 k 个数字,要求剩下的数字形成新的整数尽可能小。应该如何选取被去掉的数字?

        其中整数的长度 大于 或 等于 k,给出的整数的大小可以超过 long 类型的数字范围。 

对于两个相同长度的数字序列,最左边不同的数字决定了这两个数字的大小。

  • 例如,对于 A = 1axxx,B = 1bxxx,如果 a > b则 A > B。
  • 基于此,若要使得剩下的数字最小,需要保证靠前的数字尽可能小。

        e.g. 给定一个数字序列,例如 425,如果要求只删除一个数字,那么从左到右,有 4、2 和 5 三个选择。将每一个数字和它的左邻居进行比较。从 2 开始,2 小于它的左邻居 4。假设保留数字 4,那么所有可能的组合都是以数字 4(即 42,45)开头的。相反,如果移掉 4,留下 2,得到的是以 2 开头的组合(即 25),明显小于任何留下数字 4 的组合。因此应该移掉数字 4。如果不移掉数字 4,之后无论移掉什么数字,都不会得到最小数。

fig1

基于上述分析,可以得出「删除一个数字」的贪心策略:

  • 给定一个长度为 n 的数字序列[D_0D_1D_2D_3\ldots D_{n-1}],从左往右找到第一个位置 i(i>0)使得 D_i<D_{i-1} ,并删去 D_{i-1};如果不存在,说明整个数字序列单调不降,删去最后一个数字即可。
  • 基于此,可以每次对整个数字序列执行一次这个策略;删去一个字符后,剩下的 n−1 长度的数字序列就形成了新的子问题,可以继续使用同样的策略,直至删除 k 次。
  • 暴力的实现复杂度最差会达到 O(nk)(考虑整个数字序列是单调不降的),需要加速这个过程。

  • 考虑从左往右增量的构造最后的答案。可以用一个栈维护当前的答案序列,栈中的元素代表截止到当前位置,删除不超过 k 次个数字后所能得到的最小整数。根据之前的讨论:在使用 k 个删除次数之前,栈中的序列从栈底到栈顶单调不降。 

    • 对于每个数字,如果该数字小于栈顶元素,就不断地弹出栈顶元素,直到

      • 栈为空

      • 或者新的栈顶元素不大于当前数字

      • 或者已经删除了 k 位数字 

  • 上述步骤结束后还需要针对一些情况做额外的处理:
    • 如果删除了 m 个数字且 m<k,这种情况需要从序列尾部删除额外的 k−m 个数字。
    • 如果最终的数字序列存在前导零,要删去前导零。
    • 如果最终数字序列为空,应该返回 0。
  • 最终从栈底到栈顶的答案序列即为最小数。
    • 考虑到栈的特点是后进先出,如果通过栈实现,则需要将栈内元素依次弹出然后进行翻转才能得到最小数。为了避免翻转操作,可以使用双端队列代替栈的实现。

    • 时间复杂度:O(n),其中 n 为字符串的长度。尽管存在嵌套循环,但内部循环最多运行 k 次。由于 0<k≤n,主循环的时间复杂度被限制在 2n 以内。对于主循环之外的逻辑,时间复杂度是 O(n),因此总时间复杂度为 O(n)。

    • 空间复杂度:O(n)。栈存储数字需要线性的空间。


最原始思路实现:降低最高位的数值。 

import java.util.*;

// 移除K位数字,使得剩下的数字形成的新整数尽可能小
public class DeleteKNumberMin {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner sc = new Scanner(System.in);
		while(sc.hasNext()) {
			String nums = sc.next();
			int k = sc.nextInt();
			
			System.out.println(removeKDigits(nums, k));
			
		}
		
		sc.close();
	}

	private static String removeKDigits(String nums, int k) {
		// TODO Auto-generated method stub
		// 删除k个数字,比较相邻元素的大小
		// 从左向右遍历,找到比自己右侧数字大的数字并删除
		for(int i = 0; i < k; i ++) {
			boolean hasDelete = false;
			for(int j = 0; j < nums.length() - 1; j ++){
				if(nums.charAt(j) > nums.charAt(j+1)) {
					nums = nums.substring(0,j) + nums.substring(j+1,nums.length());
					hasDelete = true;
					break;
				}
			}
			
			// 如果没有找到要删除的数字,则删除最后一个数字
			if(hasDelete == false) {
				nums = nums.substring(0, nums.length()-1);
			}
		}
		
		// 清除整数左侧的数字 0
		int start = 0;
		for(int j = 0; j < nums.length() - 1; j ++) {
			if(nums.charAt(j) != '0') {
				break;
			}
			start ++;
		}
		nums = nums.substring(start,nums.length());
		
		// 如果整数的所有数字都被删除了,直接返回0
		if(nums.length() == 0) {
			return "0";
		}
		
		return nums;
	}

}

方案优化:贪心 + 单调栈 —— 以遍历数字作为外循环,以 k 作为内循环

import java.util.*;

// 使用单调栈存放最终的结果,进行贪心寻找
public class DeleteKNumberMinSecond {

	public static void main(String[] args) {

		Scanner sc = new Scanner(System.in);
		while(sc.hasNext()) {
			String nums = sc.next();
			int k = sc.nextInt();
			
			System.out.println(removeKDigits(nums, k));
			
		}
		
		sc.close();
	}

	private static String removeKDigits(String nums, int k) {

		// 贪心 + 单调栈(使用双端队列模拟,方便结果的输出)
		Deque<Character> deque = new LinkedList<Character>();
		
		for(int i = 0; i < nums.length(); i ++) {
			char digit = nums.charAt(i);
			// 出栈的条件:双端栈不为空、k>0(还未删除完需要删除的个数)、栈顶的元素大于当前的数字
			// 当栈顶数字大于遍历到的当前数字时,栈顶数字出栈(相当于删除数字)
			while(deque.isEmpty() == false && k > 0 && deque.peekLast() > digit) {
				deque.pollLast();
				k --;
			}
			// 遍历到的当前数字入栈
			deque.offerLast(digit);
		}
		
		/* 对一些额外情况进行考虑
		 1、删除 m 个数字且 m<k,需要从序列尾部删除额外的 k-m 个数字。
		 2、如果最终的数字序列存在前导零,删去前导零。
		 3、如果最终数字序列为空,返回 0。 
		 */
		for(int i = 0; i < k; i ++) {
			deque.pollLast();
		}
		
		StringBuilder result = new StringBuilder();
		boolean isBeginningZero = true;
		while(!deque.isEmpty()) {
			char digit = deque.pollFirst();
			if(isBeginningZero && digit != '0') {
				isBeginningZero = false;
			}
			
			if(isBeginningZero == false) {
				result.append(digit);
			}
		}
		
		if(result.length() == 0) {
			return "0";
		}
		
		return result.toString();
	}

}

标签:数字,删除,nums,int,整数,402,sc,移掉,leetcode
来源: https://blog.csdn.net/qq_34176797/article/details/119316329