leetcode--最多K次交换相邻数位后得到的最小整数

 题目是LeetCode第196场周赛的第四题,链接:1505. 最多 K 次交换相邻数位后得到的最小整数。具体描述:给你一个字符串num和一个整数k。其中,num表示一个很大的整数,字符串中的每个字符依次对应整数上的各个数位。你可以交换这个整数相邻数位的数字最多k次。请你返回你能得到的最小整数,并以字符串形式返回。

 示例1:

输入:num = "4321", k = 4
输出:"1342"
解释:4321 通过 4 次交换相邻数位得到最小整数的步骤:4321-->3412-->3142-->1342。

 示例2:

输入:num = "100", k = 1
输出:"010"
解释:输出可以包含前导 0 ,但输入保证不会有前导 0 。

 示例3:

输入:num = "36789", k = 1000
输出:"36789"
解释:不需要做任何交换。

 示例4:

输入:num = "22", k = 22
输出:"22"

 示例5:

输入:num = "9438957234785635408", k = 23
输出:"0345989723478563548"

 这道题是一道贪心算法的题目,为了使数变小,我们需要贪心地找到较小的数给放到前面的位置上去。首先给出一种简单的暴力法,就是我们总是在未处理字符串上按照从09的顺序寻找数字的位置,只要发现找到出现了某个数字i而且其相对于未处理字符串头部的位置idx不超过剩余可交换次数k,那么就把这个数给移动到字符串头部来,然后剩下的字符串(减去被交换到头部的这个数字字符)继续递归处理,同时剩余可交换次数也减小idx次。时间复杂度为 O ( n 2 ) O(n^{2}) O(n2),空间复杂度为 O ( n ) O(n) O(n)

 JAVA版代码如下:

class Solution {
    public String minInteger(String num, int k) {
        if (k == 0 || num == null) {
            return num;
        }
        for (int i = 0; i <= 9; ++i) {
            int idx = num.indexOf(String.valueOf(i));
            if (idx >= 0 && idx <= k) {
                return String.valueOf(i) + minInteger(num.substring(0, idx) + num.substring(idx + 1, num.length()), k - idx);
            }
        }
        return num;
    }
}

 JAVA版直接超时了,Python的不会,看下面的代码。

 Python版代码如下:

class Solution:
    
    def minInteger(self, num: str, k: int) -> str:
        if k == 0 or not num:
            return num
        for i in range(10):
            idx = num.find(str(i))
            if 0 <= idx <= k:
                return num[idx] + self.minInteger(num[0:idx] + num[idx + 1:], k - idx)

 提交结果如下:


 为了进一步减低复杂度,考虑每次我们在寻找一个比当前数字小的数字时需要计算从这个数字到当前数字的交换次数,一般这里需要 O ( n ) O(n) O(n),因为需要遍历这个数到当前数之间,看有多少个数还未被交换过,这个数量就是所需要的交换次数,而这正是一种区间查询的操作,所以可以用线段树来达到 O ( l o g n ) O(logn) O(logn)的复杂度,从而将总体时间复杂度降为 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度还是 O ( n ) O(n) O(n)

 JAVA版代码如下:

class Solution {
    public String minInteger(String num, int k) {
        char[] nums = num.toCharArray();
        int n = nums.length;
        SegmentTree segmentTree = new SegmentTree(n);
        LinkedList[] position = new LinkedList[10];
        for (int i = 0; i < 10; ++i) {
            position[i] = new LinkedList<Integer>();
        }
        for (int i = n - 1; i >= 0; --i) {
            position[nums[i] - '0'].addLast(i);
        }
        int i = 0;
        StringBuilder sb = new StringBuilder();
        while (i < n) {
            int digit = nums[i] - '0';
            if (position[digit].size() == 0 || (int)(position[digit].getLast()) > i) {
                ++i;
                continue;
            }
            for (int j = 0; j <= digit; ++j) {
                if (position[j].size() == 0) {
                    continue;
                }
                int originIdx = (int)position[j].getLast();
                int count = originIdx - segmentTree.getSum(originIdx + 1);
                if (count <= k) {
                    k -= count;
                    sb.append(nums[originIdx]);
                    position[j].removeLast();
                    segmentTree.update(originIdx + 1, 1);
                    break;
                }
            }
        }
        return sb.toString();
    }
}

