564. 寻找最近的回文数 : 贪心分析上下界 + 边界处理

简介: 564. 寻找最近的回文数 : 贪心分析上下界 + 边界处理

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 564. 寻找最近的回文数 ,难度为 困难


Tag : 「贪心」、「脑筋急转弯」


给定一个表示整数的字符串 nn ,返回与它最近的回文整数(不包括自身)。如果不止一个,返回较小的那个。


“最近的”定义为两个整数差的绝对值最小。


示例 1:


输入: n = "123"
输出: "121"
复制代码


示例 2:


输入: n = "1"
输出: "0"
解释: 0 和 2是最近的回文,但我们返回最小的,也就是 0。
复制代码


提示:


  • 1 <= n.length <= 181<=n.length<=18
  • nn 只由数字组成
  • nn 不含前导 00
  • nn 代表在 [1, 10^{18} - 1][1,10181] 范围内的整数


上下界分析 + 边界处理



对于任意字符串数值 ss 而言(令其长度为 nn),先考虑如何找到「第一个比其大」和「第一个比其小」的回文串数值(上下界)。


由于是找「最近」的数值,因此一个自然的想法是优先修改低位数字,但由于回文串本身的对称性质,每个「低位」的修改带来的副作用是需要同时修改对应的「高位」,因此一个真正可以修改的位置是(相对)中间的位置。


举个 🌰,对于长度为奇数回文串数值 abcdeabcde,其中 dede 的修改会导致 abab 同步修改,因此最近一个可以修改的低位是 cc;对于长度为偶数的回文串数值 abcdabcd 而言,其中 dd 的修改会导致 aa 同步修改,因此最近一个可以修改的位置是 bcbc


当对目标位置进行修改后,其余位置的数值可由回文串性质唯一确定,因此只需要找到可修改位置相邻数值即可。


例如对于 abcdeabcde 来说,最近的回文数值的前三位可能是 abcabcabc+1abc+1abc-1abc1 三者之一,其他位置的数值随着前三位的确定而唯一确定。


上述分析对于一般情况成立,而边界情况是指 abc + 1abc+1abc - 1abc1 导致整体长度发生变化,即 abc=999abc=999abc=100abc=100 的情况,此时直接套用上述方法得到的值分别为 10000011000001999999,但真实最近的值为 10000110000199999999


展开来说就是:对于 abc = 999abc=999(对应原串为 999xx999xx 的情况)而言,按照上述逻辑得到的是 10001000,进行回文补充后得到 10000011000001(事实上应该是 100001100001 更优);对于 abc = 100abc=100(对应原串为 100xx100xx 的情况)而言,按照上述逻辑得到是 9999,进行回文补充后得到 999999(事实上应该是 99999999 更优)。


因此对于长度为 nn 的数值,值考虑枚举前一半数的三种情况(前一半数不变,前一半数加一或减一)可能会过错最优解,我们需要将与长度相关的边界值也纳入考虑,即将「长度为 n - 1n1 的回文串最大值(99...9999...99)」和「长度为 n + 1n+1 的回文串最小值(10...0110...01)」也纳入考虑,也就是对应的 10^{n - 1} - 110n1110^n + 110n+1 也纳入考虑。然后从上述 55 个值中选绝对差值最小的作为答案。


一些细节:在枚举完前一半数后,我们需要根据原串长度的奇偶性来决定,如何生成后一半数,从而确保构建的回文串长度仍和原串长度相等。


即对于原串长度 nn 为奇数的 abcdeabcde 而言,在处理完 abcabc 之后,生成后一半数时,只需要根据前两位 abab 生成对应的 baba,而无须处理中间字符 cc;而对于原串长度 nn 为偶数的 abcdabcd 而言,在处理完 abab 之后,生成另外一般数值需要根据整一个 abab 来生成 baba,没有中间字符需要被跳过。


代码:


class Solution {
    public String nearestPalindromic(String s) {
        int n = s.length();
        long cur = Long.parseLong(s);
        Set<Long> set = new HashSet<>();
        set.add((long) Math.pow(10, n - 1) - 1);
        set.add((long) Math.pow(10, n) + 1);
        long t = Long.parseLong(s.substring(0, (n + 1) / 2));
        for (long i = t - 1; i <= t + 1; i++) {
            long temp = -1;
            if (n % 2 == 0) temp = getNum(i, true);
            else temp = getNum(i, false);
            if (temp != cur) set.add(temp);
        }
        long ans = -1;
        for (long i : set) {
            if (ans == -1) ans = i;
            else if (Math.abs(i - cur) < Math.abs(ans - cur)) ans = i;
            else if (Math.abs(i - cur) == Math.abs(ans - cur) && i < ans) ans = i;
        }
        return String.valueOf(ans);
    }
    long getNum(long k, boolean isEven) {
        StringBuilder sb = new StringBuilder();
        sb.append(k);
        int idx = isEven ? sb.length() - 1 : sb.length() - 2;
        while (idx >= 0) sb.append(sb.charAt(idx--));
        return Long.parseLong(sb.toString());
    }
}
复制代码


  • 时间复杂度:O(\log{n})O(logn)
  • 空间复杂度:O(1)O(1)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.564 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
3天前
|
算法 测试技术 C++
【数位dp】【动态规划】C++算法:233.数字 1 的个数
【数位dp】【动态规划】C++算法:233.数字 1 的个数
|
3天前
【每日一题Day146】给定行和列的和求可行矩阵 | 贪心
【每日一题Day146】给定行和列的和求可行矩阵 | 贪心
22 0
|
8月前
欧拉筛(最优的方法,对于找质数,细节讲解)
欧拉筛(最优的方法,对于找质数,细节讲解)
52 0
|
3天前
|
存储 算法
leetcode1237. 找出给定方程的正整数解
leetcode1237. 找出给定方程的正整数解
8 0
|
3天前
|
算法 测试技术 C++
【组合数学】【动态规划】【前缀和】1735生成乘积数组的方案数
【组合数学】【动态规划】【前缀和】1735生成乘积数组的方案数
|
3天前
代码随想录Day30 贪心05 LeetCode T435无重叠区间 T763划分字母区间 T56 合并区间
代码随想录Day30 贪心05 LeetCode T435无重叠区间 T763划分字母区间 T56 合并区间
32 0
|
3天前
|
算法
回溯-求出数组的所有子序列【学习算法】
回溯-求出数组的所有子序列【学习算法】
29 0
|
10月前
|
算法 内存技术
求组合数三种算法
求组合数三种算法
50 0
|
算法
LeetCode:376. 摆动序列——说什么贪心和动规~
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。 相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。 子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。