力扣556中等-下一个更大元素 III
题意
🎈给你一个正整数 n ,请你找出符合条件的最小整数,其由重新排列 n 中存在的每位数字组成,并且其值大于 n 。如果不存在这样的正整数,则返回 -1 。
注意 ,返回的整数应当是一个 32 位整数 ,如果存在满足题意的答案,但不是 32 位整数 ,同样返回 -1 。
示例 1:
输入:n = 12 输出:21 示例 2:
输入:n = 21 输出:-1
提示:
1 <= n <= 231 - 1 通过次数23,819提交次数69,008
AC代码
👀java版:
class Solution {
public int nextGreaterElement(int n) {
char[] chs = (n+"").toCharArray();
int len = chs.length;
int i;
for( i=len-2;i>=0;i--){
if(chs[i]<chs[i+1]){
int min1 = i+1;
for(int j=i+1;j<len;j++){
if(chs[j]>chs[i]&&chs[j]<=chs[min1]){
min1=j;
}
}
char c=chs[i];
chs[i]=chs[min1];
chs[min1]=c;
break;
}
}
if(i<0) return -1;
Arrays.sort(chs,i+1,len);
// System.out.println(chs[0]);
long sum=0;
for(i=0;i<len;i++){
sum*=10;
sum+=(chs[i]-'0');
}
if(sum>Integer.MAX_VALUE) return -1;
else return (int)sum;
}
}
👀C++版:
class Solution(object):
def nextGreaterElement(self, n):
"""
:type n: int
:rtype: int
"""
nums = [int(x) for x in str(n)]
if sorted(nums)[::-1] == nums:
return -1
m = len(nums)
for i in range(m - 2, -1, -1):
if nums[i] < nums[i + 1]:
for j in range(m - 1, i, -1):
if nums[j] > nums[i]:
nums[i], nums[j] = nums[j], nums[i]
nums[i + 1:] = sorted(nums[i + 1:])
break
break
res = 0
for i in nums:
res = 10 * res + i
return res if res<2**31 else -1
分析
我们想要找到下一个更大的元素,也就是从末位开始找到一个降序的位置,然后将降序的位置置换为后边序列中比他大的最小元素,后边的序列再按照从后向前的降序排列,即为大于n的最小元素。
举例
- 从右往左遍历 找到第一个不是顺序排列的数字,而不是第一个比最右的数小的数
- 找到了这个数i之后再最后的数开始往右遍历,找到第一个比i大的数j与i交换,然后再对i后面的所有数重新排序 eg: 2302431 -> 2303124
从右往左遍历先找到第一个比右边小的数字2,然后找到从右往左找到第一个比2大的数字3,交换这两个数字,然后421重新排序为124 最后得到2303124。
🤦♂️如果上边不能理解,换一种理解思路:
- 从右边开始逆序遍历,找到n右边第一个逆序的地方设为i和j,也就是i<j,此时n的末尾是i;
- 在逆序遍历过程中将每次得到的一个数j插入一个链表末尾中,n不断/10,此时链表从头到尾会是一个升序链表;
- 当遇到i<j的时候,n先除10,将i剔除掉,此时链表末尾是j,要链表中找到第一个大于i的结点,将其值与i交换,并将这个值放入到n的末尾;然后顺序将链表的值拼接到n的末尾,如果拼接后的n>原来的n,则返回结果,否则返回-1。
- 没遇到i<j的情况则最后还是返回-1。
题解过程分析
- 第一个for循环的含义是从后向前遍历,找到一个逆序的字符对,然后再对当前字符对进行处理,if(chs[i]<chs[i+1])即这部分代码。
- 内部for循环的含义是查找当前位置之后的一直到结尾的位置的数字的大于当前位置值的最小值,并使用min1记录下对应的位置,便于之后进行交换,具体代码if(chs[j]>chs[i]&&chs[j]<=chs[min1])。
- 交换i和min1位置的数字之后,break跳出循环,然后使用排序把i到最后的数字进行升序排序,确保i之后的数字大的在后边,这样得出来的值在大于n的情况下才最小。
- 因为使用的char进行存储的数字的值,所以定义一个sum用于统计字符数组中的和,从前向后遍历并统计字符数组的和。
- 还有一点注意的是返回-1的情况Fenwick两种,一种是没有比当前值n大的代码如下:if(i<0) return -1; ,第二种是有比当前n大的,但是其值的范围大于了整型的最大值,就是Integer.MAX_VALUE,也应该返回-1,代码如下:if(sum>Integer.MAX_VALUE) return -1;
- 最后返回sum的时候注意,因为sum最初定义为long行,预防计算过程中溢出,需要把sum强制转换成int类型的数组返回。
图解分析过程
复杂度分析
- 时间复杂度:O(logn)
- 空间复杂度:O(logn)
总结
💨一般看到这种题,我们能想到就是DFS的方式,之前我也尝试的DFS的方式,然后再加上剪枝,最后的时间复杂还是比较高,造成了最后的超时,因为dfs需要用for循环遍历每一个位置上的元素,直到最后,如果长度是10位数字的话需要遍历10!=3628800个结果,所以就会浪费很大的时间复杂度,这个问题因为要找的是币当前值大的最小的值,我们应该思考的方向是尽量从后向前替换相应的值,确保交换对应位置的值之后在大于当前值的情况下尽可能的小。
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