class SegmentTree {
    int n;
    int[] tree;

    public SegmentTree(int val) {
        n = val;
        tree = new int[val + 1];
    }

    public int lowbit(int x) {
        return x & (-x);
    }

    public void update(int idx, int x) {
        while (idx <= n) {
            tree[idx] += x;
            idx += lowbit(idx);
        }
    }

    public int getSum(int idx) {
        int res = 0;
        while (idx > 0) {
            res += tree[idx];
            idx -= lowbit(idx);
        }
        return res;
    }
}

 提交结果如下:


 Python版代码如下:

class Solution:
    
    def minInteger(self, num: str, k: int) -> str:
        n = len(num)
        # 线段树上0代表未被换到前面去,1代表被换了
        # 所以getSum得到的就是当前位置前面被换过了的数量
        # 等价于未处理字符串的开头位置
        segmentTree = SegmentTree(n)
        # 记录0~9各个数字的位置
        position = [[] for _ in range(10)]
        for i in range(n - 1, -1, -1):
            position[int(num[i])].append(i)
        res = []
        i = 0
        while i < n:
            digit = int(num[i])
            if not position[digit] or position[digit][-1] > i:
                # 已经被交换过的了,忽略掉
                i += 1
                continue
            for j in range(digit + 1):
                if not position[j]:
                    continue
                # 在num中的索引
                originIdx = position[j][-1]
                # 找到一个比num[i]小的了,但还需要看距离是否够
                count = segmentTree.getSum(originIdx + 1)
                # 实际需要多少次的交换
                needCount = originIdx - count
                if needCount <= k:
                    k -= needCount
                    position[j].pop()
                    res.append(str(j))
                    segmentTree.update(originIdx + 1, 1)
                    break
        return ''.join(res)


class SegmentTree:
    def __init__(self, n):
        self.n = n
        self.tree = [0] * (n + 1)
    
    def lowbit(self, x):
        return x & (-x)
    
    def update(self, i, x):
        while i <= self.n:
            self.tree[i] += x
            i += self.lowbit(i)

    def getSum(self, i):
        res = 0
        while i > 0:
            res += self.tree[i]
            i -= self.lowbit(i)
        return res

 提交结果如下:


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LeetCode-Editor是一种在线编码工具,它提供了一个用户友好的界面编写和运行代码。在使用LeetCode-Editor时,有时候会出现乱码的问题。 乱码的原因可能是由于编码格式不兼容或者编码错误导致的。在这种情况下,我们可以尝试以下几种解决方法: 1. 检查文件编码格式:首先,我们可以检查所编辑的文件的编码格式。通常来说,常用的编码格式有UTF-8和ASCII等。我们可以将编码格式更改为正确的格式。在LeetCode-Editor中,可以通过界面设置或编辑器设置来更改编码格式。 2. 使用正确的字符集:如果乱码是由于使用了不同的字符集导致的,我们可以尝试更改使用正确的字符集。常见的字符集如Unicode或者UTF-8等。在LeetCode-Editor中,可以在编辑器中选择正确的字符集。 3. 使用合适的编辑器:有时候,乱码问题可能与LeetCode-Editor自身相关。我们可以尝试使用其他编码工具,如Text Editor、Sublime Text或者IDE,看是否能够解决乱码问题。 4. 查找特殊字符:如果乱码问题只出现在某些特殊字符上,我们可以尝试找到并替换这些字符。通过仔细检查代码,我们可以找到导致乱码的特定字符,并进行修正或替换。 总之,解决LeetCode-Editor乱码问题的方法有很多。根据具体情况,我们可以尝试更改文件编码格式、使用正确的字符集、更换编辑器或者查找并替换特殊字符等方法来解决这个问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值